diff --git a/.gitignore b/.gitignore index 92e2728c3..d81178e11 100644 --- a/.gitignore +++ b/.gitignore @@ -10,3 +10,4 @@ Thumbs.db *.DS_Store *~ .idea/ +api/ diff --git a/.htaccess b/.htaccess index 189e6b4b8..3b6168c75 100644 --- a/.htaccess +++ b/.htaccess @@ -2,7 +2,7 @@ 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. +# 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. # 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 index b7311238a..58c971801 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,6 +1,6 @@ # Resources -* [docs](https://www.anuko.com/time_tracker/features.htm) - detailed documentation about this project (needs updating). +* [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. @@ -29,4 +29,4 @@ 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). +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 cf2f8601f..022c0c030 100644 --- a/README.md +++ b/README.md @@ -3,6 +3,9 @@ ## 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 diff --git a/WEB-INF/config.php.dist b/WEB-INF/config.php.dist index bbf647cec..6b6a68d84 100644 --- a/WEB-INF/config.php.dist +++ b/WEB-INF/config.php.dist @@ -88,7 +88,7 @@ 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. @@ -133,7 +133,7 @@ define('REPORT_FOOTER', true); define('AUTH_MODULE', 'db'); // LDAP authentication examples. -// Go to https://www.anuko.com/time_tracker/install_guide/ldap_auth/index.htm for detailed configuration instructions. +// Go to https://www.anuko.com/time-tracker/install-guide/ldap-auth/index.htm for detailed configuration instructions. // Configuration example for OpenLDAP server: // define('AUTH_MODULE', 'ldap'); @@ -176,8 +176,6 @@ define('AUTH_MODULE', 'db'); // define('MONTHLY_QUOTA_YEAR_START', 2010); // If nothing is specified, it falls back to 2015. // define('MONTHLY_QUOTA_YEAR_END', 2025); // If nothing is specified, it falls back to 2030. -// Height in pixels for the note input field in time.php. Defaults to 40. -// define('NOTE_INPUT_HEIGHT', 100); // A comma-separated list of default plugins for new group registrations. // Example below enables charts and attachments. diff --git a/WEB-INF/lib/Auth.class.php b/WEB-INF/lib/Auth.class.php index ccf2d915d..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 { diff --git a/WEB-INF/lib/DateAndTime.class.php b/WEB-INF/lib/DateAndTime.class.php deleted file mode 100644 index 1184bd853..000000000 --- a/WEB-INF/lib/DateAndTime.class.php +++ /dev/null @@ -1,348 +0,0 @@ -$sDate parsed, or false on error. - */ - -function my_strptime($sDate, $sFormat) -{ - $aResult = array - ( - 'tm_sec' => 0, - 'tm_min' => 0, - 'tm_hour' => 0, - 'tm_mday' => 1, - 'tm_mon' => 0, - 'tm_year' => 0, - 'tm_wday' => 0, - 'tm_yday' => 0, - 'unparsed' => $sDate, - ); - - while($sFormat != "") - { - // ===== Search a %x element, Check the static string before the %x ===== - $nIdxFound = strpos($sFormat, '%'); - if($nIdxFound === false) - { - - // There is no more format. Check the last static string. - $aResult['unparsed'] = ($sFormat == $sDate) ? "" : $sDate; - break; - } - - $sFormatBefore = mb_substr($sFormat, 0, $nIdxFound); - $sDateBefore = mb_substr($sDate, 0, $nIdxFound); - - if($sFormatBefore != $sDateBefore) break; - - // ===== Read the value of the %x found ===== - $sFormat = mb_substr($sFormat, $nIdxFound); - $sDate = mb_substr($sDate, $nIdxFound); - - $aResult['unparsed'] = $sDate; - - $sFormatCurrent = mb_substr($sFormat, 0, 2); - $sFormatAfter = mb_substr($sFormat, 2); - - $nValue = -1; - $sDateAfter = ""; - switch($sFormatCurrent) - { - case '%S': // Seconds after the minute (0-59) - - sscanf($sDate, "%2d%[^\\n]", $nValue, $sDateAfter); - - if(($nValue < 0) || ($nValue > 59)) return false; - - $aResult['tm_sec'] = $nValue; - break; - - // ---------- - case '%M': // Minutes after the hour (0-59) - sscanf($sDate, "%2d%[^\\n]", $nValue, $sDateAfter); - - if(($nValue < 0) || ($nValue > 59)) return false; - - $aResult['tm_min'] = $nValue; - break; - - // ---------- - case '%H': // Hour since midnight (0-23) - sscanf($sDate, "%2d%[^\\n]", $nValue, $sDateAfter); - - if(($nValue < 0) || ($nValue > 23)) return false; - - $aResult['tm_hour'] = $nValue; - break; - - // ---------- - case '%d': // Day of the month (1-31) - sscanf($sDate, "%2d%[^\\n]", $nValue, $sDateAfter); - - if(($nValue < 1) || ($nValue > 31)) return false; - - $aResult['tm_mday'] = $nValue; - break; - - // ---------- - case '%m': // Months since January (0-11) - sscanf($sDate, "%2d%[^\\n]", $nValue, $sDateAfter); - - if(($nValue < 1) || ($nValue > 12)) return false; - - $aResult['tm_mon'] = ($nValue - 1); - break; - - // ---------- - case '%Y': // Years since 1900 - sscanf($sDate, "%4d%[^\\n]", $nValue, $sDateAfter); - - if($nValue < 1900) return false; - - $aResult['tm_year'] = ($nValue - 1900); - break; - - // ---------- - default: - //sscanf($sDate, "%s%[^\\n]", $skip, $sDateAfter); - preg_match('/^(.+)(\s|$)/uU', $sDate, $matches); - if (isset($matches[1])) { - $sDateAfter = mb_substr($sDate, mb_strlen($matches[1])); - } else { - $sDateAfter = ''; - } - //break 2; // Break Switch and while - break; - } - - // ===== Next please ===== - $sFormat = $sFormatAfter; - $sDate = $sDateAfter; - - $aResult['unparsed'] = $sDate; - - } // END while($sFormat != "") - - - // ===== Create the other value of the result array ===== - $nParsedDateTimestamp = mktime($aResult['tm_hour'], $aResult['tm_min'], $aResult['tm_sec'], - $aResult['tm_mon'] + 1, $aResult['tm_mday'], $aResult['tm_year'] + 1900); - - // Before PHP 5.1 return -1 when error - if(($nParsedDateTimestamp === false) - ||($nParsedDateTimestamp === -1)) return false; - - $aResult['tm_wday'] = (int) strftime("%w", $nParsedDateTimestamp); // Days since Sunday (0-6) - $aResult['tm_yday'] = (strftime("%j", $nParsedDateTimestamp) - 1); // Days since January 1 (0-365) - - return $aResult; -} // END of function - -class DateAndTime { - var $mHour = 0; - var $mMinute = 0; - var $mSecond = 0; - var $mMonth; - var $mDay; // day of week - var $mDate; // day of month - var $mYear; - var $mIntrFormat = "%d.%m.%Y %H:%M:%S"; //29.02.2004 16:21:42 internal format date - var $mLocalFormat; - var $mParseResult = 0; - var $mAutoComplete = true; - - /** - * Constructor - * - * @param String $format - * @param String $strfDateTime - * @return DateAndTime - */ - function __construct($format="",$strfDateTime="") { - $this->mLocalFormat = ($format ? $format : $this->mIntrFormat); - $d = ($strfDateTime ? $strfDateTime : $this->do_strftime($this->mLocalFormat)); - $this->parseVal($d); - } - - function setFormat($format) { - $this->mLocalFormat = $format; - } - - function getFormat() { - return $this->mLocalFormat; - } - - //01 to 31 - function getDate() { return $this->mDate; } - - //0 (for Sunday) through 6 (for Saturday) - function getDay() { return $this->mDay; } - - //01 through 12 - function getMonth() { return $this->mMonth; } - - //1999 or 2003 - function getYear() { return $this->mYear; } - - function setDate($value) { $this->mDate = $value; } - function setMonth($value) { $this->mMonth = $value; } - function setYear($value) { $this->mYear = $value; } - - function setTimestamp($ts) { - $this->mDate = date("d",$ts); - $this->mDay = date("w",$ts); - $this->mMonth = date("m",$ts); - $this->mYear = date("Y",$ts); - $this->mHour = date("H",$ts); - $this->mMinute = date("i",$ts); - $this->mSecond = date("s",$ts); - } - - /** - * Return UNIX timestamp - */ - function getTimestamp() { - return @mktime($this->mHour, $this->mMinute, $this->mSecond, $this->mMonth, $this->mDate, $this->mYear); - } - - function compare($datetime) { - $ts1 = $this->getTimestamp(); - $ts2 = $datetime->getTimestamp(); - if ($ts1<$ts2) return -1; - if ($ts1==$ts2) return 0; - if ($ts1>$ts2) return 1; - } - - function toString($format="") { - if ($this->mParseResult==0) { - if ($format) { - return $this->do_strftime($format, $this->getTimestamp()); - } else { - return $this->do_strftime($this->mLocalFormat, $this->getTimestamp()); - } - } else { - if ($format) { - return $this->do_strftime($format); - } else { - return $this->do_strftime($this->mLocalFormat); - } - } - } - - function parseVal($szDate, $format="") { - $useformat = ($format ? $format : $this->mLocalFormat); - $res = my_strptime($szDate, $useformat); - if ($res !== false) { - $this->mDate = $res['tm_mday']; - $this->mDay = $res['tm_wday']; - $this->mMonth = $res['tm_mon'] + 1; // tm_mon - Months since January (0-11) - $this->mYear = 1900 + $res['tm_year']; // tm_year - Years since 1900 - $this->mHour = $res['tm_hour']; - $this->mMinute = $res['tm_min']; - $this->mSecond = $res['tm_sec']; - $this->mParseResult = 0; - } elseif ($this->mAutoComplete) { - $this->setTimestamp(time()); - $this->mParseResult = 1; - } - } - - function isError() { - if ($this->mParseResult != 0) return true; - return false; - } - - function 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 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; - - // 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); - /* This block is commented out because we currently do not use these formatters. - $abbrev_month = mb_substr($i18n->monthNames[$this->mMonth], 0, 3, 'utf-8'); - $format = str_replace('%b', $abbrev_month, $format); - $format = str_replace('%h', $abbrev_month, $format); - $format = str_replace('%z', date('O'), $format); - $format = str_replace('%Z', date('O'), $format); // format as 'O' for consistency with JS strftime - if (strpos($format, '%c') !== false) { - $format = str_replace('%c', $this->preprocessFormatString('%a %d %b %Y %T %Z'), $format); - }*/ - return $format; - } - - function do_strftime($format, $timestamp = null) - { - if (!is_null($timestamp)) { - return strftime($this->preprocessFormatString($format), $timestamp); - } else { - return strftime($this->preprocessFormatString($format)); - } - } -} diff --git a/WEB-INF/lib/I18n.class.php b/WEB-INF/lib/I18n.class.php index ed8380815..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 { @@ -51,9 +51,16 @@ function get($key) { return $value; } + // get - keyExists determines if a key exists. + function keyExists($key) { + $value = $this->get($key); + return ($value !== null); + } + // 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 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'; @@ -105,6 +112,35 @@ function load($langName) { } } } + + // Now load custom translation for group. + global $user; + $customTranslation = $user->getCustomTranslation(); + if ($customTranslation != null) { + $lines = preg_split("/\r\n|\n|\r/", $customTranslation); + for ($i = 0; $i < count($lines); $i++) { + $parts = explode('=', $lines[$i]); + if (count($parts) != 2) continue; + + $key = trim($parts[0]); + $value = trim($parts[1]); + // Escape single quotes and backslashes. + $value = addcslashes($value, "'\\"); + $value = htmlspecialchars($value); + + $pos = strpos($key, "."); + if (!($pos === false)) { + $p = explode(".", $key); + $str = ""; + foreach ($p as $w) { + $str .= "[\"".$w."\"]"; + } + eval("\$this->keys".$str."='".$value."';"); + } else { + $this->keys[$key] = $value; + } + } + } } // hasLang determines if a file for requested language exists. diff --git a/WEB-INF/lib/Period.class.php b/WEB-INF/lib/Period.class.php deleted file mode 100644 index 2d2269e52..000000000 --- a/WEB-INF/lib/Period.class.php +++ /dev/null @@ -1,136 +0,0 @@ -getWeekStart(); - - $this->startDate = new DateAndTime(); - $this->startDate->setFormat($date_point->getFormat()); - $this->endDate = new DateAndTime(); - $this->endDate->setFormat($date_point->getFormat()); - $t_arr = localtime($date_point->getTimestamp()); - $t_arr[5] = $t_arr[5] + 1900; - - if ($t_arr[6] < $weekStartDay) { - $startWeekBias = $weekStartDay - 7; - } else { - $startWeekBias = $weekStartDay; - } - - switch ($period_type) { - case INTERVAL_THIS_DAY: - $this->startDate->setTimestamp($date_point->getTimestamp()); - $this->endDate->setTimestamp($date_point->getTimestamp()); - break; - - case INTERVAL_LAST_DAY: - $this->startDate->setTimestamp(mktime(0,0,0,$t_arr[4]+1,$t_arr[3]-1,$t_arr[5])); - $this->endDate->setTimestamp(mktime(0,0,0,$t_arr[4]+1,$t_arr[3]-1,$t_arr[5])); - break; - - case INTERVAL_THIS_WEEK: - $this->startDate->setTimestamp(mktime(0,0,0,$t_arr[4]+1,$t_arr[3]-$t_arr[6]+$startWeekBias,$t_arr[5])); - $this->endDate->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: - $this->startDate->setTimestamp(mktime(0,0,0,$t_arr[4]+1,$t_arr[3]-$t_arr[6]-7+$startWeekBias,$t_arr[5])); - $this->endDate->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: - $this->startDate->setTimestamp(mktime(0,0,0,$t_arr[4]+1,1,$t_arr[5])); - $this->endDate->setTimestamp(mktime(0,0,0,$t_arr[4]+2,0,$t_arr[5])); - break; - case INTERVAL_LAST_MONTH: - $this->startDate->setTimestamp(mktime(0,0,0,$t_arr[4],1,$t_arr[5])); - $this->endDate->setTimestamp(mktime(0,0,0,$t_arr[4]+1,0,$t_arr[5])); - break; - - case INTERVAL_THIS_YEAR: - $this->startDate->setTimestamp(mktime(0, 0, 0, 1, 1, $t_arr[5])); - $this->endDate->setTimestamp(mktime(0, 0, 0, 12, 31, $t_arr[5])); - break; - } - } - - function setPeriod($b_date, $e_date) { - $this->startDate = $b_date; - $this->endDate = $e_date; - } - - // return date string - function getStartDate($format="") { - return $this->startDate->toString($format); - } - - // return date string - function getEndDate($format="") { - return $this->endDate->toString($format); - } -} diff --git a/WEB-INF/lib/common.lib.php b/WEB-INF/lib/common.lib.php index d5b43f57f..79aba106b 100644 --- a/WEB-INF/lib/common.lib.php +++ b/WEB-INF/lib/common.lib.php @@ -126,16 +126,24 @@ function isTrue($val) } // 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, ''; + return ''; } elseif ($encode === 'javascript_charcode') { - $string = '' . $text . ''; - for ($x = 0, $y = strlen($string); $x < $y; $x++) { + for ($x = 0, $_length = strlen($string); $x < $_length; $x++) { $ord[] = ord($string[ $x ]); } - $_ret = "\n"; - return $_ret; + return ''; } elseif ($encode === 'hex') { preg_match('!^(.*)(\?.*)$!', $address, $match); if (!empty($match[ 2 ])) { @@ -132,6 +137,6 @@ function smarty_function_mailto($params) return '' . $text_encode . ''; } else { // no encoding - return '' . $text . ''; + return $string; } } diff --git a/WEB-INF/lib/smarty/plugins/function.math.php b/WEB-INF/lib/smarty/plugins/function.math.php index 7348d9649..f9cf67fe7 100644 --- a/WEB-INF/lib/smarty/plugins/function.math.php +++ b/WEB-INF/lib/smarty/plugins/function.math.php @@ -12,7 +12,7 @@ * Name: math * Purpose: handle math computations in template * - * @link http://www.smarty.net/manual/en/language.function.math.php {math} + * @link https://www.smarty.net/manual/en/language.function.math.php {math} * (Smarty online manual) * @author Monte Ohrt * @@ -28,7 +28,12 @@ function smarty_function_math($params, $template) 'int' => true, 'abs' => true, 'ceil' => true, + 'acos' => true, + 'acosh' => true, 'cos' => true, + 'cosh' => true, + 'deg2rad' => true, + 'rad2deg' => true, 'exp' => true, 'floor' => true, 'log' => true, @@ -39,27 +44,51 @@ function smarty_function_math($params, $template) 'pow' => true, 'rand' => true, 'round' => true, + 'asin' => true, + 'asinh' => true, 'sin' => true, + 'sinh' => true, 'sqrt' => true, 'srand' => true, - 'tan' => true + 'atan' => true, + 'atanh' => true, + 'tan' => true, + 'tanh' => true ); + // be sure equation parameter is present if (empty($params[ 'equation' ])) { trigger_error("math: missing equation parameter", E_USER_WARNING); return; } $equation = $params[ 'equation' ]; + + // Remove whitespaces + $equation = preg_replace('/\s+/', '', $equation); + + // Adapted from https://www.php.net/manual/en/function.eval.php#107377 + $number = '(?:\d+(?:[,.]\d+)?|pi|π)'; // What is a number + $functionsOrVars = '((?:0x[a-fA-F0-9]+)|([a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*))'; + $operators = '[,+\/*\^%-]'; // Allowed math operators + $regexp = '/^(('.$number.'|'.$functionsOrVars.'|('.$functionsOrVars.'\s*\((?1)*\)|\((?1)*\)))(?:'.$operators.'(?1))?)+$/'; + + if (!preg_match($regexp, $equation)) { + trigger_error("math: illegal characters", E_USER_WARNING); + return; + } + // make sure parenthesis are balanced if (substr_count($equation, '(') !== substr_count($equation, ')')) { trigger_error("math: unbalanced parenthesis", E_USER_WARNING); return; } + // disallow backticks if (strpos($equation, '`') !== false) { trigger_error("math: backtick character not allowed in equation", E_USER_WARNING); return; } + // also disallow dollar signs if (strpos($equation, '$') !== false) { trigger_error("math: dollar signs not allowed in equation", E_USER_WARNING); @@ -96,6 +125,7 @@ function smarty_function_math($params, $template) } $smarty_math_result = null; eval("\$smarty_math_result = " . $equation . ";"); + if (empty($params[ 'format' ])) { if (empty($params[ 'assign' ])) { return $smarty_math_result; diff --git a/WEB-INF/lib/smarty/plugins/modifier.capitalize.php b/WEB-INF/lib/smarty/plugins/modifier.capitalize.php index c5fc400a6..2903d61d7 100644 --- a/WEB-INF/lib/smarty/plugins/modifier.capitalize.php +++ b/WEB-INF/lib/smarty/plugins/modifier.capitalize.php @@ -22,6 +22,8 @@ */ function smarty_modifier_capitalize($string, $uc_digits = false, $lc_rest = false) { + $string = (string) $string; + if (Smarty::$_MBSTRING) { if ($lc_rest) { // uppercase (including hyphenated words) diff --git a/WEB-INF/lib/smarty/plugins/modifier.count.php b/WEB-INF/lib/smarty/plugins/modifier.count.php new file mode 100644 index 000000000..ca35fc115 --- /dev/null +++ b/WEB-INF/lib/smarty/plugins/modifier.count.php @@ -0,0 +1,36 @@ + Prior to PHP 8.0.0, if the parameter was neither an array nor an object that implements the Countable interface, + * > 1 would be returned, unless value was null, in which case 0 would be returned. + */ + + if ($arrayOrObject instanceof Countable || is_array($arrayOrObject)) { + return count($arrayOrObject, (int) $mode); + } elseif ($arrayOrObject === null) { + return 0; + } + return 1; +} diff --git a/WEB-INF/lib/smarty/plugins/modifier.date_format.php b/WEB-INF/lib/smarty/plugins/modifier.date_format.php index c8e88c5c9..e3589fd07 100644 --- a/WEB-INF/lib/smarty/plugins/modifier.date_format.php +++ b/WEB-INF/lib/smarty/plugins/modifier.date_format.php @@ -15,7 +15,7 @@ * - format: strftime format for output * - default_date: default date if $string is empty * - * @link http://www.smarty.net/manual/en/language.modifier.date.format.php date_format (Smarty online manual) + * @link https://www.smarty.net/manual/en/language.modifier.date.format.php date_format (Smarty online manual) * @author Monte Ohrt * * @param string $string input date string @@ -78,7 +78,8 @@ function smarty_modifier_date_format($string, $format = null, $default_date = '' } $format = str_replace($_win_from, $_win_to, $format); } - return strftime($format, $timestamp); + // @ to suppress deprecation errors when running in PHP8.1 or higher. + return @strftime($format, $timestamp); } else { return date($format, $timestamp); } diff --git a/WEB-INF/lib/smarty/plugins/modifier.escape.php b/WEB-INF/lib/smarty/plugins/modifier.escape.php index 150901c7c..11e44682e 100644 --- a/WEB-INF/lib/smarty/plugins/modifier.escape.php +++ b/WEB-INF/lib/smarty/plugins/modifier.escape.php @@ -11,7 +11,7 @@ * Name: escape * Purpose: escape string for output * - * @link http://www.smarty.net/docs/en/language.modifier.escape + * @link https://www.smarty.net/docs/en/language.modifier.escape * @author Monte Ohrt * * @param string $string input string @@ -23,98 +23,25 @@ */ function smarty_modifier_escape($string, $esc_type = 'html', $char_set = null, $double_encode = true) { - static $_double_encode = null; static $is_loaded_1 = false; static $is_loaded_2 = false; - if ($_double_encode === null) { - $_double_encode = version_compare(PHP_VERSION, '5.2.3', '>='); - } if (!$char_set) { $char_set = Smarty::$_CHARSET; } + + $string = (string)$string; + switch ($esc_type) { case 'html': - if ($_double_encode) { - // php >=5.3.2 - go native - return htmlspecialchars($string, ENT_QUOTES, $char_set, $double_encode); - } else { - if ($double_encode) { - // php <5.2.3 - only handle double encoding - return htmlspecialchars($string, ENT_QUOTES, $char_set); - } else { - // php <5.2.3 - prevent double encoding - $string = preg_replace('!&(#?\w+);!', '%%%SMARTY_START%%%\\1%%%SMARTY_END%%%', $string); - $string = htmlspecialchars($string, ENT_QUOTES, $char_set); - $string = str_replace( - array( - '%%%SMARTY_START%%%', - '%%%SMARTY_END%%%' - ), - array( - '&', - ';' - ), - $string - ); - return $string; - } - } + return htmlspecialchars($string, ENT_QUOTES, $char_set, $double_encode); // no break case 'htmlall': if (Smarty::$_MBSTRING) { - // mb_convert_encoding ignores htmlspecialchars() - if ($_double_encode) { - // php >=5.3.2 - go native - $string = htmlspecialchars($string, ENT_QUOTES, $char_set, $double_encode); - } else { - if ($double_encode) { - // php <5.2.3 - only handle double encoding - $string = htmlspecialchars($string, ENT_QUOTES, $char_set); - } else { - // php <5.2.3 - prevent double encoding - $string = preg_replace('!&(#?\w+);!', '%%%SMARTY_START%%%\\1%%%SMARTY_END%%%', $string); - $string = htmlspecialchars($string, ENT_QUOTES, $char_set); - $string = - str_replace( - array( - '%%%SMARTY_START%%%', - '%%%SMARTY_END%%%' - ), - array( - '&', - ';' - ), - $string - ); - return $string; - } - } - // htmlentities() won't convert everything, so use mb_convert_encoding - return mb_convert_encoding($string, 'HTML-ENTITIES', $char_set); + $string = mb_convert_encoding($string, 'UTF-8', $char_set); + return htmlentities($string, ENT_QUOTES, 'UTF-8', $double_encode); } // no MBString fallback - if ($_double_encode) { - return htmlentities($string, ENT_QUOTES, $char_set, $double_encode); - } else { - if ($double_encode) { - return htmlentities($string, ENT_QUOTES, $char_set); - } else { - $string = preg_replace('!&(#?\w+);!', '%%%SMARTY_START%%%\\1%%%SMARTY_END%%%', $string); - $string = htmlentities($string, ENT_QUOTES, $char_set); - $string = str_replace( - array( - '%%%SMARTY_START%%%', - '%%%SMARTY_END%%%' - ), - array( - '&', - ';' - ), - $string - ); - return $string; - } - } + return htmlentities($string, ENT_QUOTES, $char_set, $double_encode); // no break case 'url': return rawurlencode($string); @@ -184,7 +111,11 @@ function smarty_modifier_escape($string, $esc_type = 'html', $char_set = null, $ '"' => '\\"', "\r" => '\\r', "\n" => '\\n', - ' '<\/' + ' '<\/', + // see https://html.spec.whatwg.org/multipage/scripting.html#restrictions-for-contents-of-script-elements + '#ms', '', $source); // capture html elements not to be messed with $_offset = 0; diff --git a/WEB-INF/lib/smarty/plugins/shared.escape_special_chars.php b/WEB-INF/lib/smarty/plugins/shared.escape_special_chars.php index 6b18d3eec..a204b092c 100644 --- a/WEB-INF/lib/smarty/plugins/shared.escape_special_chars.php +++ b/WEB-INF/lib/smarty/plugins/shared.escape_special_chars.php @@ -20,13 +20,7 @@ function smarty_function_escape_special_chars($string) { if (!is_array($string)) { - if (version_compare(PHP_VERSION, '5.2.3', '>=')) { - $string = htmlspecialchars($string, ENT_COMPAT, Smarty::$_CHARSET, false); - } else { - $string = preg_replace('!&(#?\w+);!', '%%%SMARTY_START%%%\\1%%%SMARTY_END%%%', $string); - $string = htmlspecialchars($string); - $string = str_replace(array('%%%SMARTY_START%%%', '%%%SMARTY_END%%%'), array('&', ';'), $string); - } + $string = htmlspecialchars($string, ENT_COMPAT, Smarty::$_CHARSET, false); } return $string; } diff --git a/WEB-INF/lib/smarty/plugins/shared.mb_str_replace.php b/WEB-INF/lib/smarty/plugins/shared.mb_str_replace.php index 206cf9ea6..7e85f7aae 100644 --- a/WEB-INF/lib/smarty/plugins/shared.mb_str_replace.php +++ b/WEB-INF/lib/smarty/plugins/shared.mb_str_replace.php @@ -44,9 +44,43 @@ function smarty_mb_str_replace($search, $replace, $subject, &$count = 0) } } } else { - $parts = mb_split(preg_quote($search), $subject); + $mb_reg_charset = mb_regex_encoding(); + // Check if mbstring regex is using UTF-8 + $reg_is_unicode = !strcasecmp($mb_reg_charset, "UTF-8"); + if(!$reg_is_unicode) { + // ...and set to UTF-8 if not + mb_regex_encoding("UTF-8"); + } + + // See if charset used by Smarty is matching one used by regex... + $current_charset = mb_regex_encoding(); + $convert_result = (bool)strcasecmp(Smarty::$_CHARSET, $current_charset); + if($convert_result) { + // ...convert to it if not. + $subject = mb_convert_encoding($subject, $current_charset, Smarty::$_CHARSET); + $search = mb_convert_encoding($search, $current_charset, Smarty::$_CHARSET); + $replace = mb_convert_encoding($replace, $current_charset, Smarty::$_CHARSET); + } + + $parts = mb_split(preg_quote($search), $subject ?? "") ?: array(); + // If original regex encoding was not unicode... + if(!$reg_is_unicode) { + // ...restore original regex encoding to avoid breaking the system. + mb_regex_encoding($mb_reg_charset); + } + if($parts === false) { + // This exception is thrown if call to mb_split failed. + // Usually it happens, when $search or $replace are not valid for given mb_regex_encoding(). + // There may be other cases for it to fail, please file an issue if you find a reproducible one. + throw new SmartyException("Source string is not a valid $current_charset sequence (probably)"); + } + $count = count($parts) - 1; $subject = implode($replace, $parts); + // Convert results back to charset used by Smarty, if needed. + if($convert_result) { + $subject = mb_convert_encoding($subject, Smarty::$_CHARSET, $current_charset); + } } return $subject; } diff --git a/WEB-INF/lib/smarty/sysplugins/smarty_cacheresource.php b/WEB-INF/lib/smarty/sysplugins/smarty_cacheresource.php index 91e9f3924..db68f9bfd 100644 --- a/WEB-INF/lib/smarty/sysplugins/smarty_cacheresource.php +++ b/WEB-INF/lib/smarty/sysplugins/smarty_cacheresource.php @@ -205,11 +205,11 @@ public static function load(Smarty $smarty, $type = null) } // try sysplugins dir if (isset(self::$sysplugins[ $type ])) { - $cache_resource_class = 'Smarty_Internal_CacheResource_' . ucfirst($type); + $cache_resource_class = 'Smarty_Internal_CacheResource_' . smarty_ucfirst_ascii($type); return $smarty->_cache[ 'cacheresource_handlers' ][ $type ] = new $cache_resource_class(); } // try plugins dir - $cache_resource_class = 'Smarty_CacheResource_' . ucfirst($type); + $cache_resource_class = 'Smarty_CacheResource_' . smarty_ucfirst_ascii($type); if ($smarty->loadPlugin($cache_resource_class)) { return $smarty->_cache[ 'cacheresource_handlers' ][ $type ] = new $cache_resource_class(); } diff --git a/WEB-INF/lib/smarty/sysplugins/smarty_cacheresource_keyvaluestore.php b/WEB-INF/lib/smarty/sysplugins/smarty_cacheresource_keyvaluestore.php index 59bf1d4a8..4b1c0f6d8 100644 --- a/WEB-INF/lib/smarty/sysplugins/smarty_cacheresource_keyvaluestore.php +++ b/WEB-INF/lib/smarty/sysplugins/smarty_cacheresource_keyvaluestore.php @@ -244,7 +244,7 @@ protected function getTemplateUid(Smarty $smarty, $resource_name) */ protected function sanitize($string) { - $string = trim($string, '|'); + $string = trim((string)$string, '|'); if (!$string) { return ''; } @@ -428,7 +428,7 @@ protected function listInvalidationKeys( $t[] = 'IVK#COMPILE' . $_compile; } $_name .= '#'; - $cid = trim($cache_id, '|'); + $cid = trim((string)$cache_id, '|'); if (!$cid) { return $t; } diff --git a/WEB-INF/lib/smarty/sysplugins/smarty_internal_cacheresource_file.php b/WEB-INF/lib/smarty/sysplugins/smarty_internal_cacheresource_file.php index 61618449d..c77ae9e17 100644 --- a/WEB-INF/lib/smarty/sysplugins/smarty_internal_cacheresource_file.php +++ b/WEB-INF/lib/smarty/sysplugins/smarty_internal_cacheresource_file.php @@ -196,12 +196,8 @@ public function clear(Smarty $smarty, $resource_name, $cache_id, $compile_id, $e */ public function hasLock(Smarty $smarty, Smarty_Template_Cached $cached) { - if (version_compare(PHP_VERSION, '5.3.0', '>=')) { - clearstatcache(true, $cached->lock_id); - } else { - clearstatcache(); - } - if (is_file($cached->lock_id)) { + clearstatcache(true, $cached->lock_id ?? ''); + if (null !== $cached->lock_id && is_file($cached->lock_id)) { $t = filemtime($cached->lock_id); return $t && (time() - $t < $smarty->locking_timeout); } else { diff --git a/WEB-INF/lib/smarty/sysplugins/smarty_internal_compile_block.php b/WEB-INF/lib/smarty/sysplugins/smarty_internal_compile_block.php index 8ff15d8e5..cbaccd2b3 100644 --- a/WEB-INF/lib/smarty/sysplugins/smarty_internal_compile_block.php +++ b/WEB-INF/lib/smarty/sysplugins/smarty_internal_compile_block.php @@ -125,7 +125,7 @@ public function compile($args, Smarty_Internal_TemplateCompilerBase $compiler, $ // setup buffer for template function code $compiler->parser->current_buffer = new Smarty_Internal_ParseTree_Template(); $output = "cStyleComment(" {block {$_name}} ") . "\n"; $output .= "class {$_className} extends Smarty_Internal_Block\n"; $output .= "{\n"; foreach ($_block as $property => $value) { @@ -155,7 +155,7 @@ public function compile($args, Smarty_Internal_TemplateCompilerBase $compiler, $ } $output .= "}\n"; $output .= "}\n"; - $output .= "/* {/block {$_name}} */\n\n"; + $output .= $compiler->cStyleComment(" {/block {$_name}} ") . "\n\n"; $output .= "?>\n"; $compiler->parser->current_buffer->append_subtree( $compiler->parser, diff --git a/WEB-INF/lib/smarty/sysplugins/smarty_internal_compile_for.php b/WEB-INF/lib/smarty/sysplugins/smarty_internal_compile_for.php index 3f113e56d..969e22c1a 100644 --- a/WEB-INF/lib/smarty/sysplugins/smarty_internal_compile_for.php +++ b/WEB-INF/lib/smarty/sysplugins/smarty_internal_compile_for.php @@ -18,7 +18,7 @@ class Smarty_Internal_Compile_For extends Smarty_Internal_CompileBase { /** * Compiles code for the {for} tag - * Smarty 3 does implement two different syntax's: + * Smarty supports two different syntax's: * - {for $var in $array} * For looping over arrays or iterators * - {for $x=0; $x<$y; $x++} diff --git a/WEB-INF/lib/smarty/sysplugins/smarty_internal_compile_foreach.php b/WEB-INF/lib/smarty/sysplugins/smarty_internal_compile_foreach.php index a68da5409..edfe358be 100644 --- a/WEB-INF/lib/smarty/sysplugins/smarty_internal_compile_foreach.php +++ b/WEB-INF/lib/smarty/sysplugins/smarty_internal_compile_foreach.php @@ -219,9 +219,9 @@ public function compile($args, Smarty_Internal_TemplateCompilerBase $compiler) if (isset($itemAttr[ 'index' ])) { $output .= "{$itemVar}->index = -1;\n"; } - $output .= "{$itemVar}->do_else = true;\n"; + $output .= "{$itemVar}->do_else = true;\n"; $output .= "if (\$_from !== null) foreach (\$_from as {$keyTerm}{$itemVar}->value) {\n"; - $output .= "{$itemVar}->do_else = false;\n"; + $output .= "{$itemVar}->do_else = false;\n"; if (isset($attributes[ 'key' ]) && isset($itemAttr[ 'key' ])) { $output .= "\$_smarty_tpl->tpl_vars['{$key}']->value = {$itemVar}->key;\n"; } diff --git a/WEB-INF/lib/smarty/sysplugins/smarty_internal_compile_function.php b/WEB-INF/lib/smarty/sysplugins/smarty_internal_compile_function.php index d0f2b0f4a..b05a82b74 100644 --- a/WEB-INF/lib/smarty/sysplugins/smarty_internal_compile_function.php +++ b/WEB-INF/lib/smarty/sysplugins/smarty_internal_compile_function.php @@ -134,7 +134,7 @@ public function compile($args, Smarty_Internal_TemplateCompilerBase $compiler) if ($compiler->template->compiled->has_nocache_code) { $compiler->parent_compiler->tpl_function[ $_name ][ 'call_name_caching' ] = $_funcNameCaching; $output = "cStyleComment(" {$_funcNameCaching} ") . "\n"; $output .= "if (!function_exists('{$_funcNameCaching}')) {\n"; $output .= "function {$_funcNameCaching} (Smarty_Internal_Template \$_smarty_tpl,\$params) {\n"; $output .= "ob_start();\n"; @@ -157,9 +157,9 @@ public function compile($args, Smarty_Internal_TemplateCompilerBase $compiler) $output = "template->compiled->nocache_hash}%%*/smarty->ext->_tplFunction->restoreTemplateVariables(\\\$_smarty_tpl, '{$_name}');?>\n"; $output .= "/*/%%SmartyNocache:{$compiler->template->compiled->nocache_hash}%%*/\";\n?>"; - $output .= "template->compiled->nocache_hash}', \$_smarty_tpl->compiled->nocache_hash, ob_get_clean());\n"; + $output .= "template->compiled->nocache_hash}', \$_smarty_tpl->compiled->nocache_hash ?? '', ob_get_clean());\n"; $output .= "}\n}\n"; - $output .= "/*/ {$_funcName}_nocache */\n\n"; + $output .= $compiler->cStyleComment("/ {$_funcName}_nocache ") . "\n\n"; $output .= "?>\n"; $compiler->parser->current_buffer->append_subtree( $compiler->parser, @@ -179,7 +179,7 @@ public function compile($args, Smarty_Internal_TemplateCompilerBase $compiler) } $compiler->parent_compiler->tpl_function[ $_name ][ 'call_name' ] = $_funcName; $output = "cStyleComment(" {$_funcName} ") . "\n"; $output .= "if (!function_exists('{$_funcName}')) {\n"; $output .= "function {$_funcName}(Smarty_Internal_Template \$_smarty_tpl,\$params) {\n"; $output .= $_paramsCode; @@ -196,7 +196,7 @@ public function compile($args, Smarty_Internal_TemplateCompilerBase $compiler) ); $compiler->parser->current_buffer->append_subtree($compiler->parser, $_functionCode); $output = "cStyleComment("/ {$_funcName} ") . "\n\n"; $output .= "?>\n"; $compiler->parser->current_buffer->append_subtree( $compiler->parser, diff --git a/WEB-INF/lib/smarty/sysplugins/smarty_internal_compile_include.php b/WEB-INF/lib/smarty/sysplugins/smarty_internal_compile_include.php index 716c91d49..bf62461bc 100644 --- a/WEB-INF/lib/smarty/sysplugins/smarty_internal_compile_include.php +++ b/WEB-INF/lib/smarty/sysplugins/smarty_internal_compile_include.php @@ -318,14 +318,14 @@ public function compileInlineTemplate( } // get compiled code $compiled_code = "cStyleComment(" Start inline template \"{$sourceInfo}\" =============================") . "\n"; $compiled_code .= "function {$tpl->compiled->unifunc} (Smarty_Internal_Template \$_smarty_tpl) {\n"; $compiled_code .= "?>\n" . $tpl->compiler->compileTemplateSource($tpl, null, $compiler->parent_compiler); $compiled_code .= "\n"; $compiled_code .= $tpl->compiler->postFilter($tpl->compiler->blockOrFunctionCode); $compiled_code .= "cStyleComment(" End inline template \"{$sourceInfo}\" =============================") . "\n"; $compiled_code .= '?>'; unset($tpl->compiler); if ($tpl->compiled->has_nocache_code) { diff --git a/WEB-INF/lib/smarty/sysplugins/smarty_internal_compile_include_php.php b/WEB-INF/lib/smarty/sysplugins/smarty_internal_compile_include_php.php deleted file mode 100644 index 1b0fdaad3..000000000 --- a/WEB-INF/lib/smarty/sysplugins/smarty_internal_compile_include_php.php +++ /dev/null @@ -1,110 +0,0 @@ -smarty instanceof SmartyBC)) { - throw new SmartyException("{include_php} is deprecated, use SmartyBC class to enable"); - } - // check and get attributes - $_attr = $this->getAttributes($compiler, $args); - /** - * - * - * @var Smarty_Internal_Template $_smarty_tpl - * used in evaluated code - */ - $_smarty_tpl = $compiler->template; - $_filepath = false; - $_file = null; - eval('$_file = @' . $_attr[ 'file' ] . ';'); - if (!isset($compiler->smarty->security_policy) && file_exists($_file)) { - $_filepath = $compiler->smarty->_realpath($_file, true); - } else { - if (isset($compiler->smarty->security_policy)) { - $_dir = $compiler->smarty->security_policy->trusted_dir; - } else { - $_dir = $compiler->smarty->trusted_dir; - } - if (!empty($_dir)) { - foreach ((array)$_dir as $_script_dir) { - $_path = $compiler->smarty->_realpath($_script_dir . DIRECTORY_SEPARATOR . $_file, true); - if (file_exists($_path)) { - $_filepath = $_path; - break; - } - } - } - } - if ($_filepath === false) { - $compiler->trigger_template_error("{include_php} file '{$_file}' is not readable", null, true); - } - if (isset($compiler->smarty->security_policy)) { - $compiler->smarty->security_policy->isTrustedPHPDir($_filepath); - } - if (isset($_attr[ 'assign' ])) { - // output will be stored in a smarty variable instead of being displayed - $_assign = $_attr[ 'assign' ]; - } - $_once = '_once'; - if (isset($_attr[ 'once' ])) { - if ($_attr[ 'once' ] === 'false') { - $_once = ''; - } - } - if (isset($_assign)) { - return "assign({$_assign},ob_get_clean());\n?>"; - } else { - return "\n"; - } - } -} diff --git a/WEB-INF/lib/smarty/sysplugins/smarty_internal_compile_insert.php b/WEB-INF/lib/smarty/sysplugins/smarty_internal_compile_insert.php index 4bdc3952e..29031d910 100644 --- a/WEB-INF/lib/smarty/sysplugins/smarty_internal_compile_insert.php +++ b/WEB-INF/lib/smarty/sysplugins/smarty_internal_compile_insert.php @@ -89,11 +89,11 @@ public function compile($args, Smarty_Internal_TemplateCompilerBase $compiler) if (isset($compiler->smarty->security_policy)) { $_dir = $compiler->smarty->security_policy->trusted_dir; } else { - $_dir = $compiler->smarty instanceof SmartyBC ? $compiler->smarty->trusted_dir : null; + $_dir = null; } if (!empty($_dir)) { foreach ((array)$_dir as $_script_dir) { - $_script_dir = rtrim($_script_dir, '/\\') . DIRECTORY_SEPARATOR; + $_script_dir = rtrim($_script_dir ?? '', '/\\') . DIRECTORY_SEPARATOR; if (file_exists($_script_dir . $_script)) { $_filepath = $_script_dir . $_script; break; diff --git a/WEB-INF/lib/smarty/sysplugins/smarty_internal_compile_private_foreachsection.php b/WEB-INF/lib/smarty/sysplugins/smarty_internal_compile_private_foreachsection.php index d3aab24bb..246350dc8 100644 --- a/WEB-INF/lib/smarty/sysplugins/smarty_internal_compile_private_foreachsection.php +++ b/WEB-INF/lib/smarty/sysplugins/smarty_internal_compile_private_foreachsection.php @@ -143,7 +143,7 @@ public function matchProperty($source) foreach ($this->resultOffsets as $key => $offset) { foreach ($match[ $offset ] as $m) { if (!empty($m)) { - $this->matchResults[ $key ][ strtolower($m) ] = true; + $this->matchResults[ $key ][ smarty_strtolower_ascii($m) ] = true; } } } @@ -213,12 +213,12 @@ public function matchBlockSource(Smarty_Internal_TemplateCompilerBase $compiler) */ public function compileSpecialVariable($args, Smarty_Internal_TemplateCompilerBase $compiler, $parameter) { - $tag = strtolower(trim($parameter[ 0 ], '"\'')); + $tag = smarty_strtolower_ascii(trim($parameter[ 0 ], '"\'')); $name = isset($parameter[ 1 ]) ? $compiler->getId($parameter[ 1 ]) : false; if (!$name) { $compiler->trigger_template_error("missing or illegal \$smarty.{$tag} name attribute", null, true); } - $property = isset($parameter[ 2 ]) ? strtolower($compiler->getId($parameter[ 2 ])) : false; + $property = isset($parameter[ 2 ]) ? smarty_strtolower_ascii($compiler->getId($parameter[ 2 ])) : false; if (!$property || !in_array($property, $this->nameProperties)) { $compiler->trigger_template_error("missing or illegal \$smarty.{$tag} property attribute", null, true); } diff --git a/WEB-INF/lib/smarty/sysplugins/smarty_internal_compile_private_modifier.php b/WEB-INF/lib/smarty/sysplugins/smarty_internal_compile_private_modifier.php index 72773fff8..aea082f01 100644 --- a/WEB-INF/lib/smarty/sysplugins/smarty_internal_compile_private_modifier.php +++ b/WEB-INF/lib/smarty/sysplugins/smarty_internal_compile_private_modifier.php @@ -109,6 +109,9 @@ public function compile($args, Smarty_Internal_TemplateCompilerBase $compiler, $ if (!is_object($compiler->smarty->security_policy) || $compiler->smarty->security_policy->isTrustedPhpModifier($modifier, $compiler) ) { + trigger_error('Using php-function "' . $modifier . '" as a modifier is deprecated and will be ' . + 'removed in a future release. Use Smarty::registerPlugin to explicitly register ' . + 'a custom modifier.', E_USER_DEPRECATED); $output = "{$modifier}({$params})"; } $compiler->known_modifier_type[ $modifier ] = $type; diff --git a/WEB-INF/lib/smarty/sysplugins/smarty_internal_compile_private_php.php b/WEB-INF/lib/smarty/sysplugins/smarty_internal_compile_private_php.php deleted file mode 100644 index ff48c6fbc..000000000 --- a/WEB-INF/lib/smarty/sysplugins/smarty_internal_compile_private_php.php +++ /dev/null @@ -1,253 +0,0 @@ -getAttributes($compiler, $args); - $compiler->has_code = false; - if ($_attr[ 'type' ] === 'xml') { - $compiler->tag_nocache = true; - $output = addcslashes($_attr[ 'code' ], "'\\"); - $compiler->parser->current_buffer->append_subtree( - $compiler->parser, - new Smarty_Internal_ParseTree_Tag( - $compiler->parser, - $compiler->processNocacheCode( - "\n", - true - ) - ) - ); - return ''; - } - if ($_attr[ 'type' ] !== 'tag') { - if ($compiler->php_handling === Smarty::PHP_REMOVE) { - return ''; - } elseif ($compiler->php_handling === Smarty::PHP_QUOTE) { - $output = - preg_replace_callback( - '#(<\?(?:php|=)?)|(<%)|()|(\?>)|(%>)|(<\/script>)#i', - array($this, 'quote'), - $_attr[ 'code' ] - ); - $compiler->parser->current_buffer->append_subtree( - $compiler->parser, - new Smarty_Internal_ParseTree_Text($output) - ); - return ''; - } elseif ($compiler->php_handling === Smarty::PHP_PASSTHRU || $_attr[ 'type' ] === 'unmatched') { - $compiler->tag_nocache = true; - $output = addcslashes($_attr[ 'code' ], "'\\"); - $compiler->parser->current_buffer->append_subtree( - $compiler->parser, - new Smarty_Internal_ParseTree_Tag( - $compiler->parser, - $compiler->processNocacheCode( - "\n", - true - ) - ) - ); - return ''; - } elseif ($compiler->php_handling === Smarty::PHP_ALLOW) { - if (!($compiler->smarty instanceof SmartyBC)) { - $compiler->trigger_template_error( - '$smarty->php_handling PHP_ALLOW not allowed. Use SmartyBC to enable it', - null, - true - ); - } - $compiler->has_code = true; - return $_attr[ 'code' ]; - } else { - $compiler->trigger_template_error('Illegal $smarty->php_handling value', null, true); - } - } else { - $compiler->has_code = true; - if (!($compiler->smarty instanceof SmartyBC)) { - $compiler->trigger_template_error( - '{php}{/php} tags not allowed. Use SmartyBC to enable them', - null, - true - ); - } - $ldel = preg_quote($compiler->smarty->left_delimiter, '#'); - $rdel = preg_quote($compiler->smarty->right_delimiter, '#'); - preg_match("#^({$ldel}php\\s*)((.)*?)({$rdel})#", $_attr[ 'code' ], $match); - if (!empty($match[ 2 ])) { - if ('nocache' === trim($match[ 2 ])) { - $compiler->tag_nocache = true; - } else { - $compiler->trigger_template_error("illegal value of option flag '{$match[2]}'", null, true); - } - } - return preg_replace( - array("#^{$ldel}\\s*php\\s*(.)*?{$rdel}#", "#{$ldel}\\s*/\\s*php\\s*{$rdel}$#"), - array(''), - $_attr[ 'code' ] - ); - } - } - - /** - * Lexer code for PHP tags - * - * This code has been moved from lexer here fo easier debugging and maintenance - * - * @param Smarty_Internal_Templatelexer $lex - * - * @throws \SmartyCompilerException - */ - public function parsePhp(Smarty_Internal_Templatelexer $lex) - { - $lex->token = Smarty_Internal_Templateparser::TP_PHP; - $close = 0; - $lex->taglineno = $lex->line; - $closeTag = '?>'; - if (strpos($lex->value, 'is_xml = true; - $lex->phpType = 'xml'; - return; - } elseif (strpos($lex->value, 'phpType = 'php'; - } elseif (strpos($lex->value, '<%') === 0) { - $lex->phpType = 'asp'; - $closeTag = '%>'; - } elseif (strpos($lex->value, '%>') === 0) { - $lex->phpType = 'unmatched'; - } elseif (strpos($lex->value, '?>') === 0) { - if ($lex->is_xml) { - $lex->is_xml = false; - $lex->phpType = 'xml'; - return; - } - $lex->phpType = 'unmatched'; - } elseif (strpos($lex->value, 'phpType = 'script'; - $closeTag = ''; - } elseif (strpos($lex->value, $lex->smarty->left_delimiter) === 0) { - if ($lex->isAutoLiteral()) { - $lex->token = Smarty_Internal_Templateparser::TP_TEXT; - return; - } - $closeTag = "{$lex->smarty->left_delimiter}/php{$lex->smarty->right_delimiter}"; - if ($lex->value === $closeTag) { - $lex->compiler->trigger_template_error("unexpected closing tag '{$closeTag}'"); - } - $lex->phpType = 'tag'; - } - if ($lex->phpType === 'unmatched') { - return; - } - if (($lex->phpType === 'php' || $lex->phpType === 'asp') - && - ($lex->compiler->php_handling === Smarty::PHP_PASSTHRU || - $lex->compiler->php_handling === Smarty::PHP_QUOTE) - ) { - return; - } - $start = $lex->counter + strlen($lex->value); - $body = true; - if (preg_match('~' . preg_quote($closeTag, '~') . '~i', $lex->data, $match, PREG_OFFSET_CAPTURE, $start)) { - $close = $match[ 0 ][ 1 ]; - } else { - $lex->compiler->trigger_template_error("missing closing tag '{$closeTag}'"); - } - while ($body) { - if (preg_match( - '~([/][*])|([/][/][^\n]*)|(\'[^\'\\\\]*(?:\\.[^\'\\\\]*)*\')|("[^"\\\\]*(?:\\.[^"\\\\]*)*")~', - $lex->data, - $match, - PREG_OFFSET_CAPTURE, - $start - ) - ) { - $value = $match[ 0 ][ 0 ]; - $from = $pos = $match[ 0 ][ 1 ]; - if ($pos > $close) { - $body = false; - } else { - $start = $pos + strlen($value); - $phpCommentStart = $value === '/*'; - if ($phpCommentStart) { - $phpCommentEnd = preg_match('~([*][/])~', $lex->data, $match, PREG_OFFSET_CAPTURE, $start); - if ($phpCommentEnd) { - $pos2 = $match[ 0 ][ 1 ]; - $start = $pos2 + strlen($match[ 0 ][ 0 ]); - } - } - while ($close > $pos && $close < $start) { - if (preg_match( - '~' . preg_quote($closeTag, '~') . '~i', - $lex->data, - $match, - PREG_OFFSET_CAPTURE, - $from - ) - ) { - $close = $match[ 0 ][ 1 ]; - $from = $close + strlen($match[ 0 ][ 0 ]); - } else { - $lex->compiler->trigger_template_error("missing closing tag '{$closeTag}'"); - } - } - if ($phpCommentStart && (!$phpCommentEnd || $pos2 > $close)) { - $lex->taglineno = $lex->line + substr_count(substr($lex->data, $lex->counter, $start), "\n"); - $lex->compiler->trigger_template_error("missing PHP comment closing tag '*/'"); - } - } - } else { - $body = false; - } - } - $lex->value = substr($lex->data, $lex->counter, $close + strlen($closeTag) - $lex->counter); - } - - /* - * Call back function for $php_handling = PHP_QUOTE - * - */ - /** - * @param $match - * - * @return string - */ - private function quote($match) - { - return htmlspecialchars($match[ 0 ], ENT_QUOTES); - } -} diff --git a/WEB-INF/lib/smarty/sysplugins/smarty_internal_compile_private_print_expression.php b/WEB-INF/lib/smarty/sysplugins/smarty_internal_compile_private_print_expression.php index 23cae8aef..96bd37244 100644 --- a/WEB-INF/lib/smarty/sysplugins/smarty_internal_compile_private_print_expression.php +++ b/WEB-INF/lib/smarty/sysplugins/smarty_internal_compile_private_print_expression.php @@ -93,7 +93,7 @@ public function compile($args, Smarty_Internal_TemplateCompilerBase $compiler, $ } // autoescape html if ($compiler->template->smarty->escape_html) { - $output = "htmlspecialchars({$output}, ENT_QUOTES, '" . addslashes(Smarty::$_CHARSET) . "')"; + $output = "htmlspecialchars((string) {$output}, ENT_QUOTES, '" . addslashes(Smarty::$_CHARSET) . "')"; } // loop over registered filters if (!empty($compiler->template->smarty->registered_filters[ Smarty::FILTER_VARIABLE ])) { diff --git a/WEB-INF/lib/smarty/sysplugins/smarty_internal_compile_private_special_variable.php b/WEB-INF/lib/smarty/sysplugins/smarty_internal_compile_private_special_variable.php index d53ef51ff..590cba5af 100644 --- a/WEB-INF/lib/smarty/sysplugins/smarty_internal_compile_private_special_variable.php +++ b/WEB-INF/lib/smarty/sysplugins/smarty_internal_compile_private_special_variable.php @@ -29,7 +29,7 @@ class Smarty_Internal_Compile_Private_Special_Variable extends Smarty_Internal_C public function compile($args, Smarty_Internal_TemplateCompilerBase $compiler, $parameter) { $_index = preg_split("/\]\[/", substr($parameter, 1, strlen($parameter) - 2)); - $variable = strtolower($compiler->getId($_index[ 0 ])); + $variable = smarty_strtolower_ascii($compiler->getId($_index[ 0 ])); if ($variable === false) { $compiler->trigger_template_error("special \$Smarty variable name index can not be variable", null, true); } @@ -40,7 +40,7 @@ public function compile($args, Smarty_Internal_TemplateCompilerBase $compiler, $ case 'foreach': case 'section': if (!isset(Smarty_Internal_TemplateCompilerBase::$_tag_objects[ $variable ])) { - $class = 'Smarty_Internal_Compile_' . ucfirst($variable); + $class = 'Smarty_Internal_Compile_' . smarty_ucfirst_ascii($variable); Smarty_Internal_TemplateCompilerBase::$_tag_objects[ $variable ] = new $class; } return Smarty_Internal_TemplateCompilerBase::$_tag_objects[ $variable ]->compileSpecialVariable( @@ -76,7 +76,7 @@ public function compile($args, Smarty_Internal_TemplateCompilerBase $compiler, $ $compiler->trigger_template_error("(secure mode) super globals not permitted"); break; } - $compiled_ref = '$_' . strtoupper($variable); + $compiled_ref = '$_' . smarty_strtoupper_ascii($variable); break; case 'template': return 'basename($_smarty_tpl->source->filepath)'; diff --git a/WEB-INF/lib/smarty/sysplugins/smarty_internal_config_file_compiler.php b/WEB-INF/lib/smarty/sysplugins/smarty_internal_config_file_compiler.php index 90c5dcefa..469b9667a 100644 --- a/WEB-INF/lib/smarty/sysplugins/smarty_internal_config_file_compiler.php +++ b/WEB-INF/lib/smarty/sysplugins/smarty_internal_config_file_compiler.php @@ -157,10 +157,12 @@ public function compileTemplate(Smarty_Internal_Template $template) $this->smarty->_debug->end_compile($this->template); } // template header code - $template_header = - "template->source->filepath}' */ ?>\n"; + $template_header = sprintf( + "\n", + Smarty::SMARTY_VERSION, + date("Y-m-d H:i:s"), + str_replace('*/', '* /' , $this->template->source->filepath) + ); $code = 'smarty->ext->configLoad->_loadConfigVars($_smarty_tpl, ' . var_export($this->config_data, true) . '); ?>'; return $template_header . $this->template->smarty->ext->_codeFrame->create($this->template, $code); diff --git a/WEB-INF/lib/smarty/sysplugins/smarty_internal_data.php b/WEB-INF/lib/smarty/sysplugins/smarty_internal_data.php index 98e3e57b3..1b64185b8 100644 --- a/WEB-INF/lib/smarty/sysplugins/smarty_internal_data.php +++ b/WEB-INF/lib/smarty/sysplugins/smarty_internal_data.php @@ -121,7 +121,7 @@ public function assign($tpl_var, $value = null, $nocache = false) * appends values to template variables * * @api Smarty::append() - * @link http://www.smarty.net/docs/en/api.append.tpl + * @link https://www.smarty.net/docs/en/api.append.tpl * * @param array|string $tpl_var the template variable name(s) * @param mixed $value the value to append @@ -182,7 +182,7 @@ public function assignByRef($tpl_var, &$value, $nocache = false) * Returns a single or all template variables * * @api Smarty::getTemplateVars() - * @link http://www.smarty.net/docs/en/api.get.template.vars.tpl + * @link https://www.smarty.net/docs/en/api.get.template.vars.tpl * * @param string $varName variable name or null * @param \Smarty_Internal_Data|\Smarty_Internal_Template|\Smarty $_ptr optional pointer to data object @@ -195,26 +195,6 @@ public function getTemplateVars($varName = null, Smarty_Internal_Data $_ptr = nu return $this->ext->getTemplateVars->getTemplateVars($this, $varName, $_ptr, $searchParents); } - /** - * gets the object of a Smarty variable - * - * @param string $variable the name of the Smarty variable - * @param Smarty_Internal_Data $_ptr optional pointer to data object - * @param boolean $searchParents search also in parent data - * @param bool $error_enable - * - * @return Smarty_Variable|Smarty_Undefined_Variable the object of the variable - * @deprecated since 3.1.28 please use Smarty_Internal_Data::getTemplateVars() instead. - */ - public function getVariable( - $variable = null, - Smarty_Internal_Data $_ptr = null, - $searchParents = true, - $error_enable = true - ) { - return $this->ext->getTemplateVars->_getVariable($this, $variable, $_ptr, $searchParents, $error_enable); - } - /** * Follow the parent chain an merge template and config variables * diff --git a/WEB-INF/lib/smarty/sysplugins/smarty_internal_debug.php b/WEB-INF/lib/smarty/sysplugins/smarty_internal_debug.php index 24b233e26..570819d26 100644 --- a/WEB-INF/lib/smarty/sysplugins/smarty_internal_debug.php +++ b/WEB-INF/lib/smarty/sysplugins/smarty_internal_debug.php @@ -210,7 +210,7 @@ public function display_debug($obj, $full = false) // copy the working dirs from application $debObj->setCompileDir($smarty->getCompileDir()); // init properties by hand as user may have edited the original Smarty class - $debObj->setPluginsDir(is_dir(dirname(__FILE__) . '/../plugins') ? dirname(__FILE__) . + $debObj->setPluginsDir(is_dir(__DIR__ . '/../plugins') ? __DIR__ . '/../plugins' : $smarty->getPluginsDir()); $debObj->force_compile = false; $debObj->compile_check = Smarty::COMPILECHECK_ON; @@ -221,7 +221,7 @@ public function display_debug($obj, $full = false) $debObj->debugging_ctrl = 'NONE'; $debObj->error_reporting = E_ALL & ~E_NOTICE; $debObj->debug_tpl = - isset($smarty->debug_tpl) ? $smarty->debug_tpl : 'file:' . dirname(__FILE__) . '/../debug.tpl'; + isset($smarty->debug_tpl) ? $smarty->debug_tpl : 'file:' . __DIR__ . '/../debug.tpl'; $debObj->registered_plugins = array(); $debObj->registered_resources = array(); $debObj->registered_filters = array(); diff --git a/WEB-INF/lib/smarty/sysplugins/smarty_internal_errorhandler.php b/WEB-INF/lib/smarty/sysplugins/smarty_internal_errorhandler.php index 56dca18fa..c2b653ef4 100644 --- a/WEB-INF/lib/smarty/sysplugins/smarty_internal_errorhandler.php +++ b/WEB-INF/lib/smarty/sysplugins/smarty_internal_errorhandler.php @@ -1,61 +1,60 @@ previousErrorHandler = set_error_handler([$this, 'handleError']); + } + + /** + * Disable error handler + */ + public function deactivate() { + restore_error_handler(); + $this->previousErrorHandler = null; } /** * Error Handler to mute expected messages * - * @link http://php.net/set_error_handler + * @link https://php.net/set_error_handler * * @param integer $errno Error level * @param $errstr @@ -65,49 +64,21 @@ public static function muteExpectedErrors() * * @return bool */ - public static function mutingErrorHandler($errno, $errstr, $errfile, $errline, $errcontext = array()) + public function handleError($errno, $errstr, $errfile, $errline, $errcontext = []) { - $_is_muted_directory = false; - // add the SMARTY_DIR to the list of muted directories - if (!isset(self::$mutedDirectories[ SMARTY_DIR ])) { - $smarty_dir = realpath(SMARTY_DIR); - if ($smarty_dir !== false) { - self::$mutedDirectories[ SMARTY_DIR ] = - array('file' => $smarty_dir, 'length' => strlen($smarty_dir),); - } + if ($this->allowUndefinedVars && $errstr == 'Attempt to read property "value" on null') { + return; // suppresses this error } - // walk the muted directories and test against $errfile - foreach (self::$mutedDirectories as $key => &$dir) { - if (!$dir) { - // resolve directory and length for speedy comparisons - $file = realpath($key); - if ($file === false) { - // this directory does not exist, remove and skip it - unset(self::$mutedDirectories[ $key ]); - continue; - } - $dir = array('file' => $file, 'length' => strlen($file),); - } - if (!strncmp($errfile, $dir[ 'file' ], $dir[ 'length' ])) { - $_is_muted_directory = true; - break; - } - } - // pass to next error handler if this error did not occur inside SMARTY_DIR - // or the error was within smarty but masked to be ignored - if (!$_is_muted_directory || ($errno && $errno & error_reporting())) { - if (self::$previousErrorHandler) { - return call_user_func( - self::$previousErrorHandler, - $errno, - $errstr, - $errfile, - $errline, - $errcontext - ); - } else { - return false; - } + + if ($this->allowUndefinedArrayKeys && preg_match( + '/^(Undefined array key|Trying to access array offset on value of type null)/', + $errstr + )) { + return; // suppresses this error } + + // pass all other errors through to the previous error handler or to the default PHP error handler + return $this->previousErrorHandler ? + call_user_func($this->previousErrorHandler, $errno, $errstr, $errfile, $errline, $errcontext) : false; } } diff --git a/WEB-INF/lib/smarty/sysplugins/smarty_internal_extension_handler.php b/WEB-INF/lib/smarty/sysplugins/smarty_internal_extension_handler.php index b07615526..3ef040ab1 100644 --- a/WEB-INF/lib/smarty/sysplugins/smarty_internal_extension_handler.php +++ b/WEB-INF/lib/smarty/sysplugins/smarty_internal_extension_handler.php @@ -36,6 +36,7 @@ * @property Smarty_Internal_Method_RegisterPlugin $registerPlugin * @property mixed|\Smarty_Template_Cached configLoad */ +#[\AllowDynamicProperties] class Smarty_Internal_Extension_Handler { public $objType = null; @@ -88,20 +89,19 @@ public function _callExternalMethod(Smarty_Internal_Data $data, $name, $args) $objType = $data->_objType; $propertyType = false; if (!isset($this->resolvedProperties[ $match[ 0 ] ][ $objType ])) { - $property = isset($this->resolvedProperties[ 'property' ][ $basename ]) ? - $this->resolvedProperties[ 'property' ][ $basename ] : - $property = $this->resolvedProperties[ 'property' ][ $basename ] = strtolower( - join( - '_', - preg_split( - '/([A-Z][^A-Z]*)/', - $basename, - -1, - PREG_SPLIT_NO_EMPTY | - PREG_SPLIT_DELIM_CAPTURE - ) + $property = $this->resolvedProperties['property'][$basename] ?? + $this->resolvedProperties['property'][$basename] = smarty_strtolower_ascii( + join( + '_', + preg_split( + '/([A-Z][^A-Z]*)/', + $basename, + -1, + PREG_SPLIT_NO_EMPTY | + PREG_SPLIT_DELIM_CAPTURE ) - ); + ) + ); if ($property !== false) { if (property_exists($data, $property)) { $propertyType = $this->resolvedProperties[ $match[ 0 ] ][ $objType ] = 1; @@ -145,7 +145,7 @@ public function _callExternalMethod(Smarty_Internal_Data $data, $name, $args) public function upperCase($name) { $_name = explode('_', $name); - $_name = array_map('ucfirst', $_name); + $_name = array_map('smarty_ucfirst_ascii', $_name); return implode('_', $_name); } diff --git a/WEB-INF/lib/smarty/sysplugins/smarty_internal_method_append.php b/WEB-INF/lib/smarty/sysplugins/smarty_internal_method_append.php index 881375efe..e207734e8 100644 --- a/WEB-INF/lib/smarty/sysplugins/smarty_internal_method_append.php +++ b/WEB-INF/lib/smarty/sysplugins/smarty_internal_method_append.php @@ -22,7 +22,7 @@ class Smarty_Internal_Method_Append * appends values to template variables * * @api Smarty::append() - * @link http://www.smarty.net/docs/en/api.append.tpl + * @link https://www.smarty.net/docs/en/api.append.tpl * * @param \Smarty_Internal_Data|\Smarty_Internal_Template|\Smarty $data * @param array|string $tpl_var the template variable name(s) diff --git a/WEB-INF/lib/smarty/sysplugins/smarty_internal_method_appendbyref.php b/WEB-INF/lib/smarty/sysplugins/smarty_internal_method_appendbyref.php index c95904460..b5be69b54 100644 --- a/WEB-INF/lib/smarty/sysplugins/smarty_internal_method_appendbyref.php +++ b/WEB-INF/lib/smarty/sysplugins/smarty_internal_method_appendbyref.php @@ -15,7 +15,7 @@ class Smarty_Internal_Method_AppendByRef * appends values to template variables by reference * * @api Smarty::appendByRef() - * @link http://www.smarty.net/docs/en/api.append.by.ref.tpl + * @link https://www.smarty.net/docs/en/api.append.by.ref.tpl * * @param \Smarty_Internal_Data|\Smarty_Internal_Template|\Smarty $data * @param string $tpl_var the template variable name diff --git a/WEB-INF/lib/smarty/sysplugins/smarty_internal_method_clearallassign.php b/WEB-INF/lib/smarty/sysplugins/smarty_internal_method_clearallassign.php index 29ff2ffb0..6fb0c8f3d 100644 --- a/WEB-INF/lib/smarty/sysplugins/smarty_internal_method_clearallassign.php +++ b/WEB-INF/lib/smarty/sysplugins/smarty_internal_method_clearallassign.php @@ -22,7 +22,7 @@ class Smarty_Internal_Method_ClearAllAssign * clear all the assigned template variables. * * @api Smarty::clearAllAssign() - * @link http://www.smarty.net/docs/en/api.clear.all.assign.tpl + * @link https://www.smarty.net/docs/en/api.clear.all.assign.tpl * * @param \Smarty_Internal_Data|\Smarty_Internal_Template|\Smarty $data * diff --git a/WEB-INF/lib/smarty/sysplugins/smarty_internal_method_clearallcache.php b/WEB-INF/lib/smarty/sysplugins/smarty_internal_method_clearallcache.php index 30d55f7d2..b74d30580 100644 --- a/WEB-INF/lib/smarty/sysplugins/smarty_internal_method_clearallcache.php +++ b/WEB-INF/lib/smarty/sysplugins/smarty_internal_method_clearallcache.php @@ -22,7 +22,7 @@ class Smarty_Internal_Method_ClearAllCache * Empty cache folder * * @api Smarty::clearAllCache() - * @link http://www.smarty.net/docs/en/api.clear.all.cache.tpl + * @link https://www.smarty.net/docs/en/api.clear.all.cache.tpl * * @param \Smarty $smarty * @param integer $exp_time expiration time diff --git a/WEB-INF/lib/smarty/sysplugins/smarty_internal_method_clearassign.php b/WEB-INF/lib/smarty/sysplugins/smarty_internal_method_clearassign.php index 22bfa2d31..12b755c06 100644 --- a/WEB-INF/lib/smarty/sysplugins/smarty_internal_method_clearassign.php +++ b/WEB-INF/lib/smarty/sysplugins/smarty_internal_method_clearassign.php @@ -22,7 +22,7 @@ class Smarty_Internal_Method_ClearAssign * clear the given assigned template variable(s). * * @api Smarty::clearAssign() - * @link http://www.smarty.net/docs/en/api.clear.assign.tpl + * @link https://www.smarty.net/docs/en/api.clear.assign.tpl * * @param \Smarty_Internal_Data|\Smarty_Internal_Template|\Smarty $data * @param string|array $tpl_var the template variable(s) to clear diff --git a/WEB-INF/lib/smarty/sysplugins/smarty_internal_method_clearcache.php b/WEB-INF/lib/smarty/sysplugins/smarty_internal_method_clearcache.php index a5dd4e26e..df766eee8 100644 --- a/WEB-INF/lib/smarty/sysplugins/smarty_internal_method_clearcache.php +++ b/WEB-INF/lib/smarty/sysplugins/smarty_internal_method_clearcache.php @@ -22,7 +22,7 @@ class Smarty_Internal_Method_ClearCache * Empty cache for a specific template * * @api Smarty::clearCache() - * @link http://www.smarty.net/docs/en/api.clear.cache.tpl + * @link https://www.smarty.net/docs/en/api.clear.cache.tpl * * @param \Smarty $smarty * @param string $template_name template name diff --git a/WEB-INF/lib/smarty/sysplugins/smarty_internal_method_clearcompiledtemplate.php b/WEB-INF/lib/smarty/sysplugins/smarty_internal_method_clearcompiledtemplate.php index bf4929807..db0a49b00 100644 --- a/WEB-INF/lib/smarty/sysplugins/smarty_internal_method_clearcompiledtemplate.php +++ b/WEB-INF/lib/smarty/sysplugins/smarty_internal_method_clearcompiledtemplate.php @@ -22,7 +22,7 @@ class Smarty_Internal_Method_ClearCompiledTemplate * Delete compiled template file * * @api Smarty::clearCompiledTemplate() - * @link http://www.smarty.net/docs/en/api.clear.compiled.template.tpl + * @link https://www.smarty.net/docs/en/api.clear.compiled.template.tpl * * @param \Smarty $smarty * @param string $resource_name template name diff --git a/WEB-INF/lib/smarty/sysplugins/smarty_internal_method_clearconfig.php b/WEB-INF/lib/smarty/sysplugins/smarty_internal_method_clearconfig.php index 15bf492a1..d1b730322 100644 --- a/WEB-INF/lib/smarty/sysplugins/smarty_internal_method_clearconfig.php +++ b/WEB-INF/lib/smarty/sysplugins/smarty_internal_method_clearconfig.php @@ -22,7 +22,7 @@ class Smarty_Internal_Method_ClearConfig * clear a single or all config variables * * @api Smarty::clearConfig() - * @link http://www.smarty.net/docs/en/api.clear.config.tpl + * @link https://www.smarty.net/docs/en/api.clear.config.tpl * * @param \Smarty_Internal_Data|\Smarty_Internal_Template|\Smarty $data * @param string|null $name variable name or null diff --git a/WEB-INF/lib/smarty/sysplugins/smarty_internal_method_configload.php b/WEB-INF/lib/smarty/sysplugins/smarty_internal_method_configload.php index 2e6254880..c3174d2d0 100644 --- a/WEB-INF/lib/smarty/sysplugins/smarty_internal_method_configload.php +++ b/WEB-INF/lib/smarty/sysplugins/smarty_internal_method_configload.php @@ -22,7 +22,7 @@ class Smarty_Internal_Method_ConfigLoad * load a config file, optionally load just selected sections * * @api Smarty::configLoad() - * @link http://www.smarty.net/docs/en/api.config.load.tpl + * @link https://www.smarty.net/docs/en/api.config.load.tpl * * @param \Smarty_Internal_Data|\Smarty_Internal_Template|\Smarty $data * @param string $config_file filename @@ -42,7 +42,7 @@ public function configLoad(Smarty_Internal_Data $data, $config_file, $sections = * load a config file, optionally load just selected sections * * @api Smarty::configLoad() - * @link http://www.smarty.net/docs/en/api.config.load.tpl + * @link https://www.smarty.net/docs/en/api.config.load.tpl * * @param \Smarty|\Smarty_Internal_Data|\Smarty_Internal_Template $data * @param string $config_file filename diff --git a/WEB-INF/lib/smarty/sysplugins/smarty_internal_method_createdata.php b/WEB-INF/lib/smarty/sysplugins/smarty_internal_method_createdata.php index f95097519..c684c0870 100644 --- a/WEB-INF/lib/smarty/sysplugins/smarty_internal_method_createdata.php +++ b/WEB-INF/lib/smarty/sysplugins/smarty_internal_method_createdata.php @@ -22,7 +22,7 @@ class Smarty_Internal_Method_CreateData * creates a data object * * @api Smarty::createData() - * @link http://www.smarty.net/docs/en/api.create.data.tpl + * @link https://www.smarty.net/docs/en/api.create.data.tpl * * @param \Smarty_Internal_TemplateBase|\Smarty_Internal_Template|\Smarty $obj * @param \Smarty_Internal_Template|\Smarty_Internal_Data|\Smarty_Data|\Smarty $parent next higher level of Smarty diff --git a/WEB-INF/lib/smarty/sysplugins/smarty_internal_method_getconfigvars.php b/WEB-INF/lib/smarty/sysplugins/smarty_internal_method_getconfigvars.php index 1d11e44c1..763bdf989 100644 --- a/WEB-INF/lib/smarty/sysplugins/smarty_internal_method_getconfigvars.php +++ b/WEB-INF/lib/smarty/sysplugins/smarty_internal_method_getconfigvars.php @@ -22,7 +22,7 @@ class Smarty_Internal_Method_GetConfigVars * Returns a single or all config variables * * @api Smarty::getConfigVars() - * @link http://www.smarty.net/docs/en/api.get.config.vars.tpl + * @link https://www.smarty.net/docs/en/api.get.config.vars.tpl * * @param \Smarty_Internal_Data|\Smarty_Internal_Template|\Smarty $data * @param string $varname variable name or null diff --git a/WEB-INF/lib/smarty/sysplugins/smarty_internal_method_getregisteredobject.php b/WEB-INF/lib/smarty/sysplugins/smarty_internal_method_getregisteredobject.php index df6ede130..0b3a071d3 100644 --- a/WEB-INF/lib/smarty/sysplugins/smarty_internal_method_getregisteredobject.php +++ b/WEB-INF/lib/smarty/sysplugins/smarty_internal_method_getregisteredobject.php @@ -22,7 +22,7 @@ class Smarty_Internal_Method_GetRegisteredObject * return a reference to a registered object * * @api Smarty::getRegisteredObject() - * @link http://www.smarty.net/docs/en/api.get.registered.object.tpl + * @link https://www.smarty.net/docs/en/api.get.registered.object.tpl * * @param \Smarty_Internal_TemplateBase|\Smarty_Internal_Template|\Smarty $obj * @param string $object_name object name diff --git a/WEB-INF/lib/smarty/sysplugins/smarty_internal_method_gettags.php b/WEB-INF/lib/smarty/sysplugins/smarty_internal_method_gettags.php index c07ae07ec..0d1335a8d 100644 --- a/WEB-INF/lib/smarty/sysplugins/smarty_internal_method_gettags.php +++ b/WEB-INF/lib/smarty/sysplugins/smarty_internal_method_gettags.php @@ -22,7 +22,7 @@ class Smarty_Internal_Method_GetTags * Return array of tag/attributes of all tags used by an template * * @api Smarty::getTags() - * @link http://www.smarty.net/docs/en/api.get.tags.tpl + * @link https://www.smarty.net/docs/en/api.get.tags.tpl * * @param \Smarty_Internal_TemplateBase|\Smarty_Internal_Template|\Smarty $obj * @param null|string|Smarty_Internal_Template $template diff --git a/WEB-INF/lib/smarty/sysplugins/smarty_internal_method_gettemplatevars.php b/WEB-INF/lib/smarty/sysplugins/smarty_internal_method_gettemplatevars.php index 9ef7d46bb..0470785b1 100644 --- a/WEB-INF/lib/smarty/sysplugins/smarty_internal_method_gettemplatevars.php +++ b/WEB-INF/lib/smarty/sysplugins/smarty_internal_method_gettemplatevars.php @@ -22,7 +22,7 @@ class Smarty_Internal_Method_GetTemplateVars * Returns a single or all template variables * * @api Smarty::getTemplateVars() - * @link http://www.smarty.net/docs/en/api.get.template.vars.tpl + * @link https://www.smarty.net/docs/en/api.get.template.vars.tpl * * @param \Smarty_Internal_Data|\Smarty_Internal_Template|\Smarty $data * @param string $varName variable name or null diff --git a/WEB-INF/lib/smarty/sysplugins/smarty_internal_method_loadfilter.php b/WEB-INF/lib/smarty/sysplugins/smarty_internal_method_loadfilter.php index 66d80d474..af788a24e 100644 --- a/WEB-INF/lib/smarty/sysplugins/smarty_internal_method_loadfilter.php +++ b/WEB-INF/lib/smarty/sysplugins/smarty_internal_method_loadfilter.php @@ -30,7 +30,7 @@ class Smarty_Internal_Method_LoadFilter * * @api Smarty::loadFilter() * - * @link http://www.smarty.net/docs/en/api.load.filter.tpl + * @link https://www.smarty.net/docs/en/api.load.filter.tpl * * @param \Smarty_Internal_TemplateBase|\Smarty_Internal_Template|\Smarty $obj * @param string $type filter type diff --git a/WEB-INF/lib/smarty/sysplugins/smarty_internal_method_loadplugin.php b/WEB-INF/lib/smarty/sysplugins/smarty_internal_method_loadplugin.php index 3bd659cb8..6ddcaec94 100644 --- a/WEB-INF/lib/smarty/sysplugins/smarty_internal_method_loadplugin.php +++ b/WEB-INF/lib/smarty/sysplugins/smarty_internal_method_loadplugin.php @@ -40,7 +40,7 @@ public function loadPlugin(Smarty $smarty, $plugin_name, $check) throw new SmartyException("plugin {$plugin_name} is not a valid name format"); } if (!empty($match[ 2 ])) { - $file = SMARTY_SYSPLUGINS_DIR . strtolower($plugin_name) . '.php'; + $file = SMARTY_SYSPLUGINS_DIR . smarty_strtolower_ascii($plugin_name) . '.php'; if (isset($this->plugin_files[ $file ])) { if ($this->plugin_files[ $file ] !== false) { return $this->plugin_files[ $file ]; @@ -60,7 +60,7 @@ public function loadPlugin(Smarty $smarty, $plugin_name, $check) } // plugin filename is expected to be: [type].[name].php $_plugin_filename = "{$match[1]}.{$match[4]}.php"; - $_lower_filename = strtolower($_plugin_filename); + $_lower_filename = smarty_strtolower_ascii($_plugin_filename); if (isset($this->plugin_files)) { if (isset($this->plugin_files[ 'plugins_dir' ][ $_lower_filename ])) { if (!$smarty->use_include_path || $this->plugin_files[ 'plugins_dir' ][ $_lower_filename ] !== false) { diff --git a/WEB-INF/lib/smarty/sysplugins/smarty_internal_method_mustcompile.php b/WEB-INF/lib/smarty/sysplugins/smarty_internal_method_mustcompile.php index 39318838e..381346c8f 100644 --- a/WEB-INF/lib/smarty/sysplugins/smarty_internal_method_mustcompile.php +++ b/WEB-INF/lib/smarty/sysplugins/smarty_internal_method_mustcompile.php @@ -32,7 +32,7 @@ public function mustCompile(Smarty_Internal_Template $_template) { if (!$_template->source->exists) { if ($_template->_isSubTpl()) { - $parent_resource = " in '$_template->parent->template_resource}'"; + $parent_resource = " in '{$_template->parent->template_resource}'"; } else { $parent_resource = ''; } diff --git a/WEB-INF/lib/smarty/sysplugins/smarty_internal_method_registercacheresource.php b/WEB-INF/lib/smarty/sysplugins/smarty_internal_method_registercacheresource.php index 648365619..5608b3fd0 100644 --- a/WEB-INF/lib/smarty/sysplugins/smarty_internal_method_registercacheresource.php +++ b/WEB-INF/lib/smarty/sysplugins/smarty_internal_method_registercacheresource.php @@ -22,7 +22,7 @@ class Smarty_Internal_Method_RegisterCacheResource * Registers a resource to fetch a template * * @api Smarty::registerCacheResource() - * @link http://www.smarty.net/docs/en/api.register.cacheresource.tpl + * @link https://www.smarty.net/docs/en/api.register.cacheresource.tpl * * @param \Smarty_Internal_TemplateBase|\Smarty_Internal_Template|\Smarty $obj * @param string $name name of resource type diff --git a/WEB-INF/lib/smarty/sysplugins/smarty_internal_method_registerclass.php b/WEB-INF/lib/smarty/sysplugins/smarty_internal_method_registerclass.php index 8d18547e2..76a69c6e5 100644 --- a/WEB-INF/lib/smarty/sysplugins/smarty_internal_method_registerclass.php +++ b/WEB-INF/lib/smarty/sysplugins/smarty_internal_method_registerclass.php @@ -22,7 +22,7 @@ class Smarty_Internal_Method_RegisterClass * Registers static classes to be used in templates * * @api Smarty::registerClass() - * @link http://www.smarty.net/docs/en/api.register.class.tpl + * @link https://www.smarty.net/docs/en/api.register.class.tpl * * @param \Smarty_Internal_TemplateBase|\Smarty_Internal_Template|\Smarty $obj * @param string $class_name diff --git a/WEB-INF/lib/smarty/sysplugins/smarty_internal_method_registerdefaultpluginhandler.php b/WEB-INF/lib/smarty/sysplugins/smarty_internal_method_registerdefaultpluginhandler.php index a9fb78dc6..4cda5b056 100644 --- a/WEB-INF/lib/smarty/sysplugins/smarty_internal_method_registerdefaultpluginhandler.php +++ b/WEB-INF/lib/smarty/sysplugins/smarty_internal_method_registerdefaultpluginhandler.php @@ -22,7 +22,7 @@ class Smarty_Internal_Method_RegisterDefaultPluginHandler * Registers a default plugin handler * * @api Smarty::registerDefaultPluginHandler() - * @link http://www.smarty.net/docs/en/api.register.default.plugin.handler.tpl + * @link https://www.smarty.net/docs/en/api.register.default.plugin.handler.tpl * * @param \Smarty_Internal_TemplateBase|\Smarty_Internal_Template|\Smarty $obj * @param callable $callback class/method name diff --git a/WEB-INF/lib/smarty/sysplugins/smarty_internal_method_registerfilter.php b/WEB-INF/lib/smarty/sysplugins/smarty_internal_method_registerfilter.php index c0f9fff10..9719eb2b6 100644 --- a/WEB-INF/lib/smarty/sysplugins/smarty_internal_method_registerfilter.php +++ b/WEB-INF/lib/smarty/sysplugins/smarty_internal_method_registerfilter.php @@ -30,7 +30,7 @@ class Smarty_Internal_Method_RegisterFilter * * @api Smarty::registerFilter() * - * @link http://www.smarty.net/docs/en/api.register.filter.tpl + * @link https://www.smarty.net/docs/en/api.register.filter.tpl * * @param \Smarty_Internal_TemplateBase|\Smarty_Internal_Template|\Smarty $obj * @param string $type filter type diff --git a/WEB-INF/lib/smarty/sysplugins/smarty_internal_method_registerobject.php b/WEB-INF/lib/smarty/sysplugins/smarty_internal_method_registerobject.php index 4646e4f22..8e6fe0521 100644 --- a/WEB-INF/lib/smarty/sysplugins/smarty_internal_method_registerobject.php +++ b/WEB-INF/lib/smarty/sysplugins/smarty_internal_method_registerobject.php @@ -22,7 +22,7 @@ class Smarty_Internal_Method_RegisterObject * Registers object to be used in templates * * @api Smarty::registerObject() - * @link http://www.smarty.net/docs/en/api.register.object.tpl + * @link https://www.smarty.net/docs/en/api.register.object.tpl * * @param \Smarty_Internal_TemplateBase|\Smarty_Internal_Template|\Smarty $obj * @param string $object_name diff --git a/WEB-INF/lib/smarty/sysplugins/smarty_internal_method_registerplugin.php b/WEB-INF/lib/smarty/sysplugins/smarty_internal_method_registerplugin.php index ed18d84bb..74c0ae908 100644 --- a/WEB-INF/lib/smarty/sysplugins/smarty_internal_method_registerplugin.php +++ b/WEB-INF/lib/smarty/sysplugins/smarty_internal_method_registerplugin.php @@ -22,7 +22,7 @@ class Smarty_Internal_Method_RegisterPlugin * Registers plugin to be used in templates * * @api Smarty::registerPlugin() - * @link http://www.smarty.net/docs/en/api.register.plugin.tpl + * @link https://www.smarty.net/docs/en/api.register.plugin.tpl * * @param \Smarty_Internal_TemplateBase|\Smarty_Internal_Template|\Smarty $obj * @param string $type plugin type diff --git a/WEB-INF/lib/smarty/sysplugins/smarty_internal_method_registerresource.php b/WEB-INF/lib/smarty/sysplugins/smarty_internal_method_registerresource.php index 7c7d0f78a..302657ae0 100644 --- a/WEB-INF/lib/smarty/sysplugins/smarty_internal_method_registerresource.php +++ b/WEB-INF/lib/smarty/sysplugins/smarty_internal_method_registerresource.php @@ -22,25 +22,18 @@ class Smarty_Internal_Method_RegisterResource * Registers a resource to fetch a template * * @api Smarty::registerResource() - * @link http://www.smarty.net/docs/en/api.register.resource.tpl + * @link https://www.smarty.net/docs/en/api.register.resource.tpl * * @param \Smarty_Internal_TemplateBase|\Smarty_Internal_Template|\Smarty $obj * @param string $name name of resource type - * @param Smarty_Resource|array $resource_handler or instance of - * Smarty_Resource, - * or array of - * callbacks to - * handle - * resource - * (deprecated) + * @param Smarty_Resource $resource_handler instance of Smarty_Resource * * @return \Smarty|\Smarty_Internal_Template */ - public function registerResource(Smarty_Internal_TemplateBase $obj, $name, $resource_handler) + public function registerResource(Smarty_Internal_TemplateBase $obj, $name, Smarty_Resource $resource_handler) { $smarty = $obj->_getSmartyObj(); - $smarty->registered_resources[ $name ] = - $resource_handler instanceof Smarty_Resource ? $resource_handler : array($resource_handler, false); + $smarty->registered_resources[ $name ] = $resource_handler; return $obj; } } diff --git a/WEB-INF/lib/smarty/sysplugins/smarty_internal_method_unloadfilter.php b/WEB-INF/lib/smarty/sysplugins/smarty_internal_method_unloadfilter.php index 55e1596be..e41e8dffc 100644 --- a/WEB-INF/lib/smarty/sysplugins/smarty_internal_method_unloadfilter.php +++ b/WEB-INF/lib/smarty/sysplugins/smarty_internal_method_unloadfilter.php @@ -16,7 +16,7 @@ class Smarty_Internal_Method_UnloadFilter extends Smarty_Internal_Method_LoadFil * * @api Smarty::unloadFilter() * - * @link http://www.smarty.net/docs/en/api.unload.filter.tpl + * @link https://www.smarty.net/docs/en/api.unload.filter.tpl * * @param \Smarty_Internal_TemplateBase|\Smarty_Internal_Template|\Smarty $obj * @param string $type filter type diff --git a/WEB-INF/lib/smarty/sysplugins/smarty_internal_method_unregistercacheresource.php b/WEB-INF/lib/smarty/sysplugins/smarty_internal_method_unregistercacheresource.php index b99903867..377397e97 100644 --- a/WEB-INF/lib/smarty/sysplugins/smarty_internal_method_unregistercacheresource.php +++ b/WEB-INF/lib/smarty/sysplugins/smarty_internal_method_unregistercacheresource.php @@ -22,7 +22,7 @@ class Smarty_Internal_Method_UnregisterCacheResource * Registers a resource to fetch a template * * @api Smarty::unregisterCacheResource() - * @link http://www.smarty.net/docs/en/api.unregister.cacheresource.tpl + * @link https://www.smarty.net/docs/en/api.unregister.cacheresource.tpl * * @param \Smarty_Internal_TemplateBase|\Smarty_Internal_Template|\Smarty $obj * @param $name diff --git a/WEB-INF/lib/smarty/sysplugins/smarty_internal_method_unregisterfilter.php b/WEB-INF/lib/smarty/sysplugins/smarty_internal_method_unregisterfilter.php index 9cb494a52..ebc9337d0 100644 --- a/WEB-INF/lib/smarty/sysplugins/smarty_internal_method_unregisterfilter.php +++ b/WEB-INF/lib/smarty/sysplugins/smarty_internal_method_unregisterfilter.php @@ -16,7 +16,7 @@ class Smarty_Internal_Method_UnregisterFilter extends Smarty_Internal_Method_Reg * * @api Smarty::unregisterFilter() * - * @link http://www.smarty.net/docs/en/api.unregister.filter.tpl + * @link https://www.smarty.net/docs/en/api.unregister.filter.tpl * * @param \Smarty_Internal_TemplateBase|\Smarty_Internal_Template|\Smarty $obj * @param string $type filter type diff --git a/WEB-INF/lib/smarty/sysplugins/smarty_internal_method_unregisterobject.php b/WEB-INF/lib/smarty/sysplugins/smarty_internal_method_unregisterobject.php index 1e592b339..77d619637 100644 --- a/WEB-INF/lib/smarty/sysplugins/smarty_internal_method_unregisterobject.php +++ b/WEB-INF/lib/smarty/sysplugins/smarty_internal_method_unregisterobject.php @@ -22,7 +22,7 @@ class Smarty_Internal_Method_UnregisterObject * Registers plugin to be used in templates * * @api Smarty::unregisterObject() - * @link http://www.smarty.net/docs/en/api.unregister.object.tpl + * @link https://www.smarty.net/docs/en/api.unregister.object.tpl * * @param \Smarty_Internal_TemplateBase|\Smarty_Internal_Template|\Smarty $obj * @param string $object_name name of object diff --git a/WEB-INF/lib/smarty/sysplugins/smarty_internal_method_unregisterplugin.php b/WEB-INF/lib/smarty/sysplugins/smarty_internal_method_unregisterplugin.php index f39e31678..2431d5c23 100644 --- a/WEB-INF/lib/smarty/sysplugins/smarty_internal_method_unregisterplugin.php +++ b/WEB-INF/lib/smarty/sysplugins/smarty_internal_method_unregisterplugin.php @@ -22,7 +22,7 @@ class Smarty_Internal_Method_UnregisterPlugin * Registers plugin to be used in templates * * @api Smarty::unregisterPlugin() - * @link http://www.smarty.net/docs/en/api.unregister.plugin.tpl + * @link https://www.smarty.net/docs/en/api.unregister.plugin.tpl * * @param \Smarty_Internal_TemplateBase|\Smarty_Internal_Template|\Smarty $obj * @param string $type plugin type diff --git a/WEB-INF/lib/smarty/sysplugins/smarty_internal_method_unregisterresource.php b/WEB-INF/lib/smarty/sysplugins/smarty_internal_method_unregisterresource.php index a79db4299..bbb6a861d 100644 --- a/WEB-INF/lib/smarty/sysplugins/smarty_internal_method_unregisterresource.php +++ b/WEB-INF/lib/smarty/sysplugins/smarty_internal_method_unregisterresource.php @@ -22,7 +22,7 @@ class Smarty_Internal_Method_UnregisterResource * Registers a resource to fetch a template * * @api Smarty::unregisterResource() - * @link http://www.smarty.net/docs/en/api.unregister.resource.tpl + * @link https://www.smarty.net/docs/en/api.unregister.resource.tpl * * @param \Smarty_Internal_TemplateBase|\Smarty_Internal_Template|\Smarty $obj * @param string $type name of resource type diff --git a/WEB-INF/lib/smarty/sysplugins/smarty_internal_parsetree_template.php b/WEB-INF/lib/smarty/sysplugins/smarty_internal_parsetree_template.php index ab4c3ec3b..829c420fe 100644 --- a/WEB-INF/lib/smarty/sysplugins/smarty_internal_parsetree_template.php +++ b/WEB-INF/lib/smarty/sysplugins/smarty_internal_parsetree_template.php @@ -87,83 +87,83 @@ public function to_smarty_php(Smarty_Internal_Templateparser $parser) $code = ''; foreach ($this->getChunkedSubtrees() as $chunk) { - $text = ''; - switch ($chunk['mode']) { - case 'textstripped': - foreach ($chunk['subtrees'] as $subtree) { - $text .= $subtree->to_smarty_php($parser); - } - $code .= preg_replace( - '/((<%)|(%>)|(<\?php)|(<\?)|(\?>)|(<\/?script))/', - "\n", - $parser->compiler->processText($text) - ); - break; - case 'text': - foreach ($chunk['subtrees'] as $subtree) { - $text .= $subtree->to_smarty_php($parser); - } - $code .= preg_replace( - '/((<%)|(%>)|(<\?php)|(<\?)|(\?>)|(<\/?script))/', - "\n", - $text - ); - break; - case 'tag': - foreach ($chunk['subtrees'] as $subtree) { - $text = $parser->compiler->appendCode($text, $subtree->to_smarty_php($parser)); - } - $code .= $text; - break; - default: - foreach ($chunk['subtrees'] as $subtree) { - $text = $subtree->to_smarty_php($parser); - } - $code .= $text; + $text = ''; + switch ($chunk['mode']) { + case 'textstripped': + foreach ($chunk['subtrees'] as $subtree) { + $text .= $subtree->to_smarty_php($parser); + } + $code .= preg_replace( + '/((<%)|(%>)|(<\?php)|(<\?)|(\?>)|(<\/?script))/', + "\n", + $parser->compiler->processText($text) + ); + break; + case 'text': + foreach ($chunk['subtrees'] as $subtree) { + $text .= $subtree->to_smarty_php($parser); + } + $code .= preg_replace( + '/((<%)|(%>)|(<\?php)|(<\?)|(\?>)|(<\/?script))/', + "\n", + $text + ); + break; + case 'tag': + foreach ($chunk['subtrees'] as $subtree) { + $text = $parser->compiler->appendCode($text, $subtree->to_smarty_php($parser)); + } + $code .= $text; + break; + default: + foreach ($chunk['subtrees'] as $subtree) { + $text = $subtree->to_smarty_php($parser); + } + $code .= $text; - } + } } return $code; } private function getChunkedSubtrees() { - $chunks = array(); - $currentMode = null; - $currentChunk = array(); - for ($key = 0, $cnt = count($this->subtrees); $key < $cnt; $key++) { + $chunks = array(); + $currentMode = null; + $currentChunk = array(); + for ($key = 0, $cnt = count($this->subtrees); $key < $cnt; $key++) { - if ($this->subtrees[ $key ]->data === '' && in_array($currentMode, array('textstripped', 'text', 'tag'))) { - continue; - } + if ($this->subtrees[ $key ]->data === '' && in_array($currentMode, array('textstripped', 'text', 'tag'))) { + continue; + } - if ($this->subtrees[ $key ] instanceof Smarty_Internal_ParseTree_Text - && $this->subtrees[ $key ]->isToBeStripped()) { - $newMode = 'textstripped'; - } elseif ($this->subtrees[ $key ] instanceof Smarty_Internal_ParseTree_Text) { - $newMode = 'text'; - } elseif ($this->subtrees[ $key ] instanceof Smarty_Internal_ParseTree_Tag) { - $newMode = 'tag'; - } else { - $newMode = 'other'; - } + if ($this->subtrees[ $key ] instanceof Smarty_Internal_ParseTree_Text + && $this->subtrees[ $key ]->isToBeStripped()) { + $newMode = 'textstripped'; + } elseif ($this->subtrees[ $key ] instanceof Smarty_Internal_ParseTree_Text) { + $newMode = 'text'; + } elseif ($this->subtrees[ $key ] instanceof Smarty_Internal_ParseTree_Tag) { + $newMode = 'tag'; + } else { + $newMode = 'other'; + } - if ($newMode == $currentMode) { - $currentChunk[] = $this->subtrees[ $key ]; - } else { - $chunks[] = array( - 'mode' => $currentMode, - 'subtrees' => $currentChunk - ); - $currentMode = $newMode; - $currentChunk = array($this->subtrees[ $key ]); - } - } - if ($currentMode && $currentChunk) { - $chunks[] = array( - 'mode' => $currentMode, - 'subtrees' => $currentChunk - ); - } - return $chunks; + if ($newMode == $currentMode) { + $currentChunk[] = $this->subtrees[ $key ]; + } else { + $chunks[] = array( + 'mode' => $currentMode, + 'subtrees' => $currentChunk + ); + $currentMode = $newMode; + $currentChunk = array($this->subtrees[ $key ]); + } + } + if ($currentMode && $currentChunk) { + $chunks[] = array( + 'mode' => $currentMode, + 'subtrees' => $currentChunk + ); + } + return $chunks; } } diff --git a/WEB-INF/lib/smarty/sysplugins/smarty_internal_parsetree_text.php b/WEB-INF/lib/smarty/sysplugins/smarty_internal_parsetree_text.php index 399e84941..58116c811 100644 --- a/WEB-INF/lib/smarty/sysplugins/smarty_internal_parsetree_text.php +++ b/WEB-INF/lib/smarty/sysplugins/smarty_internal_parsetree_text.php @@ -17,30 +17,30 @@ class Smarty_Internal_ParseTree_Text extends Smarty_Internal_ParseTree { - /** - * Wether this section should be stripped on output to smarty php - * @var bool - */ - private $toBeStripped = false; + /** + * Wether this section should be stripped on output to smarty php + * @var bool + */ + private $toBeStripped = false; - /** - * Create template text buffer - * - * @param string $data text - * @param bool $toBeStripped wether this section should be stripped on output to smarty php - */ + /** + * Create template text buffer + * + * @param string $data text + * @param bool $toBeStripped wether this section should be stripped on output to smarty php + */ public function __construct($data, $toBeStripped = false) { $this->data = $data; $this->toBeStripped = $toBeStripped; } - /** - * Wether this section should be stripped on output to smarty php - * @return bool - */ - public function isToBeStripped() { - return $this->toBeStripped; + /** + * Wether this section should be stripped on output to smarty php + * @return bool + */ + public function isToBeStripped() { + return $this->toBeStripped; } /** diff --git a/WEB-INF/lib/smarty/sysplugins/smarty_internal_resource_registered.php b/WEB-INF/lib/smarty/sysplugins/smarty_internal_resource_registered.php deleted file mode 100644 index df526101f..000000000 --- a/WEB-INF/lib/smarty/sysplugins/smarty_internal_resource_registered.php +++ /dev/null @@ -1,101 +0,0 @@ -filepath = $source->type . ':' . $source->name; - $source->uid = sha1($source->filepath . $source->smarty->_joined_template_dir); - $source->timestamp = $this->getTemplateTimestamp($source); - $source->exists = !!$source->timestamp; - } - - /** - * populate Source Object with timestamp and exists from Resource - * - * @param Smarty_Template_Source $source source object - * - * @return void - */ - public function populateTimestamp(Smarty_Template_Source $source) - { - $source->timestamp = $this->getTemplateTimestamp($source); - $source->exists = !!$source->timestamp; - } - - /** - * Get timestamp (epoch) the template source was modified - * - * @param Smarty_Template_Source $source source object - * - * @return integer|boolean timestamp (epoch) the template was modified, false if resources has no timestamp - */ - public function getTemplateTimestamp(Smarty_Template_Source $source) - { - // return timestamp - $time_stamp = false; - call_user_func_array( - $source->smarty->registered_resources[ $source->type ][ 0 ][ 1 ], - array($source->name, &$time_stamp, $source->smarty) - ); - return is_numeric($time_stamp) ? (int)$time_stamp : $time_stamp; - } - - /** - * Load template's source by invoking the registered callback into current template object - * - * @param Smarty_Template_Source $source source object - * - * @return string template source - * @throws SmartyException if source cannot be loaded - */ - public function getContent(Smarty_Template_Source $source) - { - // return template string - $content = null; - $t = call_user_func_array( - $source->smarty->registered_resources[ $source->type ][ 0 ][ 0 ], - array($source->name, &$content, $source->smarty) - ); - if (is_bool($t) && !$t) { - throw new SmartyException("Unable to read template {$source->type} '{$source->name}'"); - } - return $content; - } - - /** - * Determine basename for compiled filename - * - * @param Smarty_Template_Source $source source object - * - * @return string resource's basename - */ - public function getBasename(Smarty_Template_Source $source) - { - return basename($source->name); - } -} diff --git a/WEB-INF/lib/smarty/sysplugins/smarty_internal_resource_stream.php b/WEB-INF/lib/smarty/sysplugins/smarty_internal_resource_stream.php index 9956bd073..5f0203498 100644 --- a/WEB-INF/lib/smarty/sysplugins/smarty_internal_resource_stream.php +++ b/WEB-INF/lib/smarty/sysplugins/smarty_internal_resource_stream.php @@ -13,7 +13,7 @@ * Smarty Internal Plugin Resource Stream * Implements the streams as resource for Smarty template * - * @link http://php.net/streams + * @link https://php.net/streams * @package Smarty * @subpackage TemplateResources */ diff --git a/WEB-INF/lib/smarty/sysplugins/smarty_internal_runtime_codeframe.php b/WEB-INF/lib/smarty/sysplugins/smarty_internal_runtime_codeframe.php index 983ca6180..d0ca751e2 100644 --- a/WEB-INF/lib/smarty/sysplugins/smarty_internal_runtime_codeframe.php +++ b/WEB-INF/lib/smarty/sysplugins/smarty_internal_runtime_codeframe.php @@ -44,9 +44,12 @@ public function create( $properties[ 'file_dependency' ] = $_template->cached->file_dependency; $properties[ 'cache_lifetime' ] = $_template->cache_lifetime; } - $output = "source->filepath) . "' */\n\n"; + $output = sprintf( + "source->filepath) + ); $output .= "/* @var Smarty_Internal_Template \$_smarty_tpl */\n"; $dec = "\$_smarty_tpl->_decodeProperties(\$_smarty_tpl, " . var_export($properties, true) . ',' . ($cache ? 'true' : 'false') . ')'; diff --git a/WEB-INF/lib/smarty/sysplugins/smarty_internal_runtime_make_nocache.php b/WEB-INF/lib/smarty/sysplugins/smarty_internal_runtime_make_nocache.php index 53069148d..7994aa048 100644 --- a/WEB-INF/lib/smarty/sysplugins/smarty_internal_runtime_make_nocache.php +++ b/WEB-INF/lib/smarty/sysplugins/smarty_internal_runtime_make_nocache.php @@ -22,7 +22,7 @@ public function save(Smarty_Internal_Template $tpl, $var) { if (isset($tpl->tpl_vars[ $var ])) { $export = - preg_replace('/^Smarty_Variable::__set_state[(]|[)]$/', '', var_export($tpl->tpl_vars[ $var ], true)); + preg_replace('/^\\\\?Smarty_Variable::__set_state[(]|[)]$/', '', var_export($tpl->tpl_vars[ $var ], true)); if (preg_match('/(\w+)::__set_state/', $export, $match)) { throw new SmartyException("{make_nocache \${$var}} in template '{$tpl->source->name}': variable does contain object '{$match[1]}' not implementing method '__set_state'"); } diff --git a/WEB-INF/lib/smarty/sysplugins/smarty_internal_runtime_writefile.php b/WEB-INF/lib/smarty/sysplugins/smarty_internal_runtime_writefile.php index 4383e6f38..492d5eb25 100644 --- a/WEB-INF/lib/smarty/sysplugins/smarty_internal_runtime_writefile.php +++ b/WEB-INF/lib/smarty/sysplugins/smarty_internal_runtime_writefile.php @@ -29,12 +29,6 @@ public function writeFile($_filepath, $_contents, Smarty $smarty) { $_error_reporting = error_reporting(); error_reporting($_error_reporting & ~E_NOTICE & ~E_WARNING); - $_file_perms = property_exists($smarty, '_file_perms') ? $smarty->_file_perms : 0644; - $_dir_perms = - property_exists($smarty, '_dir_perms') ? (isset($smarty->_dir_perms) ? $smarty->_dir_perms : 0777) : 0771; - if ($_file_perms !== null) { - $old_umask = umask(0); - } $_dirpath = dirname($_filepath); // if subdirs, create dir structure if ($_dirpath !== '.') { @@ -42,7 +36,7 @@ public function writeFile($_filepath, $_contents, Smarty $smarty) // loop if concurrency problem occurs // see https://bugs.php.net/bug.php?id=35326 while (!is_dir($_dirpath)) { - if (@mkdir($_dirpath, $_dir_perms, true)) { + if (@mkdir($_dirpath, 0777, true)) { break; } clearstatcache(); @@ -89,11 +83,8 @@ public function writeFile($_filepath, $_contents, Smarty $smarty) error_reporting($_error_reporting); throw new SmartyException("unable to write file {$_filepath}"); } - if ($_file_perms !== null) { - // set file permissions - chmod($_filepath, $_file_perms); - umask($old_umask); - } + // set file permissions + @chmod($_filepath, 0666 & ~umask()); error_reporting($_error_reporting); return true; } diff --git a/WEB-INF/lib/smarty/sysplugins/smarty_internal_template.php b/WEB-INF/lib/smarty/sysplugins/smarty_internal_template.php index bae22a7d5..72d1d52e0 100644 --- a/WEB-INF/lib/smarty/sysplugins/smarty_internal_template.php +++ b/WEB-INF/lib/smarty/sysplugins/smarty_internal_template.php @@ -24,6 +24,7 @@ * * @method bool mustCompile() */ +#[\AllowDynamicProperties] class Smarty_Internal_Template extends Smarty_Internal_TemplateBase { /** @@ -292,7 +293,7 @@ public function _subTemplateRender( $smarty = &$this->smarty; $_templateId = $smarty->_getTemplateId($template, $cache_id, $compile_id, $caching, $tpl); // recursive call ? - if (isset($tpl->templateId) ? $tpl->templateId : $tpl->_getTemplateId() !== $_templateId) { + if ((isset($tpl->templateId) ? $tpl->templateId : $tpl->_getTemplateId()) !== $_templateId) { // already in template cache? if (isset(self::$tplObjCache[ $_templateId ])) { // copy data from cached object @@ -358,7 +359,7 @@ public function _subTemplateRender( } if ($tpl->caching === 9999) { if (!isset($tpl->compiled)) { - $this->loadCompiled(true); + $tpl->loadCompiled(true); } if ($tpl->compiled->has_nocache_code) { $this->cached->hashes[ $tpl->compiled->nocache_hash ] = true; diff --git a/WEB-INF/lib/smarty/sysplugins/smarty_internal_templatebase.php b/WEB-INF/lib/smarty/sysplugins/smarty_internal_templatebase.php index 200c11bb5..918362e91 100644 --- a/WEB-INF/lib/smarty/sysplugins/smarty_internal_templatebase.php +++ b/WEB-INF/lib/smarty/sysplugins/smarty_internal_templatebase.php @@ -138,7 +138,7 @@ public function display($template = null, $cache_id = null, $compile_id = null, * test if cache is valid * * @api Smarty::isCached() - * @link http://www.smarty.net/docs/en/api.is.cached.tpl + * @link https://www.smarty.net/docs/en/api.is.cached.tpl * * @param null|string|\Smarty_Internal_Template $template the resource handle of the template file or template * object @@ -199,6 +199,12 @@ private function _execute($template, $cache_id, $compile_id, $parent, $function) try { $_smarty_old_error_level = isset($smarty->error_reporting) ? error_reporting($smarty->error_reporting) : null; + + if ($smarty->isMutingUndefinedOrNullWarnings()) { + $errorHandler = new Smarty_Internal_ErrorHandler(); + $errorHandler->activate(); + } + if ($this->_objType === 2) { /* @var Smarty_Internal_Template $this */ $template->tplFunctions = $this->tplFunctions; @@ -242,14 +248,23 @@ private function _execute($template, $cache_id, $compile_id, $parent, $function) } } } + + if (isset($errorHandler)) { + $errorHandler->deactivate(); + } + if (isset($_smarty_old_error_level)) { error_reporting($_smarty_old_error_level); } return $result; - } catch (Exception $e) { + } catch (Throwable $e) { while (ob_get_level() > $level) { ob_end_clean(); } + if (isset($errorHandler)) { + $errorHandler->deactivate(); + } + if (isset($_smarty_old_error_level)) { error_reporting($_smarty_old_error_level); } @@ -261,7 +276,7 @@ private function _execute($template, $cache_id, $compile_id, $parent, $function) * Registers plugin to be used in templates * * @api Smarty::registerPlugin() - * @link http://www.smarty.net/docs/en/api.register.plugin.tpl + * @link https://www.smarty.net/docs/en/api.register.plugin.tpl * * @param string $type plugin type * @param string $name name of template tag @@ -281,7 +296,7 @@ public function registerPlugin($type, $name, $callback, $cacheable = true, $cach * load a filter of specified type and name * * @api Smarty::loadFilter() - * @link http://www.smarty.net/docs/en/api.load.filter.tpl + * @link https://www.smarty.net/docs/en/api.load.filter.tpl * * @param string $type filter type * @param string $name filter name @@ -298,7 +313,7 @@ public function loadFilter($type, $name) * Registers a filter function * * @api Smarty::registerFilter() - * @link http://www.smarty.net/docs/en/api.register.filter.tpl + * @link https://www.smarty.net/docs/en/api.register.filter.tpl * * @param string $type filter type * @param callable $callback @@ -316,7 +331,7 @@ public function registerFilter($type, $callback, $name = null) * Registers object to be used in templates * * @api Smarty::registerObject() - * @link http://www.smarty.net/docs/en/api.register.object.tpl + * @link https://www.smarty.net/docs/en/api.register.object.tpl * * @param string $object_name * @param object $object the referenced PHP object to register diff --git a/WEB-INF/lib/smarty/sysplugins/smarty_internal_templatecompilerbase.php b/WEB-INF/lib/smarty/sysplugins/smarty_internal_templatecompilerbase.php index 3cc957dec..d5c18d31a 100644 --- a/WEB-INF/lib/smarty/sysplugins/smarty_internal_templatecompilerbase.php +++ b/WEB-INF/lib/smarty/sysplugins/smarty_internal_templatecompilerbase.php @@ -203,13 +203,6 @@ abstract class Smarty_Internal_TemplateCompilerBase */ public $blockOrFunctionCode = ''; - /** - * php_handling setting either from Smarty or security - * - * @var int - */ - public $php_handling = 0; - /** * flags for used modifier plugins * @@ -429,20 +422,12 @@ public function compileTemplateSource( try { // save template object in compiler class $this->template = $template; - if (property_exists($this->template->smarty, 'plugin_search_order')) { - $this->plugin_search_order = $this->template->smarty->plugin_search_order; - } if ($this->smarty->debugging) { if (!isset($this->smarty->_debug)) { $this->smarty->_debug = new Smarty_Internal_Debug(); } $this->smarty->_debug->start_compile($this->template); } - if (isset($this->template->smarty->security_policy)) { - $this->php_handling = $this->template->smarty->security_policy->php_handling; - } else { - $this->php_handling = $this->template->smarty->php_handling; - } $this->parent_compiler = $parent_compiler ? $parent_compiler : $this; $nocache = isset($nocache) ? $nocache : false; if (empty($template->compiled->nocache_hash)) { @@ -620,18 +605,18 @@ public function compilePHPFunctionCall($name, $parameter) if (strcasecmp($name, 'isset') === 0 || strcasecmp($name, 'empty') === 0 || strcasecmp($name, 'array') === 0 || is_callable($name) ) { - $func_name = strtolower($name); + $func_name = smarty_strtolower_ascii($name); if ($func_name === 'isset') { if (count($parameter) === 0) { $this->trigger_template_error('Illegal number of parameter in "isset()"'); } - $pa = array(); - foreach ($parameter as $p) { - $pa[] = $this->syntaxMatchesVariable($p) ? 'isset(' . $p . ')' : '(' . $p . ' !== null )'; - } - return '(' . implode(' && ', $pa) . ')'; + $pa = array(); + foreach ($parameter as $p) { + $pa[] = $this->syntaxMatchesVariable($p) ? 'isset(' . $p . ')' : '(' . $p . ' !== null )'; + } + return '(' . implode(' && ', $pa) . ')'; } elseif (in_array( $func_name, @@ -649,12 +634,8 @@ public function compilePHPFunctionCall($name, $parameter) $this->trigger_template_error("Illegal number of parameter in '{$func_name()}'"); } if ($func_name === 'empty') { - if (!$this->syntaxMatchesVariable($parameter[0]) && version_compare(PHP_VERSION, '5.5.0', '<')) { - return '(' . $parameter[ 0 ] . ' === false )'; - } else { - return $func_name . '(' . - str_replace("')->value", "',null,true,false)->value", $parameter[ 0 ]) . ')'; - } + return $func_name . '(' . + str_replace("')->value", "',null,true,false)->value", $parameter[ 0 ]) . ')'; } else { return $func_name . '(' . $parameter[ 0 ] . ')'; } @@ -667,16 +648,16 @@ public function compilePHPFunctionCall($name, $parameter) } } - /** - * Determines whether the passed string represents a valid (PHP) variable. - * This is important, because `isset()` only works on variables and `empty()` can only be passed - * a variable prior to php5.5 - * @param $string - * @return bool - */ - private function syntaxMatchesVariable($string) { - static $regex_pattern = '/^\$[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*((->)[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*|\[.*]*\])*$/'; - return 1 === preg_match($regex_pattern, trim($string)); + /** + * Determines whether the passed string represents a valid (PHP) variable. + * This is important, because `isset()` only works on variables and `empty()` can only be passed + * a variable prior to php5.5 + * @param $string + * @return bool + */ + private function syntaxMatchesVariable($string) { + static $regex_pattern = '/^\$[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*((->)[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*|\[.*]*\])*$/'; + return 1 === preg_match($regex_pattern, trim($string)); } /** @@ -691,11 +672,11 @@ public function processText($text) { if (strpos($text, '<') === false) { - return preg_replace($this->stripRegEx, '', $text); + return preg_replace($this->stripRegEx, '', $text); } - $store = array(); - $_store = 0; + $store = array(); + $_store = 0; // capture html elements not to be messed with $_offset = 0; @@ -784,7 +765,7 @@ public function getTagCompiler($tag) if (!isset(self::$_tag_objects[ $tag ])) { // lazy load internal compiler plugin $_tag = explode('_', $tag); - $_tag = array_map('ucfirst', $_tag); + $_tag = array_map('smarty_ucfirst_ascii', $_tag); $class_name = 'Smarty_Internal_Compile_' . implode('_', $_tag); if (class_exists($class_name) && (!isset($this->smarty->security_policy) || $this->smarty->security_policy->isTrustedTag($tag, $this)) @@ -1150,8 +1131,12 @@ public function trigger_template_error($args = null, $line = null, $tagline = nu echo ob_get_clean(); flush(); } - $e = new SmartyCompilerException($error_text); - $e->line = $line; + $e = new SmartyCompilerException( + $error_text, + 0, + $this->template->source->filepath, + $line + ); $e->source = trim(preg_replace('![\t\r\n]+!', ' ', $match[ $line - 1 ])); $e->desc = $args; $e->template = $this->template->source->filepath; @@ -1455,6 +1440,10 @@ public function compileCheckPlugins($requiredPlugins) */ abstract protected function doCompile($_content, $isTemplateSource = false); + public function cStyleComment($string) { + return '/*' . str_replace('*/', '* /' , $string) . '*/'; + } + /** * Compile Tag * diff --git a/WEB-INF/lib/smarty/sysplugins/smarty_internal_templatelexer.php b/WEB-INF/lib/smarty/sysplugins/smarty_internal_templatelexer.php index 867a31d26..5ca482058 100644 --- a/WEB-INF/lib/smarty/sysplugins/smarty_internal_templatelexer.php +++ b/WEB-INF/lib/smarty/sysplugins/smarty_internal_templatelexer.php @@ -18,12 +18,6 @@ */ class Smarty_Internal_Templatelexer { - const TEXT = 1; - const TAG = 2; - const TAGBODY = 3; - const LITERAL = 4; - const DOUBLEQUOTEDSTRING = 5; - /** * Source * @@ -80,7 +74,7 @@ class Smarty_Internal_Templatelexer */ public $phpType = ''; - /** + /** * state number * * @var int @@ -229,10 +223,6 @@ class Smarty_Internal_Templatelexer */ private $yy_global_literal = null; - private $_yy_state = 1; - - private $_yy_stack = array(); - /** * constructor * @@ -245,14 +235,14 @@ public function __construct($source, Smarty_Internal_TemplateCompilerBase $compi $this->dataLength = strlen($this->data); $this->counter = 0; if (preg_match('/^\xEF\xBB\xBF/i', $this->data, $match)) { - $this->counter += strlen($match[ 0 ]); + $this->counter += strlen($match[0]); } $this->line = 1; $this->smarty = $compiler->template->smarty; $this->compiler = $compiler; $this->compiler->initDelimiterPreg(); - $this->smarty_token_names[ 'LDEL' ] = $this->smarty->getLeftDelimiter(); - $this->smarty_token_names[ 'RDEL' ] = $this->smarty->getRightDelimiter(); + $this->smarty_token_names['LDEL'] = $this->smarty->getLeftDelimiter(); + $this->smarty_token_names['RDEL'] = $this->smarty->getRightDelimiter(); } /** @@ -265,17 +255,17 @@ public function PrintTrace() $this->yyTracePrompt = '
'; } - /** + /** * replace placeholders with runtime preg code * * @param string $preg * * @return string */ - public function replace($preg) - { + public function replace($preg) + { return $this->compiler->replaceDelimiter($preg); - } + } /** * check if current value is an autoliteral left delimiter @@ -286,7 +276,11 @@ public function isAutoLiteral() { return $this->smarty->getAutoLiteral() && isset($this->value[ $this->compiler->getLdelLength() ]) ? strpos(" \n\t\r", $this->value[ $this->compiler->getLdelLength() ]) !== false : false; - } // end function + } + + + private $_yy_state = 1; + private $_yy_stack = array(); public function yylex() { @@ -296,62 +290,60 @@ public function yylex() public function yypushstate($state) { if ($this->yyTraceFILE) { - fprintf($this->yyTraceFILE, "%sState push %s\n", $this->yyTracePrompt, - isset($this->state_name[ $this->_yy_state ]) ? $this->state_name[ $this->_yy_state ] : $this->_yy_state); + fprintf($this->yyTraceFILE, "%sState push %s\n", $this->yyTracePrompt, isset($this->state_name[$this->_yy_state]) ? $this->state_name[$this->_yy_state] : $this->_yy_state); } array_push($this->_yy_stack, $this->_yy_state); $this->_yy_state = $state; if ($this->yyTraceFILE) { - fprintf($this->yyTraceFILE, "%snew State %s\n", $this->yyTracePrompt, - isset($this->state_name[ $this->_yy_state ]) ? $this->state_name[ $this->_yy_state ] : $this->_yy_state); + fprintf($this->yyTraceFILE, "%snew State %s\n", $this->yyTracePrompt, isset($this->state_name[$this->_yy_state]) ? $this->state_name[$this->_yy_state] : $this->_yy_state); } } public function yypopstate() { - if ($this->yyTraceFILE) { - fprintf($this->yyTraceFILE, "%sState pop %s\n", $this->yyTracePrompt, - isset($this->state_name[ $this->_yy_state ]) ? $this->state_name[ $this->_yy_state ] : $this->_yy_state); + if ($this->yyTraceFILE) { + fprintf($this->yyTraceFILE, "%sState pop %s\n", $this->yyTracePrompt, isset($this->state_name[$this->_yy_state]) ? $this->state_name[$this->_yy_state] : $this->_yy_state); } - $this->_yy_state = array_pop($this->_yy_stack); + $this->_yy_state = array_pop($this->_yy_stack); if ($this->yyTraceFILE) { - fprintf($this->yyTraceFILE, "%snew State %s\n", $this->yyTracePrompt, - isset($this->state_name[ $this->_yy_state ]) ? $this->state_name[ $this->_yy_state ] : $this->_yy_state); + fprintf($this->yyTraceFILE, "%snew State %s\n", $this->yyTracePrompt, isset($this->state_name[$this->_yy_state]) ? $this->state_name[$this->_yy_state] : $this->_yy_state); } + } public function yybegin($state) { - $this->_yy_state = $state; + $this->_yy_state = $state; if ($this->yyTraceFILE) { - fprintf($this->yyTraceFILE, "%sState set %s\n", $this->yyTracePrompt, - isset($this->state_name[ $this->_yy_state ]) ? $this->state_name[ $this->_yy_state ] : $this->_yy_state); + fprintf($this->yyTraceFILE, "%sState set %s\n", $this->yyTracePrompt, isset($this->state_name[$this->_yy_state]) ? $this->state_name[$this->_yy_state] : $this->_yy_state); } } + + public function yylex1() { if (!isset($this->yy_global_pattern1)) { - $this->yy_global_pattern1 = - $this->replace("/\G([{][}])|\G((SMARTYldel)SMARTYal[*])|\G((SMARTYldel)SMARTYalphp([ ].*?)?SMARTYrdel|(SMARTYldel)SMARTYal[\/]phpSMARTYrdel)|\G((SMARTYldel)SMARTYautoliteral\\s+SMARTYliteral)|\G((SMARTYldel)SMARTYalliteral\\s*SMARTYrdel)|\G((SMARTYldel)SMARTYal[\/]literal\\s*SMARTYrdel)|\G((SMARTYldel)SMARTYal)|\G([<][?]((php\\s+|=)|\\s+)|[<][%]|[<][?]xml\\s+|[<]script\\s+language\\s*=\\s*[\"']?\\s*php\\s*[\"']?\\s*[>]|[?][>]|[%][>])|\G([\S\s])/isS"); + $this->yy_global_pattern1 = $this->replace("/\G([{][}])|\G((SMARTYldel)SMARTYal[*])|\G((SMARTYldel)SMARTYautoliteral\\s+SMARTYliteral)|\G((SMARTYldel)SMARTYalliteral\\s*SMARTYrdel)|\G((SMARTYldel)SMARTYal[\/]literal\\s*SMARTYrdel)|\G((SMARTYldel)SMARTYal)|\G([\S\s])/isS"); } if (!isset($this->dataLength)) { $this->dataLength = strlen($this->data); } - if ($this->counter >= $this->dataLength) { + if ($this->counter >= $this->dataLength) { return false; // end of input } + do { - if (preg_match($this->yy_global_pattern1, $this->data, $yymatches, 0, $this->counter)) { - if (!isset($yymatches[ 0 ][ 1 ])) { - $yymatches = preg_grep("/(.|\s)+/", $yymatches); + if (preg_match($this->yy_global_pattern1,$this->data, $yymatches, 0, $this->counter)) { + if (!isset($yymatches[ 0 ][1])) { + $yymatches = preg_grep("/(.|\s)+/", $yymatches); } else { $yymatches = array_filter($yymatches); } if (empty($yymatches)) { throw new Exception('Error: lexing failed because a rule matched' . - ' an empty string. Input "' . substr($this->data, - $this->counter, 5) . '... state TEXT'); + ' an empty string. Input "' . substr($this->data, + $this->counter, 5) . '... state TEXT'); } next($yymatches); // skip global match $this->token = key($yymatches); // token number @@ -369,110 +361,102 @@ public function yylex1() } elseif ($r === false) { $this->counter += strlen($this->value); $this->line += substr_count($this->value, "\n"); - if ($this->counter >= $this->dataLength) { + if ($this->counter >= $this->dataLength) { return false; // end of input } // skip this token continue; - } - } else { - throw new Exception('Unexpected input at line ' . $this->line . - ': ' . $this->data[ $this->counter ]); + } } else { + throw new Exception('Unexpected input at line' . $this->line . + ': ' . $this->data[$this->counter]); } break; } while (true); - } + } // end function + + + const TEXT = 1; public function yy_r1_1() { - $this->token = Smarty_Internal_Templateparser::TP_TEXT; - } + $this->token = Smarty_Internal_Templateparser::TP_TEXT; + } public function yy_r1_2() { - $to = $this->dataLength; - preg_match("/[*]{$this->compiler->getRdelPreg()}[\n]?/", $this->data, $match, PREG_OFFSET_CAPTURE, - $this->counter); - if (isset($match[ 0 ][ 1 ])) { - $to = $match[ 0 ][ 1 ] + strlen($match[ 0 ][ 0 ]); + + $to = $this->dataLength; + preg_match("/[*]{$this->compiler->getRdelPreg()}[\n]?/",$this->data,$match,PREG_OFFSET_CAPTURE,$this->counter); + if (isset($match[0][1])) { + $to = $match[0][1] + strlen($match[0][0]); } else { - $this->compiler->trigger_template_error("missing or misspelled comment closing tag '{$this->smarty->getRightDelimiter()}'"); + $this->compiler->trigger_template_error ("missing or misspelled comment closing tag '{$this->smarty->getRightDelimiter()}'"); } - $this->value = substr($this->data, $this->counter, $to - $this->counter); + $this->value = substr($this->data,$this->counter,$to-$this->counter); return false; - } - + } public function yy_r1_4() { - $this->compiler->getTagCompiler('private_php')->parsePhp($this); - } - public function yy_r1_8() - { $this->token = Smarty_Internal_Templateparser::TP_TEXT; - } - - public function yy_r1_10() + } + public function yy_r1_6() { + $this->token = Smarty_Internal_Templateparser::TP_LITERALSTART; $this->yypushstate(self::LITERAL); - } - - public function yy_r1_12() + } + public function yy_r1_8() { + $this->token = Smarty_Internal_Templateparser::TP_LITERALEND; $this->yypushstate(self::LITERAL); - } // end function - - public function yy_r1_14() + } + public function yy_r1_10() { + $this->yypushstate(self::TAG); return true; - } - - public function yy_r1_16() + } + public function yy_r1_12() { - $this->compiler->getTagCompiler('private_php')->parsePhp($this); - } - public function yy_r1_19() - { - if (!isset($this->yy_global_text)) { - $this->yy_global_text = - $this->replace('/(SMARTYldel)SMARTYal|[<][?]((php\s+|=)|\s+)|[<][%]|[<][?]xml\s+|[<]script\s+language\s*=\s*["\']?\s*php\s*["\']?\s*[>]|[?][>]|[%][>]SMARTYliteral/isS'); - } - $to = $this->dataLength; - preg_match($this->yy_global_text, $this->data, $match, PREG_OFFSET_CAPTURE, $this->counter); - if (isset($match[ 0 ][ 1 ])) { - $to = $match[ 0 ][ 1 ]; - } - $this->value = substr($this->data, $this->counter, $to - $this->counter); - $this->token = Smarty_Internal_Templateparser::TP_TEXT; - } + if (!isset($this->yy_global_text)) { + $this->yy_global_text = $this->replace('/(SMARTYldel)SMARTYal/isS'); + } + $to = $this->dataLength; + preg_match($this->yy_global_text, $this->data,$match,PREG_OFFSET_CAPTURE,$this->counter); + if (isset($match[0][1])) { + $to = $match[0][1]; + } + $this->value = substr($this->data,$this->counter,$to-$this->counter); + $this->token = Smarty_Internal_Templateparser::TP_TEXT; + } + public function yylex2() { if (!isset($this->yy_global_pattern2)) { - $this->yy_global_pattern2 = - $this->replace("/\G((SMARTYldel)SMARTYal(if|elseif|else if|while)\\s+)|\G((SMARTYldel)SMARTYalfor\\s+)|\G((SMARTYldel)SMARTYalforeach(?![^\s]))|\G((SMARTYldel)SMARTYalsetfilter\\s+)|\G((SMARTYldel)SMARTYalmake_nocache\\s+)|\G((SMARTYldel)SMARTYal[0-9]*[a-zA-Z_]\\w*(\\s+nocache)?\\s*SMARTYrdel)|\G((SMARTYldel)SMARTYal[$]smarty\\.block\\.(child|parent)\\s*SMARTYrdel)|\G((SMARTYldel)SMARTYal[\/][0-9]*[a-zA-Z_]\\w*\\s*SMARTYrdel)|\G((SMARTYldel)SMARTYal[$][0-9]*[a-zA-Z_]\\w*(\\s+nocache)?\\s*SMARTYrdel)|\G((SMARTYldel)SMARTYal[\/])|\G((SMARTYldel)SMARTYal)/isS"); + $this->yy_global_pattern2 = $this->replace("/\G((SMARTYldel)SMARTYal(if|elseif|else if|while)\\s+)|\G((SMARTYldel)SMARTYalfor\\s+)|\G((SMARTYldel)SMARTYalforeach(?![^\s]))|\G((SMARTYldel)SMARTYalsetfilter\\s+)|\G((SMARTYldel)SMARTYalmake_nocache\\s+)|\G((SMARTYldel)SMARTYal[0-9]*[a-zA-Z_]\\w*(\\s+nocache)?\\s*SMARTYrdel)|\G((SMARTYldel)SMARTYal[$]smarty\\.block\\.(child|parent)\\s*SMARTYrdel)|\G((SMARTYldel)SMARTYal[\/][0-9]*[a-zA-Z_]\\w*\\s*SMARTYrdel)|\G((SMARTYldel)SMARTYal[$][0-9]*[a-zA-Z_]\\w*(\\s+nocache)?\\s*SMARTYrdel)|\G((SMARTYldel)SMARTYal[\/])|\G((SMARTYldel)SMARTYal)/isS"); } if (!isset($this->dataLength)) { $this->dataLength = strlen($this->data); } - if ($this->counter >= $this->dataLength) { + if ($this->counter >= $this->dataLength) { return false; // end of input } + do { - if (preg_match($this->yy_global_pattern2, $this->data, $yymatches, 0, $this->counter)) { - if (!isset($yymatches[ 0 ][ 1 ])) { - $yymatches = preg_grep("/(.|\s)+/", $yymatches); + if (preg_match($this->yy_global_pattern2,$this->data, $yymatches, 0, $this->counter)) { + if (!isset($yymatches[ 0 ][1])) { + $yymatches = preg_grep("/(.|\s)+/", $yymatches); } else { $yymatches = array_filter($yymatches); } if (empty($yymatches)) { throw new Exception('Error: lexing failed because a rule matched' . - ' an empty string. Input "' . substr($this->data, - $this->counter, 5) . '... state TAG'); + ' an empty string. Input "' . substr($this->data, + $this->counter, 5) . '... state TAG'); } next($yymatches); // skip global match $this->token = key($yymatches); // token number @@ -490,79 +474,82 @@ public function yylex2() } elseif ($r === false) { $this->counter += strlen($this->value); $this->line += substr_count($this->value, "\n"); - if ($this->counter >= $this->dataLength) { + if ($this->counter >= $this->dataLength) { return false; // end of input } // skip this token continue; - } - } else { - throw new Exception('Unexpected input at line ' . $this->line . - ': ' . $this->data[ $this->counter ]); + } } else { + throw new Exception('Unexpected input at line' . $this->line . + ': ' . $this->data[$this->counter]); } break; } while (true); - } + } // end function + + + const TAG = 2; public function yy_r2_1() { + $this->token = Smarty_Internal_Templateparser::TP_LDELIF; $this->yybegin(self::TAGBODY); $this->taglineno = $this->line; - } - + } public function yy_r2_4() { + $this->token = Smarty_Internal_Templateparser::TP_LDELFOR; $this->yybegin(self::TAGBODY); $this->taglineno = $this->line; - } - + } public function yy_r2_6() { + $this->token = Smarty_Internal_Templateparser::TP_LDELFOREACH; $this->yybegin(self::TAGBODY); $this->taglineno = $this->line; - } - + } public function yy_r2_8() { + $this->token = Smarty_Internal_Templateparser::TP_LDELSETFILTER; $this->yybegin(self::TAGBODY); $this->taglineno = $this->line; - } - + } public function yy_r2_10() { + $this->token = Smarty_Internal_Templateparser::TP_LDELMAKENOCACHE; $this->yybegin(self::TAGBODY); $this->taglineno = $this->line; - } - + } public function yy_r2_12() { + $this->yypopstate(); $this->token = Smarty_Internal_Templateparser::TP_SIMPLETAG; $this->taglineno = $this->line; - } - + } public function yy_r2_15() { - $this->yypopstate(); - $this->token = Smarty_Internal_Templateparser::TP_SMARTYBLOCKCHILDPARENT; - $this->taglineno = $this->line; - } + $this->yypopstate(); + $this->token = Smarty_Internal_Templateparser::TP_SMARTYBLOCKCHILDPARENT; + $this->taglineno = $this->line; + } public function yy_r2_18() { + $this->yypopstate(); $this->token = Smarty_Internal_Templateparser::TP_CLOSETAG; $this->taglineno = $this->line; - } - + } public function yy_r2_20() { - if ($this->_yy_stack[ count($this->_yy_stack) - 1 ] === self::TEXT) { + + if ($this->_yy_stack[count($this->_yy_stack)-1] === self::TEXT) { $this->yypopstate(); $this->token = Smarty_Internal_Templateparser::TP_SIMPELOUTPUT; $this->taglineno = $this->line; @@ -572,45 +559,46 @@ public function yy_r2_20() $this->yybegin(self::TAGBODY); $this->taglineno = $this->line; } - } // end function - + } public function yy_r2_23() { + $this->token = Smarty_Internal_Templateparser::TP_LDELSLASH; $this->yybegin(self::TAGBODY); $this->taglineno = $this->line; - } - + } public function yy_r2_25() { + $this->token = Smarty_Internal_Templateparser::TP_LDEL; $this->yybegin(self::TAGBODY); $this->taglineno = $this->line; - } + } + public function yylex3() { if (!isset($this->yy_global_pattern3)) { - $this->yy_global_pattern3 = - $this->replace("/\G(\\s*SMARTYrdel)|\G((SMARTYldel)SMARTYal)|\G([\"])|\G('[^'\\\\]*(?:\\\\.[^'\\\\]*)*')|\G([$][0-9]*[a-zA-Z_]\\w*)|\G([$])|\G(\\s+is\\s+in\\s+)|\G(\\s+as\\s+)|\G(\\s+to\\s+)|\G(\\s+step\\s+)|\G(\\s+instanceof\\s+)|\G(\\s*([!=][=]{1,2}|[<][=>]?|[>][=]?|[&|]{2})\\s*)|\G(\\s+(eq|ne|neq|gt|ge|gte|lt|le|lte|mod|and|or|xor)\\s+)|\G(\\s+is\\s+(not\\s+)?(odd|even|div)\\s+by\\s+)|\G(\\s+is\\s+(not\\s+)?(odd|even))|\G([!]\\s*|not\\s+)|\G([(](int(eger)?|bool(ean)?|float|double|real|string|binary|array|object)[)]\\s*)|\G(\\s*[(]\\s*)|\G(\\s*[)])|\G(\\[\\s*)|\G(\\s*\\])|\G(\\s*[-][>]\\s*)|\G(\\s*[=][>]\\s*)|\G(\\s*[=]\\s*)|\G(([+]|[-]){2})|\G(\\s*([+]|[-])\\s*)|\G(\\s*([*]{1,2}|[%\/^&]|[<>]{2})\\s*)|\G([@])|\G(array\\s*[(]\\s*)|\G([#])|\G(\\s+[0-9]*[a-zA-Z_][a-zA-Z0-9_\-:]*\\s*[=]\\s*)|\G(([0-9]*[a-zA-Z_]\\w*)?(\\\\[0-9]*[a-zA-Z_]\\w*)+)|\G([0-9]*[a-zA-Z_]\\w*)|\G(\\d+)|\G([`])|\G([|][@]?)|\G([.])|\G(\\s*[,]\\s*)|\G(\\s*[;]\\s*)|\G([:]{2})|\G(\\s*[:]\\s*)|\G(\\s*[?]\\s*)|\G(0[xX][0-9a-fA-F]+)|\G(\\s+)|\G([\S\s])/isS"); + $this->yy_global_pattern3 = $this->replace("/\G(\\s*SMARTYrdel)|\G((SMARTYldel)SMARTYal)|\G([\"])|\G('[^'\\\\]*(?:\\\\.[^'\\\\]*)*')|\G([$][0-9]*[a-zA-Z_]\\w*)|\G([$])|\G(\\s+is\\s+in\\s+)|\G(\\s+as\\s+)|\G(\\s+to\\s+)|\G(\\s+step\\s+)|\G(\\s+instanceof\\s+)|\G(\\s*([!=][=]{1,2}|[<][=>]?|[>][=]?|[&|]{2})\\s*)|\G(\\s+(eq|ne|neq|gt|ge|gte|lt|le|lte|mod|and|or|xor)\\s+)|\G(\\s+is\\s+(not\\s+)?(odd|even|div)\\s+by\\s+)|\G(\\s+is\\s+(not\\s+)?(odd|even))|\G([!]\\s*|not\\s+)|\G([(](int(eger)?|bool(ean)?|float|double|real|string|binary|array|object)[)]\\s*)|\G(\\s*[(]\\s*)|\G(\\s*[)])|\G(\\[\\s*)|\G(\\s*\\])|\G(\\s*[-][>]\\s*)|\G(\\s*[=][>]\\s*)|\G(\\s*[=]\\s*)|\G(([+]|[-]){2})|\G(\\s*([+]|[-])\\s*)|\G(\\s*([*]{1,2}|[%\/^&]|[<>]{2})\\s*)|\G([@])|\G(array\\s*[(]\\s*)|\G([#])|\G(\\s+[0-9]*[a-zA-Z_][a-zA-Z0-9_\-:]*\\s*[=]\\s*)|\G(([0-9]*[a-zA-Z_]\\w*)?(\\\\[0-9]*[a-zA-Z_]\\w*)+)|\G([0-9]*[a-zA-Z_]\\w*)|\G(\\d+)|\G([`])|\G([|][@]?)|\G([.])|\G(\\s*[,]\\s*)|\G(\\s*[;]\\s*)|\G([:]{2})|\G(\\s*[:]\\s*)|\G(\\s*[?]\\s*)|\G(0[xX][0-9a-fA-F]+)|\G(\\s+)|\G([\S\s])/isS"); } if (!isset($this->dataLength)) { $this->dataLength = strlen($this->data); } - if ($this->counter >= $this->dataLength) { + if ($this->counter >= $this->dataLength) { return false; // end of input } + do { - if (preg_match($this->yy_global_pattern3, $this->data, $yymatches, 0, $this->counter)) { - if (!isset($yymatches[ 0 ][ 1 ])) { - $yymatches = preg_grep("/(.|\s)+/", $yymatches); + if (preg_match($this->yy_global_pattern3,$this->data, $yymatches, 0, $this->counter)) { + if (!isset($yymatches[ 0 ][1])) { + $yymatches = preg_grep("/(.|\s)+/", $yymatches); } else { $yymatches = array_filter($yymatches); } if (empty($yymatches)) { throw new Exception('Error: lexing failed because a rule matched' . - ' an empty string. Input "' . substr($this->data, - $this->counter, 5) . '... state TAGBODY'); + ' an empty string. Input "' . substr($this->data, + $this->counter, 5) . '... state TAGBODY'); } next($yymatches); // skip global match $this->token = key($yymatches); // token number @@ -628,281 +616,285 @@ public function yylex3() } elseif ($r === false) { $this->counter += strlen($this->value); $this->line += substr_count($this->value, "\n"); - if ($this->counter >= $this->dataLength) { + if ($this->counter >= $this->dataLength) { return false; // end of input } // skip this token continue; - } - } else { - throw new Exception('Unexpected input at line ' . $this->line . - ': ' . $this->data[ $this->counter ]); + } } else { + throw new Exception('Unexpected input at line' . $this->line . + ': ' . $this->data[$this->counter]); } break; } while (true); - } + } // end function + + + const TAGBODY = 3; public function yy_r3_1() { + $this->token = Smarty_Internal_Templateparser::TP_RDEL; $this->yypopstate(); - } - + } public function yy_r3_2() { + $this->yypushstate(self::TAG); return true; - } - + } public function yy_r3_4() { + $this->token = Smarty_Internal_Templateparser::TP_QUOTE; $this->yypushstate(self::DOUBLEQUOTEDSTRING); $this->compiler->enterDoubleQuote(); - } - + } public function yy_r3_5() { - $this->token = Smarty_Internal_Templateparser::TP_SINGLEQUOTESTRING; - } + $this->token = Smarty_Internal_Templateparser::TP_SINGLEQUOTESTRING; + } public function yy_r3_6() { - $this->token = Smarty_Internal_Templateparser::TP_DOLLARID; - } + $this->token = Smarty_Internal_Templateparser::TP_DOLLARID; + } public function yy_r3_7() { - $this->token = Smarty_Internal_Templateparser::TP_DOLLAR; - } + $this->token = Smarty_Internal_Templateparser::TP_DOLLAR; + } public function yy_r3_8() { - $this->token = Smarty_Internal_Templateparser::TP_ISIN; - } + $this->token = Smarty_Internal_Templateparser::TP_ISIN; + } public function yy_r3_9() { - $this->token = Smarty_Internal_Templateparser::TP_AS; - } + $this->token = Smarty_Internal_Templateparser::TP_AS; + } public function yy_r3_10() { - $this->token = Smarty_Internal_Templateparser::TP_TO; - } + $this->token = Smarty_Internal_Templateparser::TP_TO; + } public function yy_r3_11() { - $this->token = Smarty_Internal_Templateparser::TP_STEP; - } + $this->token = Smarty_Internal_Templateparser::TP_STEP; + } public function yy_r3_12() { - $this->token = Smarty_Internal_Templateparser::TP_INSTANCEOF; - } + $this->token = Smarty_Internal_Templateparser::TP_INSTANCEOF; + } public function yy_r3_13() { - $this->token = Smarty_Internal_Templateparser::TP_LOGOP; - } + $this->token = Smarty_Internal_Templateparser::TP_LOGOP; + } public function yy_r3_15() { - $this->token = Smarty_Internal_Templateparser::TP_SLOGOP; - } + $this->token = Smarty_Internal_Templateparser::TP_SLOGOP; + } public function yy_r3_17() { - $this->token = Smarty_Internal_Templateparser::TP_TLOGOP; - } + $this->token = Smarty_Internal_Templateparser::TP_TLOGOP; + } public function yy_r3_20() { - $this->token = Smarty_Internal_Templateparser::TP_SINGLECOND; - } + $this->token = Smarty_Internal_Templateparser::TP_SINGLECOND; + } public function yy_r3_23() { - $this->token = Smarty_Internal_Templateparser::TP_NOT; - } + $this->token = Smarty_Internal_Templateparser::TP_NOT; + } public function yy_r3_24() { - $this->token = Smarty_Internal_Templateparser::TP_TYPECAST; - } + $this->token = Smarty_Internal_Templateparser::TP_TYPECAST; + } public function yy_r3_28() { - $this->token = Smarty_Internal_Templateparser::TP_OPENP; - } + $this->token = Smarty_Internal_Templateparser::TP_OPENP; + } public function yy_r3_29() { - $this->token = Smarty_Internal_Templateparser::TP_CLOSEP; - } + $this->token = Smarty_Internal_Templateparser::TP_CLOSEP; + } public function yy_r3_30() { - $this->token = Smarty_Internal_Templateparser::TP_OPENB; - } + $this->token = Smarty_Internal_Templateparser::TP_OPENB; + } public function yy_r3_31() { - $this->token = Smarty_Internal_Templateparser::TP_CLOSEB; - } + $this->token = Smarty_Internal_Templateparser::TP_CLOSEB; + } public function yy_r3_32() { - $this->token = Smarty_Internal_Templateparser::TP_PTR; - } + $this->token = Smarty_Internal_Templateparser::TP_PTR; + } public function yy_r3_33() { - $this->token = Smarty_Internal_Templateparser::TP_APTR; - } + $this->token = Smarty_Internal_Templateparser::TP_APTR; + } public function yy_r3_34() { - $this->token = Smarty_Internal_Templateparser::TP_EQUAL; - } + $this->token = Smarty_Internal_Templateparser::TP_EQUAL; + } public function yy_r3_35() { - $this->token = Smarty_Internal_Templateparser::TP_INCDEC; - } + $this->token = Smarty_Internal_Templateparser::TP_INCDEC; + } public function yy_r3_37() { - $this->token = Smarty_Internal_Templateparser::TP_UNIMATH; - } + $this->token = Smarty_Internal_Templateparser::TP_UNIMATH; + } public function yy_r3_39() { - $this->token = Smarty_Internal_Templateparser::TP_MATH; - } + $this->token = Smarty_Internal_Templateparser::TP_MATH; + } public function yy_r3_41() { - $this->token = Smarty_Internal_Templateparser::TP_AT; - } + $this->token = Smarty_Internal_Templateparser::TP_AT; + } public function yy_r3_42() { - $this->token = Smarty_Internal_Templateparser::TP_ARRAYOPEN; - } + $this->token = Smarty_Internal_Templateparser::TP_ARRAYOPEN; + } public function yy_r3_43() { - $this->token = Smarty_Internal_Templateparser::TP_HATCH; - } + $this->token = Smarty_Internal_Templateparser::TP_HATCH; + } public function yy_r3_44() { + // resolve conflicts with shorttag and right_delimiter starting with '=' - if (substr($this->data, $this->counter + strlen($this->value) - 1, $this->compiler->getRdelLength()) === - $this->smarty->getRightDelimiter()) { - preg_match('/\s+/', $this->value, $match); - $this->value = $match[ 0 ]; + if (substr($this->data, $this->counter + strlen($this->value) - 1, $this->compiler->getRdelLength()) === $this->smarty->getRightDelimiter()) { + preg_match('/\s+/',$this->value,$match); + $this->value = $match[0]; $this->token = Smarty_Internal_Templateparser::TP_SPACE; } else { $this->token = Smarty_Internal_Templateparser::TP_ATTR; } - } - + } public function yy_r3_45() { - $this->token = Smarty_Internal_Templateparser::TP_NAMESPACE; - } + $this->token = Smarty_Internal_Templateparser::TP_NAMESPACE; + } public function yy_r3_48() { - $this->token = Smarty_Internal_Templateparser::TP_ID; - } + $this->token = Smarty_Internal_Templateparser::TP_ID; + } public function yy_r3_49() { - $this->token = Smarty_Internal_Templateparser::TP_INTEGER; - } + $this->token = Smarty_Internal_Templateparser::TP_INTEGER; + } public function yy_r3_50() { + $this->token = Smarty_Internal_Templateparser::TP_BACKTICK; $this->yypopstate(); - } - + } public function yy_r3_51() { - $this->token = Smarty_Internal_Templateparser::TP_VERT; - } + $this->token = Smarty_Internal_Templateparser::TP_VERT; + } public function yy_r3_52() { - $this->token = Smarty_Internal_Templateparser::TP_DOT; - } + $this->token = Smarty_Internal_Templateparser::TP_DOT; + } public function yy_r3_53() { - $this->token = Smarty_Internal_Templateparser::TP_COMMA; - } + $this->token = Smarty_Internal_Templateparser::TP_COMMA; + } public function yy_r3_54() { - $this->token = Smarty_Internal_Templateparser::TP_SEMICOLON; - } + $this->token = Smarty_Internal_Templateparser::TP_SEMICOLON; + } public function yy_r3_55() { - $this->token = Smarty_Internal_Templateparser::TP_DOUBLECOLON; - } + $this->token = Smarty_Internal_Templateparser::TP_DOUBLECOLON; + } public function yy_r3_56() { - $this->token = Smarty_Internal_Templateparser::TP_COLON; - } + $this->token = Smarty_Internal_Templateparser::TP_COLON; + } public function yy_r3_57() { - $this->token = Smarty_Internal_Templateparser::TP_QMARK; - } + $this->token = Smarty_Internal_Templateparser::TP_QMARK; + } public function yy_r3_58() { - $this->token = Smarty_Internal_Templateparser::TP_HEX; - } + $this->token = Smarty_Internal_Templateparser::TP_HEX; + } public function yy_r3_59() { - $this->token = Smarty_Internal_Templateparser::TP_SPACE; - } // end function + $this->token = Smarty_Internal_Templateparser::TP_SPACE; + } public function yy_r3_60() { + $this->token = Smarty_Internal_Templateparser::TP_TEXT; - } + } + + public function yylex4() { if (!isset($this->yy_global_pattern4)) { - $this->yy_global_pattern4 = - $this->replace("/\G((SMARTYldel)SMARTYalliteral\\s*SMARTYrdel)|\G((SMARTYldel)SMARTYal[\/]literal\\s*SMARTYrdel)|\G([\S\s])/isS"); + $this->yy_global_pattern4 = $this->replace("/\G((SMARTYldel)SMARTYalliteral\\s*SMARTYrdel)|\G((SMARTYldel)SMARTYal[\/]literal\\s*SMARTYrdel)|\G([\S\s])/isS"); } if (!isset($this->dataLength)) { $this->dataLength = strlen($this->data); } - if ($this->counter >= $this->dataLength) { + if ($this->counter >= $this->dataLength) { return false; // end of input } + do { - if (preg_match($this->yy_global_pattern4, $this->data, $yymatches, 0, $this->counter)) { - if (!isset($yymatches[ 0 ][ 1 ])) { - $yymatches = preg_grep("/(.|\s)+/", $yymatches); + if (preg_match($this->yy_global_pattern4,$this->data, $yymatches, 0, $this->counter)) { + if (!isset($yymatches[ 0 ][1])) { + $yymatches = preg_grep("/(.|\s)+/", $yymatches); } else { $yymatches = array_filter($yymatches); } if (empty($yymatches)) { throw new Exception('Error: lexing failed because a rule matched' . - ' an empty string. Input "' . substr($this->data, - $this->counter, 5) . '... state LITERAL'); + ' an empty string. Input "' . substr($this->data, + $this->counter, 5) . '... state LITERAL'); } next($yymatches); // skip global match $this->token = key($yymatches); // token number @@ -920,76 +912,80 @@ public function yylex4() } elseif ($r === false) { $this->counter += strlen($this->value); $this->line += substr_count($this->value, "\n"); - if ($this->counter >= $this->dataLength) { + if ($this->counter >= $this->dataLength) { return false; // end of input } // skip this token continue; - } - } else { - throw new Exception('Unexpected input at line ' . $this->line . - ': ' . $this->data[ $this->counter ]); + } } else { + throw new Exception('Unexpected input at line' . $this->line . + ': ' . $this->data[$this->counter]); } break; } while (true); - } + } // end function + + + const LITERAL = 4; public function yy_r4_1() { + $this->literal_cnt++; $this->token = Smarty_Internal_Templateparser::TP_LITERAL; - } - + } public function yy_r4_3() { + if ($this->literal_cnt) { - $this->literal_cnt--; + $this->literal_cnt--; $this->token = Smarty_Internal_Templateparser::TP_LITERAL; } else { $this->token = Smarty_Internal_Templateparser::TP_LITERALEND; $this->yypopstate(); } - } - + } public function yy_r4_5() { - if (!isset($this->yy_global_literal)) { - $this->yy_global_literal = $this->replace('/(SMARTYldel)SMARTYal[\/]?literalSMARTYrdel/isS'); - } - $to = $this->dataLength; - preg_match($this->yy_global_literal, $this->data, $match, PREG_OFFSET_CAPTURE, $this->counter); - if (isset($match[ 0 ][ 1 ])) { - $to = $match[ 0 ][ 1 ]; - } else { - $this->compiler->trigger_template_error("missing or misspelled literal closing tag"); - } - $this->value = substr($this->data, $this->counter, $to - $this->counter); - $this->token = Smarty_Internal_Templateparser::TP_LITERAL; - } // end function + if (!isset($this->yy_global_literal)) { + $this->yy_global_literal = $this->replace('/(SMARTYldel)SMARTYal[\/]?literalSMARTYrdel/isS'); + } + $to = $this->dataLength; + preg_match($this->yy_global_literal, $this->data,$match,PREG_OFFSET_CAPTURE,$this->counter); + if (isset($match[0][1])) { + $to = $match[0][1]; + } else { + $this->compiler->trigger_template_error ("missing or misspelled literal closing tag"); + } + $this->value = substr($this->data,$this->counter,$to-$this->counter); + $this->token = Smarty_Internal_Templateparser::TP_LITERAL; + } + + public function yylex5() { if (!isset($this->yy_global_pattern5)) { - $this->yy_global_pattern5 = - $this->replace("/\G((SMARTYldel)SMARTYautoliteral\\s+SMARTYliteral)|\G((SMARTYldel)SMARTYalliteral\\s*SMARTYrdel)|\G((SMARTYldel)SMARTYal[\/]literal\\s*SMARTYrdel)|\G((SMARTYldel)SMARTYal[\/])|\G((SMARTYldel)SMARTYal[0-9]*[a-zA-Z_]\\w*)|\G((SMARTYldel)SMARTYal)|\G([\"])|\G([`][$])|\G([$][0-9]*[a-zA-Z_]\\w*)|\G([$])|\G(([^\"\\\\]*?)((?:\\\\.[^\"\\\\]*?)*?)(?=((SMARTYldel)SMARTYal|\\$|`\\$|\"SMARTYliteral)))|\G([\S\s])/isS"); + $this->yy_global_pattern5 = $this->replace("/\G((SMARTYldel)SMARTYautoliteral\\s+SMARTYliteral)|\G((SMARTYldel)SMARTYalliteral\\s*SMARTYrdel)|\G((SMARTYldel)SMARTYal[\/]literal\\s*SMARTYrdel)|\G((SMARTYldel)SMARTYal[\/])|\G((SMARTYldel)SMARTYal[0-9]*[a-zA-Z_]\\w*)|\G((SMARTYldel)SMARTYal)|\G([\"])|\G([`][$])|\G([$][0-9]*[a-zA-Z_]\\w*)|\G([$])|\G(([^\"\\\\]*?)((?:\\\\.[^\"\\\\]*?)*?)(?=((SMARTYldel)SMARTYal|\\$|`\\$|\"SMARTYliteral)))|\G([\S\s])/isS"); } if (!isset($this->dataLength)) { $this->dataLength = strlen($this->data); } - if ($this->counter >= $this->dataLength) { + if ($this->counter >= $this->dataLength) { return false; // end of input } + do { - if (preg_match($this->yy_global_pattern5, $this->data, $yymatches, 0, $this->counter)) { - if (!isset($yymatches[ 0 ][ 1 ])) { - $yymatches = preg_grep("/(.|\s)+/", $yymatches); + if (preg_match($this->yy_global_pattern5,$this->data, $yymatches, 0, $this->counter)) { + if (!isset($yymatches[ 0 ][1])) { + $yymatches = preg_grep("/(.|\s)+/", $yymatches); } else { $yymatches = array_filter($yymatches); } if (empty($yymatches)) { throw new Exception('Error: lexing failed because a rule matched' . - ' an empty string. Input "' . substr($this->data, - $this->counter, 5) . '... state DOUBLEQUOTEDSTRING'); + ' an empty string. Input "' . substr($this->data, + $this->counter, 5) . '... state DOUBLEQUOTEDSTRING'); } next($yymatches); // skip global match $this->token = key($yymatches); // token number @@ -1007,89 +1003,93 @@ public function yylex5() } elseif ($r === false) { $this->counter += strlen($this->value); $this->line += substr_count($this->value, "\n"); - if ($this->counter >= $this->dataLength) { + if ($this->counter >= $this->dataLength) { return false; // end of input } // skip this token continue; - } - } else { - throw new Exception('Unexpected input at line ' . $this->line . - ': ' . $this->data[ $this->counter ]); + } } else { + throw new Exception('Unexpected input at line' . $this->line . + ': ' . $this->data[$this->counter]); } break; } while (true); - } + } // end function + + + const DOUBLEQUOTEDSTRING = 5; public function yy_r5_1() { - $this->token = Smarty_Internal_Templateparser::TP_TEXT; - } + $this->token = Smarty_Internal_Templateparser::TP_TEXT; + } public function yy_r5_3() { - $this->token = Smarty_Internal_Templateparser::TP_TEXT; - } + $this->token = Smarty_Internal_Templateparser::TP_TEXT; + } public function yy_r5_5() { - $this->token = Smarty_Internal_Templateparser::TP_TEXT; - } + $this->token = Smarty_Internal_Templateparser::TP_TEXT; + } public function yy_r5_7() { + $this->yypushstate(self::TAG); return true; - } - + } public function yy_r5_9() { + $this->yypushstate(self::TAG); return true; - } - + } public function yy_r5_11() { + $this->token = Smarty_Internal_Templateparser::TP_LDEL; $this->taglineno = $this->line; $this->yypushstate(self::TAGBODY); - } - + } public function yy_r5_13() { + $this->token = Smarty_Internal_Templateparser::TP_QUOTE; $this->yypopstate(); - } - + } public function yy_r5_14() { + $this->token = Smarty_Internal_Templateparser::TP_BACKTICK; - $this->value = substr($this->value, 0, -1); + $this->value = substr($this->value,0,-1); $this->yypushstate(self::TAGBODY); $this->taglineno = $this->line; - } - + } public function yy_r5_15() { - $this->token = Smarty_Internal_Templateparser::TP_DOLLARID; - } + $this->token = Smarty_Internal_Templateparser::TP_DOLLARID; + } public function yy_r5_16() { - $this->token = Smarty_Internal_Templateparser::TP_TEXT; - } + $this->token = Smarty_Internal_Templateparser::TP_TEXT; + } public function yy_r5_17() { - $this->token = Smarty_Internal_Templateparser::TP_TEXT; - } + $this->token = Smarty_Internal_Templateparser::TP_TEXT; + } public function yy_r5_22() { + $to = $this->dataLength; - $this->value = substr($this->data, $this->counter, $to - $this->counter); + $this->value = substr($this->data,$this->counter,$to-$this->counter); $this->token = Smarty_Internal_Templateparser::TP_TEXT; - } -} + } - + } + + \ No newline at end of file diff --git a/WEB-INF/lib/smarty/sysplugins/smarty_internal_templateparser.php b/WEB-INF/lib/smarty/sysplugins/smarty_internal_templateparser.php index aaeae63b7..a2dd0d6fb 100644 --- a/WEB-INF/lib/smarty/sysplugins/smarty_internal_templateparser.php +++ b/WEB-INF/lib/smarty/sysplugins/smarty_internal_templateparser.php @@ -7,1598 +7,25 @@ class TP_yyStackEntry ** number for the token at this stack level */ public $minor; /* The user-supplied minor token value. This ** is the value of the token */ -} +}; + // line 11 "../smarty/lexer/smarty_internal_templateparser.y" /** - * Smarty Template Parser Class - * - * This is the template parser. - * It is generated from the smarty_internal_templateparser.y file - * - * @author Uwe Tews - */ +* Smarty Template Parser Class +* +* This is the template parser. +* It is generated from the smarty_internal_templateparser.y file +* +* @author Uwe Tews +*/ class Smarty_Internal_Templateparser { - // line 23 "../smarty/lexer/smarty_internal_templateparser.y" - const ERR1 = 'Security error: Call to private object member not allowed'; - const ERR2 = 'Security error: Call to dynamic object member not allowed'; - const ERR3 = 'PHP in template not allowed. Use SmartyBC to enable it'; - const TP_VERT = 1; - const TP_COLON = 2; - const TP_PHP = 3; - const TP_TEXT = 4; - const TP_STRIPON = 5; - const TP_STRIPOFF = 6; - const TP_LITERALSTART = 7; - const TP_LITERALEND = 8; - const TP_LITERAL = 9; - const TP_SIMPELOUTPUT = 10; - const TP_SIMPLETAG = 11; - const TP_SMARTYBLOCKCHILDPARENT = 12; - const TP_LDEL = 13; - const TP_RDEL = 14; - const TP_DOLLARID = 15; - const TP_EQUAL = 16; - const TP_ID = 17; - const TP_PTR = 18; - const TP_LDELMAKENOCACHE = 19; - const TP_LDELIF = 20; - const TP_LDELFOR = 21; - const TP_SEMICOLON = 22; - const TP_INCDEC = 23; - const TP_TO = 24; - const TP_STEP = 25; - const TP_LDELFOREACH = 26; - const TP_SPACE = 27; - const TP_AS = 28; - const TP_APTR = 29; - const TP_LDELSETFILTER = 30; - const TP_CLOSETAG = 31; - const TP_LDELSLASH = 32; - const TP_ATTR = 33; - const TP_INTEGER = 34; - const TP_COMMA = 35; - const TP_OPENP = 36; - const TP_CLOSEP = 37; - const TP_MATH = 38; - const TP_UNIMATH = 39; - const TP_ISIN = 40; - const TP_QMARK = 41; - const TP_NOT = 42; - const TP_TYPECAST = 43; - const TP_HEX = 44; - const TP_DOT = 45; - const TP_INSTANCEOF = 46; - const TP_SINGLEQUOTESTRING = 47; - const TP_DOUBLECOLON = 48; - const TP_NAMESPACE = 49; - const TP_AT = 50; - const TP_HATCH = 51; - const TP_OPENB = 52; - const TP_CLOSEB = 53; - const TP_DOLLAR = 54; - const TP_LOGOP = 55; - const TP_SLOGOP = 56; - const TP_TLOGOP = 57; - const TP_SINGLECOND = 58; - const TP_ARRAYOPEN = 59; - const TP_QUOTE = 60; - const TP_BACKTICK = 61; - const YY_NO_ACTION = 516; - const YY_ACCEPT_ACTION = 515; - const YY_ERROR_ACTION = 514; - const YY_SZ_ACTTAB = 2071; - const YY_SHIFT_USE_DFLT = -31; - const YY_SHIFT_MAX = 230; - const YY_REDUCE_USE_DFLT = -91; - const YY_REDUCE_MAX = 178; - const YYNOCODE = 110; - const YYSTACKDEPTH = 500; - const YYNSTATE = 327; - const YYNRULE = 187; - const YYERRORSYMBOL = 62; - const YYERRSYMDT = 'yy0'; - const YYFALLBACK = 0; - - public static $yy_action = array( - 251, 234, 237, 1, 144, 127, 428, 184, 199, 212, - 10, 54, 19, 175, 282, 215, 109, 389, 428, 428, - 224, 321, 223, 303, 203, 389, 13, 389, 281, 43, - 389, 428, 41, 40, 266, 225, 389, 213, 389, 194, - 389, 52, 4, 308, 301, 383, 34, 209, 222, 3, - 50, 153, 251, 234, 237, 1, 199, 131, 383, 198, - 305, 212, 10, 54, 383, 16, 199, 428, 109, 385, - 132, 18, 224, 321, 223, 222, 221, 12, 32, 428, - 116, 43, 385, 262, 41, 40, 266, 225, 385, 233, - 95, 194, 16, 52, 4, 131, 301, 252, 18, 265, - 164, 3, 50, 324, 251, 234, 237, 1, 23, 130, - 229, 198, 150, 212, 10, 54, 326, 11, 170, 284, - 109, 42, 22, 239, 224, 321, 223, 193, 221, 261, - 13, 52, 157, 43, 301, 286, 41, 40, 266, 225, - 205, 233, 5, 194, 96, 52, 4, 263, 301, 301, - 99, 349, 96, 3, 50, 199, 251, 234, 237, 1, - 238, 130, 241, 181, 349, 212, 10, 54, 382, 240, - 349, 36, 109, 185, 104, 256, 224, 321, 223, 132, - 191, 382, 13, 49, 91, 43, 12, 382, 41, 40, - 266, 225, 257, 233, 152, 194, 457, 52, 4, 457, - 301, 301, 228, 457, 282, 3, 50, 285, 251, 234, - 237, 1, 301, 131, 441, 198, 238, 212, 10, 54, - 349, 441, 325, 175, 109, 30, 349, 273, 224, 321, - 223, 20, 221, 295, 32, 211, 457, 39, 166, 49, - 41, 40, 266, 225, 87, 233, 205, 194, 279, 52, - 4, 24, 301, 204, 200, 280, 99, 3, 50, 199, - 251, 234, 237, 1, 31, 130, 96, 198, 205, 212, - 10, 54, 350, 55, 293, 207, 109, 283, 99, 96, - 224, 321, 223, 199, 180, 350, 13, 134, 230, 43, - 222, 350, 41, 40, 266, 225, 104, 233, 316, 194, - 279, 52, 4, 24, 301, 165, 284, 280, 85, 3, - 50, 25, 251, 234, 237, 1, 131, 129, 210, 198, - 14, 212, 10, 54, 269, 270, 301, 116, 109, 295, - 216, 211, 224, 321, 223, 171, 221, 95, 13, 28, - 219, 43, 323, 9, 41, 40, 266, 225, 151, 233, - 324, 194, 52, 52, 4, 301, 301, 30, 282, 302, - 178, 3, 50, 7, 251, 234, 237, 1, 136, 130, - 304, 179, 238, 212, 10, 54, 279, 175, 282, 24, - 109, 238, 429, 280, 224, 321, 223, 177, 221, 270, - 13, 255, 281, 43, 429, 49, 41, 40, 266, 225, - 275, 233, 318, 194, 49, 52, 4, 276, 301, 163, - 26, 199, 8, 3, 50, 119, 251, 234, 237, 1, - 11, 93, 291, 51, 107, 212, 10, 54, 226, 428, - 206, 201, 109, 148, 178, 322, 224, 321, 223, 441, - 221, 428, 13, 282, 9, 43, 441, 115, 41, 40, - 266, 225, 167, 233, 227, 194, 457, 52, 4, 457, - 301, 96, 158, 457, 101, 3, 50, 271, 251, 234, - 237, 1, 282, 130, 235, 186, 135, 212, 10, 54, - 199, 37, 119, 315, 109, 165, 284, 176, 224, 321, - 223, 104, 221, 149, 13, 281, 146, 43, 281, 300, - 41, 40, 266, 225, 30, 233, 289, 194, 21, 52, - 4, 272, 301, 211, 18, 301, 161, 3, 50, 110, - 251, 234, 237, 1, 137, 128, 282, 198, 268, 212, - 10, 54, 222, 169, 515, 92, 109, 172, 284, 31, - 224, 321, 223, 29, 221, 238, 6, 260, 53, 43, - 232, 139, 41, 40, 266, 225, 154, 233, 178, 194, - 168, 52, 4, 214, 301, 145, 99, 33, 49, 3, - 50, 245, 208, 211, 320, 282, 90, 111, 311, 183, - 98, 70, 309, 297, 236, 178, 95, 319, 142, 258, - 247, 267, 249, 264, 250, 195, 231, 199, 246, 324, - 317, 253, 254, 259, 126, 137, 133, 251, 234, 237, - 1, 326, 290, 105, 143, 156, 212, 10, 54, 88, - 84, 83, 484, 109, 322, 282, 37, 224, 321, 223, - 245, 208, 211, 320, 281, 90, 111, 298, 182, 98, - 56, 245, 298, 211, 178, 95, 103, 147, 258, 197, - 102, 75, 141, 250, 195, 231, 95, 246, 324, 258, - 279, 242, 89, 24, 250, 195, 231, 280, 246, 324, - 298, 298, 298, 298, 298, 298, 298, 16, 298, 192, - 277, 298, 298, 18, 294, 44, 45, 38, 298, 298, - 251, 234, 237, 2, 298, 296, 298, 298, 298, 212, - 10, 54, 310, 312, 313, 314, 109, 162, 298, 298, - 224, 321, 223, 298, 298, 298, 294, 282, 298, 42, - 22, 239, 251, 234, 237, 2, 298, 296, 298, 298, - 298, 212, 10, 54, 298, 159, 298, 298, 109, 298, - 298, 17, 224, 321, 223, 282, 298, 42, 22, 239, - 298, 298, 245, 298, 211, 278, 298, 103, 111, 298, - 183, 98, 70, 298, 298, 298, 298, 95, 298, 298, - 258, 298, 292, 17, 298, 250, 195, 231, 279, 246, - 324, 24, 298, 395, 245, 280, 211, 298, 298, 103, - 298, 298, 197, 102, 75, 16, 298, 140, 298, 95, - 298, 18, 258, 298, 298, 298, 298, 250, 195, 231, - 298, 246, 324, 298, 298, 298, 298, 428, 298, 395, - 395, 395, 202, 277, 298, 245, 298, 211, 298, 428, - 103, 298, 298, 197, 120, 69, 395, 395, 395, 395, - 95, 298, 298, 258, 298, 298, 298, 160, 250, 195, - 231, 86, 246, 324, 245, 16, 211, 282, 298, 103, - 196, 18, 197, 120, 69, 298, 44, 45, 38, 95, - 298, 298, 258, 298, 298, 298, 178, 250, 195, 231, - 298, 246, 324, 310, 312, 313, 314, 298, 298, 190, - 245, 298, 211, 298, 298, 103, 298, 298, 197, 102, - 75, 298, 298, 298, 298, 95, 298, 298, 258, 298, - 298, 298, 298, 250, 195, 231, 298, 246, 324, 298, - 298, 298, 245, 298, 211, 298, 199, 100, 298, 288, - 197, 120, 47, 298, 106, 298, 298, 95, 298, 353, - 258, 155, 298, 218, 298, 250, 195, 231, 298, 246, - 324, 282, 16, 42, 22, 239, 298, 245, 18, 211, - 298, 428, 103, 298, 298, 197, 120, 69, 298, 298, - 298, 298, 95, 428, 298, 258, 298, 298, 298, 298, - 250, 195, 231, 298, 246, 324, 245, 298, 211, 298, - 298, 100, 189, 298, 197, 120, 59, 245, 207, 211, - 298, 95, 103, 298, 258, 197, 120, 81, 298, 250, - 195, 231, 95, 246, 324, 258, 298, 298, 298, 298, - 250, 195, 231, 298, 246, 324, 298, 245, 298, 211, - 298, 298, 103, 298, 298, 197, 120, 80, 298, 298, - 298, 298, 95, 298, 298, 258, 298, 298, 298, 298, - 250, 195, 231, 298, 246, 324, 245, 298, 211, 298, - 298, 103, 298, 298, 197, 120, 67, 245, 298, 211, - 298, 95, 103, 298, 258, 197, 120, 57, 298, 250, - 195, 231, 95, 246, 324, 258, 298, 298, 298, 298, - 250, 195, 231, 298, 246, 324, 298, 245, 298, 211, - 298, 298, 103, 298, 298, 197, 120, 58, 298, 298, - 298, 298, 95, 298, 298, 258, 298, 298, 298, 298, - 250, 195, 231, 298, 246, 324, 245, 298, 211, 298, - 298, 103, 298, 298, 197, 120, 82, 245, 298, 211, - 298, 95, 103, 298, 258, 197, 97, 76, 298, 250, - 195, 231, 95, 246, 324, 258, 298, 298, 298, 298, - 250, 195, 231, 298, 246, 324, 298, 245, 298, 211, - 298, 298, 103, 298, 298, 197, 120, 71, 298, 298, - 298, 298, 95, 298, 298, 258, 298, 298, 298, 298, - 250, 195, 231, 298, 246, 324, 245, 298, 211, 298, - 298, 103, 298, 298, 187, 120, 61, 245, 298, 211, - 298, 95, 103, 298, 258, 197, 120, 63, 298, 250, - 195, 231, 95, 246, 324, 258, 298, 298, 298, 298, - 250, 195, 231, 298, 246, 324, 298, 245, 298, 211, - 298, 298, 103, 298, 298, 197, 94, 79, 298, 298, - 298, 298, 95, 298, 298, 258, 298, 298, 298, 298, - 250, 195, 231, 298, 246, 324, 245, 298, 211, 298, - 298, 103, 298, 298, 197, 120, 59, 245, 298, 211, - 298, 95, 103, 298, 258, 197, 120, 77, 298, 250, - 195, 231, 95, 246, 324, 258, 298, 298, 298, 298, - 250, 195, 231, 298, 246, 324, 298, 245, 298, 211, - 298, 298, 103, 298, 298, 188, 108, 64, 298, 298, - 298, 298, 95, 298, 298, 258, 298, 298, 298, 298, - 250, 195, 231, 298, 246, 324, 245, 298, 211, 298, - 298, 103, 298, 298, 197, 120, 65, 245, 298, 211, - 298, 95, 103, 298, 258, 197, 97, 66, 298, 250, - 195, 231, 95, 246, 324, 258, 298, 298, 298, 298, - 250, 195, 231, 298, 246, 324, 298, 245, 298, 211, - 298, 298, 103, 298, 298, 197, 120, 68, 298, 298, - 298, 298, 95, 298, 298, 258, 298, 298, 298, 298, - 250, 195, 231, 298, 246, 324, 245, 298, 211, 298, - 298, 103, 298, 298, 197, 120, 62, 245, 298, 211, - 298, 95, 103, 298, 258, 197, 120, 60, 298, 250, - 195, 231, 95, 246, 324, 258, 298, 298, 298, 298, - 250, 195, 231, 298, 246, 324, 298, 245, 298, 211, - 298, 298, 103, 298, 298, 197, 120, 74, 298, 298, - 298, 298, 95, 298, 298, 258, 298, 298, 298, 298, - 250, 195, 231, 298, 246, 324, 245, 298, 211, 298, - 298, 103, 298, 298, 197, 120, 72, 245, 298, 211, - 298, 95, 103, 298, 258, 197, 120, 48, 298, 250, - 195, 231, 95, 246, 324, 258, 298, 298, 298, 298, - 250, 195, 231, 298, 246, 324, 298, 245, 298, 211, - 298, 298, 103, 298, 298, 197, 120, 46, 298, 298, - 298, 298, 95, 298, 298, 258, 298, 298, 298, 298, - 250, 195, 231, 298, 246, 324, 245, 298, 211, 298, - 298, 103, 298, 298, 197, 120, 78, 245, 298, 211, - 298, 95, 103, 298, 258, 197, 120, 73, 298, 250, - 195, 231, 95, 246, 324, 258, 298, 298, 298, 298, - 250, 195, 231, 298, 246, 324, 298, 245, 298, 211, - 298, 298, 103, 298, 298, 197, 125, 298, 298, 298, - 298, 298, 95, 298, 298, 298, 298, 298, 298, 244, - 250, 195, 231, 298, 246, 324, 245, 298, 211, 298, - 298, 103, 298, 298, 197, 114, 298, 245, 298, 211, - 298, 95, 103, 298, 298, 197, 122, 298, 243, 250, - 195, 231, 95, 246, 324, 298, 298, 298, 298, 298, - 250, 195, 231, 298, 246, 324, 298, 245, 298, 211, - 298, 298, 103, 298, 298, 197, 117, 298, 298, 298, - 298, 298, 95, 298, 298, 298, 298, 298, 298, 298, - 250, 195, 231, 298, 246, 324, 245, 298, 211, 298, - 298, 103, 298, 298, 197, 121, 298, 245, 298, 211, - 298, 95, 103, 298, 298, 197, 124, 298, 298, 250, - 195, 231, 95, 246, 324, 298, 298, 298, 298, 298, - 250, 195, 231, 298, 246, 324, 298, 245, 298, 211, - 298, 298, 103, 298, 298, 197, 118, 298, 298, 298, - 298, 298, 95, 298, 298, 298, 298, 298, 298, 298, - 250, 195, 231, 298, 246, 324, 245, 298, 211, 298, - 298, 103, 298, 298, 197, 123, 298, 245, 298, 211, - 298, 95, 103, 298, 298, 197, 113, 298, 298, 250, - 195, 231, 95, 246, 324, 298, 298, 298, 298, 298, - 250, 195, 231, 220, 246, 324, 298, 27, 298, 16, - 298, 457, 298, 298, 457, 18, 298, 26, 457, 441, - 44, 45, 38, 217, 44, 45, 38, 298, 298, 298, - 298, 298, 298, 298, 298, 298, 298, 310, 312, 313, - 314, 310, 312, 313, 314, 298, 441, 298, 298, 441, - 298, 457, 220, 441, 457, 298, 298, 457, 298, 298, - 457, 457, 441, 457, 298, 298, 220, 457, 441, 298, - 298, 298, 298, 298, 457, 298, 298, 457, 298, 298, - 5, 457, 441, 298, 298, 298, 298, 298, 298, 441, - 298, 298, 441, 298, 457, 441, 441, 298, 441, 298, - 457, 298, 441, 306, 298, 298, 298, 298, 298, 441, - 298, 298, 441, 298, 457, 220, 441, 298, 298, 298, - 298, 298, 298, 457, 298, 298, 457, 298, 298, 15, - 457, 441, 35, 274, 44, 45, 38, 457, 298, 298, - 457, 298, 298, 298, 457, 441, 298, 298, 298, 298, - 298, 310, 312, 313, 314, 298, 298, 298, 441, 298, - 298, 441, 298, 457, 298, 441, 287, 298, 44, 45, - 38, 298, 441, 298, 298, 441, 298, 457, 298, 441, - 248, 298, 298, 298, 298, 310, 312, 313, 314, 298, - 44, 45, 38, 298, 298, 112, 298, 44, 45, 38, - 298, 173, 298, 298, 44, 45, 38, 310, 312, 313, - 314, 44, 45, 38, 310, 312, 313, 314, 298, 298, - 299, 310, 312, 313, 314, 44, 45, 38, 310, 312, - 313, 314, 174, 298, 298, 298, 138, 298, 298, 298, - 298, 298, 310, 312, 313, 314, 44, 45, 38, 298, - 298, 298, 44, 45, 38, 298, 44, 45, 38, 298, - 44, 45, 38, 310, 312, 313, 314, 307, 298, 310, - 312, 313, 314, 310, 312, 313, 314, 310, 312, 313, - 314, - ); - - public static $yy_lookahead = array( - 10, 11, 12, 13, 74, 15, 36, 17, 1, 19, - 20, 21, 29, 103, 84, 45, 26, 14, 48, 36, - 30, 31, 32, 53, 34, 22, 36, 24, 98, 39, - 27, 48, 42, 43, 44, 45, 33, 47, 35, 49, - 37, 51, 52, 53, 54, 14, 16, 16, 45, 59, - 60, 96, 10, 11, 12, 13, 1, 15, 27, 17, - 53, 19, 20, 21, 33, 27, 1, 36, 26, 14, - 45, 33, 30, 31, 32, 45, 34, 52, 36, 48, - 72, 39, 27, 75, 42, 43, 44, 45, 33, 47, - 82, 49, 27, 51, 52, 15, 54, 17, 33, 91, - 83, 59, 60, 95, 10, 11, 12, 13, 13, 15, - 15, 17, 17, 19, 20, 21, 97, 35, 99, 100, - 26, 86, 87, 88, 30, 31, 32, 66, 34, 49, - 36, 51, 96, 39, 54, 53, 42, 43, 44, 45, - 72, 47, 16, 49, 18, 51, 52, 79, 54, 54, - 82, 14, 18, 59, 60, 1, 10, 11, 12, 13, - 23, 15, 15, 17, 27, 19, 20, 21, 14, 17, - 33, 13, 26, 15, 48, 17, 30, 31, 32, 45, - 34, 27, 36, 46, 83, 39, 52, 33, 42, 43, - 44, 45, 34, 47, 74, 49, 10, 51, 52, 13, - 54, 54, 50, 17, 84, 59, 60, 14, 10, 11, - 12, 13, 54, 15, 45, 17, 23, 19, 20, 21, - 27, 52, 100, 103, 26, 35, 33, 37, 30, 31, - 32, 22, 34, 67, 36, 69, 50, 39, 83, 46, - 42, 43, 44, 45, 35, 47, 72, 49, 10, 51, - 52, 13, 54, 79, 80, 17, 82, 59, 60, 1, - 10, 11, 12, 13, 16, 15, 18, 17, 72, 19, - 20, 21, 14, 107, 108, 79, 26, 71, 82, 18, - 30, 31, 32, 1, 34, 27, 36, 15, 50, 39, - 45, 33, 42, 43, 44, 45, 48, 47, 53, 49, - 10, 51, 52, 13, 54, 99, 100, 17, 36, 59, - 60, 29, 10, 11, 12, 13, 15, 15, 17, 17, - 13, 19, 20, 21, 8, 9, 54, 72, 26, 67, - 75, 69, 30, 31, 32, 78, 34, 82, 36, 24, - 50, 39, 17, 36, 42, 43, 44, 45, 74, 47, - 95, 49, 51, 51, 52, 54, 54, 35, 84, 37, - 103, 59, 60, 36, 10, 11, 12, 13, 74, 15, - 108, 17, 23, 19, 20, 21, 10, 103, 84, 13, - 26, 23, 36, 17, 30, 31, 32, 7, 34, 9, - 36, 17, 98, 39, 48, 46, 42, 43, 44, 45, - 17, 47, 53, 49, 46, 51, 52, 93, 54, 78, - 16, 1, 36, 59, 60, 101, 10, 11, 12, 13, - 35, 15, 37, 17, 48, 19, 20, 21, 18, 36, - 65, 66, 26, 74, 103, 104, 30, 31, 32, 45, - 34, 48, 36, 84, 36, 39, 52, 17, 42, 43, - 44, 45, 15, 47, 17, 49, 10, 51, 52, 13, - 54, 18, 74, 17, 82, 59, 60, 34, 10, 11, - 12, 13, 84, 15, 93, 17, 15, 19, 20, 21, - 1, 2, 101, 101, 26, 99, 100, 17, 30, 31, - 32, 48, 34, 96, 36, 98, 96, 39, 98, 71, - 42, 43, 44, 45, 35, 47, 37, 49, 27, 51, - 52, 67, 54, 69, 33, 54, 74, 59, 60, 17, - 10, 11, 12, 13, 96, 15, 84, 17, 34, 19, - 20, 21, 45, 78, 63, 64, 26, 99, 100, 16, - 30, 31, 32, 16, 34, 23, 36, 17, 17, 39, - 23, 51, 42, 43, 44, 45, 72, 47, 103, 49, - 78, 51, 52, 17, 54, 74, 82, 41, 46, 59, - 60, 67, 68, 69, 70, 84, 72, 73, 53, 75, - 76, 77, 53, 61, 15, 103, 82, 14, 51, 85, - 14, 37, 17, 8, 90, 91, 92, 1, 94, 95, - 3, 4, 5, 6, 7, 96, 82, 10, 11, 12, - 13, 97, 84, 81, 96, 74, 19, 20, 21, 78, - 82, 82, 1, 26, 104, 84, 2, 30, 31, 32, - 67, 68, 69, 70, 98, 72, 73, 109, 75, 76, - 77, 67, 109, 69, 103, 82, 72, 96, 85, 75, - 76, 77, 96, 90, 91, 92, 82, 94, 95, 85, - 10, 14, 96, 13, 90, 91, 92, 17, 94, 95, - 109, 109, 109, 109, 109, 109, 109, 27, 109, 105, - 106, 109, 109, 33, 4, 38, 39, 40, 109, 109, - 10, 11, 12, 13, 109, 15, 109, 109, 109, 19, - 20, 21, 55, 56, 57, 58, 26, 74, 109, 109, - 30, 31, 32, 109, 109, 109, 4, 84, 109, 86, - 87, 88, 10, 11, 12, 13, 109, 15, 109, 109, - 109, 19, 20, 21, 109, 74, 109, 109, 26, 109, - 60, 61, 30, 31, 32, 84, 109, 86, 87, 88, - 109, 109, 67, 109, 69, 70, 109, 72, 73, 109, - 75, 76, 77, 109, 109, 109, 109, 82, 109, 109, - 85, 109, 60, 61, 109, 90, 91, 92, 10, 94, - 95, 13, 109, 2, 67, 17, 69, 109, 109, 72, - 109, 109, 75, 76, 77, 27, 109, 29, 109, 82, - 109, 33, 85, 109, 109, 109, 109, 90, 91, 92, - 109, 94, 95, 109, 109, 109, 109, 36, 109, 38, - 39, 40, 105, 106, 109, 67, 109, 69, 109, 48, - 72, 109, 109, 75, 76, 77, 55, 56, 57, 58, - 82, 109, 109, 85, 109, 109, 109, 74, 90, 91, - 92, 78, 94, 95, 67, 27, 69, 84, 109, 72, - 102, 33, 75, 76, 77, 109, 38, 39, 40, 82, - 109, 109, 85, 109, 109, 109, 103, 90, 91, 92, - 109, 94, 95, 55, 56, 57, 58, 109, 109, 102, - 67, 109, 69, 109, 109, 72, 109, 109, 75, 76, - 77, 109, 109, 109, 109, 82, 109, 109, 85, 109, - 109, 109, 109, 90, 91, 92, 109, 94, 95, 109, - 109, 109, 67, 109, 69, 109, 1, 72, 109, 106, - 75, 76, 77, 109, 79, 109, 109, 82, 109, 14, - 85, 74, 109, 18, 109, 90, 91, 92, 109, 94, - 95, 84, 27, 86, 87, 88, 109, 67, 33, 69, - 109, 36, 72, 109, 109, 75, 76, 77, 109, 109, - 109, 109, 82, 48, 109, 85, 109, 109, 109, 109, - 90, 91, 92, 109, 94, 95, 67, 109, 69, 109, - 109, 72, 102, 109, 75, 76, 77, 67, 79, 69, - 109, 82, 72, 109, 85, 75, 76, 77, 109, 90, - 91, 92, 82, 94, 95, 85, 109, 109, 109, 109, - 90, 91, 92, 109, 94, 95, 109, 67, 109, 69, - 109, 109, 72, 109, 109, 75, 76, 77, 109, 109, - 109, 109, 82, 109, 109, 85, 109, 109, 109, 109, - 90, 91, 92, 109, 94, 95, 67, 109, 69, 109, - 109, 72, 109, 109, 75, 76, 77, 67, 109, 69, - 109, 82, 72, 109, 85, 75, 76, 77, 109, 90, - 91, 92, 82, 94, 95, 85, 109, 109, 109, 109, - 90, 91, 92, 109, 94, 95, 109, 67, 109, 69, - 109, 109, 72, 109, 109, 75, 76, 77, 109, 109, - 109, 109, 82, 109, 109, 85, 109, 109, 109, 109, - 90, 91, 92, 109, 94, 95, 67, 109, 69, 109, - 109, 72, 109, 109, 75, 76, 77, 67, 109, 69, - 109, 82, 72, 109, 85, 75, 76, 77, 109, 90, - 91, 92, 82, 94, 95, 85, 109, 109, 109, 109, - 90, 91, 92, 109, 94, 95, 109, 67, 109, 69, - 109, 109, 72, 109, 109, 75, 76, 77, 109, 109, - 109, 109, 82, 109, 109, 85, 109, 109, 109, 109, - 90, 91, 92, 109, 94, 95, 67, 109, 69, 109, - 109, 72, 109, 109, 75, 76, 77, 67, 109, 69, - 109, 82, 72, 109, 85, 75, 76, 77, 109, 90, - 91, 92, 82, 94, 95, 85, 109, 109, 109, 109, - 90, 91, 92, 109, 94, 95, 109, 67, 109, 69, - 109, 109, 72, 109, 109, 75, 76, 77, 109, 109, - 109, 109, 82, 109, 109, 85, 109, 109, 109, 109, - 90, 91, 92, 109, 94, 95, 67, 109, 69, 109, - 109, 72, 109, 109, 75, 76, 77, 67, 109, 69, - 109, 82, 72, 109, 85, 75, 76, 77, 109, 90, - 91, 92, 82, 94, 95, 85, 109, 109, 109, 109, - 90, 91, 92, 109, 94, 95, 109, 67, 109, 69, - 109, 109, 72, 109, 109, 75, 76, 77, 109, 109, - 109, 109, 82, 109, 109, 85, 109, 109, 109, 109, - 90, 91, 92, 109, 94, 95, 67, 109, 69, 109, - 109, 72, 109, 109, 75, 76, 77, 67, 109, 69, - 109, 82, 72, 109, 85, 75, 76, 77, 109, 90, - 91, 92, 82, 94, 95, 85, 109, 109, 109, 109, - 90, 91, 92, 109, 94, 95, 109, 67, 109, 69, - 109, 109, 72, 109, 109, 75, 76, 77, 109, 109, - 109, 109, 82, 109, 109, 85, 109, 109, 109, 109, - 90, 91, 92, 109, 94, 95, 67, 109, 69, 109, - 109, 72, 109, 109, 75, 76, 77, 67, 109, 69, - 109, 82, 72, 109, 85, 75, 76, 77, 109, 90, - 91, 92, 82, 94, 95, 85, 109, 109, 109, 109, - 90, 91, 92, 109, 94, 95, 109, 67, 109, 69, - 109, 109, 72, 109, 109, 75, 76, 77, 109, 109, - 109, 109, 82, 109, 109, 85, 109, 109, 109, 109, - 90, 91, 92, 109, 94, 95, 67, 109, 69, 109, - 109, 72, 109, 109, 75, 76, 77, 67, 109, 69, - 109, 82, 72, 109, 85, 75, 76, 77, 109, 90, - 91, 92, 82, 94, 95, 85, 109, 109, 109, 109, - 90, 91, 92, 109, 94, 95, 109, 67, 109, 69, - 109, 109, 72, 109, 109, 75, 76, 77, 109, 109, - 109, 109, 82, 109, 109, 85, 109, 109, 109, 109, - 90, 91, 92, 109, 94, 95, 67, 109, 69, 109, - 109, 72, 109, 109, 75, 76, 77, 67, 109, 69, - 109, 82, 72, 109, 85, 75, 76, 77, 109, 90, - 91, 92, 82, 94, 95, 85, 109, 109, 109, 109, - 90, 91, 92, 109, 94, 95, 109, 67, 109, 69, - 109, 109, 72, 109, 109, 75, 76, 109, 109, 109, - 109, 109, 82, 109, 109, 109, 109, 109, 109, 89, - 90, 91, 92, 109, 94, 95, 67, 109, 69, 109, - 109, 72, 109, 109, 75, 76, 109, 67, 109, 69, - 109, 82, 72, 109, 109, 75, 76, 109, 89, 90, - 91, 92, 82, 94, 95, 109, 109, 109, 109, 109, - 90, 91, 92, 109, 94, 95, 109, 67, 109, 69, - 109, 109, 72, 109, 109, 75, 76, 109, 109, 109, - 109, 109, 82, 109, 109, 109, 109, 109, 109, 109, - 90, 91, 92, 109, 94, 95, 67, 109, 69, 109, - 109, 72, 109, 109, 75, 76, 109, 67, 109, 69, - 109, 82, 72, 109, 109, 75, 76, 109, 109, 90, - 91, 92, 82, 94, 95, 109, 109, 109, 109, 109, - 90, 91, 92, 109, 94, 95, 109, 67, 109, 69, - 109, 109, 72, 109, 109, 75, 76, 109, 109, 109, - 109, 109, 82, 109, 109, 109, 109, 109, 109, 109, - 90, 91, 92, 109, 94, 95, 67, 109, 69, 109, - 109, 72, 109, 109, 75, 76, 109, 67, 109, 69, - 109, 82, 72, 109, 109, 75, 76, 109, 109, 90, - 91, 92, 82, 94, 95, 109, 109, 109, 109, 109, - 90, 91, 92, 2, 94, 95, 109, 25, 109, 27, - 109, 10, 109, 109, 13, 33, 109, 16, 17, 18, - 38, 39, 40, 37, 38, 39, 40, 109, 109, 109, - 109, 109, 109, 109, 109, 109, 109, 55, 56, 57, - 58, 55, 56, 57, 58, 109, 45, 109, 109, 48, - 109, 50, 2, 52, 10, 109, 109, 13, 109, 109, - 10, 17, 18, 13, 109, 109, 2, 17, 18, 109, - 109, 109, 109, 109, 10, 109, 109, 13, 109, 109, - 16, 17, 18, 109, 109, 109, 109, 109, 109, 45, - 109, 109, 48, 109, 50, 45, 52, 109, 48, 109, - 50, 109, 52, 53, 109, 109, 109, 109, 109, 45, - 109, 109, 48, 109, 50, 2, 52, 109, 109, 109, - 109, 109, 109, 10, 109, 109, 13, 109, 109, 2, - 17, 18, 2, 37, 38, 39, 40, 10, 109, 109, - 13, 109, 109, 109, 17, 18, 109, 109, 109, 109, - 109, 55, 56, 57, 58, 109, 109, 109, 45, 109, - 109, 48, 109, 50, 109, 52, 14, 109, 38, 39, - 40, 109, 45, 109, 109, 48, 109, 50, 109, 52, - 14, 109, 109, 109, 109, 55, 56, 57, 58, 109, - 38, 39, 40, 109, 109, 22, 109, 38, 39, 40, - 109, 14, 109, 109, 38, 39, 40, 55, 56, 57, - 58, 38, 39, 40, 55, 56, 57, 58, 109, 109, - 61, 55, 56, 57, 58, 38, 39, 40, 55, 56, - 57, 58, 14, 109, 109, 109, 28, 109, 109, 109, - 109, 109, 55, 56, 57, 58, 38, 39, 40, 109, - 109, 109, 38, 39, 40, 109, 38, 39, 40, 109, - 38, 39, 40, 55, 56, 57, 58, 53, 109, 55, - 56, 57, 58, 55, 56, 57, 58, 55, 56, 57, - 58, - ); - - public static $yy_shift_ofst = array( - -31, 406, 406, 458, 458, 94, 510, 94, 94, 94, - 510, 458, -10, 94, 94, 354, 146, 94, 94, 94, - 94, 146, 94, 94, 94, 94, 250, 94, 94, 94, - 94, 94, 94, 302, 94, 94, 94, 198, 42, 42, - 42, 42, 42, 42, 42, 42, 1772, 828, 828, 80, - 712, 925, 301, 65, 272, 680, 1942, 1920, 1886, 1776, - 647, 1949, 1977, 2008, 2004, 1963, 1998, 1956, 2012, 2012, - 2012, 2012, 2012, 2012, 2012, 2012, 2012, 2012, 2012, 2012, - 2012, 2012, 2012, 768, 650, 272, 65, 272, 65, 134, - 126, 479, 597, 1854, 154, 290, 95, 55, 258, 366, - 248, 366, 282, 443, 437, 38, 38, 437, 7, 481, - 410, 38, 461, 621, 596, 596, 261, 596, 596, 261, - 596, 596, 596, 596, 596, -31, -31, 1840, 1791, 1917, - 1903, 1834, 158, 238, 394, 446, 38, 25, 147, 169, - 147, 25, 169, 25, 38, 38, 25, 25, 38, 25, - 307, 38, 38, 25, 527, 38, 38, 25, 38, 38, - 38, 38, 38, 596, 624, 261, 624, 327, 596, 596, - 261, 596, 261, -31, -31, -31, -31, -31, -31, 781, - 3, 31, 193, 137, -30, 186, -17, 522, 349, 469, - 322, 30, 82, 316, 346, 376, 190, 358, 393, 152, - 209, 380, 385, 245, 315, 523, 585, 554, 576, 575, - 537, 573, 569, 529, 525, 546, 500, 526, 531, 325, - 530, 487, 494, 502, 470, 433, 430, 408, 383, 327, - 374, - ); - - public static $yy_reduce_ofst = array( - 471, 504, 563, 717, 574, 685, 919, 890, 787, 758, - 855, 823, 1240, 1199, 1140, 1100, 1070, 1129, 1170, 1210, - 1269, 1280, 1310, 1339, 1350, 1380, 1409, 1420, 1450, 1479, - 1490, 1059, 1030, 1000, 930, 960, 989, 1520, 1549, 1700, - 1619, 1689, 1660, 1630, 1590, 1560, 633, 661, 867, 8, - 166, 773, 255, 541, 174, 262, 35, 35, 35, 35, - 35, 35, 35, 35, 35, 35, 35, 35, 35, 35, - 35, 35, 35, 35, 35, 35, 35, 35, 35, 35, - 35, 35, 35, 294, -70, 196, 120, 68, 274, 19, - 206, 331, 444, 428, 257, 400, 382, 257, 257, 400, - 386, 397, 257, 386, 381, 388, 359, 314, 257, 442, - 482, 491, 484, 257, 257, 455, 386, 257, 257, 438, - 257, 257, 257, 257, 257, 257, 365, 509, 509, 509, - 509, 509, 524, 536, 509, 509, 528, 514, 539, 551, - 538, 514, 556, 514, 528, 528, 514, 514, 528, 514, - 518, 528, 528, 514, 532, 528, 528, 514, 528, 528, - 528, 528, 528, -90, 520, 122, 520, 566, -90, -90, - 122, -90, 122, -45, 36, 155, 101, 61, 17, - ); - - public static $yyExpectedTokens = array( - array(), - array( - 10, 11, 12, 13, 15, 17, 19, 20, 21, 26, 30, 31, 32, 34, 36, 39, 42, 43, 44, 45, 47, 49, 51, 52, 54, 59, 60, - ), - array( - 10, 11, 12, 13, 15, 17, 19, 20, 21, 26, 30, 31, 32, 34, 36, 39, 42, 43, 44, 45, 47, 49, 51, 52, 54, 59, 60, - ), - array( - 10, 11, 12, 13, 15, 17, 19, 20, 21, 26, 30, 31, 32, 34, 36, 39, 42, 43, 44, 45, 47, 49, 51, 52, 54, 59, 60, - ), - array( - 10, 11, 12, 13, 15, 17, 19, 20, 21, 26, 30, 31, 32, 34, 36, 39, 42, 43, 44, 45, 47, 49, 51, 52, 54, 59, 60, - ), - array( - 10, 11, 12, 13, 15, 17, 19, 20, 21, 26, 30, 31, 32, 34, 36, 39, 42, 43, 44, 45, 47, 49, 51, 52, 54, 59, 60, - ), - array( - 10, 11, 12, 13, 15, 17, 19, 20, 21, 26, 30, 31, 32, 34, 36, 39, 42, 43, 44, 45, 47, 49, 51, 52, 54, 59, 60, - ), - array( - 10, 11, 12, 13, 15, 17, 19, 20, 21, 26, 30, 31, 32, 34, 36, 39, 42, 43, 44, 45, 47, 49, 51, 52, 54, 59, 60, - ), - array( - 10, 11, 12, 13, 15, 17, 19, 20, 21, 26, 30, 31, 32, 34, 36, 39, 42, 43, 44, 45, 47, 49, 51, 52, 54, 59, 60, - ), - array( - 10, 11, 12, 13, 15, 17, 19, 20, 21, 26, 30, 31, 32, 34, 36, 39, 42, 43, 44, 45, 47, 49, 51, 52, 54, 59, 60, - ), - array( - 10, 11, 12, 13, 15, 17, 19, 20, 21, 26, 30, 31, 32, 34, 36, 39, 42, 43, 44, 45, 47, 49, 51, 52, 54, 59, 60, - ), - array( - 10, 11, 12, 13, 15, 17, 19, 20, 21, 26, 30, 31, 32, 34, 36, 39, 42, 43, 44, 45, 47, 49, 51, 52, 54, 59, 60, - ), - array( - 10, 11, 12, 13, 15, 17, 19, 20, 21, 26, 30, 31, 32, 34, 36, 39, 42, 43, 44, 45, 47, 49, 51, 52, 53, 54, 59, - 60, - ), - array( - 10, 11, 12, 13, 15, 17, 19, 20, 21, 26, 30, 31, 32, 34, 36, 39, 42, 43, 44, 45, 47, 49, 51, 52, 54, 59, 60, - ), - array( - 10, 11, 12, 13, 15, 17, 19, 20, 21, 26, 30, 31, 32, 34, 36, 39, 42, 43, 44, 45, 47, 49, 51, 52, 54, 59, 60, - ), - array( - 10, 11, 12, 13, 15, 17, 19, 20, 21, 26, 30, 31, 32, 34, 36, 39, 42, 43, 44, 45, 47, 49, 51, 52, 54, 59, 60, - ), - array( - 10, 11, 12, 13, 15, 17, 19, 20, 21, 26, 30, 31, 32, 34, 36, 39, 42, 43, 44, 45, 47, 49, 51, 52, 54, 59, 60, - ), - array( - 10, 11, 12, 13, 15, 17, 19, 20, 21, 26, 30, 31, 32, 34, 36, 39, 42, 43, 44, 45, 47, 49, 51, 52, 54, 59, 60, - ), - array( - 10, 11, 12, 13, 15, 17, 19, 20, 21, 26, 30, 31, 32, 34, 36, 39, 42, 43, 44, 45, 47, 49, 51, 52, 54, 59, 60, - ), - array( - 10, 11, 12, 13, 15, 17, 19, 20, 21, 26, 30, 31, 32, 34, 36, 39, 42, 43, 44, 45, 47, 49, 51, 52, 54, 59, 60, - ), - array( - 10, 11, 12, 13, 15, 17, 19, 20, 21, 26, 30, 31, 32, 34, 36, 39, 42, 43, 44, 45, 47, 49, 51, 52, 54, 59, 60, - ), - array( - 10, 11, 12, 13, 15, 17, 19, 20, 21, 26, 30, 31, 32, 34, 36, 39, 42, 43, 44, 45, 47, 49, 51, 52, 54, 59, 60, - ), - array( - 10, 11, 12, 13, 15, 17, 19, 20, 21, 26, 30, 31, 32, 34, 36, 39, 42, 43, 44, 45, 47, 49, 51, 52, 54, 59, 60, - ), - array( - 10, 11, 12, 13, 15, 17, 19, 20, 21, 26, 30, 31, 32, 34, 36, 39, 42, 43, 44, 45, 47, 49, 51, 52, 54, 59, 60, - ), - array( - 10, 11, 12, 13, 15, 17, 19, 20, 21, 26, 30, 31, 32, 34, 36, 39, 42, 43, 44, 45, 47, 49, 51, 52, 54, 59, 60, - ), - array( - 10, 11, 12, 13, 15, 17, 19, 20, 21, 26, 30, 31, 32, 34, 36, 39, 42, 43, 44, 45, 47, 49, 51, 52, 54, 59, 60, - ), - array( - 10, 11, 12, 13, 15, 17, 19, 20, 21, 26, 30, 31, 32, 34, 36, 39, 42, 43, 44, 45, 47, 49, 51, 52, 54, 59, 60, - ), - array( - 10, 11, 12, 13, 15, 17, 19, 20, 21, 26, 30, 31, 32, 34, 36, 39, 42, 43, 44, 45, 47, 49, 51, 52, 54, 59, 60, - ), - array( - 10, 11, 12, 13, 15, 17, 19, 20, 21, 26, 30, 31, 32, 34, 36, 39, 42, 43, 44, 45, 47, 49, 51, 52, 54, 59, 60, - ), - array( - 10, 11, 12, 13, 15, 17, 19, 20, 21, 26, 30, 31, 32, 34, 36, 39, 42, 43, 44, 45, 47, 49, 51, 52, 54, 59, 60, - ), - array( - 10, 11, 12, 13, 15, 17, 19, 20, 21, 26, 30, 31, 32, 34, 36, 39, 42, 43, 44, 45, 47, 49, 51, 52, 54, 59, 60, - ), - array( - 10, 11, 12, 13, 15, 17, 19, 20, 21, 26, 30, 31, 32, 34, 36, 39, 42, 43, 44, 45, 47, 49, 51, 52, 54, 59, 60, - ), - array( - 10, 11, 12, 13, 15, 17, 19, 20, 21, 26, 30, 31, 32, 34, 36, 39, 42, 43, 44, 45, 47, 49, 51, 52, 54, 59, 60, - ), - array( - 10, 11, 12, 13, 15, 17, 19, 20, 21, 26, 30, 31, 32, 34, 36, 39, 42, 43, 44, 45, 47, 49, 51, 52, 54, 59, 60, - ), - array( - 10, 11, 12, 13, 15, 17, 19, 20, 21, 26, 30, 31, 32, 34, 36, 39, 42, 43, 44, 45, 47, 49, 51, 52, 54, 59, 60, - ), - array( - 10, 11, 12, 13, 15, 17, 19, 20, 21, 26, 30, 31, 32, 34, 36, 39, 42, 43, 44, 45, 47, 49, 51, 52, 54, 59, 60, - ), - array( - 10, 11, 12, 13, 15, 17, 19, 20, 21, 26, 30, 31, 32, 34, 36, 39, 42, 43, 44, 45, 47, 49, 51, 52, 54, 59, 60, - ), - array( - 10, 11, 12, 13, 15, 17, 19, 20, 21, 26, 30, 31, 32, 34, 36, 39, 42, 43, 44, 45, 47, 49, 51, 52, 54, 59, 60, - ), - array( - 10, 11, 12, 13, 15, 17, 19, 20, 21, 26, 30, 31, 32, 34, 36, 39, 42, 43, 44, 45, 47, 49, 51, 52, 54, 59, 60, - ), - array( - 10, 11, 12, 13, 15, 17, 19, 20, 21, 26, 30, 31, 32, 34, 36, 39, 42, 43, 44, 45, 47, 49, 51, 52, 54, 59, 60, - ), - array( - 10, 11, 12, 13, 15, 17, 19, 20, 21, 26, 30, 31, 32, 34, 36, 39, 42, 43, 44, 45, 47, 49, 51, 52, 54, 59, 60, - ), - array( - 10, 11, 12, 13, 15, 17, 19, 20, 21, 26, 30, 31, 32, 34, 36, 39, 42, 43, 44, 45, 47, 49, 51, 52, 54, 59, 60, - ), - array( - 10, 11, 12, 13, 15, 17, 19, 20, 21, 26, 30, 31, 32, 34, 36, 39, 42, 43, 44, 45, 47, 49, 51, 52, 54, 59, 60, - ), - array( - 10, 11, 12, 13, 15, 17, 19, 20, 21, 26, 30, 31, 32, 34, 36, 39, 42, 43, 44, 45, 47, 49, 51, 52, 54, 59, 60, - ), - array( - 10, 11, 12, 13, 15, 17, 19, 20, 21, 26, 30, 31, 32, 34, 36, 39, 42, 43, 44, 45, 47, 49, 51, 52, 54, 59, 60, - ), - array( - 10, 11, 12, 13, 15, 17, 19, 20, 21, 26, 30, 31, 32, 34, 36, 39, 42, 43, 44, 45, 47, 49, 51, 52, 54, 59, 60, - ), - array(25, 27, 33, 38, 39, 40, 55, 56, 57, 58,), - array(27, 33, 38, 39, 40, 55, 56, 57, 58,), - array(27, 33, 38, 39, 40, 55, 56, 57, 58,), - array(15, 17, 49, 51, 54,), - array(4, 10, 11, 12, 13, 15, 19, 20, 21, 26, 30, 31, 32, 60, 61,), - array(1, 14, 18, 27, 33, 36, 48,), - array(15, 17, 51, 54,), - array(1, 27, 33,), - array(15, 36, 54,), - array(4, 10, 11, 12, 13, 15, 19, 20, 21, 26, 30, 31, 32, 60, 61,), - array(14, 38, 39, 40, 55, 56, 57, 58,), - array(2, 38, 39, 40, 55, 56, 57, 58,), - array(37, 38, 39, 40, 55, 56, 57, 58,), - array(37, 38, 39, 40, 55, 56, 57, 58,), - array(14, 38, 39, 40, 55, 56, 57, 58,), - array(38, 39, 40, 55, 56, 57, 58, 61,), - array(14, 38, 39, 40, 55, 56, 57, 58,), - array(14, 38, 39, 40, 55, 56, 57, 58,), - array(38, 39, 40, 53, 55, 56, 57, 58,), - array(22, 38, 39, 40, 55, 56, 57, 58,), - array(28, 38, 39, 40, 55, 56, 57, 58,), - array(14, 38, 39, 40, 55, 56, 57, 58,), - array(38, 39, 40, 55, 56, 57, 58,), - array(38, 39, 40, 55, 56, 57, 58,), - array(38, 39, 40, 55, 56, 57, 58,), - array(38, 39, 40, 55, 56, 57, 58,), - array(38, 39, 40, 55, 56, 57, 58,), - array(38, 39, 40, 55, 56, 57, 58,), - array(38, 39, 40, 55, 56, 57, 58,), - array(38, 39, 40, 55, 56, 57, 58,), - array(38, 39, 40, 55, 56, 57, 58,), - array(38, 39, 40, 55, 56, 57, 58,), - array(38, 39, 40, 55, 56, 57, 58,), - array(38, 39, 40, 55, 56, 57, 58,), - array(38, 39, 40, 55, 56, 57, 58,), - array(38, 39, 40, 55, 56, 57, 58,), - array(38, 39, 40, 55, 56, 57, 58,), - array(10, 13, 17, 27, 29, 33,), - array(10, 13, 17, 27, 33,), - array(15, 36, 54,), - array(1, 27, 33,), - array(15, 36, 54,), - array(1, 27, 33,), - array(18, 45, 52,), - array(16, 18, 48,), - array(1, 2,), - array(3, 4, 5, 6, 7, 10, 11, 12, 13, 19, 20, 21, 26, 30, 31, 32,), - array(2, 10, 13, 16, 17, 18, 45, 48, 50, 52,), - array(1, 14, 27, 33,), - array(10, 13, 17, 50,), - array(13, 15, 17, 54,), - array(1, 14, 27, 33,), - array(1, 14, 27, 33,), - array(10, 13, 17,), - array(16, 18, 48,), - array(10, 13, 17,), - array(1, 29,), - array(18, 48,), - array(15, 17,), - array(27, 33,), - array(27, 33,), - array(15, 17,), - array(1, 53,), - array(27, 33,), - array(1, 18,), - array(27, 33,), - array(15, 54,), - array(1,), - array(1,), - array(1,), - array(18,), - array(1,), - array(1,), - array(18,), - array(1,), - array(1,), - array(1,), - array(1,), - array(1,), - array(), - array(), - array(2, 10, 13, 17, 18, 45, 48, 50, 52, 53,), - array(2, 10, 13, 16, 17, 18, 45, 48, 50, 52,), - array(2, 10, 13, 17, 18, 45, 48, 50, 52,), - array(2, 10, 13, 17, 18, 45, 48, 50, 52,), - array(10, 13, 17, 18, 45, 48, 50, 52,), - array(13, 15, 17, 34, 54,), - array(10, 13, 17, 50,), - array(16, 45, 52,), - array(10, 13, 17,), - array(27, 33,), - array(45, 52,), - array(15, 54,), - array(45, 52,), - array(15, 54,), - array(45, 52,), - array(45, 52,), - array(45, 52,), - array(27, 33,), - array(27, 33,), - array(45, 52,), - array(45, 52,), - array(27, 33,), - array(45, 52,), - array(13, 36,), - array(27, 33,), - array(27, 33,), - array(45, 52,), - array(16, 23,), - array(27, 33,), - array(27, 33,), - array(45, 52,), - array(27, 33,), - array(27, 33,), - array(27, 33,), - array(27, 33,), - array(27, 33,), - array(1,), - array(2,), - array(18,), - array(2,), - array(36,), - array(1,), - array(1,), - array(18,), - array(1,), - array(18,), - array(), - array(), - array(), - array(), - array(), - array(), - array(2, 36, 38, 39, 40, 48, 55, 56, 57, 58,), - array(14, 22, 24, 27, 33, 35, 37, 45,), - array(14, 16, 27, 33, 36, 48,), - array(14, 23, 27, 33, 46,), - array(14, 23, 27, 33, 46,), - array(36, 45, 48, 53,), - array(10, 13, 17, 50,), - array(29, 36, 48,), - array(23, 46, 61,), - array(23, 46, 53,), - array(35, 37,), - array(35, 37,), - array(16, 45,), - array(35, 53,), - array(8, 9,), - array(36, 48,), - array(36, 48,), - array(35, 37,), - array(23, 46,), - array(36, 48,), - array(17, 50,), - array(22, 35,), - array(7, 9,), - array(35, 37,), - array(45, 53,), - array(24,), - array(16,), - array(8,), - array(37,), - array(14,), - array(17,), - array(51,), - array(14,), - array(15,), - array(53,), - array(53,), - array(17,), - array(51,), - array(41,), - array(17,), - array(17,), - array(17,), - array(45,), - array(34,), - array(17,), - array(17,), - array(34,), - array(17,), - array(36,), - array(17,), - array(36,), - array(17,), - array(), - array(), - array(), - array(), - array(), - array(), - array(), - array(), - array(), - array(), - array(), - array(), - array(), - array(), - array(), - array(), - array(), - array(), - array(), - array(), - array(), - array(), - array(), - array(), - array(), - array(), - array(), - array(), - array(), - array(), - array(), - array(), - array(), - array(), - array(), - array(), - array(), - array(), - array(), - array(), - array(), - array(), - array(), - array(), - array(), - array(), - array(), - array(), - array(), - array(), - array(), - array(), - array(), - array(), - array(), - array(), - array(), - array(), - array(), - array(), - array(), - array(), - array(), - array(), - array(), - array(), - array(), - array(), - array(), - array(), - array(), - array(), - array(), - array(), - array(), - array(), - array(), - array(), - array(), - array(), - array(), - array(), - array(), - array(), - array(), - array(), - array(), - array(), - array(), - array(), - array(), - array(), - array(), - array(), - array(), - array(), - ); - - public static $yy_default = array( - 338, 514, 514, 499, 499, 514, 514, 476, 476, 476, - 514, 514, 514, 514, 514, 514, 514, 514, 514, 514, - 514, 514, 514, 514, 514, 514, 514, 514, 514, 514, - 514, 514, 514, 514, 514, 514, 514, 514, 514, 514, - 514, 514, 514, 514, 514, 514, 379, 358, 379, 514, - 514, 415, 514, 379, 514, 514, 351, 514, 514, 514, - 514, 514, 514, 514, 514, 514, 384, 514, 399, 475, - 351, 403, 390, 474, 500, 502, 384, 501, 363, 381, - 404, 386, 391, 379, 379, 514, 379, 514, 379, 489, - 431, 370, 327, 430, 393, 441, 514, 393, 393, 441, - 431, 441, 393, 431, 514, 379, 360, 514, 393, 379, - 373, 379, 514, 406, 402, 375, 431, 396, 398, 486, - 393, 408, 397, 407, 406, 483, 336, 430, 430, 430, - 430, 430, 514, 443, 457, 441, 367, 438, 514, 436, - 514, 435, 434, 466, 368, 348, 439, 437, 361, 467, - 441, 356, 354, 468, 514, 366, 355, 469, 362, 359, - 352, 369, 365, 371, 478, 463, 477, 441, 374, 376, - 490, 424, 487, 441, 441, 482, 482, 336, 482, 415, - 411, 415, 405, 405, 415, 442, 415, 405, 405, 514, - 514, 411, 514, 514, 425, 514, 514, 405, 415, 514, - 514, 334, 514, 411, 387, 514, 514, 514, 514, 514, - 514, 514, 514, 420, 514, 514, 514, 417, 514, 514, - 514, 411, 413, 514, 514, 514, 514, 488, 514, 457, - 514, 421, 364, 420, 340, 422, 357, 341, 409, 400, - 480, 457, 462, 401, 485, 423, 426, 342, 447, 380, - 416, 339, 428, 329, 330, 444, 445, 446, 394, 331, - 395, 429, 419, 388, 332, 418, 410, 392, 412, 333, - 335, 414, 337, 472, 417, 479, 427, 497, 347, 461, - 460, 459, 378, 346, 464, 510, 495, 511, 498, 473, - 377, 496, 503, 506, 513, 512, 509, 507, 504, 508, - 345, 458, 471, 448, 505, 454, 452, 455, 456, 450, - 491, 449, 492, 493, 494, 470, 451, 328, 453, 343, - 344, 372, 481, 432, 433, 465, 440, - ); - - public static $yyFallback = array(); - - public static $yyRuleName = array( - 'start ::= template', - 'template ::= template PHP', - 'template ::= template TEXT', - 'template ::= template STRIPON', - 'template ::= template STRIPOFF', - 'template ::= template LITERALSTART literal_e2 LITERALEND', - 'literal_e2 ::= literal_e1 LITERALSTART literal_e1 LITERALEND', - 'literal_e2 ::= literal_e1', - 'literal_e1 ::= literal_e1 LITERAL', - 'literal_e1 ::=', - 'template ::= template smartytag', - 'template ::=', - 'smartytag ::= SIMPELOUTPUT', - 'smartytag ::= SIMPLETAG', - 'smartytag ::= SMARTYBLOCKCHILDPARENT', - 'smartytag ::= LDEL tagbody RDEL', - 'smartytag ::= tag RDEL', - 'tagbody ::= outattr', - 'tagbody ::= DOLLARID eqoutattr', - 'tagbody ::= varindexed eqoutattr', - 'eqoutattr ::= EQUAL outattr', - 'outattr ::= output attributes', - 'output ::= variable', - 'output ::= value', - 'output ::= expr', - 'tag ::= LDEL ID attributes', - 'tag ::= LDEL ID', - 'tag ::= LDEL ID modifierlist attributes', - 'tag ::= LDEL ID PTR ID attributes', - 'tag ::= LDEL ID PTR ID modifierlist attributes', - 'tag ::= LDELMAKENOCACHE DOLLARID', - 'tag ::= LDELIF expr', - 'tag ::= LDELIF expr attributes', - 'tag ::= LDELIF statement', - 'tag ::= LDELIF statement attributes', - 'tag ::= LDELFOR statements SEMICOLON expr SEMICOLON varindexed foraction attributes', - 'foraction ::= EQUAL expr', - 'foraction ::= INCDEC', - 'tag ::= LDELFOR statement TO expr attributes', - 'tag ::= LDELFOR statement TO expr STEP expr attributes', - 'tag ::= LDELFOREACH SPACE expr AS varvar attributes', - 'tag ::= LDELFOREACH SPACE expr AS varvar APTR varvar attributes', - 'tag ::= LDELFOREACH attributes', - 'tag ::= LDELSETFILTER ID modparameters', - 'tag ::= LDELSETFILTER ID modparameters modifierlist', - 'smartytag ::= CLOSETAG', - 'tag ::= LDELSLASH ID', - 'tag ::= LDELSLASH ID modifierlist', - 'tag ::= LDELSLASH ID PTR ID', - 'tag ::= LDELSLASH ID PTR ID modifierlist', - 'attributes ::= attributes attribute', - 'attributes ::= attribute', - 'attributes ::=', - 'attribute ::= SPACE ID EQUAL ID', - 'attribute ::= ATTR expr', - 'attribute ::= ATTR value', - 'attribute ::= SPACE ID', - 'attribute ::= SPACE expr', - 'attribute ::= SPACE value', - 'attribute ::= SPACE INTEGER EQUAL expr', - 'statements ::= statement', - 'statements ::= statements COMMA statement', - 'statement ::= DOLLARID EQUAL INTEGER', - 'statement ::= DOLLARID EQUAL expr', - 'statement ::= varindexed EQUAL expr', - 'statement ::= OPENP statement CLOSEP', - 'expr ::= value', - 'expr ::= ternary', - 'expr ::= DOLLARID COLON ID', - 'expr ::= expr MATH value', - 'expr ::= expr UNIMATH value', - 'expr ::= expr tlop value', - 'expr ::= expr lop expr', - 'expr ::= expr scond', - 'expr ::= expr ISIN array', - 'expr ::= expr ISIN value', - 'ternary ::= OPENP expr CLOSEP QMARK DOLLARID COLON expr', - 'ternary ::= OPENP expr CLOSEP QMARK expr COLON expr', - 'value ::= variable', - 'value ::= UNIMATH value', - 'value ::= NOT value', - 'value ::= TYPECAST value', - 'value ::= variable INCDEC', - 'value ::= HEX', - 'value ::= INTEGER', - 'value ::= INTEGER DOT INTEGER', - 'value ::= INTEGER DOT', - 'value ::= DOT INTEGER', - 'value ::= ID', - 'value ::= function', - 'value ::= OPENP expr CLOSEP', - 'value ::= variable INSTANCEOF ns1', - 'value ::= variable INSTANCEOF variable', - 'value ::= SINGLEQUOTESTRING', - 'value ::= doublequoted_with_quotes', - 'value ::= varindexed DOUBLECOLON static_class_access', - 'value ::= smartytag', - 'value ::= value modifierlist', - 'value ::= NAMESPACE', - 'value ::= arraydef', - 'value ::= ns1 DOUBLECOLON static_class_access', - 'ns1 ::= ID', - 'ns1 ::= NAMESPACE', - 'variable ::= DOLLARID', - 'variable ::= varindexed', - 'variable ::= varvar AT ID', - 'variable ::= object', - 'variable ::= HATCH ID HATCH', - 'variable ::= HATCH ID HATCH arrayindex', - 'variable ::= HATCH variable HATCH', - 'variable ::= HATCH variable HATCH arrayindex', - 'varindexed ::= DOLLARID arrayindex', - 'varindexed ::= varvar arrayindex', - 'arrayindex ::= arrayindex indexdef', - 'arrayindex ::=', - 'indexdef ::= DOT DOLLARID', - 'indexdef ::= DOT varvar', - 'indexdef ::= DOT varvar AT ID', - 'indexdef ::= DOT ID', - 'indexdef ::= DOT INTEGER', - 'indexdef ::= DOT LDEL expr RDEL', - 'indexdef ::= OPENB ID CLOSEB', - 'indexdef ::= OPENB ID DOT ID CLOSEB', - 'indexdef ::= OPENB SINGLEQUOTESTRING CLOSEB', - 'indexdef ::= OPENB INTEGER CLOSEB', - 'indexdef ::= OPENB DOLLARID CLOSEB', - 'indexdef ::= OPENB variable CLOSEB', - 'indexdef ::= OPENB value CLOSEB', - 'indexdef ::= OPENB expr CLOSEB', - 'indexdef ::= OPENB CLOSEB', - 'varvar ::= DOLLARID', - 'varvar ::= DOLLAR', - 'varvar ::= varvar varvarele', - 'varvarele ::= ID', - 'varvarele ::= SIMPELOUTPUT', - 'varvarele ::= LDEL expr RDEL', - 'object ::= varindexed objectchain', - 'objectchain ::= objectelement', - 'objectchain ::= objectchain objectelement', - 'objectelement ::= PTR ID arrayindex', - 'objectelement ::= PTR varvar arrayindex', - 'objectelement ::= PTR LDEL expr RDEL arrayindex', - 'objectelement ::= PTR ID LDEL expr RDEL arrayindex', - 'objectelement ::= PTR method', - 'function ::= ns1 OPENP params CLOSEP', - 'method ::= ID OPENP params CLOSEP', - 'method ::= DOLLARID OPENP params CLOSEP', - 'params ::= params COMMA expr', - 'params ::= expr', - 'params ::=', - 'modifierlist ::= modifierlist modifier modparameters', - 'modifierlist ::= modifier modparameters', - 'modifier ::= VERT AT ID', - 'modifier ::= VERT ID', - 'modparameters ::= modparameters modparameter', - 'modparameters ::=', - 'modparameter ::= COLON value', - 'modparameter ::= COLON UNIMATH value', - 'modparameter ::= COLON array', - 'static_class_access ::= method', - 'static_class_access ::= method objectchain', - 'static_class_access ::= ID', - 'static_class_access ::= DOLLARID arrayindex', - 'static_class_access ::= DOLLARID arrayindex objectchain', - 'lop ::= LOGOP', - 'lop ::= SLOGOP', - 'tlop ::= TLOGOP', - 'scond ::= SINGLECOND', - 'arraydef ::= OPENB arrayelements CLOSEB', - 'arraydef ::= ARRAYOPEN arrayelements CLOSEP', - 'arrayelements ::= arrayelement', - 'arrayelements ::= arrayelements COMMA arrayelement', - 'arrayelements ::=', - 'arrayelement ::= value APTR expr', - 'arrayelement ::= ID APTR expr', - 'arrayelement ::= expr', - 'doublequoted_with_quotes ::= QUOTE QUOTE', - 'doublequoted_with_quotes ::= QUOTE doublequoted QUOTE', - 'doublequoted ::= doublequoted doublequotedcontent', - 'doublequoted ::= doublequotedcontent', - 'doublequotedcontent ::= BACKTICK variable BACKTICK', - 'doublequotedcontent ::= BACKTICK expr BACKTICK', - 'doublequotedcontent ::= DOLLARID', - 'doublequotedcontent ::= LDEL variable RDEL', - 'doublequotedcontent ::= LDEL expr RDEL', - 'doublequotedcontent ::= smartytag', - 'doublequotedcontent ::= TEXT', - ); +// line 23 "../smarty/lexer/smarty_internal_templateparser.y" - public static $yyRuleInfo = array( - array(0 => 63, 1 => 1), - array(0 => 64, 1 => 2), - array(0 => 64, 1 => 2), - array(0 => 64, 1 => 2), - array(0 => 64, 1 => 2), - array(0 => 64, 1 => 4), - array(0 => 65, 1 => 4), - array(0 => 65, 1 => 1), - array(0 => 66, 1 => 2), - array(0 => 66, 1 => 0), - array(0 => 64, 1 => 2), - array(0 => 64, 1 => 0), - array(0 => 67, 1 => 1), - array(0 => 67, 1 => 1), - array(0 => 67, 1 => 1), - array(0 => 67, 1 => 3), - array(0 => 67, 1 => 2), - array(0 => 68, 1 => 1), - array(0 => 68, 1 => 2), - array(0 => 68, 1 => 2), - array(0 => 71, 1 => 2), - array(0 => 70, 1 => 2), - array(0 => 73, 1 => 1), - array(0 => 73, 1 => 1), - array(0 => 73, 1 => 1), - array(0 => 69, 1 => 3), - array(0 => 69, 1 => 2), - array(0 => 69, 1 => 4), - array(0 => 69, 1 => 5), - array(0 => 69, 1 => 6), - array(0 => 69, 1 => 2), - array(0 => 69, 1 => 2), - array(0 => 69, 1 => 3), - array(0 => 69, 1 => 2), - array(0 => 69, 1 => 3), - array(0 => 69, 1 => 8), - array(0 => 81, 1 => 2), - array(0 => 81, 1 => 1), - array(0 => 69, 1 => 5), - array(0 => 69, 1 => 7), - array(0 => 69, 1 => 6), - array(0 => 69, 1 => 8), - array(0 => 69, 1 => 2), - array(0 => 69, 1 => 3), - array(0 => 69, 1 => 4), - array(0 => 67, 1 => 1), - array(0 => 69, 1 => 2), - array(0 => 69, 1 => 3), - array(0 => 69, 1 => 4), - array(0 => 69, 1 => 5), - array(0 => 74, 1 => 2), - array(0 => 74, 1 => 1), - array(0 => 74, 1 => 0), - array(0 => 84, 1 => 4), - array(0 => 84, 1 => 2), - array(0 => 84, 1 => 2), - array(0 => 84, 1 => 2), - array(0 => 84, 1 => 2), - array(0 => 84, 1 => 2), - array(0 => 84, 1 => 4), - array(0 => 80, 1 => 1), - array(0 => 80, 1 => 3), - array(0 => 79, 1 => 3), - array(0 => 79, 1 => 3), - array(0 => 79, 1 => 3), - array(0 => 79, 1 => 3), - array(0 => 77, 1 => 1), - array(0 => 77, 1 => 1), - array(0 => 77, 1 => 3), - array(0 => 77, 1 => 3), - array(0 => 77, 1 => 3), - array(0 => 77, 1 => 3), - array(0 => 77, 1 => 3), - array(0 => 77, 1 => 2), - array(0 => 77, 1 => 3), - array(0 => 77, 1 => 3), - array(0 => 85, 1 => 7), - array(0 => 85, 1 => 7), - array(0 => 76, 1 => 1), - array(0 => 76, 1 => 2), - array(0 => 76, 1 => 2), - array(0 => 76, 1 => 2), - array(0 => 76, 1 => 2), - array(0 => 76, 1 => 1), - array(0 => 76, 1 => 1), - array(0 => 76, 1 => 3), - array(0 => 76, 1 => 2), - array(0 => 76, 1 => 2), - array(0 => 76, 1 => 1), - array(0 => 76, 1 => 1), - array(0 => 76, 1 => 3), - array(0 => 76, 1 => 3), - array(0 => 76, 1 => 3), - array(0 => 76, 1 => 1), - array(0 => 76, 1 => 1), - array(0 => 76, 1 => 3), - array(0 => 76, 1 => 1), - array(0 => 76, 1 => 2), - array(0 => 76, 1 => 1), - array(0 => 76, 1 => 1), - array(0 => 76, 1 => 3), - array(0 => 91, 1 => 1), - array(0 => 91, 1 => 1), - array(0 => 75, 1 => 1), - array(0 => 75, 1 => 1), - array(0 => 75, 1 => 3), - array(0 => 75, 1 => 1), - array(0 => 75, 1 => 3), - array(0 => 75, 1 => 4), - array(0 => 75, 1 => 3), - array(0 => 75, 1 => 4), - array(0 => 72, 1 => 2), - array(0 => 72, 1 => 2), - array(0 => 96, 1 => 2), - array(0 => 96, 1 => 0), - array(0 => 97, 1 => 2), - array(0 => 97, 1 => 2), - array(0 => 97, 1 => 4), - array(0 => 97, 1 => 2), - array(0 => 97, 1 => 2), - array(0 => 97, 1 => 4), - array(0 => 97, 1 => 3), - array(0 => 97, 1 => 5), - array(0 => 97, 1 => 3), - array(0 => 97, 1 => 3), - array(0 => 97, 1 => 3), - array(0 => 97, 1 => 3), - array(0 => 97, 1 => 3), - array(0 => 97, 1 => 3), - array(0 => 97, 1 => 2), - array(0 => 82, 1 => 1), - array(0 => 82, 1 => 1), - array(0 => 82, 1 => 2), - array(0 => 98, 1 => 1), - array(0 => 98, 1 => 1), - array(0 => 98, 1 => 3), - array(0 => 95, 1 => 2), - array(0 => 99, 1 => 1), - array(0 => 99, 1 => 2), - array(0 => 100, 1 => 3), - array(0 => 100, 1 => 3), - array(0 => 100, 1 => 5), - array(0 => 100, 1 => 6), - array(0 => 100, 1 => 2), - array(0 => 90, 1 => 4), - array(0 => 101, 1 => 4), - array(0 => 101, 1 => 4), - array(0 => 102, 1 => 3), - array(0 => 102, 1 => 1), - array(0 => 102, 1 => 0), - array(0 => 78, 1 => 3), - array(0 => 78, 1 => 2), - array(0 => 103, 1 => 3), - array(0 => 103, 1 => 2), - array(0 => 83, 1 => 2), - array(0 => 83, 1 => 0), - array(0 => 104, 1 => 2), - array(0 => 104, 1 => 3), - array(0 => 104, 1 => 2), - array(0 => 93, 1 => 1), - array(0 => 93, 1 => 2), - array(0 => 93, 1 => 1), - array(0 => 93, 1 => 2), - array(0 => 93, 1 => 3), - array(0 => 87, 1 => 1), - array(0 => 87, 1 => 1), - array(0 => 86, 1 => 1), - array(0 => 88, 1 => 1), - array(0 => 94, 1 => 3), - array(0 => 94, 1 => 3), - array(0 => 105, 1 => 1), - array(0 => 105, 1 => 3), - array(0 => 105, 1 => 0), - array(0 => 106, 1 => 3), - array(0 => 106, 1 => 3), - array(0 => 106, 1 => 1), - array(0 => 92, 1 => 2), - array(0 => 92, 1 => 3), - array(0 => 107, 1 => 2), - array(0 => 107, 1 => 1), - array(0 => 108, 1 => 3), - array(0 => 108, 1 => 3), - array(0 => 108, 1 => 1), - array(0 => 108, 1 => 3), - array(0 => 108, 1 => 3), - array(0 => 108, 1 => 1), - array(0 => 108, 1 => 1), - ); - - public static $yyReduceMap = array( - 0 => 0, - 1 => 1, - 2 => 2, - 3 => 3, - 4 => 4, - 5 => 5, - 6 => 6, - 7 => 7, - 22 => 7, - 23 => 7, - 24 => 7, - 37 => 7, - 57 => 7, - 58 => 7, - 66 => 7, - 67 => 7, - 78 => 7, - 83 => 7, - 84 => 7, - 89 => 7, - 93 => 7, - 94 => 7, - 98 => 7, - 99 => 7, - 101 => 7, - 106 => 7, - 170 => 7, - 175 => 7, - 8 => 8, - 9 => 9, - 10 => 10, - 12 => 12, - 13 => 13, - 14 => 14, - 15 => 15, - 16 => 16, - 17 => 17, - 18 => 18, - 19 => 19, - 20 => 20, - 21 => 21, - 25 => 25, - 26 => 26, - 27 => 27, - 28 => 28, - 29 => 29, - 30 => 30, - 31 => 31, - 32 => 32, - 34 => 32, - 33 => 33, - 35 => 35, - 36 => 36, - 38 => 38, - 39 => 39, - 40 => 40, - 41 => 41, - 42 => 42, - 43 => 43, - 44 => 44, - 45 => 45, - 46 => 46, - 47 => 47, - 48 => 48, - 49 => 49, - 50 => 50, - 51 => 51, - 60 => 51, - 148 => 51, - 152 => 51, - 156 => 51, - 158 => 51, - 52 => 52, - 149 => 52, - 155 => 52, - 53 => 53, - 54 => 54, - 55 => 54, - 56 => 56, - 133 => 56, - 59 => 59, - 61 => 61, - 62 => 62, - 63 => 62, - 64 => 64, - 65 => 65, - 68 => 68, - 69 => 69, - 70 => 69, - 71 => 71, - 72 => 72, - 73 => 73, - 74 => 74, - 75 => 75, - 76 => 76, - 77 => 77, - 79 => 79, - 81 => 79, - 82 => 79, - 113 => 79, - 80 => 80, - 85 => 85, - 86 => 86, - 87 => 87, - 88 => 88, - 90 => 90, - 91 => 91, - 92 => 91, - 95 => 95, - 96 => 96, - 97 => 97, - 100 => 100, - 102 => 102, - 103 => 103, - 104 => 104, - 105 => 105, - 107 => 107, - 108 => 108, - 109 => 109, - 110 => 110, - 111 => 111, - 112 => 112, - 114 => 114, - 172 => 114, - 115 => 115, - 116 => 116, - 117 => 117, - 118 => 118, - 119 => 119, - 120 => 120, - 128 => 120, - 121 => 121, - 122 => 122, - 123 => 123, - 124 => 123, - 126 => 123, - 127 => 123, - 125 => 125, - 129 => 129, - 130 => 130, - 131 => 131, - 176 => 131, - 132 => 132, - 134 => 134, - 135 => 135, - 136 => 136, - 137 => 137, - 138 => 138, - 139 => 139, - 140 => 140, - 141 => 141, - 142 => 142, - 143 => 143, - 144 => 144, - 145 => 145, - 146 => 146, - 147 => 147, - 150 => 150, - 151 => 151, - 153 => 153, - 154 => 154, - 157 => 157, - 159 => 159, - 160 => 160, - 161 => 161, - 162 => 162, - 163 => 163, - 164 => 164, - 165 => 165, - 166 => 166, - 167 => 167, - 168 => 168, - 169 => 168, - 171 => 171, - 173 => 173, - 174 => 174, - 177 => 177, - 178 => 178, - 179 => 179, - 180 => 180, - 183 => 180, - 181 => 181, - 184 => 181, - 182 => 182, - 185 => 185, - 186 => 186, - ); + const ERR1 = 'Security error: Call to private object member not allowed'; + const ERR2 = 'Security error: Call to dynamic object member not allowed'; /** * result status @@ -1654,13 +81,19 @@ class Smarty_Internal_Templateparser */ public $lex; + /** + * internal error flag + * + * @var bool + */ + private $internalError = false; + /** * {strip} status * * @var bool */ public $strip = false; - /** * compiler object * @@ -1710,54 +143,6 @@ class Smarty_Internal_Templateparser */ public $template_postfix = array(); - public $yyTraceFILE; - - public $yyTracePrompt; - - public $yyidx; - - public $yyerrcnt; - - public $yystack = array(); - - public $yyTokenName = array( - '$', 'VERT', 'COLON', 'PHP', - 'TEXT', 'STRIPON', 'STRIPOFF', 'LITERALSTART', - 'LITERALEND', 'LITERAL', 'SIMPELOUTPUT', 'SIMPLETAG', - 'SMARTYBLOCKCHILDPARENT', 'LDEL', 'RDEL', 'DOLLARID', - 'EQUAL', 'ID', 'PTR', 'LDELMAKENOCACHE', - 'LDELIF', 'LDELFOR', 'SEMICOLON', 'INCDEC', - 'TO', 'STEP', 'LDELFOREACH', 'SPACE', - 'AS', 'APTR', 'LDELSETFILTER', 'CLOSETAG', - 'LDELSLASH', 'ATTR', 'INTEGER', 'COMMA', - 'OPENP', 'CLOSEP', 'MATH', 'UNIMATH', - 'ISIN', 'QMARK', 'NOT', 'TYPECAST', - 'HEX', 'DOT', 'INSTANCEOF', 'SINGLEQUOTESTRING', - 'DOUBLECOLON', 'NAMESPACE', 'AT', 'HATCH', - 'OPENB', 'CLOSEB', 'DOLLAR', 'LOGOP', - 'SLOGOP', 'TLOGOP', 'SINGLECOND', 'ARRAYOPEN', - 'QUOTE', 'BACKTICK', 'error', 'start', - 'template', 'literal_e2', 'literal_e1', 'smartytag', - 'tagbody', 'tag', 'outattr', 'eqoutattr', - 'varindexed', 'output', 'attributes', 'variable', - 'value', 'expr', 'modifierlist', 'statement', - 'statements', 'foraction', 'varvar', 'modparameters', - 'attribute', 'ternary', 'tlop', 'lop', - 'scond', 'array', 'function', 'ns1', - 'doublequoted_with_quotes', 'static_class_access', 'arraydef', 'object', - 'arrayindex', 'indexdef', 'varvarele', 'objectchain', - 'objectelement', 'method', 'params', 'modifier', - 'modparameter', 'arrayelements', 'arrayelement', 'doublequoted', - 'doublequotedcontent', - ); - - /** - * internal error flag - * - * @var bool - */ - private $internalError = false; /* Index of top element in stack */ - private $_retvalue; /* Shifts left before out of the error */ /** * constructor * @@ -1772,16 +157,9 @@ public function __construct(Smarty_Internal_Templatelexer $lex, Smarty_Internal_ $this->smarty = $this->template->smarty; $this->security = isset($this->smarty->security_policy) ? $this->smarty->security_policy : false; $this->current_buffer = $this->root_buffer = new Smarty_Internal_ParseTree_Template(); - } /* The parser's stack */ - public static function yy_destructor($yymajor, $yypminor) - { - switch ($yymajor) { - default: - break; /* If no destructor action specified: do nothing */ - } } - /** + /** * insert PHP code in current buffer * * @param string $code @@ -1823,6 +201,898 @@ public function mergePrefixCode($code) return new Smarty_Internal_ParseTree_Tag($this, $this->compiler->processNocacheCode($tmp, true)); } + + const TP_VERT = 1; + const TP_COLON = 2; + const TP_TEXT = 3; + const TP_STRIPON = 4; + const TP_STRIPOFF = 5; + const TP_LITERALSTART = 6; + const TP_LITERALEND = 7; + const TP_LITERAL = 8; + const TP_SIMPELOUTPUT = 9; + const TP_SIMPLETAG = 10; + const TP_SMARTYBLOCKCHILDPARENT = 11; + const TP_LDEL = 12; + const TP_RDEL = 13; + const TP_DOLLARID = 14; + const TP_EQUAL = 15; + const TP_ID = 16; + const TP_PTR = 17; + const TP_LDELMAKENOCACHE = 18; + const TP_LDELIF = 19; + const TP_LDELFOR = 20; + const TP_SEMICOLON = 21; + const TP_INCDEC = 22; + const TP_TO = 23; + const TP_STEP = 24; + const TP_LDELFOREACH = 25; + const TP_SPACE = 26; + const TP_AS = 27; + const TP_APTR = 28; + const TP_LDELSETFILTER = 29; + const TP_CLOSETAG = 30; + const TP_LDELSLASH = 31; + const TP_ATTR = 32; + const TP_INTEGER = 33; + const TP_COMMA = 34; + const TP_OPENP = 35; + const TP_CLOSEP = 36; + const TP_MATH = 37; + const TP_UNIMATH = 38; + const TP_ISIN = 39; + const TP_QMARK = 40; + const TP_NOT = 41; + const TP_TYPECAST = 42; + const TP_HEX = 43; + const TP_DOT = 44; + const TP_INSTANCEOF = 45; + const TP_SINGLEQUOTESTRING = 46; + const TP_DOUBLECOLON = 47; + const TP_NAMESPACE = 48; + const TP_AT = 49; + const TP_HATCH = 50; + const TP_OPENB = 51; + const TP_CLOSEB = 52; + const TP_DOLLAR = 53; + const TP_LOGOP = 54; + const TP_SLOGOP = 55; + const TP_TLOGOP = 56; + const TP_SINGLECOND = 57; + const TP_ARRAYOPEN = 58; + const TP_QUOTE = 59; + const TP_BACKTICK = 60; + const YY_NO_ACTION = 514; + const YY_ACCEPT_ACTION = 513; + const YY_ERROR_ACTION = 512; + + const YY_SZ_ACTTAB = 1997; +public static $yy_action = array( + 249, 250, 239, 1, 27, 127, 220, 184, 160, 213, + 11, 54, 278, 10, 173, 34, 108, 387, 282, 279, + 223, 321, 221, 8, 194, 387, 18, 387, 85, 41, + 387, 285, 42, 44, 264, 222, 387, 209, 387, 198, + 387, 52, 5, 307, 288, 288, 164, 283, 224, 4, + 50, 249, 250, 239, 1, 232, 131, 381, 189, 205, + 213, 11, 54, 39, 35, 243, 31, 108, 94, 17, + 381, 223, 321, 221, 439, 226, 381, 33, 49, 426, + 41, 439, 89, 42, 44, 264, 222, 9, 235, 163, + 198, 426, 52, 5, 131, 288, 212, 284, 102, 106, + 4, 50, 249, 250, 239, 1, 232, 129, 426, 189, + 347, 213, 11, 54, 175, 324, 347, 208, 108, 22, + 426, 301, 223, 321, 221, 302, 226, 135, 18, 49, + 52, 41, 26, 288, 42, 44, 264, 222, 16, 235, + 294, 198, 204, 52, 5, 170, 288, 32, 90, 267, + 268, 4, 50, 249, 250, 239, 1, 20, 129, 185, + 179, 255, 213, 11, 54, 455, 288, 192, 455, 108, + 175, 167, 455, 223, 321, 221, 439, 226, 256, 18, + 55, 292, 41, 439, 132, 42, 44, 264, 222, 427, + 235, 12, 198, 165, 52, 5, 232, 288, 288, 347, + 153, 427, 4, 50, 249, 250, 239, 1, 232, 129, + 286, 181, 347, 213, 11, 54, 24, 13, 347, 49, + 108, 232, 320, 426, 223, 321, 221, 195, 201, 173, + 18, 49, 139, 41, 296, 426, 42, 44, 264, 222, + 7, 235, 286, 198, 49, 52, 5, 147, 288, 117, + 150, 317, 263, 4, 50, 249, 250, 239, 1, 95, + 130, 173, 189, 155, 213, 11, 54, 22, 244, 271, + 192, 108, 323, 286, 101, 223, 321, 221, 294, 226, + 204, 18, 348, 257, 41, 166, 283, 42, 44, 264, + 222, 28, 235, 300, 198, 348, 52, 5, 247, 288, + 117, 348, 94, 206, 4, 50, 249, 250, 239, 1, + 95, 129, 22, 189, 277, 213, 11, 54, 91, 274, + 224, 426, 108, 323, 216, 156, 223, 321, 221, 132, + 180, 262, 18, 426, 100, 41, 12, 288, 42, 44, + 264, 222, 15, 235, 216, 198, 254, 52, 5, 233, + 288, 210, 190, 192, 100, 4, 50, 249, 250, 239, + 1, 3, 131, 94, 189, 192, 213, 11, 54, 269, + 10, 204, 290, 108, 325, 216, 224, 223, 321, 221, + 23, 226, 211, 33, 315, 100, 45, 513, 92, 42, + 44, 264, 222, 102, 235, 178, 198, 268, 52, 5, + 275, 288, 161, 192, 37, 25, 4, 50, 249, 250, + 239, 1, 286, 129, 172, 187, 305, 213, 11, 54, + 164, 283, 310, 141, 108, 281, 281, 236, 223, 321, + 221, 169, 226, 230, 18, 122, 171, 41, 225, 175, + 42, 44, 264, 222, 144, 235, 303, 198, 134, 52, + 5, 265, 288, 151, 286, 192, 175, 4, 50, 249, + 250, 239, 1, 286, 128, 94, 189, 143, 213, 11, + 54, 219, 152, 207, 193, 108, 149, 281, 31, 223, + 321, 221, 100, 226, 21, 6, 286, 288, 41, 158, + 16, 42, 44, 264, 222, 102, 235, 238, 198, 286, + 52, 5, 157, 288, 281, 122, 168, 283, 4, 50, + 249, 250, 239, 1, 30, 93, 308, 51, 215, 213, + 11, 54, 53, 251, 140, 248, 108, 245, 304, 116, + 223, 321, 221, 111, 226, 176, 18, 270, 266, 41, + 224, 322, 42, 44, 264, 222, 7, 235, 259, 198, + 147, 52, 5, 257, 288, 43, 40, 38, 83, 4, + 50, 241, 214, 204, 319, 280, 88, 107, 138, 182, + 97, 64, 311, 312, 313, 316, 95, 281, 298, 258, + 142, 234, 94, 105, 272, 197, 231, 482, 237, 323, + 37, 133, 324, 241, 214, 204, 319, 314, 88, 107, + 296, 183, 97, 82, 84, 43, 40, 38, 95, 296, + 296, 258, 296, 296, 296, 159, 272, 197, 231, 296, + 237, 323, 311, 312, 313, 316, 241, 296, 204, 296, + 296, 103, 296, 296, 199, 104, 77, 296, 296, 110, + 296, 95, 296, 296, 258, 278, 296, 296, 34, 272, + 197, 231, 279, 237, 323, 43, 40, 38, 296, 296, + 296, 241, 26, 204, 196, 276, 103, 296, 16, 199, + 104, 77, 311, 312, 313, 316, 95, 192, 296, 258, + 146, 296, 296, 296, 272, 197, 231, 296, 237, 323, + 286, 393, 39, 35, 243, 296, 296, 296, 296, 191, + 276, 296, 26, 318, 252, 253, 126, 296, 16, 249, + 250, 239, 1, 296, 296, 131, 296, 261, 213, 11, + 54, 296, 296, 296, 426, 108, 393, 393, 393, 223, + 321, 221, 241, 296, 204, 299, 426, 103, 107, 296, + 183, 97, 82, 393, 393, 393, 393, 95, 296, 260, + 258, 52, 296, 296, 288, 272, 197, 231, 296, 237, + 323, 293, 296, 296, 296, 296, 296, 249, 250, 239, + 2, 296, 295, 296, 296, 296, 213, 11, 54, 296, + 296, 177, 296, 108, 136, 296, 296, 223, 321, 221, + 296, 296, 296, 293, 43, 40, 38, 296, 296, 249, + 250, 239, 2, 296, 295, 43, 40, 38, 213, 11, + 54, 311, 312, 313, 316, 108, 296, 291, 14, 223, + 321, 221, 311, 312, 313, 316, 296, 296, 241, 296, + 204, 296, 192, 103, 296, 296, 199, 104, 77, 296, + 296, 296, 296, 95, 383, 296, 258, 296, 296, 297, + 14, 272, 197, 231, 296, 237, 323, 383, 296, 296, + 241, 296, 204, 383, 296, 99, 296, 287, 199, 120, + 48, 241, 112, 204, 296, 95, 103, 296, 258, 199, + 120, 74, 296, 272, 197, 231, 95, 237, 323, 258, + 455, 296, 296, 455, 272, 197, 231, 455, 237, 323, + 241, 296, 204, 296, 296, 103, 200, 296, 199, 120, + 74, 296, 296, 296, 296, 95, 296, 296, 258, 278, + 296, 296, 34, 272, 197, 231, 279, 237, 323, 241, + 455, 204, 296, 296, 99, 202, 296, 199, 120, 56, + 241, 211, 204, 296, 95, 103, 296, 258, 199, 120, + 74, 296, 272, 197, 231, 95, 237, 323, 258, 227, + 296, 296, 296, 272, 197, 231, 296, 237, 323, 241, + 296, 204, 148, 296, 103, 203, 86, 199, 120, 73, + 296, 296, 286, 296, 95, 296, 296, 258, 278, 296, + 296, 34, 272, 197, 231, 279, 237, 323, 241, 296, + 204, 175, 296, 103, 296, 296, 199, 120, 75, 241, + 296, 204, 296, 95, 103, 296, 258, 199, 120, 63, + 296, 272, 197, 231, 95, 237, 323, 258, 229, 192, + 296, 296, 272, 197, 231, 296, 237, 323, 241, 296, + 204, 380, 296, 103, 296, 296, 199, 120, 58, 296, + 296, 296, 296, 95, 380, 296, 258, 296, 296, 296, + 380, 272, 197, 231, 296, 237, 323, 241, 296, 204, + 296, 296, 103, 296, 296, 199, 120, 71, 241, 296, + 204, 296, 95, 103, 296, 258, 199, 120, 79, 296, + 272, 197, 231, 95, 237, 323, 258, 296, 296, 296, + 154, 272, 197, 231, 87, 237, 323, 241, 296, 204, + 286, 296, 103, 296, 296, 199, 120, 70, 296, 296, + 296, 296, 95, 296, 296, 258, 296, 296, 296, 175, + 272, 197, 231, 296, 237, 323, 241, 296, 204, 296, + 296, 103, 296, 296, 199, 120, 56, 241, 296, 204, + 296, 95, 103, 296, 258, 199, 120, 46, 296, 272, + 197, 231, 95, 237, 323, 258, 296, 296, 296, 296, + 272, 197, 231, 296, 237, 323, 241, 296, 204, 296, + 296, 103, 296, 296, 199, 120, 78, 296, 296, 296, + 296, 95, 296, 296, 258, 296, 296, 296, 296, 272, + 197, 231, 296, 237, 323, 241, 296, 204, 296, 296, + 103, 296, 296, 199, 120, 66, 241, 296, 204, 296, + 95, 103, 296, 258, 199, 120, 59, 296, 272, 197, + 231, 95, 237, 323, 258, 296, 296, 296, 296, 272, + 197, 231, 296, 237, 323, 241, 296, 204, 296, 296, + 103, 296, 296, 186, 109, 57, 296, 296, 296, 296, + 95, 296, 296, 258, 296, 296, 296, 296, 272, 197, + 231, 296, 237, 323, 241, 296, 204, 296, 296, 103, + 296, 296, 188, 120, 67, 241, 296, 204, 296, 95, + 103, 296, 258, 199, 96, 62, 296, 272, 197, 231, + 95, 237, 323, 258, 296, 296, 296, 296, 272, 197, + 231, 296, 237, 323, 241, 296, 204, 296, 296, 103, + 296, 296, 199, 120, 80, 296, 296, 296, 296, 95, + 296, 296, 258, 296, 296, 296, 296, 272, 197, 231, + 296, 237, 323, 241, 296, 204, 296, 296, 103, 296, + 296, 199, 120, 76, 241, 296, 204, 296, 95, 103, + 296, 258, 199, 120, 81, 296, 272, 197, 231, 95, + 237, 323, 258, 296, 296, 296, 296, 272, 197, 231, + 296, 237, 323, 241, 296, 204, 296, 296, 103, 296, + 296, 199, 120, 65, 296, 296, 296, 296, 95, 296, + 296, 258, 296, 296, 296, 296, 272, 197, 231, 296, + 237, 323, 241, 296, 204, 296, 296, 103, 296, 296, + 199, 96, 68, 241, 296, 204, 296, 95, 103, 296, + 258, 199, 120, 61, 296, 272, 197, 231, 95, 237, + 323, 258, 296, 296, 296, 296, 272, 197, 231, 296, + 237, 323, 241, 296, 204, 296, 296, 103, 296, 296, + 199, 98, 69, 296, 296, 296, 296, 95, 296, 296, + 258, 296, 296, 296, 296, 272, 197, 231, 296, 237, + 323, 241, 296, 204, 296, 296, 103, 296, 296, 199, + 120, 72, 241, 296, 204, 296, 95, 103, 296, 258, + 199, 120, 47, 296, 272, 197, 231, 95, 237, 323, + 258, 296, 296, 296, 296, 272, 197, 231, 296, 237, + 323, 241, 192, 204, 296, 296, 103, 296, 296, 199, + 120, 60, 296, 296, 351, 296, 95, 296, 217, 258, + 296, 296, 296, 296, 272, 197, 231, 26, 237, 323, + 241, 296, 204, 16, 296, 103, 426, 296, 199, 125, + 296, 241, 296, 204, 296, 95, 103, 296, 426, 199, + 118, 296, 242, 272, 197, 231, 95, 237, 323, 296, + 296, 296, 296, 246, 272, 197, 231, 296, 237, 323, + 241, 296, 204, 278, 296, 103, 34, 296, 199, 121, + 279, 296, 296, 296, 296, 95, 296, 296, 296, 296, + 26, 296, 162, 272, 197, 231, 16, 237, 323, 241, + 296, 204, 296, 296, 103, 296, 296, 199, 123, 296, + 241, 296, 204, 296, 95, 103, 296, 296, 199, 114, + 296, 296, 272, 197, 231, 95, 237, 323, 296, 296, + 296, 296, 296, 272, 197, 231, 296, 237, 323, 241, + 296, 204, 296, 145, 103, 296, 296, 199, 124, 296, + 296, 296, 296, 286, 95, 39, 35, 243, 296, 296, + 296, 296, 272, 197, 231, 296, 237, 323, 241, 296, + 204, 296, 296, 103, 296, 296, 199, 115, 296, 241, + 296, 204, 296, 95, 103, 296, 296, 199, 113, 296, + 296, 272, 197, 231, 95, 237, 323, 296, 296, 296, + 296, 296, 272, 197, 231, 228, 237, 323, 241, 296, + 204, 296, 455, 103, 296, 455, 199, 119, 3, 455, + 439, 296, 296, 95, 296, 296, 296, 296, 296, 296, + 296, 272, 197, 231, 228, 237, 323, 296, 296, 296, + 296, 455, 296, 296, 455, 296, 296, 439, 455, 439, + 439, 228, 455, 296, 439, 296, 296, 137, 455, 296, + 296, 455, 296, 296, 32, 455, 439, 286, 296, 39, + 35, 243, 29, 296, 26, 296, 439, 296, 296, 439, + 16, 455, 296, 439, 306, 43, 40, 38, 296, 296, + 296, 296, 296, 439, 296, 296, 439, 296, 455, 296, + 439, 26, 311, 312, 313, 316, 296, 16, 228, 296, + 296, 296, 43, 40, 38, 455, 296, 296, 455, 296, + 296, 296, 455, 439, 296, 296, 19, 296, 296, 311, + 312, 313, 316, 455, 296, 296, 455, 296, 296, 296, + 455, 439, 296, 296, 296, 43, 40, 38, 296, 296, + 439, 296, 296, 439, 174, 455, 296, 439, 296, 240, + 309, 296, 311, 312, 313, 316, 296, 289, 439, 296, + 36, 439, 296, 455, 296, 439, 296, 296, 43, 40, + 38, 296, 296, 43, 40, 38, 296, 296, 296, 296, + 296, 43, 40, 38, 296, 311, 312, 313, 316, 296, + 311, 312, 313, 316, 296, 43, 40, 38, 311, 312, + 313, 316, 273, 43, 40, 38, 296, 296, 296, 296, + 296, 296, 311, 312, 313, 316, 296, 296, 296, 296, + 311, 312, 313, 316, 455, 296, 296, 455, 43, 40, + 38, 455, 439, 218, 43, 40, 38, 296, 296, 296, + 296, 296, 296, 296, 296, 311, 312, 313, 316, 296, + 296, 311, 312, 313, 316, 296, 296, 296, 296, 439, + 296, 296, 439, 296, 455, 296, 439, + ); + public static $yy_lookahead = array( + 9, 10, 11, 12, 12, 14, 14, 16, 16, 18, + 19, 20, 9, 34, 102, 12, 25, 13, 70, 16, + 29, 30, 31, 35, 33, 21, 35, 23, 95, 38, + 26, 52, 41, 42, 43, 44, 32, 46, 34, 48, + 36, 50, 51, 52, 53, 53, 98, 99, 44, 58, + 59, 9, 10, 11, 12, 22, 14, 13, 16, 15, + 18, 19, 20, 85, 86, 87, 15, 25, 17, 21, + 26, 29, 30, 31, 44, 33, 32, 35, 45, 35, + 38, 51, 34, 41, 42, 43, 44, 35, 46, 77, + 48, 47, 50, 51, 14, 53, 16, 13, 47, 47, + 58, 59, 9, 10, 11, 12, 22, 14, 35, 16, + 26, 18, 19, 20, 102, 103, 32, 44, 25, 34, + 47, 36, 29, 30, 31, 52, 33, 14, 35, 45, + 50, 38, 26, 53, 41, 42, 43, 44, 32, 46, + 66, 48, 68, 50, 51, 77, 53, 15, 35, 7, + 8, 58, 59, 9, 10, 11, 12, 12, 14, 14, + 16, 16, 18, 19, 20, 9, 53, 1, 12, 25, + 102, 82, 16, 29, 30, 31, 44, 33, 33, 35, + 106, 107, 38, 51, 44, 41, 42, 43, 44, 35, + 46, 51, 48, 82, 50, 51, 22, 53, 53, 13, + 73, 47, 58, 59, 9, 10, 11, 12, 22, 14, + 83, 16, 26, 18, 19, 20, 28, 12, 32, 45, + 25, 22, 70, 35, 29, 30, 31, 65, 33, 102, + 35, 45, 73, 38, 60, 47, 41, 42, 43, 44, + 35, 46, 83, 48, 45, 50, 51, 95, 53, 71, + 95, 52, 74, 58, 59, 9, 10, 11, 12, 81, + 14, 102, 16, 73, 18, 19, 20, 34, 90, 36, + 1, 25, 94, 83, 81, 29, 30, 31, 66, 33, + 68, 35, 13, 96, 38, 98, 99, 41, 42, 43, + 44, 15, 46, 100, 48, 26, 50, 51, 14, 53, + 71, 32, 17, 74, 58, 59, 9, 10, 11, 12, + 81, 14, 34, 16, 36, 18, 19, 20, 82, 107, + 44, 35, 25, 94, 71, 95, 29, 30, 31, 44, + 33, 78, 35, 47, 81, 38, 51, 53, 41, 42, + 43, 44, 15, 46, 71, 48, 16, 50, 51, 22, + 53, 78, 79, 1, 81, 58, 59, 9, 10, 11, + 12, 15, 14, 17, 16, 1, 18, 19, 20, 66, + 34, 68, 36, 25, 16, 71, 44, 29, 30, 31, + 28, 33, 78, 35, 52, 81, 38, 62, 63, 41, + 42, 43, 44, 47, 46, 6, 48, 8, 50, 51, + 16, 53, 73, 1, 2, 40, 58, 59, 9, 10, + 11, 12, 83, 14, 77, 16, 52, 18, 19, 20, + 98, 99, 52, 95, 25, 97, 97, 92, 29, 30, + 31, 77, 33, 49, 35, 100, 14, 38, 16, 102, + 41, 42, 43, 44, 73, 46, 14, 48, 14, 50, + 51, 36, 53, 73, 83, 1, 102, 58, 59, 9, + 10, 11, 12, 83, 14, 17, 16, 50, 18, 19, + 20, 17, 71, 64, 65, 25, 73, 97, 15, 29, + 30, 31, 81, 33, 26, 35, 83, 53, 38, 73, + 32, 41, 42, 43, 44, 47, 46, 92, 48, 83, + 50, 51, 95, 53, 97, 100, 98, 99, 58, 59, + 9, 10, 11, 12, 23, 14, 52, 16, 16, 18, + 19, 20, 16, 7, 50, 16, 25, 13, 13, 16, + 29, 30, 31, 16, 33, 16, 35, 33, 33, 38, + 44, 16, 41, 42, 43, 44, 35, 46, 16, 48, + 95, 50, 51, 96, 53, 37, 38, 39, 81, 58, + 59, 66, 67, 68, 69, 83, 71, 72, 95, 74, + 75, 76, 54, 55, 56, 57, 81, 97, 60, 84, + 95, 13, 17, 80, 89, 90, 91, 1, 93, 94, + 2, 81, 103, 66, 67, 68, 69, 99, 71, 72, + 108, 74, 75, 76, 81, 37, 38, 39, 81, 108, + 108, 84, 108, 108, 108, 95, 89, 90, 91, 108, + 93, 94, 54, 55, 56, 57, 66, 108, 68, 108, + 108, 71, 108, 108, 74, 75, 76, 108, 108, 21, + 108, 81, 108, 108, 84, 9, 108, 108, 12, 89, + 90, 91, 16, 93, 94, 37, 38, 39, 108, 108, + 108, 66, 26, 68, 104, 105, 71, 108, 32, 74, + 75, 76, 54, 55, 56, 57, 81, 1, 108, 84, + 73, 108, 108, 108, 89, 90, 91, 108, 93, 94, + 83, 2, 85, 86, 87, 108, 108, 108, 108, 104, + 105, 108, 26, 3, 4, 5, 6, 108, 32, 9, + 10, 11, 12, 108, 108, 14, 108, 16, 18, 19, + 20, 108, 108, 108, 35, 25, 37, 38, 39, 29, + 30, 31, 66, 108, 68, 69, 47, 71, 72, 108, + 74, 75, 76, 54, 55, 56, 57, 81, 108, 48, + 84, 50, 108, 108, 53, 89, 90, 91, 108, 93, + 94, 3, 108, 108, 108, 108, 108, 9, 10, 11, + 12, 108, 14, 108, 108, 108, 18, 19, 20, 108, + 108, 13, 108, 25, 27, 108, 108, 29, 30, 31, + 108, 108, 108, 3, 37, 38, 39, 108, 108, 9, + 10, 11, 12, 108, 14, 37, 38, 39, 18, 19, + 20, 54, 55, 56, 57, 25, 108, 59, 60, 29, + 30, 31, 54, 55, 56, 57, 108, 108, 66, 108, + 68, 108, 1, 71, 108, 108, 74, 75, 76, 108, + 108, 108, 108, 81, 13, 108, 84, 108, 108, 59, + 60, 89, 90, 91, 108, 93, 94, 26, 108, 108, + 66, 108, 68, 32, 108, 71, 108, 105, 74, 75, + 76, 66, 78, 68, 108, 81, 71, 108, 84, 74, + 75, 76, 108, 89, 90, 91, 81, 93, 94, 84, + 9, 108, 108, 12, 89, 90, 91, 16, 93, 94, + 66, 108, 68, 108, 108, 71, 101, 108, 74, 75, + 76, 108, 108, 108, 108, 81, 108, 108, 84, 9, + 108, 108, 12, 89, 90, 91, 16, 93, 94, 66, + 49, 68, 108, 108, 71, 101, 108, 74, 75, 76, + 66, 78, 68, 108, 81, 71, 108, 84, 74, 75, + 76, 108, 89, 90, 91, 81, 93, 94, 84, 49, + 108, 108, 108, 89, 90, 91, 108, 93, 94, 66, + 108, 68, 73, 108, 71, 101, 77, 74, 75, 76, + 108, 108, 83, 108, 81, 108, 108, 84, 9, 108, + 108, 12, 89, 90, 91, 16, 93, 94, 66, 108, + 68, 102, 108, 71, 108, 108, 74, 75, 76, 66, + 108, 68, 108, 81, 71, 108, 84, 74, 75, 76, + 108, 89, 90, 91, 81, 93, 94, 84, 49, 1, + 108, 108, 89, 90, 91, 108, 93, 94, 66, 108, + 68, 13, 108, 71, 108, 108, 74, 75, 76, 108, + 108, 108, 108, 81, 26, 108, 84, 108, 108, 108, + 32, 89, 90, 91, 108, 93, 94, 66, 108, 68, + 108, 108, 71, 108, 108, 74, 75, 76, 66, 108, + 68, 108, 81, 71, 108, 84, 74, 75, 76, 108, + 89, 90, 91, 81, 93, 94, 84, 108, 108, 108, + 73, 89, 90, 91, 77, 93, 94, 66, 108, 68, + 83, 108, 71, 108, 108, 74, 75, 76, 108, 108, + 108, 108, 81, 108, 108, 84, 108, 108, 108, 102, + 89, 90, 91, 108, 93, 94, 66, 108, 68, 108, + 108, 71, 108, 108, 74, 75, 76, 66, 108, 68, + 108, 81, 71, 108, 84, 74, 75, 76, 108, 89, + 90, 91, 81, 93, 94, 84, 108, 108, 108, 108, + 89, 90, 91, 108, 93, 94, 66, 108, 68, 108, + 108, 71, 108, 108, 74, 75, 76, 108, 108, 108, + 108, 81, 108, 108, 84, 108, 108, 108, 108, 89, + 90, 91, 108, 93, 94, 66, 108, 68, 108, 108, + 71, 108, 108, 74, 75, 76, 66, 108, 68, 108, + 81, 71, 108, 84, 74, 75, 76, 108, 89, 90, + 91, 81, 93, 94, 84, 108, 108, 108, 108, 89, + 90, 91, 108, 93, 94, 66, 108, 68, 108, 108, + 71, 108, 108, 74, 75, 76, 108, 108, 108, 108, + 81, 108, 108, 84, 108, 108, 108, 108, 89, 90, + 91, 108, 93, 94, 66, 108, 68, 108, 108, 71, + 108, 108, 74, 75, 76, 66, 108, 68, 108, 81, + 71, 108, 84, 74, 75, 76, 108, 89, 90, 91, + 81, 93, 94, 84, 108, 108, 108, 108, 89, 90, + 91, 108, 93, 94, 66, 108, 68, 108, 108, 71, + 108, 108, 74, 75, 76, 108, 108, 108, 108, 81, + 108, 108, 84, 108, 108, 108, 108, 89, 90, 91, + 108, 93, 94, 66, 108, 68, 108, 108, 71, 108, + 108, 74, 75, 76, 66, 108, 68, 108, 81, 71, + 108, 84, 74, 75, 76, 108, 89, 90, 91, 81, + 93, 94, 84, 108, 108, 108, 108, 89, 90, 91, + 108, 93, 94, 66, 108, 68, 108, 108, 71, 108, + 108, 74, 75, 76, 108, 108, 108, 108, 81, 108, + 108, 84, 108, 108, 108, 108, 89, 90, 91, 108, + 93, 94, 66, 108, 68, 108, 108, 71, 108, 108, + 74, 75, 76, 66, 108, 68, 108, 81, 71, 108, + 84, 74, 75, 76, 108, 89, 90, 91, 81, 93, + 94, 84, 108, 108, 108, 108, 89, 90, 91, 108, + 93, 94, 66, 108, 68, 108, 108, 71, 108, 108, + 74, 75, 76, 108, 108, 108, 108, 81, 108, 108, + 84, 108, 108, 108, 108, 89, 90, 91, 108, 93, + 94, 66, 108, 68, 108, 108, 71, 108, 108, 74, + 75, 76, 66, 108, 68, 108, 81, 71, 108, 84, + 74, 75, 76, 108, 89, 90, 91, 81, 93, 94, + 84, 108, 108, 108, 108, 89, 90, 91, 108, 93, + 94, 66, 1, 68, 108, 108, 71, 108, 108, 74, + 75, 76, 108, 108, 13, 108, 81, 108, 17, 84, + 108, 108, 108, 108, 89, 90, 91, 26, 93, 94, + 66, 108, 68, 32, 108, 71, 35, 108, 74, 75, + 108, 66, 108, 68, 108, 81, 71, 108, 47, 74, + 75, 108, 88, 89, 90, 91, 81, 93, 94, 108, + 108, 108, 108, 88, 89, 90, 91, 108, 93, 94, + 66, 108, 68, 9, 108, 71, 12, 108, 74, 75, + 16, 108, 108, 108, 108, 81, 108, 108, 108, 108, + 26, 108, 28, 89, 90, 91, 32, 93, 94, 66, + 108, 68, 108, 108, 71, 108, 108, 74, 75, 108, + 66, 108, 68, 108, 81, 71, 108, 108, 74, 75, + 108, 108, 89, 90, 91, 81, 93, 94, 108, 108, + 108, 108, 108, 89, 90, 91, 108, 93, 94, 66, + 108, 68, 108, 73, 71, 108, 108, 74, 75, 108, + 108, 108, 108, 83, 81, 85, 86, 87, 108, 108, + 108, 108, 89, 90, 91, 108, 93, 94, 66, 108, + 68, 108, 108, 71, 108, 108, 74, 75, 108, 66, + 108, 68, 108, 81, 71, 108, 108, 74, 75, 108, + 108, 89, 90, 91, 81, 93, 94, 108, 108, 108, + 108, 108, 89, 90, 91, 2, 93, 94, 66, 108, + 68, 108, 9, 71, 108, 12, 74, 75, 15, 16, + 17, 108, 108, 81, 108, 108, 108, 108, 108, 108, + 108, 89, 90, 91, 2, 93, 94, 108, 108, 108, + 108, 9, 108, 108, 12, 108, 108, 44, 16, 17, + 47, 2, 49, 108, 51, 108, 108, 73, 9, 108, + 108, 12, 108, 108, 15, 16, 17, 83, 108, 85, + 86, 87, 24, 108, 26, 108, 44, 108, 108, 47, + 32, 49, 108, 51, 52, 37, 38, 39, 108, 108, + 108, 108, 108, 44, 108, 108, 47, 108, 49, 108, + 51, 26, 54, 55, 56, 57, 108, 32, 2, 108, + 108, 108, 37, 38, 39, 9, 108, 108, 12, 108, + 108, 108, 16, 17, 108, 108, 2, 108, 108, 54, + 55, 56, 57, 9, 108, 108, 12, 108, 108, 108, + 16, 17, 108, 108, 108, 37, 38, 39, 108, 108, + 44, 108, 108, 47, 13, 49, 108, 51, 108, 13, + 52, 108, 54, 55, 56, 57, 108, 13, 44, 108, + 2, 47, 108, 49, 108, 51, 108, 108, 37, 38, + 39, 108, 108, 37, 38, 39, 108, 108, 108, 108, + 108, 37, 38, 39, 108, 54, 55, 56, 57, 108, + 54, 55, 56, 57, 108, 37, 38, 39, 54, 55, + 56, 57, 36, 37, 38, 39, 108, 108, 108, 108, + 108, 108, 54, 55, 56, 57, 108, 108, 108, 108, + 54, 55, 56, 57, 9, 108, 108, 12, 37, 38, + 39, 16, 17, 36, 37, 38, 39, 108, 108, 108, + 108, 108, 108, 108, 108, 54, 55, 56, 57, 108, + 108, 54, 55, 56, 57, 108, 108, 108, 108, 44, + 108, 108, 47, 108, 49, 108, 51, +); + const YY_SHIFT_USE_DFLT = -22; + const YY_SHIFT_MAX = 230; + public static $yy_shift_ofst = array( + -22, 501, 501, 93, 399, 399, 450, 93, 93, 93, + 399, 450, -9, 93, 93, 93, 93, 93, 93, 144, + 93, 195, 93, 93, 93, 246, 195, 93, 93, 93, + 93, 93, 297, 93, 93, 93, 93, 348, 42, 42, + 42, 42, 42, 42, 42, 42, 1768, 1795, 1795, 701, + 758, 1521, 80, 676, 113, 790, 1927, 1828, 1896, 568, + 768, 1861, 757, 1866, 1874, 1888, 618, 518, 1921, 1921, + 1921, 1921, 1921, 1921, 1921, 1921, 1921, 1921, 1921, 1921, + 1921, 1921, 1921, 1584, 636, 285, 676, 676, 346, 113, + 113, 402, 700, 1723, -8, 910, 831, 269, 1028, 51, + 3, 3, 422, 448, 352, 106, 422, 106, 458, 364, + 434, 454, 106, 166, 166, 166, 166, 565, 166, 166, + 166, 586, 565, 166, 166, -22, -22, 1752, 1769, 1826, + 1844, 1945, 145, 979, 156, 132, 284, 106, 140, 106, + 30, 140, 140, 30, 106, 106, 106, 140, 106, 106, + 140, 106, 327, 106, 106, 106, 140, 140, 106, 140, + 205, 106, 284, 166, 565, 588, 565, 588, 565, 166, + 166, -12, 166, -22, -22, -22, -22, -22, -22, 689, + 4, 44, 84, 186, 73, 881, 199, 188, 174, 286, + 48, 336, 384, 389, 332, 142, -21, 52, 154, 33, + 85, 276, 278, 233, 515, 509, 474, 516, 502, 464, + 491, 415, 417, 432, 514, 370, 463, 506, 365, 513, + -12, 517, 504, 519, 505, 511, 496, 525, 532, 330, + 358, +); + const YY_REDUCE_USE_DFLT = -89; + const YY_REDUCE_MAX = 178; + public static $yy_reduce_ofst = array( + 325, 527, 495, 666, 595, 560, 863, 874, 834, 805, + 762, 794, 1179, 1455, 1208, 1012, 1386, 1139, 1070, 1110, + 1150, 1219, 1248, 1277, 1288, 1317, 1346, 1357, 1415, 1426, + 1081, 1041, 1001, 972, 943, 932, 903, 1484, 1495, 1622, + 1633, 1662, 1593, 1564, 1553, 1524, 1704, 607, 1590, 178, + 74, 1027, 229, 899, 273, 212, -22, -22, -22, -22, + -22, -22, -22, -22, -22, -22, -22, -22, -22, -22, + -22, -22, -22, -22, -22, -22, -22, -22, -22, -22, + -22, -22, -22, 380, 329, 187, 159, 127, -52, 253, + 304, 12, 303, 152, 193, 328, 68, 68, 68, 322, + 328, 407, 405, 322, 68, 190, 335, 416, 403, 68, + 401, 354, 371, 68, 68, 68, 337, 322, 68, 68, + 68, 68, 408, 68, 68, 68, 409, 455, 455, 455, + 455, 455, 510, 480, 455, 455, 477, 482, 457, 482, + 473, 457, 457, 485, 482, 482, 482, 457, 482, 482, + 457, 482, 503, 482, 482, 482, 457, 457, 482, 457, + 520, 482, 523, -88, 498, 489, 498, 489, 498, -88, + -88, -67, -88, 111, 155, 89, 236, 230, 162, +); + public static $yyExpectedTokens = array( + array(), + array(9, 10, 11, 12, 14, 16, 18, 19, 20, 25, 29, 30, 31, 33, 35, 38, 41, 42, 43, 44, 46, 48, 50, 51, 53, 58, 59, ), + array(9, 10, 11, 12, 14, 16, 18, 19, 20, 25, 29, 30, 31, 33, 35, 38, 41, 42, 43, 44, 46, 48, 50, 51, 53, 58, 59, ), + array(9, 10, 11, 12, 14, 16, 18, 19, 20, 25, 29, 30, 31, 33, 35, 38, 41, 42, 43, 44, 46, 48, 50, 51, 53, 58, 59, ), + array(9, 10, 11, 12, 14, 16, 18, 19, 20, 25, 29, 30, 31, 33, 35, 38, 41, 42, 43, 44, 46, 48, 50, 51, 53, 58, 59, ), + array(9, 10, 11, 12, 14, 16, 18, 19, 20, 25, 29, 30, 31, 33, 35, 38, 41, 42, 43, 44, 46, 48, 50, 51, 53, 58, 59, ), + array(9, 10, 11, 12, 14, 16, 18, 19, 20, 25, 29, 30, 31, 33, 35, 38, 41, 42, 43, 44, 46, 48, 50, 51, 53, 58, 59, ), + array(9, 10, 11, 12, 14, 16, 18, 19, 20, 25, 29, 30, 31, 33, 35, 38, 41, 42, 43, 44, 46, 48, 50, 51, 53, 58, 59, ), + array(9, 10, 11, 12, 14, 16, 18, 19, 20, 25, 29, 30, 31, 33, 35, 38, 41, 42, 43, 44, 46, 48, 50, 51, 53, 58, 59, ), + array(9, 10, 11, 12, 14, 16, 18, 19, 20, 25, 29, 30, 31, 33, 35, 38, 41, 42, 43, 44, 46, 48, 50, 51, 53, 58, 59, ), + array(9, 10, 11, 12, 14, 16, 18, 19, 20, 25, 29, 30, 31, 33, 35, 38, 41, 42, 43, 44, 46, 48, 50, 51, 53, 58, 59, ), + array(9, 10, 11, 12, 14, 16, 18, 19, 20, 25, 29, 30, 31, 33, 35, 38, 41, 42, 43, 44, 46, 48, 50, 51, 53, 58, 59, ), + array(9, 10, 11, 12, 14, 16, 18, 19, 20, 25, 29, 30, 31, 33, 35, 38, 41, 42, 43, 44, 46, 48, 50, 51, 52, 53, 58, 59, ), + array(9, 10, 11, 12, 14, 16, 18, 19, 20, 25, 29, 30, 31, 33, 35, 38, 41, 42, 43, 44, 46, 48, 50, 51, 53, 58, 59, ), + array(9, 10, 11, 12, 14, 16, 18, 19, 20, 25, 29, 30, 31, 33, 35, 38, 41, 42, 43, 44, 46, 48, 50, 51, 53, 58, 59, ), + array(9, 10, 11, 12, 14, 16, 18, 19, 20, 25, 29, 30, 31, 33, 35, 38, 41, 42, 43, 44, 46, 48, 50, 51, 53, 58, 59, ), + array(9, 10, 11, 12, 14, 16, 18, 19, 20, 25, 29, 30, 31, 33, 35, 38, 41, 42, 43, 44, 46, 48, 50, 51, 53, 58, 59, ), + array(9, 10, 11, 12, 14, 16, 18, 19, 20, 25, 29, 30, 31, 33, 35, 38, 41, 42, 43, 44, 46, 48, 50, 51, 53, 58, 59, ), + array(9, 10, 11, 12, 14, 16, 18, 19, 20, 25, 29, 30, 31, 33, 35, 38, 41, 42, 43, 44, 46, 48, 50, 51, 53, 58, 59, ), + array(9, 10, 11, 12, 14, 16, 18, 19, 20, 25, 29, 30, 31, 33, 35, 38, 41, 42, 43, 44, 46, 48, 50, 51, 53, 58, 59, ), + array(9, 10, 11, 12, 14, 16, 18, 19, 20, 25, 29, 30, 31, 33, 35, 38, 41, 42, 43, 44, 46, 48, 50, 51, 53, 58, 59, ), + array(9, 10, 11, 12, 14, 16, 18, 19, 20, 25, 29, 30, 31, 33, 35, 38, 41, 42, 43, 44, 46, 48, 50, 51, 53, 58, 59, ), + array(9, 10, 11, 12, 14, 16, 18, 19, 20, 25, 29, 30, 31, 33, 35, 38, 41, 42, 43, 44, 46, 48, 50, 51, 53, 58, 59, ), + array(9, 10, 11, 12, 14, 16, 18, 19, 20, 25, 29, 30, 31, 33, 35, 38, 41, 42, 43, 44, 46, 48, 50, 51, 53, 58, 59, ), + array(9, 10, 11, 12, 14, 16, 18, 19, 20, 25, 29, 30, 31, 33, 35, 38, 41, 42, 43, 44, 46, 48, 50, 51, 53, 58, 59, ), + array(9, 10, 11, 12, 14, 16, 18, 19, 20, 25, 29, 30, 31, 33, 35, 38, 41, 42, 43, 44, 46, 48, 50, 51, 53, 58, 59, ), + array(9, 10, 11, 12, 14, 16, 18, 19, 20, 25, 29, 30, 31, 33, 35, 38, 41, 42, 43, 44, 46, 48, 50, 51, 53, 58, 59, ), + array(9, 10, 11, 12, 14, 16, 18, 19, 20, 25, 29, 30, 31, 33, 35, 38, 41, 42, 43, 44, 46, 48, 50, 51, 53, 58, 59, ), + array(9, 10, 11, 12, 14, 16, 18, 19, 20, 25, 29, 30, 31, 33, 35, 38, 41, 42, 43, 44, 46, 48, 50, 51, 53, 58, 59, ), + array(9, 10, 11, 12, 14, 16, 18, 19, 20, 25, 29, 30, 31, 33, 35, 38, 41, 42, 43, 44, 46, 48, 50, 51, 53, 58, 59, ), + array(9, 10, 11, 12, 14, 16, 18, 19, 20, 25, 29, 30, 31, 33, 35, 38, 41, 42, 43, 44, 46, 48, 50, 51, 53, 58, 59, ), + array(9, 10, 11, 12, 14, 16, 18, 19, 20, 25, 29, 30, 31, 33, 35, 38, 41, 42, 43, 44, 46, 48, 50, 51, 53, 58, 59, ), + array(9, 10, 11, 12, 14, 16, 18, 19, 20, 25, 29, 30, 31, 33, 35, 38, 41, 42, 43, 44, 46, 48, 50, 51, 53, 58, 59, ), + array(9, 10, 11, 12, 14, 16, 18, 19, 20, 25, 29, 30, 31, 33, 35, 38, 41, 42, 43, 44, 46, 48, 50, 51, 53, 58, 59, ), + array(9, 10, 11, 12, 14, 16, 18, 19, 20, 25, 29, 30, 31, 33, 35, 38, 41, 42, 43, 44, 46, 48, 50, 51, 53, 58, 59, ), + array(9, 10, 11, 12, 14, 16, 18, 19, 20, 25, 29, 30, 31, 33, 35, 38, 41, 42, 43, 44, 46, 48, 50, 51, 53, 58, 59, ), + array(9, 10, 11, 12, 14, 16, 18, 19, 20, 25, 29, 30, 31, 33, 35, 38, 41, 42, 43, 44, 46, 48, 50, 51, 53, 58, 59, ), + array(9, 10, 11, 12, 14, 16, 18, 19, 20, 25, 29, 30, 31, 33, 35, 38, 41, 42, 43, 44, 46, 48, 50, 51, 53, 58, 59, ), + array(9, 10, 11, 12, 14, 16, 18, 19, 20, 25, 29, 30, 31, 33, 35, 38, 41, 42, 43, 44, 46, 48, 50, 51, 53, 58, 59, ), + array(9, 10, 11, 12, 14, 16, 18, 19, 20, 25, 29, 30, 31, 33, 35, 38, 41, 42, 43, 44, 46, 48, 50, 51, 53, 58, 59, ), + array(9, 10, 11, 12, 14, 16, 18, 19, 20, 25, 29, 30, 31, 33, 35, 38, 41, 42, 43, 44, 46, 48, 50, 51, 53, 58, 59, ), + array(9, 10, 11, 12, 14, 16, 18, 19, 20, 25, 29, 30, 31, 33, 35, 38, 41, 42, 43, 44, 46, 48, 50, 51, 53, 58, 59, ), + array(9, 10, 11, 12, 14, 16, 18, 19, 20, 25, 29, 30, 31, 33, 35, 38, 41, 42, 43, 44, 46, 48, 50, 51, 53, 58, 59, ), + array(9, 10, 11, 12, 14, 16, 18, 19, 20, 25, 29, 30, 31, 33, 35, 38, 41, 42, 43, 44, 46, 48, 50, 51, 53, 58, 59, ), + array(9, 10, 11, 12, 14, 16, 18, 19, 20, 25, 29, 30, 31, 33, 35, 38, 41, 42, 43, 44, 46, 48, 50, 51, 53, 58, 59, ), + array(9, 10, 11, 12, 14, 16, 18, 19, 20, 25, 29, 30, 31, 33, 35, 38, 41, 42, 43, 44, 46, 48, 50, 51, 53, 58, 59, ), + array(24, 26, 32, 37, 38, 39, 54, 55, 56, 57, ), + array(26, 32, 37, 38, 39, 54, 55, 56, 57, ), + array(26, 32, 37, 38, 39, 54, 55, 56, 57, ), + array(14, 16, 48, 50, 53, ), + array(3, 9, 10, 11, 12, 14, 18, 19, 20, 25, 29, 30, 31, 59, 60, ), + array(1, 13, 17, 26, 32, 35, 47, ), + array(14, 16, 50, 53, ), + array(1, 26, 32, ), + array(14, 35, 53, ), + array(3, 9, 10, 11, 12, 14, 18, 19, 20, 25, 29, 30, 31, 59, 60, ), + array(36, 37, 38, 39, 54, 55, 56, 57, ), + array(37, 38, 39, 52, 54, 55, 56, 57, ), + array(36, 37, 38, 39, 54, 55, 56, 57, ), + array(13, 37, 38, 39, 54, 55, 56, 57, ), + array(13, 37, 38, 39, 54, 55, 56, 57, ), + array(13, 37, 38, 39, 54, 55, 56, 57, ), + array(27, 37, 38, 39, 54, 55, 56, 57, ), + array(13, 37, 38, 39, 54, 55, 56, 57, ), + array(13, 37, 38, 39, 54, 55, 56, 57, ), + array(2, 37, 38, 39, 54, 55, 56, 57, ), + array(21, 37, 38, 39, 54, 55, 56, 57, ), + array(37, 38, 39, 54, 55, 56, 57, 60, ), + array(37, 38, 39, 54, 55, 56, 57, ), + array(37, 38, 39, 54, 55, 56, 57, ), + array(37, 38, 39, 54, 55, 56, 57, ), + array(37, 38, 39, 54, 55, 56, 57, ), + array(37, 38, 39, 54, 55, 56, 57, ), + array(37, 38, 39, 54, 55, 56, 57, ), + array(37, 38, 39, 54, 55, 56, 57, ), + array(37, 38, 39, 54, 55, 56, 57, ), + array(37, 38, 39, 54, 55, 56, 57, ), + array(37, 38, 39, 54, 55, 56, 57, ), + array(37, 38, 39, 54, 55, 56, 57, ), + array(37, 38, 39, 54, 55, 56, 57, ), + array(37, 38, 39, 54, 55, 56, 57, ), + array(37, 38, 39, 54, 55, 56, 57, ), + array(37, 38, 39, 54, 55, 56, 57, ), + array(9, 12, 16, 26, 28, 32, ), + array(9, 12, 16, 26, 32, ), + array(17, 44, 51, ), + array(1, 26, 32, ), + array(1, 26, 32, ), + array(15, 17, 47, ), + array(14, 35, 53, ), + array(14, 35, 53, ), + array(1, 2, ), + array(3, 4, 5, 6, 9, 10, 11, 12, 18, 19, 20, 25, 29, 30, 31, ), + array(2, 9, 12, 15, 16, 17, 44, 47, 49, 51, ), + array(12, 14, 16, 53, ), + array(9, 12, 16, 49, ), + array(1, 13, 26, 32, ), + array(1, 13, 26, 32, ), + array(1, 13, 26, 32, ), + array(15, 17, 47, ), + array(9, 12, 16, ), + array(9, 12, 16, ), + array(14, 16, ), + array(17, 47, ), + array(1, 28, ), + array(26, 32, ), + array(14, 16, ), + array(26, 32, ), + array(26, 32, ), + array(1, 52, ), + array(14, 53, ), + array(1, 17, ), + array(26, 32, ), + array(1, ), + array(1, ), + array(1, ), + array(1, ), + array(17, ), + array(1, ), + array(1, ), + array(1, ), + array(1, ), + array(17, ), + array(1, ), + array(1, ), + array(), + array(), + array(2, 9, 12, 16, 17, 44, 47, 49, 51, 52, ), + array(2, 9, 12, 15, 16, 17, 44, 47, 49, 51, ), + array(2, 9, 12, 16, 17, 44, 47, 49, 51, ), + array(2, 9, 12, 16, 17, 44, 47, 49, 51, ), + array(9, 12, 16, 17, 44, 47, 49, 51, ), + array(12, 14, 16, 33, 53, ), + array(9, 12, 16, 49, ), + array(9, 12, 16, ), + array(15, 44, 51, ), + array(14, 53, ), + array(26, 32, ), + array(44, 51, ), + array(26, 32, ), + array(44, 51, ), + array(44, 51, ), + array(44, 51, ), + array(44, 51, ), + array(26, 32, ), + array(26, 32, ), + array(26, 32, ), + array(44, 51, ), + array(26, 32, ), + array(26, 32, ), + array(44, 51, ), + array(26, 32, ), + array(15, 22, ), + array(26, 32, ), + array(26, 32, ), + array(26, 32, ), + array(44, 51, ), + array(44, 51, ), + array(26, 32, ), + array(44, 51, ), + array(12, 35, ), + array(26, 32, ), + array(14, 53, ), + array(1, ), + array(17, ), + array(2, ), + array(17, ), + array(2, ), + array(17, ), + array(1, ), + array(1, ), + array(35, ), + array(1, ), + array(), + array(), + array(), + array(), + array(), + array(), + array(2, 35, 37, 38, 39, 47, 54, 55, 56, 57, ), + array(13, 21, 23, 26, 32, 34, 36, 44, ), + array(13, 15, 26, 32, 35, 47, ), + array(13, 22, 26, 32, 45, ), + array(13, 22, 26, 32, 45, ), + array(35, 44, 47, 52, ), + array(9, 12, 16, 49, ), + array(22, 45, 52, ), + array(28, 35, 47, ), + array(22, 45, 60, ), + array(35, 47, ), + array(21, 34, ), + array(34, 36, ), + array(16, 49, ), + array(6, 8, ), + array(44, 52, ), + array(7, 8, ), + array(34, 52, ), + array(35, 47, ), + array(35, 47, ), + array(22, 45, ), + array(34, 36, ), + array(15, 44, ), + array(34, 36, ), + array(34, 36, ), + array(13, ), + array(16, ), + array(50, ), + array(7, ), + array(16, ), + array(52, ), + array(23, ), + array(36, ), + array(50, ), + array(14, ), + array(13, ), + array(52, ), + array(15, ), + array(16, ), + array(40, ), + array(16, ), + array(35, ), + array(16, ), + array(33, ), + array(16, ), + array(33, ), + array(35, ), + array(44, ), + array(16, ), + array(16, ), + array(16, ), + array(16, ), + array(), + array(), + array(), + array(), + array(), + array(), + array(), + array(), + array(), + array(), + array(), + array(), + array(), + array(), + array(), + array(), + array(), + array(), + array(), + array(), + array(), + array(), + array(), + array(), + array(), + array(), + array(), + array(), + array(), + array(), + array(), + array(), + array(), + array(), + array(), + array(), + array(), + array(), + array(), + array(), + array(), + array(), + array(), + array(), + array(), + array(), + array(), + array(), + array(), + array(), + array(), + array(), + array(), + array(), + array(), + array(), + array(), + array(), + array(), + array(), + array(), + array(), + array(), + array(), + array(), + array(), + array(), + array(), + array(), + array(), + array(), + array(), + array(), + array(), + array(), + array(), + array(), + array(), + array(), + array(), + array(), + array(), + array(), + array(), + array(), + array(), + array(), + array(), + array(), + array(), + array(), + array(), + array(), + array(), + array(), +); + public static $yy_default = array( + 336, 512, 512, 512, 497, 497, 512, 474, 474, 474, + 512, 512, 512, 512, 512, 512, 512, 512, 512, 512, + 512, 512, 512, 512, 512, 512, 512, 512, 512, 512, + 512, 512, 512, 512, 512, 512, 512, 512, 512, 512, + 512, 512, 512, 512, 512, 512, 377, 377, 356, 512, + 512, 413, 512, 377, 512, 512, 512, 512, 512, 512, + 512, 512, 382, 512, 349, 512, 512, 512, 382, 379, + 389, 388, 384, 402, 473, 397, 498, 500, 401, 361, + 472, 499, 349, 377, 377, 487, 377, 377, 429, 512, + 512, 368, 326, 428, 512, 439, 391, 391, 391, 429, + 439, 439, 512, 429, 391, 377, 512, 377, 377, 391, + 512, 371, 358, 395, 394, 396, 373, 429, 400, 404, + 391, 404, 484, 406, 405, 481, 334, 428, 428, 428, + 428, 428, 512, 441, 439, 455, 512, 363, 435, 354, + 434, 437, 433, 432, 359, 357, 364, 436, 353, 367, + 466, 365, 512, 352, 350, 360, 467, 465, 346, 464, + 439, 366, 512, 369, 461, 475, 488, 476, 485, 372, + 422, 439, 374, 480, 439, 480, 480, 439, 334, 413, + 409, 413, 403, 403, 413, 440, 403, 413, 403, 413, + 512, 512, 512, 332, 409, 512, 512, 512, 423, 403, + 512, 409, 512, 512, 512, 512, 512, 512, 512, 418, + 385, 512, 512, 512, 512, 512, 512, 512, 415, 512, + 455, 512, 512, 512, 411, 486, 409, 512, 512, 512, + 512, 419, 407, 362, 445, 418, 425, 424, 420, 339, + 460, 421, 483, 398, 416, 340, 399, 455, 378, 337, + 338, 330, 328, 329, 442, 443, 444, 438, 392, 393, + 427, 426, 386, 417, 408, 390, 410, 331, 333, 335, + 412, 470, 414, 415, 503, 478, 495, 471, 459, 458, + 375, 457, 344, 462, 508, 493, 376, 496, 456, 509, + 494, 501, 504, 511, 510, 507, 505, 502, 506, 345, + 468, 469, 446, 355, 341, 452, 450, 454, 448, 453, + 447, 489, 490, 491, 463, 449, 492, 451, 327, 342, + 343, 370, 430, 431, 479, 477, +); + const YYNOCODE = 109; + const YYSTACKDEPTH = 500; + const YYNSTATE = 326; + const YYNRULE = 186; + const YYERRORSYMBOL = 61; + const YYERRSYMDT = 'yy0'; + const YYFALLBACK = 0; + public static $yyFallback = array( + ); public function Trace($TraceFILE, $zTracePrompt) { if (!$TraceFILE) { @@ -1840,18 +1110,250 @@ public function PrintTrace() $this->yyTracePrompt = '
'; } + public $yyTraceFILE; + public $yyTracePrompt; + public $yyidx; /* Index of top element in stack */ + public $yyerrcnt; /* Shifts left before out of the error */ + public $yystack = array(); /* The parser's stack */ + + public $yyTokenName = array( + '$', 'VERT', 'COLON', 'TEXT', + 'STRIPON', 'STRIPOFF', 'LITERALSTART', 'LITERALEND', + 'LITERAL', 'SIMPELOUTPUT', 'SIMPLETAG', 'SMARTYBLOCKCHILDPARENT', + 'LDEL', 'RDEL', 'DOLLARID', 'EQUAL', + 'ID', 'PTR', 'LDELMAKENOCACHE', 'LDELIF', + 'LDELFOR', 'SEMICOLON', 'INCDEC', 'TO', + 'STEP', 'LDELFOREACH', 'SPACE', 'AS', + 'APTR', 'LDELSETFILTER', 'CLOSETAG', 'LDELSLASH', + 'ATTR', 'INTEGER', 'COMMA', 'OPENP', + 'CLOSEP', 'MATH', 'UNIMATH', 'ISIN', + 'QMARK', 'NOT', 'TYPECAST', 'HEX', + 'DOT', 'INSTANCEOF', 'SINGLEQUOTESTRING', 'DOUBLECOLON', + 'NAMESPACE', 'AT', 'HATCH', 'OPENB', + 'CLOSEB', 'DOLLAR', 'LOGOP', 'SLOGOP', + 'TLOGOP', 'SINGLECOND', 'ARRAYOPEN', 'QUOTE', + 'BACKTICK', 'error', 'start', 'template', + 'literal_e2', 'literal_e1', 'smartytag', 'tagbody', + 'tag', 'outattr', 'eqoutattr', 'varindexed', + 'output', 'attributes', 'variable', 'value', + 'expr', 'modifierlist', 'statement', 'statements', + 'foraction', 'varvar', 'modparameters', 'attribute', + 'ternary', 'tlop', 'lop', 'scond', + 'array', 'function', 'ns1', 'doublequoted_with_quotes', + 'static_class_access', 'arraydef', 'object', 'arrayindex', + 'indexdef', 'varvarele', 'objectchain', 'objectelement', + 'method', 'params', 'modifier', 'modparameter', + 'arrayelements', 'arrayelement', 'doublequoted', 'doublequotedcontent', + ); + + public static $yyRuleName = array( + 'start ::= template', + 'template ::= template TEXT', + 'template ::= template STRIPON', + 'template ::= template STRIPOFF', + 'template ::= template LITERALSTART literal_e2 LITERALEND', + 'literal_e2 ::= literal_e1 LITERALSTART literal_e1 LITERALEND', + 'literal_e2 ::= literal_e1', + 'literal_e1 ::= literal_e1 LITERAL', + 'literal_e1 ::=', + 'template ::= template smartytag', + 'template ::=', + 'smartytag ::= SIMPELOUTPUT', + 'smartytag ::= SIMPLETAG', + 'smartytag ::= SMARTYBLOCKCHILDPARENT', + 'smartytag ::= LDEL tagbody RDEL', + 'smartytag ::= tag RDEL', + 'tagbody ::= outattr', + 'tagbody ::= DOLLARID eqoutattr', + 'tagbody ::= varindexed eqoutattr', + 'eqoutattr ::= EQUAL outattr', + 'outattr ::= output attributes', + 'output ::= variable', + 'output ::= value', + 'output ::= expr', + 'tag ::= LDEL ID attributes', + 'tag ::= LDEL ID', + 'tag ::= LDEL ID modifierlist attributes', + 'tag ::= LDEL ID PTR ID attributes', + 'tag ::= LDEL ID PTR ID modifierlist attributes', + 'tag ::= LDELMAKENOCACHE DOLLARID', + 'tag ::= LDELIF expr', + 'tag ::= LDELIF expr attributes', + 'tag ::= LDELIF statement', + 'tag ::= LDELIF statement attributes', + 'tag ::= LDELFOR statements SEMICOLON expr SEMICOLON varindexed foraction attributes', + 'foraction ::= EQUAL expr', + 'foraction ::= INCDEC', + 'tag ::= LDELFOR statement TO expr attributes', + 'tag ::= LDELFOR statement TO expr STEP expr attributes', + 'tag ::= LDELFOREACH SPACE expr AS varvar attributes', + 'tag ::= LDELFOREACH SPACE expr AS varvar APTR varvar attributes', + 'tag ::= LDELFOREACH attributes', + 'tag ::= LDELSETFILTER ID modparameters', + 'tag ::= LDELSETFILTER ID modparameters modifierlist', + 'smartytag ::= CLOSETAG', + 'tag ::= LDELSLASH ID', + 'tag ::= LDELSLASH ID modifierlist', + 'tag ::= LDELSLASH ID PTR ID', + 'tag ::= LDELSLASH ID PTR ID modifierlist', + 'attributes ::= attributes attribute', + 'attributes ::= attribute', + 'attributes ::=', + 'attribute ::= SPACE ID EQUAL ID', + 'attribute ::= ATTR expr', + 'attribute ::= ATTR value', + 'attribute ::= SPACE ID', + 'attribute ::= SPACE expr', + 'attribute ::= SPACE value', + 'attribute ::= SPACE INTEGER EQUAL expr', + 'statements ::= statement', + 'statements ::= statements COMMA statement', + 'statement ::= DOLLARID EQUAL INTEGER', + 'statement ::= DOLLARID EQUAL expr', + 'statement ::= varindexed EQUAL expr', + 'statement ::= OPENP statement CLOSEP', + 'expr ::= value', + 'expr ::= ternary', + 'expr ::= DOLLARID COLON ID', + 'expr ::= expr MATH value', + 'expr ::= expr UNIMATH value', + 'expr ::= expr tlop value', + 'expr ::= expr lop expr', + 'expr ::= expr scond', + 'expr ::= expr ISIN array', + 'expr ::= expr ISIN value', + 'ternary ::= OPENP expr CLOSEP QMARK DOLLARID COLON expr', + 'ternary ::= OPENP expr CLOSEP QMARK expr COLON expr', + 'value ::= variable', + 'value ::= UNIMATH value', + 'value ::= NOT value', + 'value ::= TYPECAST value', + 'value ::= variable INCDEC', + 'value ::= HEX', + 'value ::= INTEGER', + 'value ::= INTEGER DOT INTEGER', + 'value ::= INTEGER DOT', + 'value ::= DOT INTEGER', + 'value ::= ID', + 'value ::= function', + 'value ::= OPENP expr CLOSEP', + 'value ::= variable INSTANCEOF ns1', + 'value ::= variable INSTANCEOF variable', + 'value ::= SINGLEQUOTESTRING', + 'value ::= doublequoted_with_quotes', + 'value ::= varindexed DOUBLECOLON static_class_access', + 'value ::= smartytag', + 'value ::= value modifierlist', + 'value ::= NAMESPACE', + 'value ::= arraydef', + 'value ::= ns1 DOUBLECOLON static_class_access', + 'ns1 ::= ID', + 'ns1 ::= NAMESPACE', + 'variable ::= DOLLARID', + 'variable ::= varindexed', + 'variable ::= varvar AT ID', + 'variable ::= object', + 'variable ::= HATCH ID HATCH', + 'variable ::= HATCH ID HATCH arrayindex', + 'variable ::= HATCH variable HATCH', + 'variable ::= HATCH variable HATCH arrayindex', + 'varindexed ::= DOLLARID arrayindex', + 'varindexed ::= varvar arrayindex', + 'arrayindex ::= arrayindex indexdef', + 'arrayindex ::=', + 'indexdef ::= DOT DOLLARID', + 'indexdef ::= DOT varvar', + 'indexdef ::= DOT varvar AT ID', + 'indexdef ::= DOT ID', + 'indexdef ::= DOT INTEGER', + 'indexdef ::= DOT LDEL expr RDEL', + 'indexdef ::= OPENB ID CLOSEB', + 'indexdef ::= OPENB ID DOT ID CLOSEB', + 'indexdef ::= OPENB SINGLEQUOTESTRING CLOSEB', + 'indexdef ::= OPENB INTEGER CLOSEB', + 'indexdef ::= OPENB DOLLARID CLOSEB', + 'indexdef ::= OPENB variable CLOSEB', + 'indexdef ::= OPENB value CLOSEB', + 'indexdef ::= OPENB expr CLOSEB', + 'indexdef ::= OPENB CLOSEB', + 'varvar ::= DOLLARID', + 'varvar ::= DOLLAR', + 'varvar ::= varvar varvarele', + 'varvarele ::= ID', + 'varvarele ::= SIMPELOUTPUT', + 'varvarele ::= LDEL expr RDEL', + 'object ::= varindexed objectchain', + 'objectchain ::= objectelement', + 'objectchain ::= objectchain objectelement', + 'objectelement ::= PTR ID arrayindex', + 'objectelement ::= PTR varvar arrayindex', + 'objectelement ::= PTR LDEL expr RDEL arrayindex', + 'objectelement ::= PTR ID LDEL expr RDEL arrayindex', + 'objectelement ::= PTR method', + 'function ::= ns1 OPENP params CLOSEP', + 'method ::= ID OPENP params CLOSEP', + 'method ::= DOLLARID OPENP params CLOSEP', + 'params ::= params COMMA expr', + 'params ::= expr', + 'params ::=', + 'modifierlist ::= modifierlist modifier modparameters', + 'modifierlist ::= modifier modparameters', + 'modifier ::= VERT AT ID', + 'modifier ::= VERT ID', + 'modparameters ::= modparameters modparameter', + 'modparameters ::=', + 'modparameter ::= COLON value', + 'modparameter ::= COLON UNIMATH value', + 'modparameter ::= COLON array', + 'static_class_access ::= method', + 'static_class_access ::= method objectchain', + 'static_class_access ::= ID', + 'static_class_access ::= DOLLARID arrayindex', + 'static_class_access ::= DOLLARID arrayindex objectchain', + 'lop ::= LOGOP', + 'lop ::= SLOGOP', + 'tlop ::= TLOGOP', + 'scond ::= SINGLECOND', + 'arraydef ::= OPENB arrayelements CLOSEB', + 'arraydef ::= ARRAYOPEN arrayelements CLOSEP', + 'arrayelements ::= arrayelement', + 'arrayelements ::= arrayelements COMMA arrayelement', + 'arrayelements ::=', + 'arrayelement ::= value APTR expr', + 'arrayelement ::= ID APTR expr', + 'arrayelement ::= expr', + 'doublequoted_with_quotes ::= QUOTE QUOTE', + 'doublequoted_with_quotes ::= QUOTE doublequoted QUOTE', + 'doublequoted ::= doublequoted doublequotedcontent', + 'doublequoted ::= doublequotedcontent', + 'doublequotedcontent ::= BACKTICK variable BACKTICK', + 'doublequotedcontent ::= BACKTICK expr BACKTICK', + 'doublequotedcontent ::= DOLLARID', + 'doublequotedcontent ::= LDEL variable RDEL', + 'doublequotedcontent ::= LDEL expr RDEL', + 'doublequotedcontent ::= smartytag', + 'doublequotedcontent ::= TEXT', + ); + public function tokenName($tokenType) { if ($tokenType === 0) { return 'End of Input'; } if ($tokenType > 0 && $tokenType < count($this->yyTokenName)) { - return $this->yyTokenName[ $tokenType ]; + return $this->yyTokenName[$tokenType]; } else { return 'Unknown'; } } + public static function yy_destructor($yymajor, $yypminor) + { + switch ($yymajor) { + default: break; /* If no destructor action specified: do nothing */ + } + } + public function yy_pop_parser_stack() { if (empty($this->yystack)) { @@ -1860,18 +1362,19 @@ public function yy_pop_parser_stack() $yytos = array_pop($this->yystack); if ($this->yyTraceFILE && $this->yyidx >= 0) { fwrite($this->yyTraceFILE, - $this->yyTracePrompt . 'Popping ' . $this->yyTokenName[ $yytos->major ] . - "\n"); + $this->yyTracePrompt . 'Popping ' . $this->yyTokenName[$yytos->major] . + "\n"); } $yymajor = $yytos->major; self::yy_destructor($yymajor, $yytos->minor); $this->yyidx--; + return $yymajor; } public function __destruct() { - while ($this->yystack !== array()) { + while ($this->yystack !== Array()) { $this->yy_pop_parser_stack(); } if (is_resource($this->yyTraceFILE)) { @@ -1883,14 +1386,14 @@ public function yy_get_expected_tokens($token) { static $res3 = array(); static $res4 = array(); - $state = $this->yystack[ $this->yyidx ]->stateno; - $expected = self::$yyExpectedTokens[ $state ]; - if (isset($res3[ $state ][ $token ])) { - if ($res3[ $state ][ $token ]) { + $state = $this->yystack[$this->yyidx]->stateno; + $expected = self::$yyExpectedTokens[$state]; + if (isset($res3[$state][$token])) { + if ($res3[$state][$token]) { return $expected; } } else { - if ($res3[ $state ][ $token ] = in_array($token, self::$yyExpectedTokens[ $state ], true)) { + if ($res3[$state][$token] = in_array($token, self::$yyExpectedTokens[$state], true)) { return $expected; } } @@ -1910,21 +1413,20 @@ public function yy_get_expected_tokens($token) return array_unique($expected); } $yyruleno = $yyact - self::YYNSTATE; - $this->yyidx -= self::$yyRuleInfo[ $yyruleno ][ 1 ]; + $this->yyidx -= self::$yyRuleInfo[$yyruleno][1]; $nextstate = $this->yy_find_reduce_action( - $this->yystack[ $this->yyidx ]->stateno, - self::$yyRuleInfo[ $yyruleno ][ 0 ]); - if (isset(self::$yyExpectedTokens[ $nextstate ])) { - $expected = array_merge($expected, self::$yyExpectedTokens[ $nextstate ]); - if (isset($res4[ $nextstate ][ $token ])) { - if ($res4[ $nextstate ][ $token ]) { + $this->yystack[$this->yyidx]->stateno, + self::$yyRuleInfo[$yyruleno][0]); + if (isset(self::$yyExpectedTokens[$nextstate])) { + $expected = array_merge($expected, self::$yyExpectedTokens[$nextstate]); + if (isset($res4[$nextstate][$token])) { + if ($res4[$nextstate][$token]) { $this->yyidx = $yyidx; $this->yystack = $stack; return array_unique($expected); } } else { - if ($res4[ $nextstate ][ $token ] = - in_array($token, self::$yyExpectedTokens[ $nextstate ], true)) { + if ($res4[$nextstate][$token] = in_array($token, self::$yyExpectedTokens[$nextstate], true)) { $this->yyidx = $yyidx; $this->yystack = $stack; return array_unique($expected); @@ -1936,8 +1438,8 @@ public function yy_get_expected_tokens($token) $this->yyidx++; $x = new TP_yyStackEntry; $x->stateno = $nextstate; - $x->major = self::$yyRuleInfo[ $yyruleno ][ 0 ]; - $this->yystack[ $this->yyidx ] = $x; + $x->major = self::$yyRuleInfo[$yyruleno][0]; + $this->yystack[$this->yyidx] = $x; continue 2; } elseif ($nextstate === self::YYNSTATE + self::YYNRULE + 1) { $this->yyidx = $yyidx; @@ -1958,8 +1460,9 @@ public function yy_get_expected_tokens($token) } break; } while (true); - $this->yyidx = $yyidx; - $this->yystack = $stack; + $this->yyidx = $yyidx; + $this->yystack = $stack; + return array_unique($expected); } @@ -1970,16 +1473,16 @@ public function yy_is_expected_token($token) if ($token === 0) { return true; // 0 is not part of this } - $state = $this->yystack[ $this->yyidx ]->stateno; - if (isset($res[ $state ][ $token ])) { - if ($res[ $state ][ $token ]) { + $state = $this->yystack[$this->yyidx]->stateno; + if (isset($res[$state][$token])) { + if ($res[$state][$token]) { return true; } } else { - if ($res[ $state ][ $token ] = in_array($token, self::$yyExpectedTokens[ $state ], true)) { + if ($res[$state][$token] = in_array($token, self::$yyExpectedTokens[$state], true)) { return true; } - } + } $stack = $this->yystack; $yyidx = $this->yyidx; do { @@ -1996,20 +1499,18 @@ public function yy_is_expected_token($token) return true; } $yyruleno = $yyact - self::YYNSTATE; - $this->yyidx -= self::$yyRuleInfo[ $yyruleno ][ 1 ]; + $this->yyidx -= self::$yyRuleInfo[$yyruleno][1]; $nextstate = $this->yy_find_reduce_action( - $this->yystack[ $this->yyidx ]->stateno, - self::$yyRuleInfo[ $yyruleno ][ 0 ]); - if (isset($res2[ $nextstate ][ $token ])) { - if ($res2[ $nextstate ][ $token ]) { + $this->yystack[$this->yyidx]->stateno, + self::$yyRuleInfo[$yyruleno][0]); + if (isset($res2[$nextstate][$token])) { + if ($res2[$nextstate][$token]) { $this->yyidx = $yyidx; $this->yystack = $stack; return true; } } else { - if ($res2[ $nextstate ][ $token ] = - (isset(self::$yyExpectedTokens[ $nextstate ]) && - in_array($token, self::$yyExpectedTokens[ $nextstate ], true))) { + if ($res2[$nextstate][$token] = (isset(self::$yyExpectedTokens[$nextstate]) && in_array($token, self::$yyExpectedTokens[$nextstate], true))) { $this->yyidx = $yyidx; $this->yystack = $stack; return true; @@ -2020,8 +1521,8 @@ public function yy_is_expected_token($token) $this->yyidx++; $x = new TP_yyStackEntry; $x->stateno = $nextstate; - $x->major = self::$yyRuleInfo[ $yyruleno ][ 0 ]; - $this->yystack[ $this->yyidx ] = $x; + $x->major = self::$yyRuleInfo[$yyruleno][0]; + $this->yystack[$this->yyidx] = $x; continue 2; } elseif ($nextstate === self::YYNSTATE + self::YYNRULE + 1) { $this->yyidx = $yyidx; @@ -2048,65 +1549,69 @@ public function yy_is_expected_token($token) } while (true); $this->yyidx = $yyidx; $this->yystack = $stack; + return true; } - public function yy_find_shift_action($iLookAhead) + public function yy_find_shift_action($iLookAhead) { - $stateno = $this->yystack[ $this->yyidx ]->stateno; + $stateno = $this->yystack[$this->yyidx]->stateno; + /* if ($this->yyidx < 0) return self::YY_NO_ACTION; */ - if (!isset(self::$yy_shift_ofst[ $stateno ])) { + if (!isset(self::$yy_shift_ofst[$stateno])) { // no shift actions - return self::$yy_default[ $stateno ]; + return self::$yy_default[$stateno]; } - $i = self::$yy_shift_ofst[ $stateno ]; + $i = self::$yy_shift_ofst[$stateno]; if ($i === self::YY_SHIFT_USE_DFLT) { - return self::$yy_default[ $stateno ]; + return self::$yy_default[$stateno]; } if ($iLookAhead === self::YYNOCODE) { return self::YY_NO_ACTION; } $i += $iLookAhead; if ($i < 0 || $i >= self::YY_SZ_ACTTAB || - self::$yy_lookahead[ $i ] != $iLookAhead) { + self::$yy_lookahead[$i] != $iLookAhead) { if (count(self::$yyFallback) && $iLookAhead < count(self::$yyFallback) - && ($iFallback = self::$yyFallback[ $iLookAhead ]) != 0) { + && ($iFallback = self::$yyFallback[$iLookAhead]) != 0) { if ($this->yyTraceFILE) { fwrite($this->yyTraceFILE, $this->yyTracePrompt . 'FALLBACK ' . - $this->yyTokenName[ $iLookAhead ] . ' => ' . - $this->yyTokenName[ $iFallback ] . "\n"); + $this->yyTokenName[$iLookAhead] . ' => ' . + $this->yyTokenName[$iFallback] . "\n"); } + return $this->yy_find_shift_action($iFallback); } - return self::$yy_default[ $stateno ]; + + return self::$yy_default[$stateno]; } else { - return self::$yy_action[ $i ]; + return self::$yy_action[$i]; } } public function yy_find_reduce_action($stateno, $iLookAhead) { /* $stateno = $this->yystack[$this->yyidx]->stateno; */ - if (!isset(self::$yy_reduce_ofst[ $stateno ])) { - return self::$yy_default[ $stateno ]; + + if (!isset(self::$yy_reduce_ofst[$stateno])) { + return self::$yy_default[$stateno]; } - $i = self::$yy_reduce_ofst[ $stateno ]; + $i = self::$yy_reduce_ofst[$stateno]; if ($i === self::YY_REDUCE_USE_DFLT) { - return self::$yy_default[ $stateno ]; + return self::$yy_default[$stateno]; } if ($iLookAhead === self::YYNOCODE) { return self::YY_NO_ACTION; } $i += $iLookAhead; if ($i < 0 || $i >= self::YY_SZ_ACTTAB || - self::$yy_lookahead[ $i ] != $iLookAhead) { - return self::$yy_default[ $stateno ]; + self::$yy_lookahead[$i] != $iLookAhead) { + return self::$yy_default[$stateno]; } else { - return self::$yy_action[ $i ]; + return self::$yy_action[$i]; } } - // line 234 "../smarty/lexer/smarty_internal_templateparser.y" public function yy_shift($yyNewState, $yyMajor, $yypMinor) { $this->yyidx++; @@ -2118,9 +1623,11 @@ public function yy_shift($yyNewState, $yyMajor, $yypMinor) while ($this->yyidx >= 0) { $this->yy_pop_parser_stack(); } - // line 221 "../smarty/lexer/smarty_internal_templateparser.y" - $this->internalError = true; - $this->compiler->trigger_template_error('Stack overflow in template parser'); +// line 220 "../smarty/lexer/smarty_internal_templateparser.y" + + $this->internalError = true; + $this->compiler->trigger_template_error('Stack overflow in template parser'); + return; } $yytos = new TP_yyStackEntry; @@ -2134,1357 +1641,1150 @@ public function yy_shift($yyNewState, $yyMajor, $yypMinor) fprintf($this->yyTraceFILE, "%sStack:", $this->yyTracePrompt); for ($i = 1; $i <= $this->yyidx; $i++) { fprintf($this->yyTraceFILE, " %s", - $this->yyTokenName[ $this->yystack[ $i ]->major ]); + $this->yyTokenName[$this->yystack[$i]->major]); } - fwrite($this->yyTraceFILE, "\n"); + fwrite($this->yyTraceFILE,"\n"); } } - // line 242 "../smarty/lexer/smarty_internal_templateparser.y" - public function yy_r0() - { - $this->root_buffer->prepend_array($this, $this->template_prefix); - $this->root_buffer->append_array($this, $this->template_postfix); - $this->_retvalue = $this->root_buffer->to_smarty_php($this); - } + public static $yyRuleInfo = array( + array( 0 => 62, 1 => 1 ), + array( 0 => 63, 1 => 2 ), + array( 0 => 63, 1 => 2 ), + array( 0 => 63, 1 => 2 ), + array( 0 => 63, 1 => 4 ), + array( 0 => 64, 1 => 4 ), + array( 0 => 64, 1 => 1 ), + array( 0 => 65, 1 => 2 ), + array( 0 => 65, 1 => 0 ), + array( 0 => 63, 1 => 2 ), + array( 0 => 63, 1 => 0 ), + array( 0 => 66, 1 => 1 ), + array( 0 => 66, 1 => 1 ), + array( 0 => 66, 1 => 1 ), + array( 0 => 66, 1 => 3 ), + array( 0 => 66, 1 => 2 ), + array( 0 => 67, 1 => 1 ), + array( 0 => 67, 1 => 2 ), + array( 0 => 67, 1 => 2 ), + array( 0 => 70, 1 => 2 ), + array( 0 => 69, 1 => 2 ), + array( 0 => 72, 1 => 1 ), + array( 0 => 72, 1 => 1 ), + array( 0 => 72, 1 => 1 ), + array( 0 => 68, 1 => 3 ), + array( 0 => 68, 1 => 2 ), + array( 0 => 68, 1 => 4 ), + array( 0 => 68, 1 => 5 ), + array( 0 => 68, 1 => 6 ), + array( 0 => 68, 1 => 2 ), + array( 0 => 68, 1 => 2 ), + array( 0 => 68, 1 => 3 ), + array( 0 => 68, 1 => 2 ), + array( 0 => 68, 1 => 3 ), + array( 0 => 68, 1 => 8 ), + array( 0 => 80, 1 => 2 ), + array( 0 => 80, 1 => 1 ), + array( 0 => 68, 1 => 5 ), + array( 0 => 68, 1 => 7 ), + array( 0 => 68, 1 => 6 ), + array( 0 => 68, 1 => 8 ), + array( 0 => 68, 1 => 2 ), + array( 0 => 68, 1 => 3 ), + array( 0 => 68, 1 => 4 ), + array( 0 => 66, 1 => 1 ), + array( 0 => 68, 1 => 2 ), + array( 0 => 68, 1 => 3 ), + array( 0 => 68, 1 => 4 ), + array( 0 => 68, 1 => 5 ), + array( 0 => 73, 1 => 2 ), + array( 0 => 73, 1 => 1 ), + array( 0 => 73, 1 => 0 ), + array( 0 => 83, 1 => 4 ), + array( 0 => 83, 1 => 2 ), + array( 0 => 83, 1 => 2 ), + array( 0 => 83, 1 => 2 ), + array( 0 => 83, 1 => 2 ), + array( 0 => 83, 1 => 2 ), + array( 0 => 83, 1 => 4 ), + array( 0 => 79, 1 => 1 ), + array( 0 => 79, 1 => 3 ), + array( 0 => 78, 1 => 3 ), + array( 0 => 78, 1 => 3 ), + array( 0 => 78, 1 => 3 ), + array( 0 => 78, 1 => 3 ), + array( 0 => 76, 1 => 1 ), + array( 0 => 76, 1 => 1 ), + array( 0 => 76, 1 => 3 ), + array( 0 => 76, 1 => 3 ), + array( 0 => 76, 1 => 3 ), + array( 0 => 76, 1 => 3 ), + array( 0 => 76, 1 => 3 ), + array( 0 => 76, 1 => 2 ), + array( 0 => 76, 1 => 3 ), + array( 0 => 76, 1 => 3 ), + array( 0 => 84, 1 => 7 ), + array( 0 => 84, 1 => 7 ), + array( 0 => 75, 1 => 1 ), + array( 0 => 75, 1 => 2 ), + array( 0 => 75, 1 => 2 ), + array( 0 => 75, 1 => 2 ), + array( 0 => 75, 1 => 2 ), + array( 0 => 75, 1 => 1 ), + array( 0 => 75, 1 => 1 ), + array( 0 => 75, 1 => 3 ), + array( 0 => 75, 1 => 2 ), + array( 0 => 75, 1 => 2 ), + array( 0 => 75, 1 => 1 ), + array( 0 => 75, 1 => 1 ), + array( 0 => 75, 1 => 3 ), + array( 0 => 75, 1 => 3 ), + array( 0 => 75, 1 => 3 ), + array( 0 => 75, 1 => 1 ), + array( 0 => 75, 1 => 1 ), + array( 0 => 75, 1 => 3 ), + array( 0 => 75, 1 => 1 ), + array( 0 => 75, 1 => 2 ), + array( 0 => 75, 1 => 1 ), + array( 0 => 75, 1 => 1 ), + array( 0 => 75, 1 => 3 ), + array( 0 => 90, 1 => 1 ), + array( 0 => 90, 1 => 1 ), + array( 0 => 74, 1 => 1 ), + array( 0 => 74, 1 => 1 ), + array( 0 => 74, 1 => 3 ), + array( 0 => 74, 1 => 1 ), + array( 0 => 74, 1 => 3 ), + array( 0 => 74, 1 => 4 ), + array( 0 => 74, 1 => 3 ), + array( 0 => 74, 1 => 4 ), + array( 0 => 71, 1 => 2 ), + array( 0 => 71, 1 => 2 ), + array( 0 => 95, 1 => 2 ), + array( 0 => 95, 1 => 0 ), + array( 0 => 96, 1 => 2 ), + array( 0 => 96, 1 => 2 ), + array( 0 => 96, 1 => 4 ), + array( 0 => 96, 1 => 2 ), + array( 0 => 96, 1 => 2 ), + array( 0 => 96, 1 => 4 ), + array( 0 => 96, 1 => 3 ), + array( 0 => 96, 1 => 5 ), + array( 0 => 96, 1 => 3 ), + array( 0 => 96, 1 => 3 ), + array( 0 => 96, 1 => 3 ), + array( 0 => 96, 1 => 3 ), + array( 0 => 96, 1 => 3 ), + array( 0 => 96, 1 => 3 ), + array( 0 => 96, 1 => 2 ), + array( 0 => 81, 1 => 1 ), + array( 0 => 81, 1 => 1 ), + array( 0 => 81, 1 => 2 ), + array( 0 => 97, 1 => 1 ), + array( 0 => 97, 1 => 1 ), + array( 0 => 97, 1 => 3 ), + array( 0 => 94, 1 => 2 ), + array( 0 => 98, 1 => 1 ), + array( 0 => 98, 1 => 2 ), + array( 0 => 99, 1 => 3 ), + array( 0 => 99, 1 => 3 ), + array( 0 => 99, 1 => 5 ), + array( 0 => 99, 1 => 6 ), + array( 0 => 99, 1 => 2 ), + array( 0 => 89, 1 => 4 ), + array( 0 => 100, 1 => 4 ), + array( 0 => 100, 1 => 4 ), + array( 0 => 101, 1 => 3 ), + array( 0 => 101, 1 => 1 ), + array( 0 => 101, 1 => 0 ), + array( 0 => 77, 1 => 3 ), + array( 0 => 77, 1 => 2 ), + array( 0 => 102, 1 => 3 ), + array( 0 => 102, 1 => 2 ), + array( 0 => 82, 1 => 2 ), + array( 0 => 82, 1 => 0 ), + array( 0 => 103, 1 => 2 ), + array( 0 => 103, 1 => 3 ), + array( 0 => 103, 1 => 2 ), + array( 0 => 92, 1 => 1 ), + array( 0 => 92, 1 => 2 ), + array( 0 => 92, 1 => 1 ), + array( 0 => 92, 1 => 2 ), + array( 0 => 92, 1 => 3 ), + array( 0 => 86, 1 => 1 ), + array( 0 => 86, 1 => 1 ), + array( 0 => 85, 1 => 1 ), + array( 0 => 87, 1 => 1 ), + array( 0 => 93, 1 => 3 ), + array( 0 => 93, 1 => 3 ), + array( 0 => 104, 1 => 1 ), + array( 0 => 104, 1 => 3 ), + array( 0 => 104, 1 => 0 ), + array( 0 => 105, 1 => 3 ), + array( 0 => 105, 1 => 3 ), + array( 0 => 105, 1 => 1 ), + array( 0 => 91, 1 => 2 ), + array( 0 => 91, 1 => 3 ), + array( 0 => 106, 1 => 2 ), + array( 0 => 106, 1 => 1 ), + array( 0 => 107, 1 => 3 ), + array( 0 => 107, 1 => 3 ), + array( 0 => 107, 1 => 1 ), + array( 0 => 107, 1 => 3 ), + array( 0 => 107, 1 => 3 ), + array( 0 => 107, 1 => 1 ), + array( 0 => 107, 1 => 1 ), + ); - // line 251 "../smarty/lexer/smarty_internal_templateparser.y" - public function yy_r1() - { - $code = - $this->compiler->compileTag('private_php', - array(array('code' => $this->yystack[ $this->yyidx + 0 ]->minor), array('type' => $this->lex->phpType)), - array()); - if ($this->compiler->has_code && !empty($code)) { - $tmp = ''; - foreach ($this->compiler->prefix_code as $code) { - $tmp .= $code; + public static $yyReduceMap = array( + 0 => 0, + 1 => 1, + 2 => 2, + 3 => 3, + 4 => 4, + 5 => 5, + 6 => 6, + 21 => 6, + 22 => 6, + 23 => 6, + 36 => 6, + 56 => 6, + 57 => 6, + 65 => 6, + 66 => 6, + 77 => 6, + 82 => 6, + 83 => 6, + 88 => 6, + 92 => 6, + 93 => 6, + 97 => 6, + 98 => 6, + 100 => 6, + 105 => 6, + 169 => 6, + 174 => 6, + 7 => 7, + 8 => 8, + 9 => 9, + 11 => 11, + 12 => 12, + 13 => 13, + 14 => 14, + 15 => 15, + 16 => 16, + 17 => 17, + 18 => 18, + 19 => 19, + 20 => 20, + 24 => 24, + 25 => 25, + 26 => 26, + 27 => 27, + 28 => 28, + 29 => 29, + 30 => 30, + 31 => 31, + 33 => 31, + 32 => 32, + 34 => 34, + 35 => 35, + 37 => 37, + 38 => 38, + 39 => 39, + 40 => 40, + 41 => 41, + 42 => 42, + 43 => 43, + 44 => 44, + 45 => 45, + 46 => 46, + 47 => 47, + 48 => 48, + 49 => 49, + 50 => 50, + 59 => 50, + 147 => 50, + 151 => 50, + 155 => 50, + 157 => 50, + 51 => 51, + 148 => 51, + 154 => 51, + 52 => 52, + 53 => 53, + 54 => 53, + 55 => 55, + 132 => 55, + 58 => 58, + 60 => 60, + 61 => 61, + 62 => 61, + 63 => 63, + 64 => 64, + 67 => 67, + 68 => 68, + 69 => 68, + 70 => 70, + 71 => 71, + 72 => 72, + 73 => 73, + 74 => 74, + 75 => 75, + 76 => 76, + 78 => 78, + 80 => 78, + 81 => 78, + 112 => 78, + 79 => 79, + 84 => 84, + 85 => 85, + 86 => 86, + 87 => 87, + 89 => 89, + 90 => 90, + 91 => 90, + 94 => 94, + 95 => 95, + 96 => 96, + 99 => 99, + 101 => 101, + 102 => 102, + 103 => 103, + 104 => 104, + 106 => 106, + 107 => 107, + 108 => 108, + 109 => 109, + 110 => 110, + 111 => 111, + 113 => 113, + 171 => 113, + 114 => 114, + 115 => 115, + 116 => 116, + 117 => 117, + 118 => 118, + 119 => 119, + 127 => 119, + 120 => 120, + 121 => 121, + 122 => 122, + 123 => 122, + 125 => 122, + 126 => 122, + 124 => 124, + 128 => 128, + 129 => 129, + 130 => 130, + 175 => 130, + 131 => 131, + 133 => 133, + 134 => 134, + 135 => 135, + 136 => 136, + 137 => 137, + 138 => 138, + 139 => 139, + 140 => 140, + 141 => 141, + 142 => 142, + 143 => 143, + 144 => 144, + 145 => 145, + 146 => 146, + 149 => 149, + 150 => 150, + 152 => 152, + 153 => 153, + 156 => 156, + 158 => 158, + 159 => 159, + 160 => 160, + 161 => 161, + 162 => 162, + 163 => 163, + 164 => 164, + 165 => 165, + 166 => 166, + 167 => 167, + 168 => 167, + 170 => 170, + 172 => 172, + 173 => 173, + 176 => 176, + 177 => 177, + 178 => 178, + 179 => 179, + 182 => 179, + 180 => 180, + 183 => 180, + 181 => 181, + 184 => 184, + 185 => 185, + ); +// line 233 "../smarty/lexer/smarty_internal_templateparser.y" + public function yy_r0(){ + $this->root_buffer->prepend_array($this, $this->template_prefix); + $this->root_buffer->append_array($this, $this->template_postfix); + $this->_retvalue = $this->root_buffer->to_smarty_php($this); + } +// line 240 "../smarty/lexer/smarty_internal_templateparser.y" + public function yy_r1(){ + $text = $this->yystack[ $this->yyidx + 0 ]->minor; + + if ((string)$text == '') { + $this->current_buffer->append_subtree($this, null); + } + + $this->current_buffer->append_subtree($this, new Smarty_Internal_ParseTree_Text($text, $this->strip)); + } +// line 250 "../smarty/lexer/smarty_internal_templateparser.y" + public function yy_r2(){ + $this->strip = true; + } +// line 254 "../smarty/lexer/smarty_internal_templateparser.y" + public function yy_r3(){ + $this->strip = false; + } +// line 259 "../smarty/lexer/smarty_internal_templateparser.y" + public function yy_r4(){ + $this->current_buffer->append_subtree($this, new Smarty_Internal_ParseTree_Text($this->yystack[$this->yyidx + -1]->minor)); + } +// line 264 "../smarty/lexer/smarty_internal_templateparser.y" + public function yy_r5(){ + $this->_retvalue = $this->yystack[$this->yyidx + -3]->minor.$this->yystack[$this->yyidx + -1]->minor; + } +// line 267 "../smarty/lexer/smarty_internal_templateparser.y" + public function yy_r6(){ + $this->_retvalue = $this->yystack[$this->yyidx + 0]->minor; + } +// line 271 "../smarty/lexer/smarty_internal_templateparser.y" + public function yy_r7(){ + $this->_retvalue = $this->yystack[$this->yyidx + -1]->minor.$this->yystack[$this->yyidx + 0]->minor; + + } +// line 276 "../smarty/lexer/smarty_internal_templateparser.y" + public function yy_r8(){ + $this->_retvalue = ''; + } +// line 280 "../smarty/lexer/smarty_internal_templateparser.y" + public function yy_r9(){ + if ($this->compiler->has_code) { + $this->current_buffer->append_subtree($this, $this->mergePrefixCode($this->yystack[$this->yyidx + 0]->minor)); + } + $this->compiler->has_variable_string = false; + $this->block_nesting_level = count($this->compiler->_tag_stack); + } +// line 292 "../smarty/lexer/smarty_internal_templateparser.y" + public function yy_r11(){ + $var = trim(substr($this->yystack[$this->yyidx + 0]->minor, $this->compiler->getLdelLength(), -$this->compiler->getRdelLength()), ' $'); + if (preg_match('/^(.*)(\s+nocache)$/', $var, $match)) { + $this->_retvalue = $this->compiler->compileTag('private_print_expression',array('nocache'),array('value'=>$this->compiler->compileVariable('\''.$match[1].'\''))); + } else { + $this->_retvalue = $this->compiler->compileTag('private_print_expression',array(),array('value'=>$this->compiler->compileVariable('\''.$var.'\''))); + } + } +// line 302 "../smarty/lexer/smarty_internal_templateparser.y" + public function yy_r12(){ + $tag = trim(substr($this->yystack[$this->yyidx + 0]->minor, $this->compiler->getLdelLength(), -$this->compiler->getRdelLength())); + if ($tag == 'strip') { + $this->strip = true; + $this->_retvalue = null; + } else { + if (defined($tag)) { + if ($this->security) { + $this->security->isTrustedConstant($tag, $this->compiler); + } + $this->_retvalue = $this->compiler->compileTag('private_print_expression',array(),array('value'=>$tag)); + } else { + if (preg_match('/^(.*)(\s+nocache)$/', $tag, $match)) { + $this->_retvalue = $this->compiler->compileTag($match[1],array('\'nocache\'')); + } else { + $this->_retvalue = $this->compiler->compileTag($tag,array()); } - $this->compiler->prefix_code = array(); - $this->current_buffer->append_subtree($this, - new Smarty_Internal_ParseTree_Tag($this, $this->compiler->processNocacheCode($tmp . $code, true))); } } - - // line 255 "../smarty/lexer/smarty_internal_templateparser.y" - public function yy_r2() - { - $text = $this->yystack[ $this->yyidx + 0 ]->minor; - - if ((string)$text == '') { - $this->current_buffer->append_subtree($this, null); - } - - $this->current_buffer->append_subtree($this, new Smarty_Internal_ParseTree_Text($text, $this->strip)); } - - // line 259 "../smarty/lexer/smarty_internal_templateparser.y" - public function yy_r3() - { - $this->strip = true; +// line 323 "../smarty/lexer/smarty_internal_templateparser.y" + public function yy_r13(){ + $j = strrpos($this->yystack[$this->yyidx + 0]->minor,'.'); + if ($this->yystack[$this->yyidx + 0]->minor[$j+1] == 'c') { + // {$smarty.block.child} + $this->_retvalue = $this->compiler->compileTag('child',array(),array($this->yystack[$this->yyidx + 0]->minor)); + } else { + // {$smarty.block.parent} + $this->_retvalue = $this->compiler->compileTag('parent',array(),array($this->yystack[$this->yyidx + 0]->minor)); + } + } +// line 334 "../smarty/lexer/smarty_internal_templateparser.y" + public function yy_r14(){ + $this->_retvalue = $this->yystack[$this->yyidx + -1]->minor; + } +// line 338 "../smarty/lexer/smarty_internal_templateparser.y" + public function yy_r15(){ + $this->_retvalue = $this->yystack[$this->yyidx + -1]->minor; + } +// line 342 "../smarty/lexer/smarty_internal_templateparser.y" + public function yy_r16(){ + $this->_retvalue = $this->compiler->compileTag('private_print_expression',$this->yystack[$this->yyidx + 0]->minor[1],array('value'=>$this->yystack[$this->yyidx + 0]->minor[0])); + } +// line 351 "../smarty/lexer/smarty_internal_templateparser.y" + public function yy_r17(){ + $this->_retvalue = $this->compiler->compileTag('assign',array_merge(array(array('value'=>$this->yystack[$this->yyidx + 0]->minor[0]),array('var'=>'\''.substr($this->yystack[$this->yyidx + -1]->minor,1).'\'')),$this->yystack[$this->yyidx + 0]->minor[1])); + } +// line 355 "../smarty/lexer/smarty_internal_templateparser.y" + public function yy_r18(){ + $this->_retvalue = $this->compiler->compileTag('assign',array_merge(array(array('value'=>$this->yystack[$this->yyidx + 0]->minor[0]),array('var'=>$this->yystack[$this->yyidx + -1]->minor['var'])),$this->yystack[$this->yyidx + 0]->minor[1]),array('smarty_internal_index'=>$this->yystack[$this->yyidx + -1]->minor['smarty_internal_index'])); + } +// line 359 "../smarty/lexer/smarty_internal_templateparser.y" + public function yy_r19(){ + $this->_retvalue = $this->yystack[$this->yyidx + 0]->minor; + } +// line 363 "../smarty/lexer/smarty_internal_templateparser.y" + public function yy_r20(){ + $this->_retvalue = array($this->yystack[$this->yyidx + -1]->minor,$this->yystack[$this->yyidx + 0]->minor); + } +// line 378 "../smarty/lexer/smarty_internal_templateparser.y" + public function yy_r24(){ + if (defined($this->yystack[$this->yyidx + -1]->minor)) { + if ($this->security) { + $this->security->isTrustedConstant($this->yystack[$this->yyidx + -1]->minor, $this->compiler); + } + $this->_retvalue = $this->compiler->compileTag('private_print_expression',$this->yystack[$this->yyidx + 0]->minor,array('value'=>$this->yystack[$this->yyidx + -1]->minor)); + } else { + $this->_retvalue = $this->compiler->compileTag($this->yystack[$this->yyidx + -1]->minor,$this->yystack[$this->yyidx + 0]->minor); + } } - - // line 264 "../smarty/lexer/smarty_internal_templateparser.y" - public function yy_r4() - { +// line 388 "../smarty/lexer/smarty_internal_templateparser.y" + public function yy_r25(){ + if (defined($this->yystack[$this->yyidx + 0]->minor)) { + if ($this->security) { + $this->security->isTrustedConstant($this->yystack[$this->yyidx + 0]->minor, $this->compiler); + } + $this->_retvalue = $this->compiler->compileTag('private_print_expression',array(),array('value'=>$this->yystack[$this->yyidx + 0]->minor)); + } else { + $this->_retvalue = $this->compiler->compileTag($this->yystack[$this->yyidx + 0]->minor,array()); + } + } +// line 401 "../smarty/lexer/smarty_internal_templateparser.y" + public function yy_r26(){ + if (defined($this->yystack[$this->yyidx + -2]->minor)) { + if ($this->security) { + $this->security->isTrustedConstant($this->yystack[$this->yyidx + -2]->minor, $this->compiler); + } + $this->_retvalue = $this->compiler->compileTag('private_print_expression',$this->yystack[$this->yyidx + 0]->minor,array('value'=>$this->yystack[$this->yyidx + -2]->minor, 'modifierlist'=>$this->yystack[$this->yyidx + -1]->minor)); + } else { + $this->_retvalue = $this->compiler->compileTag($this->yystack[$this->yyidx + -2]->minor,$this->yystack[$this->yyidx + 0]->minor, array('modifierlist'=>$this->yystack[$this->yyidx + -1]->minor)); + } + } +// line 413 "../smarty/lexer/smarty_internal_templateparser.y" + public function yy_r27(){ + $this->_retvalue = $this->compiler->compileTag($this->yystack[$this->yyidx + -3]->minor,$this->yystack[$this->yyidx + 0]->minor,array('object_method'=>$this->yystack[$this->yyidx + -1]->minor)); + } +// line 418 "../smarty/lexer/smarty_internal_templateparser.y" + public function yy_r28(){ + $this->_retvalue = $this->compiler->compileTag($this->yystack[$this->yyidx + -4]->minor,$this->yystack[$this->yyidx + 0]->minor,array('modifierlist'=>$this->yystack[$this->yyidx + -1]->minor, 'object_method'=>$this->yystack[$this->yyidx + -2]->minor)); + } +// line 423 "../smarty/lexer/smarty_internal_templateparser.y" + public function yy_r29(){ + $this->_retvalue = $this->compiler->compileTag('make_nocache',array(array('var'=>'\''.substr($this->yystack[$this->yyidx + 0]->minor,1).'\''))); + } +// line 428 "../smarty/lexer/smarty_internal_templateparser.y" + public function yy_r30(){ + $tag = trim(substr($this->yystack[$this->yyidx + -1]->minor,$this->compiler->getLdelLength())); + $this->_retvalue = $this->compiler->compileTag(($tag === 'else if')? 'elseif' : $tag,array(),array('if condition'=>$this->yystack[$this->yyidx + 0]->minor)); + } +// line 433 "../smarty/lexer/smarty_internal_templateparser.y" + public function yy_r31(){ + $tag = trim(substr($this->yystack[$this->yyidx + -2]->minor,$this->compiler->getLdelLength())); + $this->_retvalue = $this->compiler->compileTag(($tag === 'else if')? 'elseif' : $tag,$this->yystack[$this->yyidx + 0]->minor,array('if condition'=>$this->yystack[$this->yyidx + -1]->minor)); + } +// line 438 "../smarty/lexer/smarty_internal_templateparser.y" + public function yy_r32(){ + $tag = trim(substr($this->yystack[$this->yyidx + -1]->minor,$this->compiler->getLdelLength())); + $this->_retvalue = $this->compiler->compileTag(($tag === 'else if')? 'elseif' : $tag,array(),array('if condition'=>$this->yystack[$this->yyidx + 0]->minor)); + } +// line 449 "../smarty/lexer/smarty_internal_templateparser.y" + public function yy_r34(){ + $this->_retvalue = $this->compiler->compileTag('for',array_merge($this->yystack[$this->yyidx + 0]->minor,array(array('start'=>$this->yystack[$this->yyidx + -6]->minor),array('ifexp'=>$this->yystack[$this->yyidx + -4]->minor),array('var'=>$this->yystack[$this->yyidx + -2]->minor),array('step'=>$this->yystack[$this->yyidx + -1]->minor))),1); + } +// line 453 "../smarty/lexer/smarty_internal_templateparser.y" + public function yy_r35(){ + $this->_retvalue = '='.$this->yystack[$this->yyidx + 0]->minor; + } +// line 461 "../smarty/lexer/smarty_internal_templateparser.y" + public function yy_r37(){ + $this->_retvalue = $this->compiler->compileTag('for',array_merge($this->yystack[$this->yyidx + 0]->minor,array(array('start'=>$this->yystack[$this->yyidx + -3]->minor),array('to'=>$this->yystack[$this->yyidx + -1]->minor))),0); + } +// line 465 "../smarty/lexer/smarty_internal_templateparser.y" + public function yy_r38(){ + $this->_retvalue = $this->compiler->compileTag('for',array_merge($this->yystack[$this->yyidx + 0]->minor,array(array('start'=>$this->yystack[$this->yyidx + -5]->minor),array('to'=>$this->yystack[$this->yyidx + -3]->minor),array('step'=>$this->yystack[$this->yyidx + -1]->minor))),0); + } +// line 470 "../smarty/lexer/smarty_internal_templateparser.y" + public function yy_r39(){ + $this->_retvalue = $this->compiler->compileTag('foreach',array_merge($this->yystack[$this->yyidx + 0]->minor,array(array('from'=>$this->yystack[$this->yyidx + -3]->minor),array('item'=>$this->yystack[$this->yyidx + -1]->minor)))); + } +// line 474 "../smarty/lexer/smarty_internal_templateparser.y" + public function yy_r40(){ + $this->_retvalue = $this->compiler->compileTag('foreach',array_merge($this->yystack[$this->yyidx + 0]->minor,array(array('from'=>$this->yystack[$this->yyidx + -5]->minor),array('item'=>$this->yystack[$this->yyidx + -1]->minor),array('key'=>$this->yystack[$this->yyidx + -3]->minor)))); + } +// line 477 "../smarty/lexer/smarty_internal_templateparser.y" + public function yy_r41(){ + $this->_retvalue = $this->compiler->compileTag('foreach',$this->yystack[$this->yyidx + 0]->minor); + } +// line 482 "../smarty/lexer/smarty_internal_templateparser.y" + public function yy_r42(){ + $this->_retvalue = $this->compiler->compileTag('setfilter',array(),array('modifier_list'=>array(array_merge(array($this->yystack[$this->yyidx + -1]->minor),$this->yystack[$this->yyidx + 0]->minor)))); + } +// line 486 "../smarty/lexer/smarty_internal_templateparser.y" + public function yy_r43(){ + $this->_retvalue = $this->compiler->compileTag('setfilter',array(),array('modifier_list'=>array_merge(array(array_merge(array($this->yystack[$this->yyidx + -2]->minor),$this->yystack[$this->yyidx + -1]->minor)),$this->yystack[$this->yyidx + 0]->minor))); + } +// line 492 "../smarty/lexer/smarty_internal_templateparser.y" + public function yy_r44(){ + $tag = trim(substr($this->yystack[$this->yyidx + 0]->minor, $this->compiler->getLdelLength(), -$this->compiler->getRdelLength()), ' /'); + if ($tag === 'strip') { $this->strip = false; + $this->_retvalue = null; + } else { + $this->_retvalue = $this->compiler->compileTag($tag.'close',array()); + } + } +// line 501 "../smarty/lexer/smarty_internal_templateparser.y" + public function yy_r45(){ + $this->_retvalue = $this->compiler->compileTag($this->yystack[$this->yyidx + 0]->minor.'close',array()); + } +// line 505 "../smarty/lexer/smarty_internal_templateparser.y" + public function yy_r46(){ + $this->_retvalue = $this->compiler->compileTag($this->yystack[$this->yyidx + -1]->minor.'close',array(),array('modifier_list'=>$this->yystack[$this->yyidx + 0]->minor)); + } +// line 510 "../smarty/lexer/smarty_internal_templateparser.y" + public function yy_r47(){ + $this->_retvalue = $this->compiler->compileTag($this->yystack[$this->yyidx + -2]->minor.'close',array(),array('object_method'=>$this->yystack[$this->yyidx + 0]->minor)); + } +// line 514 "../smarty/lexer/smarty_internal_templateparser.y" + public function yy_r48(){ + $this->_retvalue = $this->compiler->compileTag($this->yystack[$this->yyidx + -3]->minor.'close',array(),array('object_method'=>$this->yystack[$this->yyidx + -1]->minor, 'modifier_list'=>$this->yystack[$this->yyidx + 0]->minor)); + } +// line 522 "../smarty/lexer/smarty_internal_templateparser.y" + public function yy_r49(){ + $this->_retvalue = $this->yystack[$this->yyidx + -1]->minor; + $this->_retvalue[] = $this->yystack[$this->yyidx + 0]->minor; + } +// line 528 "../smarty/lexer/smarty_internal_templateparser.y" + public function yy_r50(){ + $this->_retvalue = array($this->yystack[$this->yyidx + 0]->minor); + } +// line 533 "../smarty/lexer/smarty_internal_templateparser.y" + public function yy_r51(){ + $this->_retvalue = array(); + } +// line 538 "../smarty/lexer/smarty_internal_templateparser.y" + public function yy_r52(){ + if (defined($this->yystack[$this->yyidx + 0]->minor)) { + if ($this->security) { + $this->security->isTrustedConstant($this->yystack[$this->yyidx + 0]->minor, $this->compiler); + } + $this->_retvalue = array($this->yystack[$this->yyidx + -2]->minor=>$this->yystack[$this->yyidx + 0]->minor); + } else { + $this->_retvalue = array($this->yystack[$this->yyidx + -2]->minor=>'\''.$this->yystack[$this->yyidx + 0]->minor.'\''); } - - // line 269 "../smarty/lexer/smarty_internal_templateparser.y" - public function yy_r5() - { - $this->current_buffer->append_subtree($this, - new Smarty_Internal_ParseTree_Text($this->yystack[ $this->yyidx + -1 ]->minor)); } - - // line 272 "../smarty/lexer/smarty_internal_templateparser.y" - public function yy_r6() - { - $this->_retvalue = $this->yystack[ $this->yyidx + -3 ]->minor . $this->yystack[ $this->yyidx + -1 ]->minor; +// line 549 "../smarty/lexer/smarty_internal_templateparser.y" + public function yy_r53(){ + $this->_retvalue = array(trim($this->yystack[$this->yyidx + -1]->minor," =\n\r\t")=>$this->yystack[$this->yyidx + 0]->minor); } - - // line 276 "../smarty/lexer/smarty_internal_templateparser.y" - public function yy_r7() - { - $this->_retvalue = $this->yystack[ $this->yyidx + 0 ]->minor; +// line 557 "../smarty/lexer/smarty_internal_templateparser.y" + public function yy_r55(){ + $this->_retvalue = '\''.$this->yystack[$this->yyidx + 0]->minor.'\''; } - - // line 281 "../smarty/lexer/smarty_internal_templateparser.y" - public function yy_r8() - { - $this->_retvalue = $this->yystack[ $this->yyidx + -1 ]->minor . $this->yystack[ $this->yyidx + 0 ]->minor; +// line 569 "../smarty/lexer/smarty_internal_templateparser.y" + public function yy_r58(){ + $this->_retvalue = array($this->yystack[$this->yyidx + -2]->minor=>$this->yystack[$this->yyidx + 0]->minor); } - - // line 285 "../smarty/lexer/smarty_internal_templateparser.y" - public function yy_r9() - { - $this->_retvalue = ''; +// line 582 "../smarty/lexer/smarty_internal_templateparser.y" + public function yy_r60(){ + $this->yystack[$this->yyidx + -2]->minor[]=$this->yystack[$this->yyidx + 0]->minor; + $this->_retvalue = $this->yystack[$this->yyidx + -2]->minor; } - - // line 297 "../smarty/lexer/smarty_internal_templateparser.y" - public function yy_r10() - { - if ($this->compiler->has_code) { - $this->current_buffer->append_subtree($this, - $this->mergePrefixCode($this->yystack[ $this->yyidx + 0 ]->minor)); - } - $this->compiler->has_variable_string = false; - $this->block_nesting_level = count($this->compiler->_tag_stack); +// line 587 "../smarty/lexer/smarty_internal_templateparser.y" + public function yy_r61(){ + $this->_retvalue = array('var' => '\''.substr($this->yystack[$this->yyidx + -2]->minor,1).'\'', 'value'=>$this->yystack[$this->yyidx + 0]->minor); } - - // line 307 "../smarty/lexer/smarty_internal_templateparser.y" - public function yy_r12() - { - $var = - trim(substr($this->yystack[ $this->yyidx + 0 ]->minor, $this->compiler->getLdelLength(), - -$this->compiler->getRdelLength()), ' $'); - if (preg_match('/^(.*)(\s+nocache)$/', $var, $match)) { - $this->_retvalue = - $this->compiler->compileTag('private_print_expression', array('nocache'), - array('value' => $this->compiler->compileVariable('\'' . $match[ 1 ] . '\''))); - } else { - $this->_retvalue = - $this->compiler->compileTag('private_print_expression', array(), - array('value' => $this->compiler->compileVariable('\'' . $var . '\''))); - } +// line 594 "../smarty/lexer/smarty_internal_templateparser.y" + public function yy_r63(){ + $this->_retvalue = array('var' => $this->yystack[$this->yyidx + -2]->minor, 'value'=>$this->yystack[$this->yyidx + 0]->minor); } - - // line 328 "../smarty/lexer/smarty_internal_templateparser.y" - public function yy_r13() - { - $tag = - trim(substr($this->yystack[ $this->yyidx + 0 ]->minor, $this->compiler->getLdelLength(), - -$this->compiler->getRdelLength())); - if ($tag == 'strip') { - $this->strip = true; - $this->_retvalue = null; - } else { - if (defined($tag)) { - if ($this->security) { - $this->security->isTrustedConstant($tag, $this->compiler); - } - $this->_retvalue = - $this->compiler->compileTag('private_print_expression', array(), array('value' => $tag)); - } else { - if (preg_match('/^(.*)(\s+nocache)$/', $tag, $match)) { - $this->_retvalue = $this->compiler->compileTag($match[ 1 ], array('\'nocache\'')); - } else { - $this->_retvalue = $this->compiler->compileTag($tag, array()); - } - } - } +// line 598 "../smarty/lexer/smarty_internal_templateparser.y" + public function yy_r64(){ + $this->_retvalue = $this->yystack[$this->yyidx + -1]->minor; } - - // line 339 "../smarty/lexer/smarty_internal_templateparser.y" - public function yy_r14() - { - $j = strrpos($this->yystack[ $this->yyidx + 0 ]->minor, '.'); - if ($this->yystack[ $this->yyidx + 0 ]->minor[ $j + 1 ] == 'c') { - // {$smarty.block.child} - $this->_retvalue = - $this->compiler->compileTag('child', array(), array($this->yystack[ $this->yyidx + 0 ]->minor)); - } else { - // {$smarty.block.parent} - $this->_retvalue = - $this->compiler->compileTag('parent', array(), array($this->yystack[ $this->yyidx + 0 ]->minor)); - } +// line 618 "../smarty/lexer/smarty_internal_templateparser.y" + public function yy_r67(){ + $this->_retvalue = '$_smarty_tpl->getStreamVariable(\''.substr($this->yystack[$this->yyidx + -2]->minor,1).'://' . $this->yystack[$this->yyidx + 0]->minor . '\')'; } - - // line 343 "../smarty/lexer/smarty_internal_templateparser.y" - public function yy_r15() - { - $this->_retvalue = $this->yystack[ $this->yyidx + -1 ]->minor; +// line 623 "../smarty/lexer/smarty_internal_templateparser.y" + public function yy_r68(){ + $this->_retvalue = $this->yystack[$this->yyidx + -2]->minor . trim($this->yystack[$this->yyidx + -1]->minor) . $this->yystack[$this->yyidx + 0]->minor; } - - // line 347 "../smarty/lexer/smarty_internal_templateparser.y" - public function yy_r16() - { - $this->_retvalue = $this->yystack[ $this->yyidx + -1 ]->minor; +// line 633 "../smarty/lexer/smarty_internal_templateparser.y" + public function yy_r70(){ + $this->_retvalue = $this->yystack[$this->yyidx + -1]->minor['pre']. $this->yystack[$this->yyidx + -2]->minor.$this->yystack[$this->yyidx + -1]->minor['op'].$this->yystack[$this->yyidx + 0]->minor .')'; } - - // line 356 "../smarty/lexer/smarty_internal_templateparser.y" - public function yy_r17() - { - $this->_retvalue = - $this->compiler->compileTag('private_print_expression', $this->yystack[ $this->yyidx + 0 ]->minor[ 1 ], - array('value' => $this->yystack[ $this->yyidx + 0 ]->minor[ 0 ])); +// line 637 "../smarty/lexer/smarty_internal_templateparser.y" + public function yy_r71(){ + $this->_retvalue = $this->yystack[$this->yyidx + -2]->minor.$this->yystack[$this->yyidx + -1]->minor.$this->yystack[$this->yyidx + 0]->minor; } - - // line 360 "../smarty/lexer/smarty_internal_templateparser.y" - public function yy_r18() - { - $this->_retvalue = - $this->compiler->compileTag('assign', array_merge(array( - array('value' => $this->yystack[ $this->yyidx + 0 ]->minor[ 0 ]), - array('var' => '\'' . substr($this->yystack[ $this->yyidx + -1 ]->minor, 1) . '\'') - ), $this->yystack[ $this->yyidx + 0 ]->minor[ 1 ])); +// line 641 "../smarty/lexer/smarty_internal_templateparser.y" + public function yy_r72(){ + $this->_retvalue = $this->yystack[$this->yyidx + 0]->minor . $this->yystack[$this->yyidx + -1]->minor . ')'; } - - // line 364 "../smarty/lexer/smarty_internal_templateparser.y" - public function yy_r19() - { - $this->_retvalue = - $this->compiler->compileTag('assign', array_merge(array( - array('value' => $this->yystack[ $this->yyidx + 0 ]->minor[ 0 ]), - array('var' => $this->yystack[ $this->yyidx + -1 ]->minor[ 'var' ]) - ), $this->yystack[ $this->yyidx + 0 ]->minor[ 1 ]), array( - 'smarty_internal_index' => $this->yystack[ $this->yyidx + - -1 ]->minor[ 'smarty_internal_index' ] - )); - } - - // line 368 "../smarty/lexer/smarty_internal_templateparser.y" - public function yy_r20() - { - $this->_retvalue = $this->yystack[ $this->yyidx + 0 ]->minor; +// line 645 "../smarty/lexer/smarty_internal_templateparser.y" + public function yy_r73(){ + $this->_retvalue = 'in_array('.$this->yystack[$this->yyidx + -2]->minor.','.$this->yystack[$this->yyidx + 0]->minor.')'; } - - // line 383 "../smarty/lexer/smarty_internal_templateparser.y" - public function yy_r21() - { - $this->_retvalue = array($this->yystack[ $this->yyidx + -1 ]->minor, $this->yystack[ $this->yyidx + 0 ]->minor); - } - - // line 393 "../smarty/lexer/smarty_internal_templateparser.y" - public function yy_r25() - { - if (defined($this->yystack[ $this->yyidx + -1 ]->minor)) { - if ($this->security) { - $this->security->isTrustedConstant($this->yystack[ $this->yyidx + -1 ]->minor, $this->compiler); - } - $this->_retvalue = - $this->compiler->compileTag('private_print_expression', $this->yystack[ $this->yyidx + 0 ]->minor, - array('value' => $this->yystack[ $this->yyidx + -1 ]->minor)); - } else { - $this->_retvalue = - $this->compiler->compileTag($this->yystack[ $this->yyidx + -1 ]->minor, - $this->yystack[ $this->yyidx + 0 ]->minor); - } - } - - // line 406 "../smarty/lexer/smarty_internal_templateparser.y" - public function yy_r26() - { - if (defined($this->yystack[ $this->yyidx + 0 ]->minor)) { - if ($this->security) { - $this->security->isTrustedConstant($this->yystack[ $this->yyidx + 0 ]->minor, $this->compiler); - } - $this->_retvalue = - $this->compiler->compileTag('private_print_expression', array(), - array('value' => $this->yystack[ $this->yyidx + 0 ]->minor)); - } else { - $this->_retvalue = $this->compiler->compileTag($this->yystack[ $this->yyidx + 0 ]->minor, array()); - } - } - - // line 418 "../smarty/lexer/smarty_internal_templateparser.y" - public function yy_r27() - { - if (defined($this->yystack[ $this->yyidx + -2 ]->minor)) { - if ($this->security) { - $this->security->isTrustedConstant($this->yystack[ $this->yyidx + -2 ]->minor, $this->compiler); - } - $this->_retvalue = - $this->compiler->compileTag('private_print_expression', $this->yystack[ $this->yyidx + 0 ]->minor, - array( - 'value' => $this->yystack[ $this->yyidx + -2 ]->minor, - 'modifierlist' => $this->yystack[ $this->yyidx + -1 ]->minor - )); - } else { - $this->_retvalue = - $this->compiler->compileTag($this->yystack[ $this->yyidx + -2 ]->minor, - $this->yystack[ $this->yyidx + 0 ]->minor, - array('modifierlist' => $this->yystack[ $this->yyidx + -1 ]->minor)); - } - } - - // line 423 "../smarty/lexer/smarty_internal_templateparser.y" - public function yy_r28() - { - $this->_retvalue = - $this->compiler->compileTag($this->yystack[ $this->yyidx + -3 ]->minor, - $this->yystack[ $this->yyidx + 0 ]->minor, - array('object_method' => $this->yystack[ $this->yyidx + -1 ]->minor)); - } - - // line 428 "../smarty/lexer/smarty_internal_templateparser.y" - public function yy_r29() - { - $this->_retvalue = - $this->compiler->compileTag($this->yystack[ $this->yyidx + -4 ]->minor, - $this->yystack[ $this->yyidx + 0 ]->minor, array( - 'modifierlist' => $this->yystack[ $this->yyidx + -1 ]->minor, - 'object_method' => $this->yystack[ $this->yyidx + -2 ]->minor - )); - } - - // line 433 "../smarty/lexer/smarty_internal_templateparser.y" - public function yy_r30() - { - $this->_retvalue = - $this->compiler->compileTag('make_nocache', - array(array('var' => '\'' . substr($this->yystack[ $this->yyidx + 0 ]->minor, 1) . '\''))); - } - - // line 438 "../smarty/lexer/smarty_internal_templateparser.y" - public function yy_r31() - { - $tag = trim(substr($this->yystack[ $this->yyidx + -1 ]->minor, $this->compiler->getLdelLength())); - $this->_retvalue = - $this->compiler->compileTag(($tag === 'else if') ? 'elseif' : $tag, array(), - array('if condition' => $this->yystack[ $this->yyidx + 0 ]->minor)); - } - - // line 443 "../smarty/lexer/smarty_internal_templateparser.y" - public function yy_r32() - { - $tag = trim(substr($this->yystack[ $this->yyidx + -2 ]->minor, $this->compiler->getLdelLength())); - $this->_retvalue = - $this->compiler->compileTag(($tag === 'else if') ? 'elseif' : $tag, - $this->yystack[ $this->yyidx + 0 ]->minor, - array('if condition' => $this->yystack[ $this->yyidx + -1 ]->minor)); - } - - // line 454 "../smarty/lexer/smarty_internal_templateparser.y" - public function yy_r33() - { - $tag = trim(substr($this->yystack[ $this->yyidx + -1 ]->minor, $this->compiler->getLdelLength())); - $this->_retvalue = - $this->compiler->compileTag(($tag === 'else if') ? 'elseif' : $tag, array(), - array('if condition' => $this->yystack[ $this->yyidx + 0 ]->minor)); - } - - // line 458 "../smarty/lexer/smarty_internal_templateparser.y" - public function yy_r35() - { - $this->_retvalue = - $this->compiler->compileTag('for', array_merge($this->yystack[ $this->yyidx + 0 ]->minor, array( - array('start' => $this->yystack[ $this->yyidx + -6 ]->minor), - array('ifexp' => $this->yystack[ $this->yyidx + -4 ]->minor), - array('var' => $this->yystack[ $this->yyidx + -2 ]->minor), - array('step' => $this->yystack[ $this->yyidx + -1 ]->minor) - )), 1); - } - - // line 466 "../smarty/lexer/smarty_internal_templateparser.y" - public function yy_r36() - { - $this->_retvalue = '=' . $this->yystack[ $this->yyidx + 0 ]->minor; - } - - // line 470 "../smarty/lexer/smarty_internal_templateparser.y" - public function yy_r38() - { - $this->_retvalue = - $this->compiler->compileTag('for', array_merge($this->yystack[ $this->yyidx + 0 ]->minor, array( - array('start' => $this->yystack[ $this->yyidx + -3 ]->minor), - array('to' => $this->yystack[ $this->yyidx + -1 ]->minor) - )), 0); - } - - // line 475 "../smarty/lexer/smarty_internal_templateparser.y" - public function yy_r39() - { - $this->_retvalue = - $this->compiler->compileTag('for', array_merge($this->yystack[ $this->yyidx + 0 ]->minor, array( - array('start' => $this->yystack[ $this->yyidx + -5 ]->minor), - array('to' => $this->yystack[ $this->yyidx + -3 ]->minor), - array('step' => $this->yystack[ $this->yyidx + -1 ]->minor) - )), 0); - } - - // line 479 "../smarty/lexer/smarty_internal_templateparser.y" - public function yy_r40() - { - $this->_retvalue = - $this->compiler->compileTag('foreach', array_merge($this->yystack[ $this->yyidx + 0 ]->minor, array( - array('from' => $this->yystack[ $this->yyidx + -3 ]->minor), - array('item' => $this->yystack[ $this->yyidx + -1 ]->minor) - ))); - } - - // line 482 "../smarty/lexer/smarty_internal_templateparser.y" - public function yy_r41() - { - $this->_retvalue = - $this->compiler->compileTag('foreach', array_merge($this->yystack[ $this->yyidx + 0 ]->minor, array( - array('from' => $this->yystack[ $this->yyidx + -5 ]->minor), - array('item' => $this->yystack[ $this->yyidx + -1 ]->minor), - array('key' => $this->yystack[ $this->yyidx + -3 ]->minor) - ))); - } - - // line 487 "../smarty/lexer/smarty_internal_templateparser.y" - public function yy_r42() - { - $this->_retvalue = $this->compiler->compileTag('foreach', $this->yystack[ $this->yyidx + 0 ]->minor); - } - - // line 491 "../smarty/lexer/smarty_internal_templateparser.y" - public function yy_r43() - { - $this->_retvalue = - $this->compiler->compileTag('setfilter', array(), array( - 'modifier_list' => array( - array_merge(array($this->yystack[ $this->yyidx + -1 ]->minor), - $this->yystack[ $this->yyidx + 0 ]->minor) - ) - )); - } - - // line 497 "../smarty/lexer/smarty_internal_templateparser.y" - public function yy_r44() - { - $this->_retvalue = - $this->compiler->compileTag('setfilter', array(), array( - 'modifier_list' => array_merge(array( - array_merge(array( - $this->yystack[ $this->yyidx + - -2 ]->minor - ), $this->yystack[ $this->yyidx + -1 ]->minor) - ), $this->yystack[ $this->yyidx + 0 ]->minor) - )); - } - - // line 506 "../smarty/lexer/smarty_internal_templateparser.y" - public function yy_r45() - { - $tag = - trim(substr($this->yystack[ $this->yyidx + 0 ]->minor, $this->compiler->getLdelLength(), - -$this->compiler->getRdelLength()), ' /'); - if ($tag === 'strip') { - $this->strip = false; - $this->_retvalue = null; - } else { - $this->_retvalue = $this->compiler->compileTag($tag . 'close', array()); - } - } - - // line 510 "../smarty/lexer/smarty_internal_templateparser.y" - public function yy_r46() - { - $this->_retvalue = $this->compiler->compileTag($this->yystack[ $this->yyidx + 0 ]->minor . 'close', array()); - } - - // line 515 "../smarty/lexer/smarty_internal_templateparser.y" - public function yy_r47() - { - $this->_retvalue = - $this->compiler->compileTag($this->yystack[ $this->yyidx + -1 ]->minor . 'close', array(), - array('modifier_list' => $this->yystack[ $this->yyidx + 0 ]->minor)); - } - - // line 519 "../smarty/lexer/smarty_internal_templateparser.y" - public function yy_r48() - { - $this->_retvalue = - $this->compiler->compileTag($this->yystack[ $this->yyidx + -2 ]->minor . 'close', array(), - array('object_method' => $this->yystack[ $this->yyidx + 0 ]->minor)); - } - - // line 527 "../smarty/lexer/smarty_internal_templateparser.y" - public function yy_r49() - { - $this->_retvalue = - $this->compiler->compileTag($this->yystack[ $this->yyidx + -3 ]->minor . 'close', array(), array( - 'object_method' => $this->yystack[ $this->yyidx + -1 ]->minor, - 'modifier_list' => $this->yystack[ $this->yyidx + 0 ]->minor - )); - } - - // line 533 "../smarty/lexer/smarty_internal_templateparser.y" - public function yy_r50() - { - $this->_retvalue = $this->yystack[ $this->yyidx + -1 ]->minor; - $this->_retvalue[] = $this->yystack[ $this->yyidx + 0 ]->minor; - } - - // line 538 "../smarty/lexer/smarty_internal_templateparser.y" - public function yy_r51() - { - $this->_retvalue = array($this->yystack[ $this->yyidx + 0 ]->minor); - } - - // line 543 "../smarty/lexer/smarty_internal_templateparser.y" - public function yy_r52() - { - $this->_retvalue = array(); - } - - // line 554 "../smarty/lexer/smarty_internal_templateparser.y" - public function yy_r53() - { - if (defined($this->yystack[ $this->yyidx + 0 ]->minor)) { - if ($this->security) { - $this->security->isTrustedConstant($this->yystack[ $this->yyidx + 0 ]->minor, $this->compiler); - } - $this->_retvalue = - array($this->yystack[ $this->yyidx + -2 ]->minor => $this->yystack[ $this->yyidx + 0 ]->minor); - } else { - $this->_retvalue = - array( - $this->yystack[ $this->yyidx + -2 ]->minor => '\'' . - $this->yystack[ $this->yyidx + 0 ]->minor . - '\'' - ); - } - } - - // line 562 "../smarty/lexer/smarty_internal_templateparser.y" - public function yy_r54() - { - $this->_retvalue = - array( - trim($this->yystack[ $this->yyidx + -1 ]->minor, " =\n\r\t") => $this->yystack[ $this->yyidx + - 0 ]->minor - ); - } - - // line 574 "../smarty/lexer/smarty_internal_templateparser.y" - public function yy_r56() - { - $this->_retvalue = '\'' . $this->yystack[ $this->yyidx + 0 ]->minor . '\''; - } - - // line 587 "../smarty/lexer/smarty_internal_templateparser.y" - public function yy_r59() - { - $this->_retvalue = - array($this->yystack[ $this->yyidx + -2 ]->minor => $this->yystack[ $this->yyidx + 0 ]->minor); - } - - // line 592 "../smarty/lexer/smarty_internal_templateparser.y" - public function yy_r61() - { - $this->yystack[ $this->yyidx + -2 ]->minor[] = $this->yystack[ $this->yyidx + 0 ]->minor; - $this->_retvalue = $this->yystack[ $this->yyidx + -2 ]->minor; - } - - // line 599 "../smarty/lexer/smarty_internal_templateparser.y" - public function yy_r62() - { - $this->_retvalue = - array( - 'var' => '\'' . substr($this->yystack[ $this->yyidx + -2 ]->minor, 1) . '\'', - 'value' => $this->yystack[ $this->yyidx + 0 ]->minor - ); - } - - // line 603 "../smarty/lexer/smarty_internal_templateparser.y" - public function yy_r64() - { - $this->_retvalue = - array( - 'var' => $this->yystack[ $this->yyidx + -2 ]->minor, - 'value' => $this->yystack[ $this->yyidx + 0 ]->minor - ); - } - - // line 623 "../smarty/lexer/smarty_internal_templateparser.y" - public function yy_r65() - { - $this->_retvalue = $this->yystack[ $this->yyidx + -1 ]->minor; - } - - // line 628 "../smarty/lexer/smarty_internal_templateparser.y" - public function yy_r68() - { - $this->_retvalue = - '$_smarty_tpl->getStreamVariable(\'' . - substr($this->yystack[ $this->yyidx + -2 ]->minor, 1) . - '://' . - $this->yystack[ $this->yyidx + 0 ]->minor . - '\')'; - } - - // line 638 "../smarty/lexer/smarty_internal_templateparser.y" - public function yy_r69() - { - $this->_retvalue = - $this->yystack[ $this->yyidx + -2 ]->minor . - trim($this->yystack[ $this->yyidx + -1 ]->minor) . - $this->yystack[ $this->yyidx + 0 ]->minor; - } - - // line 642 "../smarty/lexer/smarty_internal_templateparser.y" - public function yy_r71() - { - $this->_retvalue = - $this->yystack[ $this->yyidx + -1 ]->minor[ 'pre' ] . - $this->yystack[ $this->yyidx + -2 ]->minor . - $this->yystack[ $this->yyidx + -1 ]->minor[ 'op' ] . - $this->yystack[ $this->yyidx + 0 ]->minor . - ')'; - } - - // line 646 "../smarty/lexer/smarty_internal_templateparser.y" - public function yy_r72() - { - $this->_retvalue = - $this->yystack[ $this->yyidx + -2 ]->minor . - $this->yystack[ $this->yyidx + -1 ]->minor . - $this->yystack[ $this->yyidx + 0 ]->minor; - } - - // line 650 "../smarty/lexer/smarty_internal_templateparser.y" - public function yy_r73() - { - $this->_retvalue = $this->yystack[ $this->yyidx + 0 ]->minor . $this->yystack[ $this->yyidx + -1 ]->minor . ')'; - } - - // line 654 "../smarty/lexer/smarty_internal_templateparser.y" - public function yy_r74() - { - $this->_retvalue = - 'in_array(' . - $this->yystack[ $this->yyidx + -2 ]->minor . - ',' . - $this->yystack[ $this->yyidx + 0 ]->minor . - ')'; - } - - // line 662 "../smarty/lexer/smarty_internal_templateparser.y" - public function yy_r75() - { - $this->_retvalue = - 'in_array(' . - $this->yystack[ $this->yyidx + -2 ]->minor . - ',(array)' . - $this->yystack[ $this->yyidx + 0 ]->minor . - ')'; - } - - // line 666 "../smarty/lexer/smarty_internal_templateparser.y" - public function yy_r76() - { - $this->_retvalue = - $this->yystack[ $this->yyidx + -5 ]->minor . - ' ? ' . - $this->compiler->compileVariable('\'' . substr($this->yystack[ $this->yyidx + -2 ]->minor, 1) . '\'') . - ' : ' . - $this->yystack[ $this->yyidx + 0 ]->minor; - } - - // line 676 "../smarty/lexer/smarty_internal_templateparser.y" - public function yy_r77() - { - $this->_retvalue = - $this->yystack[ $this->yyidx + -5 ]->minor . - ' ? ' . - $this->yystack[ $this->yyidx + -2 ]->minor . - ' : ' . - $this->yystack[ $this->yyidx + 0 ]->minor; - } - - // line 681 "../smarty/lexer/smarty_internal_templateparser.y" - public function yy_r79() - { - $this->_retvalue = $this->yystack[ $this->yyidx + -1 ]->minor . $this->yystack[ $this->yyidx + 0 ]->minor; - } - - // line 702 "../smarty/lexer/smarty_internal_templateparser.y" - public function yy_r80() - { - $this->_retvalue = '!' . $this->yystack[ $this->yyidx + 0 ]->minor; - } - - // line 706 "../smarty/lexer/smarty_internal_templateparser.y" - public function yy_r85() - { - $this->_retvalue = $this->yystack[ $this->yyidx + -2 ]->minor . '.' . $this->yystack[ $this->yyidx + 0 ]->minor; - } - - // line 710 "../smarty/lexer/smarty_internal_templateparser.y" - public function yy_r86() - { - $this->_retvalue = $this->yystack[ $this->yyidx + -1 ]->minor . '.'; - } - - // line 715 "../smarty/lexer/smarty_internal_templateparser.y" - public function yy_r87() - { - $this->_retvalue = '.' . $this->yystack[ $this->yyidx + 0 ]->minor; - } - - // line 732 "../smarty/lexer/smarty_internal_templateparser.y" - public function yy_r88() - { - if (defined($this->yystack[ $this->yyidx + 0 ]->minor)) { - if ($this->security) { - $this->security->isTrustedConstant($this->yystack[ $this->yyidx + 0 ]->minor, $this->compiler); - } - $this->_retvalue = $this->yystack[ $this->yyidx + 0 ]->minor; - } else { - $this->_retvalue = '\'' . $this->yystack[ $this->yyidx + 0 ]->minor . '\''; - } - } - - // line 736 "../smarty/lexer/smarty_internal_templateparser.y" - public function yy_r90() - { - $this->_retvalue = '(' . $this->yystack[ $this->yyidx + -1 ]->minor . ')'; - } - - // line 754 "../smarty/lexer/smarty_internal_templateparser.y" - public function yy_r91() - { - $this->_retvalue = - $this->yystack[ $this->yyidx + -2 ]->minor . - $this->yystack[ $this->yyidx + -1 ]->minor . - $this->yystack[ $this->yyidx + 0 ]->minor; - } - - // line 765 "../smarty/lexer/smarty_internal_templateparser.y" - public function yy_r95() - { - $prefixVar = $this->compiler->getNewPrefixVariable(); - if ($this->yystack[ $this->yyidx + -2 ]->minor[ 'var' ] === '\'smarty\'') { - $this->compiler->appendPrefixCode("compiler->compileTag('private_special_variable', array(), - $this->yystack[ $this->yyidx + - -2 ]->minor[ 'smarty_internal_index' ]) . - ';?>'); - } else { - $this->compiler->appendPrefixCode("compiler->compileVariable($this->yystack[ $this->yyidx + - -2 ]->minor[ 'var' ]) . - $this->yystack[ $this->yyidx + -2 ]->minor[ 'smarty_internal_index' ] . - ';?>'); - } - $this->_retvalue = - $prefixVar . - '::' . - $this->yystack[ $this->yyidx + 0 ]->minor[ 0 ] . - $this->yystack[ $this->yyidx + 0 ]->minor[ 1 ]; - } - - // line 772 "../smarty/lexer/smarty_internal_templateparser.y" - public function yy_r96() - { - $prefixVar = $this->compiler->getNewPrefixVariable(); - $tmp = $this->compiler->appendCode('', $this->yystack[ $this->yyidx + 0 ]->minor); - $this->compiler->appendPrefixCode($this->compiler->appendCode($tmp, "")); - $this->_retvalue = $prefixVar; - } - - // line 785 "../smarty/lexer/smarty_internal_templateparser.y" - public function yy_r97() - { - $this->_retvalue = - $this->compiler->compileTag('private_modifier', array(), array( - 'value' => $this->yystack[ $this->yyidx + -1 ]->minor, - 'modifierlist' => $this->yystack[ $this->yyidx + 0 ]->minor - )); +// line 649 "../smarty/lexer/smarty_internal_templateparser.y" + public function yy_r74(){ + $this->_retvalue = 'in_array('.$this->yystack[$this->yyidx + -2]->minor.',(array)'.$this->yystack[$this->yyidx + 0]->minor.')'; } - - // line 804 "../smarty/lexer/smarty_internal_templateparser.y" - public function yy_r100() - { - if (!in_array(strtolower($this->yystack[ $this->yyidx + -2 ]->minor), array('self', 'parent')) && - (!$this->security || - $this->security->isTrustedStaticClassAccess($this->yystack[ $this->yyidx + -2 ]->minor, - $this->yystack[ $this->yyidx + 0 ]->minor, $this->compiler))) { - if (isset($this->smarty->registered_classes[ $this->yystack[ $this->yyidx + -2 ]->minor ])) { - $this->_retvalue = - $this->smarty->registered_classes[ $this->yystack[ $this->yyidx + -2 ]->minor ] . - '::' . - $this->yystack[ $this->yyidx + 0 ]->minor[ 0 ] . - $this->yystack[ $this->yyidx + 0 ]->minor[ 1 ]; - } else { - $this->_retvalue = - $this->yystack[ $this->yyidx + -2 ]->minor . - '::' . - $this->yystack[ $this->yyidx + 0 ]->minor[ 0 ] . - $this->yystack[ $this->yyidx + 0 ]->minor[ 1 ]; - } - } else { - $this->compiler->trigger_template_error('static class \'' . - $this->yystack[ $this->yyidx + -2 ]->minor . - '\' is undefined or not allowed by security setting'); - } - } - - // line 815 "../smarty/lexer/smarty_internal_templateparser.y" - public function yy_r102() - { - $this->_retvalue = $this->yystack[ $this->yyidx + 0 ]->minor; - } - - // line 818 "../smarty/lexer/smarty_internal_templateparser.y" - public function yy_r103() - { - $this->_retvalue = - $this->compiler->compileVariable('\'' . substr($this->yystack[ $this->yyidx + 0 ]->minor, 1) . '\''); +// line 657 "../smarty/lexer/smarty_internal_templateparser.y" + public function yy_r75(){ + $this->_retvalue = $this->yystack[$this->yyidx + -5]->minor.' ? '. $this->compiler->compileVariable('\''.substr($this->yystack[$this->yyidx + -2]->minor,1).'\'') . ' : '.$this->yystack[$this->yyidx + 0]->minor; } - - // line 831 "../smarty/lexer/smarty_internal_templateparser.y" - public function yy_r104() - { - if ($this->yystack[ $this->yyidx + 0 ]->minor[ 'var' ] === '\'smarty\'') { - $smarty_var = - $this->compiler->compileTag('private_special_variable', array(), - $this->yystack[ $this->yyidx + 0 ]->minor[ 'smarty_internal_index' ]); - $this->_retvalue = $smarty_var; - } else { - // used for array reset,next,prev,end,current - $this->last_variable = $this->yystack[ $this->yyidx + 0 ]->minor[ 'var' ]; - $this->last_index = $this->yystack[ $this->yyidx + 0 ]->minor[ 'smarty_internal_index' ]; - $this->_retvalue = - $this->compiler->compileVariable($this->yystack[ $this->yyidx + 0 ]->minor[ 'var' ]) . - $this->yystack[ $this->yyidx + 0 ]->minor[ 'smarty_internal_index' ]; - } +// line 661 "../smarty/lexer/smarty_internal_templateparser.y" + public function yy_r76(){ + $this->_retvalue = $this->yystack[$this->yyidx + -5]->minor.' ? '.$this->yystack[$this->yyidx + -2]->minor.' : '.$this->yystack[$this->yyidx + 0]->minor; } - - // line 841 "../smarty/lexer/smarty_internal_templateparser.y" - public function yy_r105() - { - $this->_retvalue = - '$_smarty_tpl->tpl_vars[' . - $this->yystack[ $this->yyidx + -2 ]->minor . - ']->' . - $this->yystack[ $this->yyidx + 0 ]->minor; +// line 671 "../smarty/lexer/smarty_internal_templateparser.y" + public function yy_r78(){ + $this->_retvalue = $this->yystack[$this->yyidx + -1]->minor.$this->yystack[$this->yyidx + 0]->minor; } - - // line 845 "../smarty/lexer/smarty_internal_templateparser.y" - public function yy_r107() - { - $this->_retvalue = - $this->compiler->compileConfigVariable('\'' . $this->yystack[ $this->yyidx + -1 ]->minor . '\''); +// line 676 "../smarty/lexer/smarty_internal_templateparser.y" + public function yy_r79(){ + $this->_retvalue = '!'.$this->yystack[$this->yyidx + 0]->minor; } - - // line 849 "../smarty/lexer/smarty_internal_templateparser.y" - public function yy_r108() - { - $this->_retvalue = - '(is_array($tmp = ' . - $this->compiler->compileConfigVariable('\'' . $this->yystack[ $this->yyidx + -2 ]->minor . '\'') . - ') ? $tmp' . - $this->yystack[ $this->yyidx + 0 ]->minor . - ' :null)'; +// line 697 "../smarty/lexer/smarty_internal_templateparser.y" + public function yy_r84(){ + $this->_retvalue = $this->yystack[$this->yyidx + -2]->minor.'.'.$this->yystack[$this->yyidx + 0]->minor; } - - // line 853 "../smarty/lexer/smarty_internal_templateparser.y" - public function yy_r109() - { - $this->_retvalue = $this->compiler->compileConfigVariable($this->yystack[ $this->yyidx + -1 ]->minor); - } - - // line 857 "../smarty/lexer/smarty_internal_templateparser.y" - public function yy_r110() - { - $this->_retvalue = - '(is_array($tmp = ' . - $this->compiler->compileConfigVariable($this->yystack[ $this->yyidx + -2 ]->minor) . - ') ? $tmp' . - $this->yystack[ $this->yyidx + 0 ]->minor . - ' : null)'; - } - - // line 860 "../smarty/lexer/smarty_internal_templateparser.y" - public function yy_r111() - { - $this->_retvalue = - array( - 'var' => '\'' . substr($this->yystack[ $this->yyidx + -1 ]->minor, 1) . '\'', - 'smarty_internal_index' => $this->yystack[ $this->yyidx + 0 ]->minor - ); - } - - // line 873 "../smarty/lexer/smarty_internal_templateparser.y" - public function yy_r112() - { - $this->_retvalue = - array( - 'var' => $this->yystack[ $this->yyidx + -1 ]->minor, - 'smarty_internal_index' => $this->yystack[ $this->yyidx + 0 ]->minor - ); - } - - // line 879 "../smarty/lexer/smarty_internal_templateparser.y" - public function yy_r114() - { - return; - } - - // line 882 "../smarty/lexer/smarty_internal_templateparser.y" - public function yy_r115() - { - $this->_retvalue = - '[' . - $this->compiler->compileVariable('\'' . substr($this->yystack[ $this->yyidx + 0 ]->minor, 1) . '\'') . - ']'; - } - - // line 886 "../smarty/lexer/smarty_internal_templateparser.y" - public function yy_r116() - { - $this->_retvalue = '[' . $this->compiler->compileVariable($this->yystack[ $this->yyidx + 0 ]->minor) . ']'; - } - - // line 890 "../smarty/lexer/smarty_internal_templateparser.y" - public function yy_r117() - { - $this->_retvalue = - '[' . - $this->compiler->compileVariable($this->yystack[ $this->yyidx + -2 ]->minor) . - '->' . - $this->yystack[ $this->yyidx + 0 ]->minor . - ']'; - } - - // line 894 "../smarty/lexer/smarty_internal_templateparser.y" - public function yy_r118() - { - $this->_retvalue = '[\'' . $this->yystack[ $this->yyidx + 0 ]->minor . '\']'; - } - - // line 899 "../smarty/lexer/smarty_internal_templateparser.y" - public function yy_r119() - { - $this->_retvalue = '[' . $this->yystack[ $this->yyidx + 0 ]->minor . ']'; - } - - // line 904 "../smarty/lexer/smarty_internal_templateparser.y" - public function yy_r120() - { - $this->_retvalue = '[' . $this->yystack[ $this->yyidx + -1 ]->minor . ']'; - } - - // line 908 "../smarty/lexer/smarty_internal_templateparser.y" - public function yy_r121() - { - $this->_retvalue = - '[' . - $this->compiler->compileTag('private_special_variable', array(), - '[\'section\'][\'' . $this->yystack[ $this->yyidx + -1 ]->minor . '\'][\'index\']') . - ']'; - } - - // line 911 "../smarty/lexer/smarty_internal_templateparser.y" - public function yy_r122() - { - $this->_retvalue = - '[' . - $this->compiler->compileTag('private_special_variable', array(), '[\'section\'][\'' . - $this->yystack[ $this->yyidx + - -3 ]->minor . - '\'][\'' . - $this->yystack[ $this->yyidx + - -1 ]->minor . - '\']') . - ']'; - } - - // line 917 "../smarty/lexer/smarty_internal_templateparser.y" - public function yy_r123() - { - $this->_retvalue = '[' . $this->yystack[ $this->yyidx + -1 ]->minor . ']'; - } - - // line 933 "../smarty/lexer/smarty_internal_templateparser.y" - public function yy_r125() - { - $this->_retvalue = - '[' . - $this->compiler->compileVariable('\'' . substr($this->yystack[ $this->yyidx + -1 ]->minor, 1) . '\'') . - ']'; - } - - // line 943 "../smarty/lexer/smarty_internal_templateparser.y" - public function yy_r129() - { - $this->_retvalue = '[]'; - } - - // line 947 "../smarty/lexer/smarty_internal_templateparser.y" - public function yy_r130() - { - $this->_retvalue = '\'' . substr($this->yystack[ $this->yyidx + 0 ]->minor, 1) . '\''; - } - - // line 952 "../smarty/lexer/smarty_internal_templateparser.y" - public function yy_r131() - { - $this->_retvalue = '\'\''; - } - - // line 960 "../smarty/lexer/smarty_internal_templateparser.y" - public function yy_r132() - { - $this->_retvalue = $this->yystack[ $this->yyidx + -1 ]->minor . '.' . $this->yystack[ $this->yyidx + 0 ]->minor; - } - - // line 966 "../smarty/lexer/smarty_internal_templateparser.y" - public function yy_r134() - { - $var = - trim(substr($this->yystack[ $this->yyidx + 0 ]->minor, $this->compiler->getLdelLength(), - -$this->compiler->getRdelLength()), ' $'); - $this->_retvalue = $this->compiler->compileVariable('\'' . $var . '\''); - } - - // line 973 "../smarty/lexer/smarty_internal_templateparser.y" - public function yy_r135() - { - $this->_retvalue = '(' . $this->yystack[ $this->yyidx + -1 ]->minor . ')'; - } - - // line 982 "../smarty/lexer/smarty_internal_templateparser.y" - public function yy_r136() - { - if ($this->yystack[ $this->yyidx + -1 ]->minor[ 'var' ] === '\'smarty\'') { - $this->_retvalue = - $this->compiler->compileTag('private_special_variable', array(), - $this->yystack[ $this->yyidx + -1 ]->minor[ 'smarty_internal_index' ]) . - $this->yystack[ $this->yyidx + 0 ]->minor; - } else { - $this->_retvalue = - $this->compiler->compileVariable($this->yystack[ $this->yyidx + -1 ]->minor[ 'var' ]) . - $this->yystack[ $this->yyidx + -1 ]->minor[ 'smarty_internal_index' ] . - $this->yystack[ $this->yyidx + 0 ]->minor; - } - } - - // line 987 "../smarty/lexer/smarty_internal_templateparser.y" - public function yy_r137() - { - $this->_retvalue = $this->yystack[ $this->yyidx + 0 ]->minor; - } - - // line 992 "../smarty/lexer/smarty_internal_templateparser.y" - public function yy_r138() - { - $this->_retvalue = $this->yystack[ $this->yyidx + -1 ]->minor . $this->yystack[ $this->yyidx + 0 ]->minor; +// line 701 "../smarty/lexer/smarty_internal_templateparser.y" + public function yy_r85(){ + $this->_retvalue = $this->yystack[$this->yyidx + -1]->minor.'.'; } - - // line 999 "../smarty/lexer/smarty_internal_templateparser.y" - public function yy_r139() - { - if ($this->security && substr($this->yystack[ $this->yyidx + -1 ]->minor, 0, 1) === '_') { - $this->compiler->trigger_template_error(self::ERR1); - } - $this->_retvalue = - '->' . $this->yystack[ $this->yyidx + -1 ]->minor . $this->yystack[ $this->yyidx + 0 ]->minor; +// line 705 "../smarty/lexer/smarty_internal_templateparser.y" + public function yy_r86(){ + $this->_retvalue = '.'.$this->yystack[$this->yyidx + 0]->minor; } - - // line 1006 "../smarty/lexer/smarty_internal_templateparser.y" - public function yy_r140() - { +// line 710 "../smarty/lexer/smarty_internal_templateparser.y" + public function yy_r87(){ + if (defined($this->yystack[$this->yyidx + 0]->minor)) { if ($this->security) { - $this->compiler->trigger_template_error(self::ERR2); + $this->security->isTrustedConstant($this->yystack[$this->yyidx + 0]->minor, $this->compiler); } - $this->_retvalue = - '->{' . - $this->compiler->compileVariable($this->yystack[ $this->yyidx + -1 ]->minor) . - $this->yystack[ $this->yyidx + 0 ]->minor . - '}'; + $this->_retvalue = $this->yystack[$this->yyidx + 0]->minor; + } else { + $this->_retvalue = '\''.$this->yystack[$this->yyidx + 0]->minor.'\''; } - - // line 1013 "../smarty/lexer/smarty_internal_templateparser.y" - public function yy_r141() - { - if ($this->security) { - $this->compiler->trigger_template_error(self::ERR2); - } - $this->_retvalue = - '->{' . $this->yystack[ $this->yyidx + -2 ]->minor . $this->yystack[ $this->yyidx + 0 ]->minor . '}'; } - - // line 1021 "../smarty/lexer/smarty_internal_templateparser.y" - public function yy_r142() - { - if ($this->security) { - $this->compiler->trigger_template_error(self::ERR2); - } - $this->_retvalue = - '->{\'' . - $this->yystack[ $this->yyidx + -4 ]->minor . - '\'.' . - $this->yystack[ $this->yyidx + -2 ]->minor . - $this->yystack[ $this->yyidx + 0 ]->minor . - '}'; +// line 727 "../smarty/lexer/smarty_internal_templateparser.y" + public function yy_r89(){ + $this->_retvalue = '('. $this->yystack[$this->yyidx + -1]->minor .')'; } - - // line 1029 "../smarty/lexer/smarty_internal_templateparser.y" - public function yy_r143() - { - $this->_retvalue = '->' . $this->yystack[ $this->yyidx + 0 ]->minor; +// line 731 "../smarty/lexer/smarty_internal_templateparser.y" + public function yy_r90(){ + $this->_retvalue = $this->yystack[$this->yyidx + -2]->minor.$this->yystack[$this->yyidx + -1]->minor.$this->yystack[$this->yyidx + 0]->minor; } - - // line 1037 "../smarty/lexer/smarty_internal_templateparser.y" - public function yy_r144() - { - $this->_retvalue = - $this->compiler->compilePHPFunctionCall($this->yystack[ $this->yyidx + -3 ]->minor, - $this->yystack[ $this->yyidx + -1 ]->minor); +// line 749 "../smarty/lexer/smarty_internal_templateparser.y" + public function yy_r94(){ + if ($this->security && $this->security->static_classes !== array()) { + $this->compiler->trigger_template_error('dynamic static class not allowed by security setting'); + } + $prefixVar = $this->compiler->getNewPrefixVariable(); + if ($this->yystack[$this->yyidx + -2]->minor['var'] === '\'smarty\'') { + $this->compiler->appendPrefixCode("compiler->compileTag('private_special_variable',array(),$this->yystack[$this->yyidx + -2]->minor['smarty_internal_index']).';?>'); + } else { + $this->compiler->appendPrefixCode("compiler->compileVariable($this->yystack[$this->yyidx + -2]->minor['var']).$this->yystack[$this->yyidx + -2]->minor['smarty_internal_index'].';?>'); + } + $this->_retvalue = $prefixVar .'::'.$this->yystack[$this->yyidx + 0]->minor[0].$this->yystack[$this->yyidx + 0]->minor[1]; + } +// line 760 "../smarty/lexer/smarty_internal_templateparser.y" + public function yy_r95(){ + $prefixVar = $this->compiler->getNewPrefixVariable(); + $tmp = $this->compiler->appendCode('', $this->yystack[$this->yyidx + 0]->minor); + $this->compiler->appendPrefixCode($this->compiler->appendCode($tmp, "")); + $this->_retvalue = $prefixVar; + } +// line 767 "../smarty/lexer/smarty_internal_templateparser.y" + public function yy_r96(){ + $this->_retvalue = $this->compiler->compileTag('private_modifier',array(),array('value'=>$this->yystack[$this->yyidx + -1]->minor,'modifierlist'=>$this->yystack[$this->yyidx + 0]->minor)); + } +// line 780 "../smarty/lexer/smarty_internal_templateparser.y" + public function yy_r99(){ + if (!in_array(strtolower($this->yystack[$this->yyidx + -2]->minor), array('self', 'parent')) && (!$this->security || $this->security->isTrustedStaticClassAccess($this->yystack[$this->yyidx + -2]->minor, $this->yystack[$this->yyidx + 0]->minor, $this->compiler))) { + if (isset($this->smarty->registered_classes[$this->yystack[$this->yyidx + -2]->minor])) { + $this->_retvalue = $this->smarty->registered_classes[$this->yystack[$this->yyidx + -2]->minor].'::'.$this->yystack[$this->yyidx + 0]->minor[0].$this->yystack[$this->yyidx + 0]->minor[1]; + } else { + $this->_retvalue = $this->yystack[$this->yyidx + -2]->minor.'::'.$this->yystack[$this->yyidx + 0]->minor[0].$this->yystack[$this->yyidx + 0]->minor[1]; + } + } else { + $this->compiler->trigger_template_error ('static class \''.$this->yystack[$this->yyidx + -2]->minor.'\' is undefined or not allowed by security setting'); } - - // line 1044 "../smarty/lexer/smarty_internal_templateparser.y" - public function yy_r145() - { - if ($this->security && substr($this->yystack[ $this->yyidx + -3 ]->minor, 0, 1) === '_') { - $this->compiler->trigger_template_error(self::ERR1); - } - $this->_retvalue = - $this->yystack[ $this->yyidx + -3 ]->minor . - '(' . - implode(',', $this->yystack[ $this->yyidx + -1 ]->minor) . - ')'; } - - // line 1055 "../smarty/lexer/smarty_internal_templateparser.y" - public function yy_r146() - { - if ($this->security) { - $this->compiler->trigger_template_error(self::ERR2); +// line 799 "../smarty/lexer/smarty_internal_templateparser.y" + public function yy_r101(){ + $this->_retvalue = $this->yystack[$this->yyidx + 0]->minor; } - $prefixVar = $this->compiler->getNewPrefixVariable(); - $this->compiler->appendPrefixCode("compiler->compileVariable('\'' . - substr($this->yystack[ $this->yyidx + - -3 ]->minor, 1) . - '\'') . - ';?>'); - $this->_retvalue = $prefixVar . '(' . implode(',', $this->yystack[ $this->yyidx + -1 ]->minor) . ')'; - } - - // line 1072 "../smarty/lexer/smarty_internal_templateparser.y" - public function yy_r147() - { - $this->_retvalue = - array_merge($this->yystack[ $this->yyidx + -2 ]->minor, array($this->yystack[ $this->yyidx + 0 ]->minor)); +// line 810 "../smarty/lexer/smarty_internal_templateparser.y" + public function yy_r102(){ + $this->_retvalue = $this->compiler->compileVariable('\''.substr($this->yystack[$this->yyidx + 0]->minor,1).'\''); + } +// line 813 "../smarty/lexer/smarty_internal_templateparser.y" + public function yy_r103(){ + if ($this->yystack[$this->yyidx + 0]->minor['var'] === '\'smarty\'') { + $smarty_var = $this->compiler->compileTag('private_special_variable',array(),$this->yystack[$this->yyidx + 0]->minor['smarty_internal_index']); + $this->_retvalue = $smarty_var; + } else { + // used for array reset,next,prev,end,current + $this->last_variable = $this->yystack[$this->yyidx + 0]->minor['var']; + $this->last_index = $this->yystack[$this->yyidx + 0]->minor['smarty_internal_index']; + $this->_retvalue = $this->compiler->compileVariable($this->yystack[$this->yyidx + 0]->minor['var']).$this->yystack[$this->yyidx + 0]->minor['smarty_internal_index']; + } + } +// line 826 "../smarty/lexer/smarty_internal_templateparser.y" + public function yy_r104(){ + $this->_retvalue = '$_smarty_tpl->tpl_vars['. $this->yystack[$this->yyidx + -2]->minor .']->'.$this->yystack[$this->yyidx + 0]->minor; + } +// line 836 "../smarty/lexer/smarty_internal_templateparser.y" + public function yy_r106(){ + $this->_retvalue = $this->compiler->compileConfigVariable('\'' . $this->yystack[$this->yyidx + -1]->minor . '\''); + } +// line 840 "../smarty/lexer/smarty_internal_templateparser.y" + public function yy_r107(){ + $this->_retvalue = '(is_array($tmp = ' . $this->compiler->compileConfigVariable('\'' . $this->yystack[$this->yyidx + -2]->minor . '\'') . ') ? $tmp'.$this->yystack[$this->yyidx + 0]->minor.' :null)'; + } +// line 844 "../smarty/lexer/smarty_internal_templateparser.y" + public function yy_r108(){ + $this->_retvalue = $this->compiler->compileConfigVariable($this->yystack[$this->yyidx + -1]->minor); + } +// line 848 "../smarty/lexer/smarty_internal_templateparser.y" + public function yy_r109(){ + $this->_retvalue = '(is_array($tmp = ' . $this->compiler->compileConfigVariable($this->yystack[$this->yyidx + -2]->minor) . ') ? $tmp'.$this->yystack[$this->yyidx + 0]->minor.' : null)'; + } +// line 852 "../smarty/lexer/smarty_internal_templateparser.y" + public function yy_r110(){ + $this->_retvalue = array('var'=>'\''.substr($this->yystack[$this->yyidx + -1]->minor,1).'\'', 'smarty_internal_index'=>$this->yystack[$this->yyidx + 0]->minor); + } +// line 855 "../smarty/lexer/smarty_internal_templateparser.y" + public function yy_r111(){ + $this->_retvalue = array('var'=>$this->yystack[$this->yyidx + -1]->minor, 'smarty_internal_index'=>$this->yystack[$this->yyidx + 0]->minor); + } +// line 868 "../smarty/lexer/smarty_internal_templateparser.y" + public function yy_r113(){ + return; + } +// line 874 "../smarty/lexer/smarty_internal_templateparser.y" + public function yy_r114(){ + $this->_retvalue = '['.$this->compiler->compileVariable('\''.substr($this->yystack[$this->yyidx + 0]->minor,1).'\'').']'; + } +// line 877 "../smarty/lexer/smarty_internal_templateparser.y" + public function yy_r115(){ + $this->_retvalue = '['.$this->compiler->compileVariable($this->yystack[$this->yyidx + 0]->minor).']'; + } +// line 881 "../smarty/lexer/smarty_internal_templateparser.y" + public function yy_r116(){ + $this->_retvalue = '['.$this->compiler->compileVariable($this->yystack[$this->yyidx + -2]->minor).'->'.$this->yystack[$this->yyidx + 0]->minor.']'; + } +// line 885 "../smarty/lexer/smarty_internal_templateparser.y" + public function yy_r117(){ + $this->_retvalue = '[\''. $this->yystack[$this->yyidx + 0]->minor .'\']'; + } +// line 889 "../smarty/lexer/smarty_internal_templateparser.y" + public function yy_r118(){ + $this->_retvalue = '['. $this->yystack[$this->yyidx + 0]->minor .']'; + } +// line 894 "../smarty/lexer/smarty_internal_templateparser.y" + public function yy_r119(){ + $this->_retvalue = '['. $this->yystack[$this->yyidx + -1]->minor .']'; } - - // line 1076 "../smarty/lexer/smarty_internal_templateparser.y" - public function yy_r150() - { - $this->_retvalue = - array_merge($this->yystack[ $this->yyidx + -2 ]->minor, array( - array_merge($this->yystack[ $this->yyidx + -1 ]->minor, $this->yystack[ $this->yyidx + 0 ]->minor) - )); +// line 899 "../smarty/lexer/smarty_internal_templateparser.y" + public function yy_r120(){ + $this->_retvalue = '['.$this->compiler->compileTag('private_special_variable',array(),'[\'section\'][\''.$this->yystack[$this->yyidx + -1]->minor.'\'][\'index\']').']'; + } +// line 903 "../smarty/lexer/smarty_internal_templateparser.y" + public function yy_r121(){ + $this->_retvalue = '['.$this->compiler->compileTag('private_special_variable',array(),'[\'section\'][\''.$this->yystack[$this->yyidx + -3]->minor.'\'][\''.$this->yystack[$this->yyidx + -1]->minor.'\']').']'; + } +// line 906 "../smarty/lexer/smarty_internal_templateparser.y" + public function yy_r122(){ + $this->_retvalue = '['.$this->yystack[$this->yyidx + -1]->minor.']'; + } +// line 912 "../smarty/lexer/smarty_internal_templateparser.y" + public function yy_r124(){ + $this->_retvalue = '['.$this->compiler->compileVariable('\''.substr($this->yystack[$this->yyidx + -1]->minor,1).'\'').']'; + } +// line 928 "../smarty/lexer/smarty_internal_templateparser.y" + public function yy_r128(){ + $this->_retvalue = '[]'; + } +// line 938 "../smarty/lexer/smarty_internal_templateparser.y" + public function yy_r129(){ + $this->_retvalue = '\''.substr($this->yystack[$this->yyidx + 0]->minor,1).'\''; + } +// line 942 "../smarty/lexer/smarty_internal_templateparser.y" + public function yy_r130(){ + $this->_retvalue = '\'\''; + } +// line 947 "../smarty/lexer/smarty_internal_templateparser.y" + public function yy_r131(){ + $this->_retvalue = $this->yystack[$this->yyidx + -1]->minor.'.'.$this->yystack[$this->yyidx + 0]->minor; } - - // line 1084 "../smarty/lexer/smarty_internal_templateparser.y" - public function yy_r151() - { - $this->_retvalue = - array(array_merge($this->yystack[ $this->yyidx + -1 ]->minor, $this->yystack[ $this->yyidx + 0 ]->minor)); +// line 955 "../smarty/lexer/smarty_internal_templateparser.y" + public function yy_r133(){ + $var = trim(substr($this->yystack[$this->yyidx + 0]->minor, $this->compiler->getLdelLength(), -$this->compiler->getRdelLength()), ' $'); + $this->_retvalue = $this->compiler->compileVariable('\''.$var.'\''); } - - // line 1092 "../smarty/lexer/smarty_internal_templateparser.y" - public function yy_r153() - { - $this->_retvalue = array($this->yystack[ $this->yyidx + 0 ]->minor); +// line 961 "../smarty/lexer/smarty_internal_templateparser.y" + public function yy_r134(){ + $this->_retvalue = '('.$this->yystack[$this->yyidx + -1]->minor.')'; } - - // line 1105 "../smarty/lexer/smarty_internal_templateparser.y" - public function yy_r154() - { - $this->_retvalue = - array_merge($this->yystack[ $this->yyidx + -1 ]->minor, $this->yystack[ $this->yyidx + 0 ]->minor); +// line 968 "../smarty/lexer/smarty_internal_templateparser.y" + public function yy_r135(){ + if ($this->yystack[$this->yyidx + -1]->minor['var'] === '\'smarty\'') { + $this->_retvalue = $this->compiler->compileTag('private_special_variable',array(),$this->yystack[$this->yyidx + -1]->minor['smarty_internal_index']).$this->yystack[$this->yyidx + 0]->minor; + } else { + $this->_retvalue = $this->compiler->compileVariable($this->yystack[$this->yyidx + -1]->minor['var']).$this->yystack[$this->yyidx + -1]->minor['smarty_internal_index'].$this->yystack[$this->yyidx + 0]->minor; } - - // line 1114 "../smarty/lexer/smarty_internal_templateparser.y" - public function yy_r157() - { - $this->_retvalue = - array(trim($this->yystack[ $this->yyidx + -1 ]->minor) . $this->yystack[ $this->yyidx + 0 ]->minor); } - - // line 1119 "../smarty/lexer/smarty_internal_templateparser.y" - public function yy_r159() - { - $this->_retvalue = array($this->yystack[ $this->yyidx + 0 ]->minor, '', 'method'); +// line 977 "../smarty/lexer/smarty_internal_templateparser.y" + public function yy_r136(){ + $this->_retvalue = $this->yystack[$this->yyidx + 0]->minor; } - - // line 1124 "../smarty/lexer/smarty_internal_templateparser.y" - public function yy_r160() - { - $this->_retvalue = - array($this->yystack[ $this->yyidx + -1 ]->minor, $this->yystack[ $this->yyidx + 0 ]->minor, 'method'); +// line 982 "../smarty/lexer/smarty_internal_templateparser.y" + public function yy_r137(){ + $this->_retvalue = $this->yystack[$this->yyidx + -1]->minor.$this->yystack[$this->yyidx + 0]->minor; } - - // line 1129 "../smarty/lexer/smarty_internal_templateparser.y" - public function yy_r161() - { - $this->_retvalue = array($this->yystack[ $this->yyidx + 0 ]->minor, ''); +// line 987 "../smarty/lexer/smarty_internal_templateparser.y" + public function yy_r138(){ + if ($this->security && substr($this->yystack[$this->yyidx + -1]->minor,0,1) === '_') { + $this->compiler->trigger_template_error (self::ERR1); } - - // line 1134 "../smarty/lexer/smarty_internal_templateparser.y" - public function yy_r162() - { - $this->_retvalue = - array($this->yystack[ $this->yyidx + -1 ]->minor, $this->yystack[ $this->yyidx + 0 ]->minor, 'property'); - } - - // line 1140 "../smarty/lexer/smarty_internal_templateparser.y" - public function yy_r163() - { - $this->_retvalue = - array( - $this->yystack[ $this->yyidx + -2 ]->minor, - $this->yystack[ $this->yyidx + -1 ]->minor . $this->yystack[ $this->yyidx + 0 ]->minor, 'property' - ); + $this->_retvalue = '->'.$this->yystack[$this->yyidx + -1]->minor.$this->yystack[$this->yyidx + 0]->minor; } - - // line 1144 "../smarty/lexer/smarty_internal_templateparser.y" - public function yy_r164() - { - $this->_retvalue = ' ' . trim($this->yystack[ $this->yyidx + 0 ]->minor) . ' '; - } - - // line 1163 "../smarty/lexer/smarty_internal_templateparser.y" - public function yy_r165() - { - static $lops = array( - 'eq' => ' == ', - 'ne' => ' != ', - 'neq' => ' != ', - 'gt' => ' > ', - 'ge' => ' >= ', - 'gte' => ' >= ', - 'lt' => ' < ', - 'le' => ' <= ', - 'lte' => ' <= ', - 'mod' => ' % ', - 'and' => ' && ', - 'or' => ' || ', - 'xor' => ' xor ', - ); - $op = strtolower(preg_replace('/\s*/', '', $this->yystack[ $this->yyidx + 0 ]->minor)); - $this->_retvalue = $lops[ $op ]; - } - - // line 1176 "../smarty/lexer/smarty_internal_templateparser.y" - public function yy_r166() - { - static $tlops = array( - 'isdivby' => array('op' => ' % ', 'pre' => '!('), - 'isnotdivby' => array('op' => ' % ', 'pre' => '('), - 'isevenby' => array('op' => ' / ', 'pre' => '!(1 & '), - 'isnotevenby' => array('op' => ' / ', 'pre' => '(1 & '), - 'isoddby' => array('op' => ' / ', 'pre' => '(1 & '), - 'isnotoddby' => array('op' => ' / ', 'pre' => '!(1 & '), - ); - $op = strtolower(preg_replace('/\s*/', '', $this->yystack[ $this->yyidx + 0 ]->minor)); - $this->_retvalue = $tlops[ $op ]; - } - - // line 1190 "../smarty/lexer/smarty_internal_templateparser.y" - public function yy_r167() - { - static $scond = array( - 'iseven' => '!(1 & ', +// line 994 "../smarty/lexer/smarty_internal_templateparser.y" + public function yy_r139(){ + if ($this->security) { + $this->compiler->trigger_template_error (self::ERR2); + } + $this->_retvalue = '->{'.$this->compiler->compileVariable($this->yystack[$this->yyidx + -1]->minor).$this->yystack[$this->yyidx + 0]->minor.'}'; + } +// line 1001 "../smarty/lexer/smarty_internal_templateparser.y" + public function yy_r140(){ + if ($this->security) { + $this->compiler->trigger_template_error (self::ERR2); + } + $this->_retvalue = '->{'.$this->yystack[$this->yyidx + -2]->minor.$this->yystack[$this->yyidx + 0]->minor.'}'; + } +// line 1008 "../smarty/lexer/smarty_internal_templateparser.y" + public function yy_r141(){ + if ($this->security) { + $this->compiler->trigger_template_error (self::ERR2); + } + $this->_retvalue = '->{\''.$this->yystack[$this->yyidx + -4]->minor.'\'.'.$this->yystack[$this->yyidx + -2]->minor.$this->yystack[$this->yyidx + 0]->minor.'}'; + } +// line 1016 "../smarty/lexer/smarty_internal_templateparser.y" + public function yy_r142(){ + $this->_retvalue = '->'.$this->yystack[$this->yyidx + 0]->minor; + } +// line 1024 "../smarty/lexer/smarty_internal_templateparser.y" + public function yy_r143(){ + $this->_retvalue = $this->compiler->compilePHPFunctionCall($this->yystack[$this->yyidx + -3]->minor, $this->yystack[$this->yyidx + -1]->minor); + } +// line 1032 "../smarty/lexer/smarty_internal_templateparser.y" + public function yy_r144(){ + if ($this->security && substr($this->yystack[$this->yyidx + -3]->minor,0,1) === '_') { + $this->compiler->trigger_template_error (self::ERR1); + } + $this->_retvalue = $this->yystack[$this->yyidx + -3]->minor . '('. implode(',',$this->yystack[$this->yyidx + -1]->minor) .')'; + } +// line 1039 "../smarty/lexer/smarty_internal_templateparser.y" + public function yy_r145(){ + if ($this->security) { + $this->compiler->trigger_template_error (self::ERR2); + } + $prefixVar = $this->compiler->getNewPrefixVariable(); + $this->compiler->appendPrefixCode("compiler->compileVariable('\''.substr($this->yystack[$this->yyidx + -3]->minor,1).'\'').';?>'); + $this->_retvalue = $prefixVar .'('. implode(',',$this->yystack[$this->yyidx + -1]->minor) .')'; + } +// line 1050 "../smarty/lexer/smarty_internal_templateparser.y" + public function yy_r146(){ + $this->_retvalue = array_merge($this->yystack[$this->yyidx + -2]->minor,array($this->yystack[$this->yyidx + 0]->minor)); + } +// line 1067 "../smarty/lexer/smarty_internal_templateparser.y" + public function yy_r149(){ + $this->_retvalue = array_merge($this->yystack[$this->yyidx + -2]->minor,array(array_merge($this->yystack[$this->yyidx + -1]->minor,$this->yystack[$this->yyidx + 0]->minor))); + } +// line 1071 "../smarty/lexer/smarty_internal_templateparser.y" + public function yy_r150(){ + $this->_retvalue = array(array_merge($this->yystack[$this->yyidx + -1]->minor,$this->yystack[$this->yyidx + 0]->minor)); + } +// line 1079 "../smarty/lexer/smarty_internal_templateparser.y" + public function yy_r152(){ + $this->_retvalue = array($this->yystack[$this->yyidx + 0]->minor); + } +// line 1087 "../smarty/lexer/smarty_internal_templateparser.y" + public function yy_r153(){ + $this->_retvalue = array_merge($this->yystack[$this->yyidx + -1]->minor,$this->yystack[$this->yyidx + 0]->minor); + } +// line 1100 "../smarty/lexer/smarty_internal_templateparser.y" + public function yy_r156(){ + $this->_retvalue = array(trim($this->yystack[$this->yyidx + -1]->minor).$this->yystack[$this->yyidx + 0]->minor); + } +// line 1109 "../smarty/lexer/smarty_internal_templateparser.y" + public function yy_r158(){ + $this->_retvalue = array($this->yystack[$this->yyidx + 0]->minor, '', 'method'); + } +// line 1114 "../smarty/lexer/smarty_internal_templateparser.y" + public function yy_r159(){ + $this->_retvalue = array($this->yystack[$this->yyidx + -1]->minor, $this->yystack[$this->yyidx + 0]->minor, 'method'); + } +// line 1119 "../smarty/lexer/smarty_internal_templateparser.y" + public function yy_r160(){ + $this->_retvalue = array($this->yystack[$this->yyidx + 0]->minor, ''); + } +// line 1124 "../smarty/lexer/smarty_internal_templateparser.y" + public function yy_r161(){ + $this->_retvalue = array($this->yystack[$this->yyidx + -1]->minor, $this->yystack[$this->yyidx + 0]->minor, 'property'); + } +// line 1129 "../smarty/lexer/smarty_internal_templateparser.y" + public function yy_r162(){ + $this->_retvalue = array($this->yystack[$this->yyidx + -2]->minor, $this->yystack[$this->yyidx + -1]->minor.$this->yystack[$this->yyidx + 0]->minor, 'property'); + } +// line 1135 "../smarty/lexer/smarty_internal_templateparser.y" + public function yy_r163(){ + $this->_retvalue = ' '. trim($this->yystack[$this->yyidx + 0]->minor) . ' '; + } +// line 1139 "../smarty/lexer/smarty_internal_templateparser.y" + public function yy_r164(){ + static $lops = array( + 'eq' => ' == ', + 'ne' => ' != ', + 'neq' => ' != ', + 'gt' => ' > ', + 'ge' => ' >= ', + 'gte' => ' >= ', + 'lt' => ' < ', + 'le' => ' <= ', + 'lte' => ' <= ', + 'mod' => ' % ', + 'and' => ' && ', + 'or' => ' || ', + 'xor' => ' xor ', + ); + $op = strtolower(preg_replace('/\s*/', '', $this->yystack[$this->yyidx + 0]->minor)); + $this->_retvalue = $lops[$op]; + } +// line 1158 "../smarty/lexer/smarty_internal_templateparser.y" + public function yy_r165(){ + static $tlops = array( + 'isdivby' => array('op' => ' % ', 'pre' => '!('), + 'isnotdivby' => array('op' => ' % ', 'pre' => '('), + 'isevenby' => array('op' => ' / ', 'pre' => '!(1 & '), + 'isnotevenby' => array('op' => ' / ', 'pre' => '(1 & '), + 'isoddby' => array('op' => ' / ', 'pre' => '(1 & '), + 'isnotoddby' => array('op' => ' / ', 'pre' => '!(1 & '), + ); + $op = strtolower(preg_replace('/\s*/', '', $this->yystack[$this->yyidx + 0]->minor)); + $this->_retvalue = $tlops[$op]; + } +// line 1171 "../smarty/lexer/smarty_internal_templateparser.y" + public function yy_r166(){ + static $scond = array ( + 'iseven' => '!(1 & ', 'isnoteven' => '(1 & ', - 'isodd' => '(1 & ', - 'isnotodd' => '!(1 & ', + 'isodd' => '(1 & ', + 'isnotodd' => '!(1 & ', ); - $op = strtolower(str_replace(' ', '', $this->yystack[ $this->yyidx + 0 ]->minor)); - $this->_retvalue = $scond[ $op ]; + $op = strtolower(str_replace(' ', '', $this->yystack[$this->yyidx + 0]->minor)); + $this->_retvalue = $scond[$op]; } - - // line 1201 "../smarty/lexer/smarty_internal_templateparser.y" - public function yy_r168() - { - $this->_retvalue = 'array(' . $this->yystack[ $this->yyidx + -1 ]->minor . ')'; +// line 1185 "../smarty/lexer/smarty_internal_templateparser.y" + public function yy_r167(){ + $this->_retvalue = 'array('.$this->yystack[$this->yyidx + -1]->minor.')'; } - - // line 1209 "../smarty/lexer/smarty_internal_templateparser.y" - public function yy_r171() - { - $this->_retvalue = $this->yystack[ $this->yyidx + -2 ]->minor . ',' . $this->yystack[ $this->yyidx + 0 ]->minor; +// line 1196 "../smarty/lexer/smarty_internal_templateparser.y" + public function yy_r170(){ + $this->_retvalue = $this->yystack[$this->yyidx + -2]->minor.','.$this->yystack[$this->yyidx + 0]->minor; } - - // line 1213 "../smarty/lexer/smarty_internal_templateparser.y" - public function yy_r173() - { - $this->_retvalue = - $this->yystack[ $this->yyidx + -2 ]->minor . '=>' . $this->yystack[ $this->yyidx + 0 ]->minor; +// line 1204 "../smarty/lexer/smarty_internal_templateparser.y" + public function yy_r172(){ + $this->_retvalue = $this->yystack[$this->yyidx + -2]->minor.'=>'.$this->yystack[$this->yyidx + 0]->minor; } - - // line 1229 "../smarty/lexer/smarty_internal_templateparser.y" - public function yy_r174() - { - $this->_retvalue = - '\'' . $this->yystack[ $this->yyidx + -2 ]->minor . '\'=>' . $this->yystack[ $this->yyidx + 0 ]->minor; +// line 1208 "../smarty/lexer/smarty_internal_templateparser.y" + public function yy_r173(){ + $this->_retvalue = '\''.$this->yystack[$this->yyidx + -2]->minor.'\'=>'.$this->yystack[$this->yyidx + 0]->minor; } - - // line 1235 "../smarty/lexer/smarty_internal_templateparser.y" - public function yy_r177() - { - $this->compiler->leaveDoubleQuote(); - $this->_retvalue = $this->yystack[ $this->yyidx + -1 ]->minor->to_smarty_php($this); +// line 1224 "../smarty/lexer/smarty_internal_templateparser.y" + public function yy_r176(){ + $this->compiler->leaveDoubleQuote(); + $this->_retvalue = $this->yystack[$this->yyidx + -1]->minor->to_smarty_php($this); } - - // line 1240 "../smarty/lexer/smarty_internal_templateparser.y" - public function yy_r178() - { - $this->yystack[ $this->yyidx + -1 ]->minor->append_subtree($this, $this->yystack[ $this->yyidx + 0 ]->minor); - $this->_retvalue = $this->yystack[ $this->yyidx + -1 ]->minor; +// line 1230 "../smarty/lexer/smarty_internal_templateparser.y" + public function yy_r177(){ + $this->yystack[$this->yyidx + -1]->minor->append_subtree($this, $this->yystack[$this->yyidx + 0]->minor); + $this->_retvalue = $this->yystack[$this->yyidx + -1]->minor; } - - // line 1244 "../smarty/lexer/smarty_internal_templateparser.y" - public function yy_r179() - { - $this->_retvalue = new Smarty_Internal_ParseTree_Dq($this, $this->yystack[ $this->yyidx + 0 ]->minor); +// line 1235 "../smarty/lexer/smarty_internal_templateparser.y" + public function yy_r178(){ + $this->_retvalue = new Smarty_Internal_ParseTree_Dq($this, $this->yystack[$this->yyidx + 0]->minor); } - - // line 1248 "../smarty/lexer/smarty_internal_templateparser.y" - public function yy_r180() - { - $this->_retvalue = new Smarty_Internal_ParseTree_Code('(string)' . $this->yystack[ $this->yyidx + -1 ]->minor); +// line 1239 "../smarty/lexer/smarty_internal_templateparser.y" + public function yy_r179(){ + $this->_retvalue = new Smarty_Internal_ParseTree_Code('(string)'.$this->yystack[$this->yyidx + -1]->minor); } - - // line 1252 "../smarty/lexer/smarty_internal_templateparser.y" - public function yy_r181() - { - $this->_retvalue = - new Smarty_Internal_ParseTree_Code('(string)(' . $this->yystack[ $this->yyidx + -1 ]->minor . ')'); +// line 1243 "../smarty/lexer/smarty_internal_templateparser.y" + public function yy_r180(){ + $this->_retvalue = new Smarty_Internal_ParseTree_Code('(string)('.$this->yystack[$this->yyidx + -1]->minor.')'); } - - // line 1264 "../smarty/lexer/smarty_internal_templateparser.y" - public function yy_r182() - { - $this->_retvalue = - new Smarty_Internal_ParseTree_Code('(string)$_smarty_tpl->tpl_vars[\'' . - substr($this->yystack[ $this->yyidx + 0 ]->minor, 1) . - '\']->value'); +// line 1247 "../smarty/lexer/smarty_internal_templateparser.y" + public function yy_r181(){ + $this->_retvalue = new Smarty_Internal_ParseTree_Code('(string)$_smarty_tpl->tpl_vars[\''. substr($this->yystack[$this->yyidx + 0]->minor,1) .'\']->value'); } - - // line 1268 "../smarty/lexer/smarty_internal_templateparser.y" - public function yy_r185() - { - $this->_retvalue = new Smarty_Internal_ParseTree_Tag($this, $this->yystack[ $this->yyidx + 0 ]->minor); +// line 1259 "../smarty/lexer/smarty_internal_templateparser.y" + public function yy_r184(){ + $this->_retvalue = new Smarty_Internal_ParseTree_Tag($this, $this->yystack[$this->yyidx + 0]->minor); } - - public function yy_r186() - { - $this->_retvalue = new Smarty_Internal_ParseTree_DqContent($this->yystack[ $this->yyidx + 0 ]->minor); +// line 1263 "../smarty/lexer/smarty_internal_templateparser.y" + public function yy_r185(){ + $this->_retvalue = new Smarty_Internal_ParseTree_DqContent($this->yystack[$this->yyidx + 0]->minor); } + private $_retvalue; + public function yy_reduce($yyruleno) { if ($this->yyTraceFILE && $yyruleno >= 0 - && $yyruleno < count(self::$yyRuleName)) { + && $yyruleno < count(self::$yyRuleName)) { fprintf($this->yyTraceFILE, "%sReduce (%d) [%s].\n", $this->yyTracePrompt, $yyruleno, - self::$yyRuleName[ $yyruleno ]); + self::$yyRuleName[$yyruleno]); } + $this->_retvalue = $yy_lefthand_side = null; - if (isset(self::$yyReduceMap[ $yyruleno ])) { + if (isset(self::$yyReduceMap[$yyruleno])) { // call the action $this->_retvalue = null; - $this->{'yy_r' . self::$yyReduceMap[ $yyruleno ]}(); + $this->{'yy_r' . self::$yyReduceMap[$yyruleno]}(); $yy_lefthand_side = $this->_retvalue; } - $yygoto = self::$yyRuleInfo[ $yyruleno ][ 0 ]; - $yysize = self::$yyRuleInfo[ $yyruleno ][ 1 ]; + $yygoto = self::$yyRuleInfo[$yyruleno][0]; + $yysize = self::$yyRuleInfo[$yyruleno][1]; $this->yyidx -= $yysize; for ($i = $yysize; $i; $i--) { // pop all of the right-hand side parameters array_pop($this->yystack); } - $yyact = $this->yy_find_reduce_action($this->yystack[ $this->yyidx ]->stateno, $yygoto); + $yyact = $this->yy_find_reduce_action($this->yystack[$this->yyidx]->stateno, $yygoto); if ($yyact < self::YYNSTATE) { if (!$this->yyTraceFILE && $yysize) { $this->yyidx++; @@ -3492,7 +2792,7 @@ public function yy_reduce($yyruleno) $x->stateno = $yyact; $x->major = $yygoto; $x->minor = $yy_lefthand_side; - $this->yystack[ $this->yyidx ] = $x; + $this->yystack[$this->yyidx] = $x; } else { $this->yy_shift($yyact, $yygoto, $yy_lefthand_side); } @@ -3505,37 +2805,38 @@ public function yy_parse_failed() { if ($this->yyTraceFILE) { fprintf($this->yyTraceFILE, "%sFail!\n", $this->yyTracePrompt); - } - while ($this->yyidx >= 0) { + } while ($this->yyidx >= 0) { $this->yy_pop_parser_stack(); } } public function yy_syntax_error($yymajor, $TOKEN) { - // line 214 "../smarty/lexer/smarty_internal_templateparser.y" - $this->internalError = true; - $this->yymajor = $yymajor; - $this->compiler->trigger_template_error(); +// line 213 "../smarty/lexer/smarty_internal_templateparser.y" + + $this->internalError = true; + $this->yymajor = $yymajor; + $this->compiler->trigger_template_error(); } public function yy_accept() { if ($this->yyTraceFILE) { fprintf($this->yyTraceFILE, "%sAccept!\n", $this->yyTracePrompt); - } - while ($this->yyidx >= 0) { + } while ($this->yyidx >= 0) { $this->yy_pop_parser_stack(); } - // line 207 "../smarty/lexer/smarty_internal_templateparser.y" - $this->successful = !$this->internalError; - $this->internalError = false; - $this->retvalue = $this->_retvalue; +// line 206 "../smarty/lexer/smarty_internal_templateparser.y" + + $this->successful = !$this->internalError; + $this->internalError = false; + $this->retvalue = $this->_retvalue; } public function doParse($yymajor, $yytokenvalue) { $yyerrorhit = 0; /* True if yymajor has invoked an error */ + if ($this->yyidx === null || $this->yyidx < 0) { $this->yyidx = 0; $this->yyerrcnt = -1; @@ -3545,15 +2846,17 @@ public function doParse($yymajor, $yytokenvalue) $this->yystack = array(); $this->yystack[] = $x; } - $yyendofinput = ($yymajor == 0); + $yyendofinput = ($yymajor==0); + if ($this->yyTraceFILE) { fprintf($this->yyTraceFILE, "%sInput %s\n", - $this->yyTracePrompt, $this->yyTokenName[ $yymajor ]); + $this->yyTracePrompt, $this->yyTokenName[$yymajor]); } + do { $yyact = $this->yy_find_shift_action($yymajor); if ($yymajor < self::YYERRORSYMBOL && - !$this->yy_is_expected_token($yymajor)) { + !$this->yy_is_expected_token($yymajor)) { // force a syntax error $yyact = self::YY_ERROR_ACTION; } @@ -3576,22 +2879,22 @@ public function doParse($yymajor, $yytokenvalue) if ($this->yyerrcnt < 0) { $this->yy_syntax_error($yymajor, $yytokenvalue); } - $yymx = $this->yystack[ $this->yyidx ]->major; + $yymx = $this->yystack[$this->yyidx]->major; if ($yymx === self::YYERRORSYMBOL || $yyerrorhit) { if ($this->yyTraceFILE) { fprintf($this->yyTraceFILE, "%sDiscard input token %s\n", - $this->yyTracePrompt, $this->yyTokenName[ $yymajor ]); + $this->yyTracePrompt, $this->yyTokenName[$yymajor]); } $this->yy_destructor($yymajor, $yytokenvalue); $yymajor = self::YYNOCODE; } else { while ($this->yyidx >= 0 && - $yymx !== self::YYERRORSYMBOL && - ($yyact = $this->yy_find_shift_action(self::YYERRORSYMBOL)) >= self::YYNSTATE - ) { + $yymx !== self::YYERRORSYMBOL && + ($yyact = $this->yy_find_shift_action(self::YYERRORSYMBOL)) >= self::YYNSTATE + ){ $this->yy_pop_parser_stack(); } - if ($this->yyidx < 0 || $yymajor == 0) { + if ($this->yyidx < 0 || $yymajor==0) { $this->yy_destructor($yymajor, $yytokenvalue); $this->yy_parse_failed(); $yymajor = self::YYNOCODE; diff --git a/WEB-INF/lib/smarty/sysplugins/smarty_internal_testinstall.php b/WEB-INF/lib/smarty/sysplugins/smarty_internal_testinstall.php index 504a4582c..c8ffd4cc6 100644 --- a/WEB-INF/lib/smarty/sysplugins/smarty_internal_testinstall.php +++ b/WEB-INF/lib/smarty/sysplugins/smarty_internal_testinstall.php @@ -144,7 +144,7 @@ public static function testInstall(Smarty $smarty, &$errors = null) } // test if all registered plugins_dir are accessible // and if core plugins directory is still registered - $_core_plugins_dir = realpath(dirname(__FILE__) . '/../plugins'); + $_core_plugins_dir = realpath(__DIR__ . '/../plugins'); $_core_plugins_available = false; foreach ($smarty->getPluginsDir() as $plugin_dir) { $_plugin_dir = $plugin_dir; @@ -362,7 +362,6 @@ public static function testInstall(Smarty $smarty, &$errors = null) 'smarty_internal_compile_function.php' => true, 'smarty_internal_compile_if.php' => true, 'smarty_internal_compile_include.php' => true, - 'smarty_internal_compile_include_php.php' => true, 'smarty_internal_compile_insert.php' => true, 'smarty_internal_compile_ldelim.php' => true, 'smarty_internal_compile_make_nocache.php' => true, @@ -373,7 +372,6 @@ public static function testInstall(Smarty $smarty, &$errors = null) 'smarty_internal_compile_private_modifier.php' => true, 'smarty_internal_compile_private_object_block_function.php' => true, 'smarty_internal_compile_private_object_function.php' => true, - 'smarty_internal_compile_private_php.php' => true, 'smarty_internal_compile_private_print_expression.php' => true, 'smarty_internal_compile_private_registered_block.php' => true, 'smarty_internal_compile_private_registered_function.php' => true, @@ -388,7 +386,6 @@ public static function testInstall(Smarty $smarty, &$errors = null) 'smarty_internal_config_file_compiler.php' => true, 'smarty_internal_data.php' => true, 'smarty_internal_debug.php' => true, - 'smarty_internal_errorhandler.php' => true, 'smarty_internal_extension_handler.php' => true, 'smarty_internal_method_addautoloadfilters.php' => true, 'smarty_internal_method_adddefaultmodifiers.php' => true, @@ -450,7 +447,6 @@ public static function testInstall(Smarty $smarty, &$errors = null) 'smarty_internal_resource_extends.php' => true, 'smarty_internal_resource_file.php' => true, 'smarty_internal_resource_php.php' => true, - 'smarty_internal_resource_registered.php' => true, 'smarty_internal_resource_stream.php' => true, 'smarty_internal_resource_string.php' => true, 'smarty_internal_runtime_cachemodify.php' => true, diff --git a/WEB-INF/lib/smarty/sysplugins/smarty_resource.php b/WEB-INF/lib/smarty/sysplugins/smarty_resource.php index aae7e42f7..3c43a9f46 100644 --- a/WEB-INF/lib/smarty/sysplugins/smarty_resource.php +++ b/WEB-INF/lib/smarty/sysplugins/smarty_resource.php @@ -72,17 +72,15 @@ public static function load(Smarty $smarty, $type) } // try registered resource if (isset($smarty->registered_resources[ $type ])) { - return $smarty->_cache[ 'resource_handlers' ][ $type ] = - $smarty->registered_resources[ $type ] instanceof Smarty_Resource ? - $smarty->registered_resources[ $type ] : new Smarty_Internal_Resource_Registered(); + return $smarty->_cache[ 'resource_handlers' ][ $type ] = $smarty->registered_resources[ $type ]; } // try sysplugins dir if (isset(self::$sysplugins[ $type ])) { - $_resource_class = 'Smarty_Internal_Resource_' . ucfirst($type); + $_resource_class = 'Smarty_Internal_Resource_' . smarty_ucfirst_ascii($type); return $smarty->_cache[ 'resource_handlers' ][ $type ] = new $_resource_class(); } // try plugins dir - $_resource_class = 'Smarty_Resource_' . ucfirst($type); + $_resource_class = 'Smarty_Resource_' . smarty_ucfirst_ascii($type); if ($smarty->loadPlugin($_resource_class)) { if (class_exists($_resource_class, false)) { return $smarty->_cache[ 'resource_handlers' ][ $type ] = new $_resource_class(); diff --git a/WEB-INF/lib/smarty/sysplugins/smarty_resource_custom.php b/WEB-INF/lib/smarty/sysplugins/smarty_resource_custom.php index 8d66be3ae..191fa7c90 100644 --- a/WEB-INF/lib/smarty/sysplugins/smarty_resource_custom.php +++ b/WEB-INF/lib/smarty/sysplugins/smarty_resource_custom.php @@ -47,7 +47,7 @@ protected function fetchTimestamp($name) */ public function populate(Smarty_Template_Source $source, Smarty_Internal_Template $_template = null) { - $source->filepath = $source->type . ':' . substr(preg_replace('/[^A-Za-z0-9.]/', '', $source->name), 0, 25); + $source->filepath = $source->type . ':' . $this->generateSafeName($source->name); $source->uid = sha1($source->type . ':' . $source->name); $mtime = $this->fetchTimestamp($source->name); if ($mtime !== null) { @@ -88,6 +88,17 @@ public function getContent(Smarty_Template_Source $source) */ public function getBasename(Smarty_Template_Source $source) { - return basename(substr(preg_replace('/[^A-Za-z0-9.]/', '', $source->name), 0, 25)); + return basename($this->generateSafeName($source->name)); + } + + /** + * Removes special characters from $name and limits its length to 127 characters. + * + * @param $name + * + * @return string + */ + private function generateSafeName($name): string { + return substr(preg_replace('/[^A-Za-z0-9._]/', '', (string) $name), 0, 127); } } diff --git a/WEB-INF/lib/smarty/sysplugins/smarty_security.php b/WEB-INF/lib/smarty/sysplugins/smarty_security.php index 441a7e284..97cd0521d 100644 --- a/WEB-INF/lib/smarty/sysplugins/smarty_security.php +++ b/WEB-INF/lib/smarty/sysplugins/smarty_security.php @@ -19,21 +19,9 @@ /** * This class does contain the security settings */ +#[\AllowDynamicProperties] class Smarty_Security { - /** - * This determines how Smarty handles "" tags in templates. - * possible values: - *
    - *
  • Smarty::PHP_PASSTHRU -> echo PHP tags as they are
  • - *
  • Smarty::PHP_QUOTE -> escape tags as entities
  • - *
  • Smarty::PHP_REMOVE -> remove php tags
  • - *
  • Smarty::PHP_ALLOW -> execute php tags
  • - *
- * - * @var integer - */ - public $php_handling = Smarty::PHP_PASSTHRU; /** * This is the list of template directories that are considered secure. @@ -118,7 +106,7 @@ class Smarty_Security * * @var array */ - public $php_modifiers = array('escape', 'count', 'nl2br',); + public $php_modifiers = array('escape', 'count', 'sizeof', 'nl2br',); /** * This is an array of allowed tags. @@ -341,7 +329,7 @@ public function isTrustedStaticClassAccess($class_name, $params, $compiler) * * @param string $modifier_name * @param object $compiler compiler object - * + * @deprecated * @return boolean true if modifier is trusted */ public function isTrustedPhpModifier($modifier_name, $compiler) @@ -568,35 +556,6 @@ public function isTrustedUri($uri) throw new SmartyException("URI '{$uri}' not allowed by security setting"); } - /** - * Check if directory of file resource is trusted. - * - * @param string $filepath - * - * @return boolean true if directory is trusted - * @throws SmartyException if PHP directory is not trusted - */ - public function isTrustedPHPDir($filepath) - { - if (empty($this->trusted_dir)) { - throw new SmartyException("directory '{$filepath}' not allowed by security setting (no trusted_dir specified)"); - } - // check if index is outdated - if (!$this->_trusted_dir || $this->_trusted_dir !== $this->trusted_dir) { - $this->_php_resource_dir = array(); - $this->_trusted_dir = $this->trusted_dir; - foreach ((array)$this->trusted_dir as $directory) { - $directory = $this->smarty->_realpath($directory . '/', true); - $this->_php_resource_dir[ $directory ] = true; - } - } - $addPath = $this->_checkDir($filepath, $this->_php_resource_dir); - if ($addPath !== false) { - $this->_php_resource_dir = array_merge($this->_php_resource_dir, $addPath); - } - return true; - } - /** * Remove old directories and its sub folders, add new directories * diff --git a/WEB-INF/lib/smarty/sysplugins/smarty_variable.php b/WEB-INF/lib/smarty/sysplugins/smarty_variable.php index 914d99bd7..6a534228b 100644 --- a/WEB-INF/lib/smarty/sysplugins/smarty_variable.php +++ b/WEB-INF/lib/smarty/sysplugins/smarty_variable.php @@ -7,6 +7,7 @@ * @package Smarty * @subpackage Template */ +#[\AllowDynamicProperties] class Smarty_Variable { /** diff --git a/WEB-INF/lib/smarty/sysplugins/smartycompilerexception.php b/WEB-INF/lib/smarty/sysplugins/smartycompilerexception.php index f7ad39b93..0a0a32351 100644 --- a/WEB-INF/lib/smarty/sysplugins/smartycompilerexception.php +++ b/WEB-INF/lib/smarty/sysplugins/smartycompilerexception.php @@ -7,6 +7,33 @@ */ class SmartyCompilerException extends SmartyException { + /** + * The constructor of the exception + * + * @param string $message The Exception message to throw. + * @param int $code The Exception code. + * @param string|null $filename The filename where the exception is thrown. + * @param int|null $line The line number where the exception is thrown. + * @param Throwable|null $previous The previous exception used for the exception chaining. + */ + public function __construct( + string $message = "", + int $code = 0, + ?string $filename = null, + ?int $line = null, + Throwable $previous = null + ) { + parent::__construct($message, $code, $previous); + + // These are optional parameters, should be be overridden only when present! + if ($filename) { + $this->file = $filename; + } + if ($line) { + $this->line = $line; + } + } + /** * @return string */ @@ -16,11 +43,12 @@ public function __toString() } /** - * The line number of the template error - * - * @type int|null + * @param int $line */ - public $line = null; + public function setLine($line) + { + $this->line = $line; + } /** * The template source snippet relating to the error diff --git a/WEB-INF/lib/ttAdmin.class.php b/WEB-INF/lib/ttAdmin.class.php index 35a26afbe..4a631d5e3 100644 --- a/WEB-INF/lib/ttAdmin.class.php +++ b/WEB-INF/lib/ttAdmin.class.php @@ -138,7 +138,8 @@ static function updateSelf($fields) { // Update self. $user_id = $user->id; $login_part = 'login = '.$mdb2->quote($fields['login']); - if ($fields['password1']) + $password_part = ''; + if (isset($fields['password1'])) $password_part = ', password = md5('.$mdb2->quote($fields['password1']).')'; $name_part = ', name = '.$mdb2->quote($fields['name']); $email_part = ', email = '.$mdb2->quote($fields['email']); @@ -150,6 +151,8 @@ static function updateSelf($fields) { // getGroupName obtains group name. static function getGroupName($group_id) { + $group_id = (int) $group_id; // Just in case because it is used in sql. + $result = array(); $mdb2 = getConnection(); @@ -158,10 +161,11 @@ static function getGroupName($group_id) { $res = $mdb2->query($sql); if (!is_a($res, 'PEAR_Error')) { $val = $res->fetchRow(); - return $val['name']; + if (isset($val['name'])) + return $val['name']; } - return false; + return null; } // getOrgDetails obtains group name and its top manager details. diff --git a/WEB-INF/lib/ttAdminWorkHelper.class.php b/WEB-INF/lib/ttAdminWorkHelper.class.php deleted file mode 100644 index 22098b277..000000000 --- a/WEB-INF/lib/ttAdminWorkHelper.class.php +++ /dev/null @@ -1,1028 +0,0 @@ -errors = &$errors; - - $this->work_server_uri = defined('WORK_SERVER_URI') ? WORK_SERVER_URI : "https://www.anuko.com/work/"; - $this->register_uri = $this->work_server_uri.'register'; - $this->get_work_item_uri = $this->work_server_uri.'admin_getworkitem'; - $this->update_work_item_uri = $this->work_server_uri.'admin_updateworkitem'; - $this->delete_work_item_uri = $this->work_server_uri.'admin_deleteworkitem'; - $this->approve_work_item_uri = $this->work_server_uri.'admin_approveworkitem'; - $this->approve_work_item_on_offer_uri = $this->work_server_uri.'admin_approveworkitemonoffer'; - $this->disapprove_work_item_uri = $this->work_server_uri.'admin_disapproveworkitem'; - $this->disapprove_work_item_on_offer_uri = $this->work_server_uri.'admin_disapproveworkitemonoffer'; - $this->get_offer_uri = $this->work_server_uri.'admin_getoffer'; - $this->update_offer_uri = $this->work_server_uri.'admin_updateoffer'; - $this->delete_offer_uri = $this->work_server_uri.'admin_deleteoffer'; - $this->approve_offer_uri = $this->work_server_uri.'admin_approveoffer'; - $this->disapprove_offer_uri = $this->work_server_uri.'admin_disapproveoffer'; - $this->get_pending_work_items_uri = $this->work_server_uri.'admin_getpendingworkitems'; - $this->get_pending_offers_uri = $this->work_server_uri.'admin_getpendingoffers'; - $this->get_items_uri = $this->work_server_uri.'admin_getitems'; - $this->checkSiteRegistration(); - } - - // checkSiteRegistration - obtains site id and key from local database. - // If not found, it tries to register with remote work server. - function checkSiteRegistration() { - global $i18n; - global $user; - $mdb2 = getConnection(); - - // Obtain site id. - $sql = "select param_value as id from tt_site_config where param_name = 'worksite_id'"; - $res = $mdb2->query($sql); - $val = $res->fetchRow(); - if (!$val) { - // No site id found, need to register. - $fields = array('lang' => urlencode($user->lang), - 'name' => urlencode('time tracker'), - 'origin' => urlencode('time tracker source')); - - // Urlify the data for the POST. - foreach($fields as $key=>$value) { $fields_string .= $key.'='.$value.'&'; } - $fields_string = rtrim($fields_string, '&'); - - // Open connection. - $ch = curl_init(); - - // Set the url, number of POST vars, POST data. - curl_setopt($ch, CURLOPT_URL, $this->register_uri); - curl_setopt($ch, CURLOPT_POST, true); - curl_setopt($ch, CURLOPT_POSTFIELDS, $fields_string); - curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); - - // Execute a post request. - $result = curl_exec($ch); - - // Close connection. - curl_close($ch); - - if (!$result) { - $this->errors->add($i18n->get('error.remote_work')); - return false; - } - - $result_array = json_decode($result, true); - - // Check for errors. - $call_status = $result_array['call_status']; - if (!$call_status) { - $this->errors->add($i18n->get('error.remote_work')); - return false; - } - if ($call_status['code'] != TT_CURL_SUCCESS) { - $this->errors->add($call_status['error']); - return false; - } - - $reg_status = $result_array['reg_status']; // Registration status. - if ($reg_status['site_id'] && $reg_status['site_key']) { - $this->site_id = $reg_status['site_id']; - $this->site_key = $reg_status['site_key']; - - // Registration successful. Store id and key locally for future use. - $sql = "insert into tt_site_config values('worksite_id', $this->site_id, now(), null)"; - $mdb2->exec($sql); - $sql = "insert into tt_site_config values('worksite_key', ".$mdb2->quote($this->site_key).", now(), null)"; - $mdb2->exec($sql); - } else { - $this->errors->add($i18n->get('error.remote_work')); - } - } else { - // Site id found. - $this->site_id = $val['id']; - - // Obtain site key. - $sql = "select param_value as site_key from tt_site_config where param_name = 'worksite_key'"; - $res = $mdb2->query($sql); - $val = $res->fetchRow(); - $this->site_key = $val['site_key']; - } - } - - // getPendingOffers - obtains a list of offers pending approval. - function getPendingOffers() { - global $i18n; - global $user; - $mdb2 = getConnection(); - - $curl_fields = array('lang' => urlencode($user->lang), - 'site_id' => urlencode($this->site_id), - 'site_key' => urlencode($this->site_key), - 'user_id' => urlencode($user->id) - ); - - // url-ify the data for the POST. - foreach($curl_fields as $key=>$value) { $fields_string .= $key.'='.$value.'&'; } - $fields_string = rtrim($fields_string, '&'); - - // Open connection. - $ch = curl_init(); - - // Set the url, number of POST vars, POST data. - curl_setopt($ch, CURLOPT_URL, $this->get_pending_offers_uri); - curl_setopt($ch, CURLOPT_POST, true); - curl_setopt($ch, CURLOPT_POSTFIELDS, $fields_string); - curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); - - // Execute a post request. - $result = curl_exec($ch); - - // Close connection. - curl_close($ch); - - if (!$result) { - $this->errors->add($i18n->get('error.remote_work')); - return false; - } - - $result_array = json_decode($result, true); - - // Check for errors. - $call_status = $result_array['call_status']; - if (!$call_status) { - $this->errors->add($i18n->get('error.remote_work')); - return false; - } - if ($call_status['code'] != TT_CURL_SUCCESS) { - $this->errors->add($call_status['error']); - return false; - } - - $pending_offers = $result_array['pending_offers']; - return $pending_offers; - } - - // getPendingWorkItems - obtains a list of work items pending approval. - function getPendingWorkItems() { - global $i18n; - global $user; - $mdb2 = getConnection(); - - $curl_fields = array('lang' => urlencode($user->lang), - 'site_id' => urlencode($this->site_id), - 'site_key' => urlencode($this->site_key), - 'user_id' => urlencode($user->id) - ); - - // url-ify the data for the POST. - foreach($curl_fields as $key=>$value) { $fields_string .= $key.'='.$value.'&'; } - $fields_string = rtrim($fields_string, '&'); - - // Open connection. - $ch = curl_init(); - - // Set the url, number of POST vars, POST data. - curl_setopt($ch, CURLOPT_URL, $this->get_pending_work_items_uri); - curl_setopt($ch, CURLOPT_POST, true); - curl_setopt($ch, CURLOPT_POSTFIELDS, $fields_string); - curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); - - // Execute a post request. - $result = curl_exec($ch); - - // Close connection. - curl_close($ch); - - if (!$result) { - $this->errors->add($i18n->get('error.remote_work')); - return false; - } - - $result_array = json_decode($result, true); - - // Check for errors. - $call_status = $result_array['call_status']; - if (!$call_status) { - $this->errors->add($i18n->get('error.remote_work')); - return false; - } - if ($call_status['code'] != TT_CURL_SUCCESS) { - $this->errors->add($call_status['error']); - return false; - } - - $pending_work = $result_array['pending_work']; - return $pending_work; - } - - // getWorkItem - gets work item details from remote work server. - function getWorkItem($work_id) { - global $i18n; - global $user; - $mdb2 = getConnection(); - - $curl_fields = array('lang' => urlencode($user->lang), - 'site_id' => urlencode($this->site_id), - 'site_key' => urlencode($this->site_key), - 'user_id' => urlencode($user->id), - 'work_id' => urlencode($work_id)); - - // url-ify the data for the POST. - foreach($curl_fields as $key=>$value) { $fields_string .= $key.'='.$value.'&'; } - $fields_string = rtrim($fields_string, '&'); - - // Open connection. - $ch = curl_init(); - - // Set the url, number of POST vars, POST data. - curl_setopt($ch, CURLOPT_URL, $this->get_work_item_uri); - curl_setopt($ch, CURLOPT_POST, true); - curl_setopt($ch, CURLOPT_POSTFIELDS, $fields_string); - curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); - - // Execute a post request. - $result = curl_exec($ch); - - // Close connection. - curl_close($ch); - - if (!$result) { - $this->errors->add($i18n->get('error.remote_work')); - return false; - } - - $result_array = json_decode($result, true); - - // Check for errors. - $call_status = $result_array['call_status']; - if (!$call_status) { - $this->errors->add($i18n->get('error.remote_work')); - return false; - } - if ($call_status['code'] != TT_CURL_SUCCESS) { - $this->errors->add($call_status['error']); - return false; - } - - $work_item = $result_array['work_item']; - return $work_item; - } - - // deleteWorkItem - deletes work item from remote work server. - function deleteWorkItem($work_id) { - global $i18n; - global $user; - $mdb2 = getConnection(); - - $curl_fields = array('lang' => urlencode($user->lang), - 'site_id' => urlencode($this->site_id), - 'site_key' => urlencode($this->site_key), - 'user_id' => urlencode($user->id), - 'work_id' => urlencode($work_id), - 'modified_ip' => urlencode($_SERVER['REMOTE_ADDR']), - 'modified_by' => urlencode($user->getUser()), - 'modified_by_name' => urlencode(base64_encode($user->getName())), - 'modified_by_email' => urlencode(base64_encode($user->getEmail()))); - - // url-ify the data for the POST. - foreach($curl_fields as $key=>$value) { $fields_string .= $key.'='.$value.'&'; } - $fields_string = rtrim($fields_string, '&'); - - // Open connection. - $ch = curl_init(); - - // Set the url, number of POST vars, POST data. - curl_setopt($ch, CURLOPT_URL, $this->delete_work_item_uri); - curl_setopt($ch, CURLOPT_POST, true); - curl_setopt($ch, CURLOPT_POSTFIELDS, $fields_string); - curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); - - // Execute a post request. - $result = curl_exec($ch); - - // Close connection. - curl_close($ch); - - if (!$result) { - $this->errors->add($i18n->get('error.remote_work')); - return false; - } - - $result_array = json_decode($result, true); - - // Check for errors. - $call_status = $result_array['call_status']; - if (!$call_status) { - $this->errors->add($i18n->get('error.remote_work')); - return false; - } - if ($call_status['code'] != TT_CURL_SUCCESS) { - $this->errors->add($call_status['error']); - return false; - } - - return true; - } - - // deleteOffer - deletes an offer from remote work server. - function deleteOffer($offer_id) { - global $i18n; - global $user; - $mdb2 = getConnection(); - - $curl_fields = array('lang' => urlencode($user->lang), - 'site_id' => urlencode($this->site_id), - 'site_key' => urlencode($this->site_key), - 'user_id' => urlencode($user->id), - 'offer_id' => urlencode($offer_id), - 'modified_ip' => urlencode($_SERVER['REMOTE_ADDR']), - 'modified_by' => urlencode($user->getUser()), - 'modified_by_name' => urlencode(base64_encode($user->getName())), - 'modified_by_email' => urlencode(base64_encode($user->getEmail()))); - - // url-ify the data for the POST. - foreach($curl_fields as $key=>$value) { $fields_string .= $key.'='.$value.'&'; } - $fields_string = rtrim($fields_string, '&'); - - // Open connection. - $ch = curl_init(); - - // Set the url, number of POST vars, POST data. - curl_setopt($ch, CURLOPT_URL, $this->delete_offer_uri); - curl_setopt($ch, CURLOPT_POST, true); - curl_setopt($ch, CURLOPT_POSTFIELDS, $fields_string); - curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); - - // Execute a post request. - $result = curl_exec($ch); - - // Close connection. - curl_close($ch); - - if (!$result) { - $this->errors->add($i18n->get('error.remote_work')); - return false; - } - - $result_array = json_decode($result, true); - - // Check for errors. - $call_status = $result_array['call_status']; - if (!$call_status) { - $this->errors->add($i18n->get('error.remote_work')); - return false; - } - if ($call_status['code'] != TT_CURL_SUCCESS) { - $this->errors->add($call_status['error']); - return false; - } - - return true; - } - - // getOffer - gets offer details from remote work server. - function getOffer($offer_id) { - global $i18n; - global $user; - $mdb2 = getConnection(); - - $curl_fields = array('lang' => urlencode($user->lang), - 'site_id' => urlencode($this->site_id), - 'site_key' => urlencode($this->site_key), - 'user_id' => urlencode($user->id), - 'offer_id' => urlencode($offer_id)); - - // url-ify the data for the POST. - foreach($curl_fields as $key=>$value) { $fields_string .= $key.'='.$value.'&'; } - $fields_string = rtrim($fields_string, '&'); - - // Open connection. - $ch = curl_init(); - - // Set the url, number of POST vars, POST data. - curl_setopt($ch, CURLOPT_URL, $this->get_offer_uri); - curl_setopt($ch, CURLOPT_POST, true); - curl_setopt($ch, CURLOPT_POSTFIELDS, $fields_string); - curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); - - // Execute a post request. - $result = curl_exec($ch); - - // Close connection. - curl_close($ch); - - if (!$result) { - $this->errors->add($i18n->get('error.remote_work')); - return false; - } - - $result_array = json_decode($result, true); - - // Check for errors. - $call_status = $result_array['call_status']; - if (!$call_status) { - $this->errors->add($i18n->get('error.remote_work')); - return false; - } - if ($call_status['code'] != TT_CURL_SUCCESS) { - $this->errors->add($call_status['error']); - return false; - } - - $offer = $result_array['offer']; - return $offer; - } - - // updateWorkItem - updates a work item in remote work server. - function updateWorkItem($fields) { - global $i18n; - global $user; - $mdb2 = getConnection(); - - $curl_fields = array('lang' => urlencode($user->lang), - 'site_id' => urlencode($this->site_id), - 'site_key' => urlencode($this->site_key), - 'user_id' => urlencode($user->id), - 'work_id' => urlencode($fields['work_id']), - 'subject' => urlencode(base64_encode($fields['subject'])), - 'descr_short' => urlencode(base64_encode($fields['descr_short'])), - 'descr_long' => urlencode(base64_encode($fields['descr_long'])), - 'currency' => urlencode($fields['currency']), - 'amount' => urlencode($fields['amount']), - 'moderator_comment' => urlencode(base64_encode($fields['moderator_comment'])), - 'modified_ip' => urlencode($_SERVER['REMOTE_ADDR']), - 'modified_by' => urlencode($user->getUser()), - 'modified_by_name' => urlencode(base64_encode($user->getName())), - 'modified_by_email' => urlencode(base64_encode($user->getEmail())) - ); - - // url-ify the data for the POST. - foreach($curl_fields as $key=>$value) { $fields_string .= $key.'='.$value.'&'; } - $fields_string = rtrim($fields_string, '&'); - - // Open connection. - $ch = curl_init(); - - // Set the url, number of POST vars, POST data. - curl_setopt($ch, CURLOPT_URL, $this->update_work_item_uri); - curl_setopt($ch, CURLOPT_POST, true); - curl_setopt($ch, CURLOPT_POSTFIELDS, $fields_string); - curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); - - // Execute a post request. - $result = curl_exec($ch); - - // Close connection. - curl_close($ch); - - if (!$result) { - $this->errors->add($i18n->get('error.remote_work')); - return false; - } - - $result_array = json_decode($result, true); - - // Check for errors. - $call_status = $result_array['call_status']; - if (!$call_status) { - $this->errors->add($i18n->get('error.remote_work')); - return false; - } - if ($call_status['code'] != TT_CURL_SUCCESS) { - $this->errors->add($call_status['error']); - return false; - } - - return true; - } - - // approveWorkItem - approves work item in remote work server. - function approveWorkItem($fields) { - global $i18n; - global $user; - $mdb2 = getConnection(); - - $curl_fields = array('lang' => urlencode($user->lang), - 'site_id' => urlencode($this->site_id), - 'site_key' => urlencode($this->site_key), - 'user_id' => urlencode($user->id), - 'work_id' => urlencode($fields['work_id']), - 'subject' => urlencode(base64_encode($fields['subject'])), - 'descr_short' => urlencode(base64_encode($fields['descr_short'])), - 'descr_long' => urlencode(base64_encode($fields['descr_long'])), - 'currency' => urlencode($fields['currency']), - 'amount' => urlencode($fields['amount']), - 'moderator_comment' => urlencode(base64_encode($fields['moderator_comment'])), - 'modified_ip' => urlencode($_SERVER['REMOTE_ADDR']), - 'modified_by' => urlencode($user->getUser()), - 'modified_by_name' => urlencode(base64_encode($user->getName())), - 'modified_by_email' => urlencode(base64_encode($user->getEmail()))); - - // url-ify the data for the POST. - foreach($curl_fields as $key=>$value) { $fields_string .= $key.'='.$value.'&'; } - $fields_string = rtrim($fields_string, '&'); - - // Open connection. - $ch = curl_init(); - - // Set the url, number of POST vars, POST data. - curl_setopt($ch, CURLOPT_URL, $this->approve_work_item_uri); - curl_setopt($ch, CURLOPT_POST, true); - curl_setopt($ch, CURLOPT_POSTFIELDS, $fields_string); - curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); - - // Execute a post request. - $result = curl_exec($ch); - - // Close connection. - curl_close($ch); - - if (!$result) { - $this->errors->add($i18n->get('error.remote_work')); - return false; - } - - $result_array = json_decode($result, true); - - // Check for errors. - $call_status = $result_array['call_status']; - if (!$call_status) { - $this->errors->add($i18n->get('error.remote_work')); - return false; - } - if ($call_status['code'] != TT_CURL_SUCCESS) { - $this->errors->add($call_status['error']); - return false; - } - - return true; - } - - // disapproveWorkItem - disapproves work item in remote work server. - function disapproveWorkItem($fields) { - global $i18n; - global $user; - $mdb2 = getConnection(); - - $curl_fields = array('lang' => urlencode($user->lang), - 'site_id' => urlencode($this->site_id), - 'site_key' => urlencode($this->site_key), - 'user_id' => urlencode($user->id), - 'work_id' => urlencode($fields['work_id']), - 'subject' => urlencode(base64_encode($fields['subject'])), - 'descr_short' => urlencode(base64_encode($fields['descr_short'])), - 'descr_long' => urlencode(base64_encode($fields['descr_long'])), - 'currency' => urlencode($fields['currency']), - 'amount' => urlencode($fields['amount']), - 'moderator_comment' => urlencode(base64_encode($fields['moderator_comment'])), - 'modified_ip' => urlencode($_SERVER['REMOTE_ADDR']), - 'modified_by' => urlencode($user->getUser()), - 'modified_by_name' => urlencode(base64_encode($user->getName())), - 'modified_by_email' => urlencode(base64_encode($user->getEmail()))); - - // url-ify the data for the POST. - foreach($curl_fields as $key=>$value) { $fields_string .= $key.'='.$value.'&'; } - $fields_string = rtrim($fields_string, '&'); - // Open connection. - $ch = curl_init(); - - // Set the url, number of POST vars, POST data. - curl_setopt($ch, CURLOPT_URL, $this->disapprove_work_item_uri); - curl_setopt($ch, CURLOPT_POST, true); - curl_setopt($ch, CURLOPT_POSTFIELDS, $fields_string); - curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); - - // Execute a post request. - $result = curl_exec($ch); - - // Close connection. - curl_close($ch); - - if (!$result) { - $this->errors->add($i18n->get('error.remote_work')); - return false; - } - - $result_array = json_decode($result, true); - - // Check for errors. - $call_status = $result_array['call_status']; - if (!$call_status) { - $this->errors->add($i18n->get('error.remote_work')); - return false; - } - if ($call_status['code'] != TT_CURL_SUCCESS) { - $this->errors->add($call_status['error']); - return false; - } - - return true; - } - - // disapproveWorkItemOnOffer - disapproves work item posted on offer in remote work server. - function disapproveWorkItemOnOffer($fields) { - global $i18n; - global $user; - $mdb2 = getConnection(); - - $curl_fields = array('lang' => urlencode($user->lang), - 'site_id' => urlencode($this->site_id), - 'site_key' => urlencode($this->site_key), - 'user_id' => urlencode($user->id), - 'work_id' => urlencode($fields['work_id']), - 'descr_short' => urlencode(base64_encode($fields['descr_short'])), - 'descr_long' => urlencode(base64_encode($fields['descr_long'])), - 'moderator_comment' => urlencode(base64_encode($fields['moderator_comment'])), - 'modified_ip' => urlencode($_SERVER['REMOTE_ADDR']), - 'modified_by' => urlencode($user->getUser()), - 'modified_by_name' => urlencode(base64_encode($user->getName())), - 'modified_by_email' => urlencode(base64_encode($user->getEmail()))); - - // url-ify the data for the POST. - foreach($curl_fields as $key=>$value) { $fields_string .= $key.'='.$value.'&'; } - $fields_string = rtrim($fields_string, '&'); - // Open connection. - $ch = curl_init(); - - // Set the url, number of POST vars, POST data. - curl_setopt($ch, CURLOPT_URL, $this->disapprove_work_item_on_offer_uri); - curl_setopt($ch, CURLOPT_POST, true); - curl_setopt($ch, CURLOPT_POSTFIELDS, $fields_string); - curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); - - // Execute a post request. - $result = curl_exec($ch); - - // Close connection. - curl_close($ch); - - if (!$result) { - $this->errors->add($i18n->get('error.remote_work')); - return false; - } - - $result_array = json_decode($result, true); - - // Check for errors. - $call_status = $result_array['call_status']; - if (!$call_status) { - $this->errors->add($i18n->get('error.remote_work')); - return false; - } - if ($call_status['code'] != TT_CURL_SUCCESS) { - $this->errors->add($call_status['error']); - return false; - } - - return true; - } - - // approveWorkItemOnOffer - approves work item posted on offer in remote work server. - function approveWorkItemOnOffer($fields) { - global $i18n; - global $user; - $mdb2 = getConnection(); - - $curl_fields = array('lang' => urlencode($user->lang), - 'site_id' => urlencode($this->site_id), - 'site_key' => urlencode($this->site_key), - 'user_id' => urlencode($user->id), - 'work_id' => urlencode($fields['work_id']), - 'descr_short' => urlencode(base64_encode($fields['descr_short'])), - 'descr_long' => urlencode(base64_encode($fields['descr_long'])), - 'moderator_comment' => urlencode(base64_encode($fields['moderator_comment'])), - 'modified_ip' => urlencode($_SERVER['REMOTE_ADDR']), - 'modified_by' => urlencode($user->getUser()), - 'modified_by_name' => urlencode(base64_encode($user->getName())), - 'modified_by_email' => urlencode(base64_encode($user->getEmail()))); - - // url-ify the data for the POST. - foreach($curl_fields as $key=>$value) { $fields_string .= $key.'='.$value.'&'; } - $fields_string = rtrim($fields_string, '&'); - // Open connection. - $ch = curl_init(); - - // Set the url, number of POST vars, POST data. - curl_setopt($ch, CURLOPT_URL, $this->approve_work_item_on_offer_uri); - curl_setopt($ch, CURLOPT_POST, true); - curl_setopt($ch, CURLOPT_POSTFIELDS, $fields_string); - curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); - - // Execute a post request. - $result = curl_exec($ch); - - // Close connection. - curl_close($ch); - - if (!$result) { - $this->errors->add($i18n->get('error.remote_work')); - return false; - } - - $result_array = json_decode($result, true); - - // Check for errors. - $call_status = $result_array['call_status']; - if (!$call_status) { - $this->errors->add($i18n->get('error.remote_work')); - return false; - } - if ($call_status['code'] != TT_CURL_SUCCESS) { - $this->errors->add($call_status['error']); - return false; - } - - return true; - } - - // updateOffer - updates an offer in remote work server. - function updateOffer($fields) { - global $i18n; - global $user; - $mdb2 = getConnection(); - - $curl_fields = array('lang' => urlencode($user->lang), - 'site_id' => urlencode($this->site_id), - 'site_key' => urlencode($this->site_key), - 'user_id' => urlencode($user->id), - 'offer_id' => urlencode($fields['offer_id']), - 'subject' => urlencode(base64_encode($fields['subject'])), - 'descr_short' => urlencode(base64_encode($fields['descr_short'])), - 'descr_long' => urlencode(base64_encode($fields['descr_long'])), - 'currency' => urlencode($fields['currency']), - 'amount' => urlencode($fields['amount']), - 'payment_info' => urlencode(base64_encode($fields['payment_info'])), - 'moderator_comment' => urlencode(base64_encode($fields['moderator_comment'])), - 'modified_ip' => urlencode($_SERVER['REMOTE_ADDR']), - 'modified_by' => urlencode($user->getUser()), - 'modified_by_name' => urlencode(base64_encode($user->getName())), - 'modified_by_email' => urlencode(base64_encode($user->getEmail())) - ); - - // url-ify the data for the POST. - foreach($curl_fields as $key=>$value) { $fields_string .= $key.'='.$value.'&'; } - $fields_string = rtrim($fields_string, '&'); - - // Open connection. - $ch = curl_init(); - - // Set the url, number of POST vars, POST data. - curl_setopt($ch, CURLOPT_URL, $this->update_offer_uri); - curl_setopt($ch, CURLOPT_POST, true); - curl_setopt($ch, CURLOPT_POSTFIELDS, $fields_string); - curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); - - // Execute a post request. - $result = curl_exec($ch); - - // Close connection. - curl_close($ch); - - if (!$result) { - $this->errors->add($i18n->get('error.remote_work')); - return false; - } - - $result_array = json_decode($result, true); - - // Check for errors. - $call_status = $result_array['call_status']; - if (!$call_status) { - $this->errors->add($i18n->get('error.remote_work')); - return false; - } - if ($call_status['code'] != TT_CURL_SUCCESS) { - $this->errors->add($call_status['error']); - return false; - } - - return true; - } - - // approveOffer - approves offer in remote work server. - function approveOffer($fields) { - global $i18n; - global $user; - $mdb2 = getConnection(); - - $curl_fields = array('lang' => urlencode($user->lang), - 'site_id' => urlencode($this->site_id), - 'site_key' => urlencode($this->site_key), - 'user_id' => urlencode($user->id), - 'offer_id' => urlencode($fields['offer_id']), - 'subject' => urlencode(base64_encode($fields['subject'])), - 'descr_short' => urlencode(base64_encode($fields['descr_short'])), - 'descr_long' => urlencode(base64_encode($fields['descr_long'])), - 'currency' => urlencode($fields['currency']), - 'amount' => urlencode($fields['amount']), - 'moderator_comment' => urlencode(base64_encode($fields['moderator_comment'])), - 'modified_ip' => urlencode($_SERVER['REMOTE_ADDR']), - 'modified_by' => urlencode($user->getUser()), - 'modified_by_name' => urlencode(base64_encode($user->getName())), - 'modified_by_email' => urlencode(base64_encode($user->getEmail()))); - - // url-ify the data for the POST. - foreach($curl_fields as $key=>$value) { $fields_string .= $key.'='.$value.'&'; } - $fields_string = rtrim($fields_string, '&'); - - // Open connection. - $ch = curl_init(); - - // Set the url, number of POST vars, POST data. - curl_setopt($ch, CURLOPT_URL, $this->approve_offer_uri); - curl_setopt($ch, CURLOPT_POST, true); - curl_setopt($ch, CURLOPT_POSTFIELDS, $fields_string); - curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); - - // Execute a post request. - $result = curl_exec($ch); - - // Close connection. - curl_close($ch); - - if (!$result) { - $this->errors->add($i18n->get('error.remote_work')); - return false; - } - - $result_array = json_decode($result, true); - - // Check for errors. - $call_status = $result_array['call_status']; - if (!$call_status) { - $this->errors->add($i18n->get('error.remote_work')); - return false; - } - if ($call_status['code'] != TT_CURL_SUCCESS) { - $this->errors->add($call_status['error']); - return false; - } - - return true; - } - - - // disapproveOffer - disapproves offer in remote work server. - function disapproveOffer($fields) { - global $i18n; - global $user; - $mdb2 = getConnection(); - - $curl_fields = array('lang' => urlencode($user->lang), - 'site_id' => urlencode($this->site_id), - 'site_key' => urlencode($this->site_key), - 'user_id' => urlencode($user->id), - 'offer_id' => urlencode($fields['offer_id']), - 'subject' => urlencode(base64_encode($fields['subject'])), - 'descr_short' => urlencode(base64_encode($fields['descr_short'])), - 'descr_long' => urlencode(base64_encode($fields['descr_long'])), - 'currency' => urlencode($fields['currency']), - 'amount' => urlencode($fields['amount']), - 'moderator_comment' => urlencode(base64_encode($fields['moderator_comment'])), - 'modified_ip' => urlencode($_SERVER['REMOTE_ADDR']), - 'modified_by' => urlencode($user->getUser()), - 'modified_by_name' => urlencode(base64_encode($user->getName())), - 'modified_by_email' => urlencode(base64_encode($user->getEmail()))); - - // url-ify the data for the POST. - foreach($curl_fields as $key=>$value) { $fields_string .= $key.'='.$value.'&'; } - $fields_string = rtrim($fields_string, '&'); - - // Open connection. - $ch = curl_init(); - - // Set the url, number of POST vars, POST data. - curl_setopt($ch, CURLOPT_URL, $this->disapprove_offer_uri); - curl_setopt($ch, CURLOPT_POST, true); - curl_setopt($ch, CURLOPT_POSTFIELDS, $fields_string); - curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); - - // Execute a post request. - $result = curl_exec($ch); - - // Close connection. - curl_close($ch); - - if (!$result) { - $this->errors->add($i18n->get('error.remote_work')); - return false; - } - - $result_array = json_decode($result, true); - - // Check for errors. - $call_status = $result_array['call_status']; - if (!$call_status) { - $this->errors->add($i18n->get('error.remote_work')); - return false; - } - if ($call_status['code'] != TT_CURL_SUCCESS) { - $this->errors->add($call_status['error']); - return false; - } - - return true; - } - - // getItems - obtains a list of all items relevant to admin in one API call to Remote Work Server. - function getItems() { - global $i18n; - global $user; - $mdb2 = getConnection(); - - $group_id = $user->getGroup(); - $org_id = $user->org_id; - - $curl_fields = array('lang' => urlencode($user->lang), - 'site_id' => urlencode($this->site_id), - 'site_key' => urlencode($this->site_key), - 'user_id' => urlencode($user->id) - ); - - // url-ify the data for the POST. - foreach($curl_fields as $key=>$value) { $fields_string .= $key.'='.$value.'&'; } - $fields_string = rtrim($fields_string, '&'); - - // Open connection. - $ch = curl_init(); - - // Set the url, number of POST vars, POST data. - curl_setopt($ch, CURLOPT_URL, $this->get_items_uri); - curl_setopt($ch, CURLOPT_POST, true); - curl_setopt($ch, CURLOPT_POSTFIELDS, $fields_string); - curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); - - // Execute a post request. - $result = curl_exec($ch); - - // Close connection. - curl_close($ch); - - if (!$result) { - $this->errors->add($i18n->get('error.remote_work')); - return false; - } - - $result_array = json_decode($result, true); - - // Check for errors. - $call_status = $result_array['call_status']; - if (!$call_status) { - $this->errors->add($i18n->get('error.remote_work')); - return false; - } - if ($call_status['code'] != TT_CURL_SUCCESS) { - $this->errors->add($call_status['error']); - return false; - } - - unset($result_array['call_status']); // Remove call_status element. - return $result_array; - } -} diff --git a/WEB-INF/lib/ttBehalfUser.class.php b/WEB-INF/lib/ttBehalfUser.class.php index 408602a10..e85770c1b 100644 --- a/WEB-INF/lib/ttBehalfUser.class.php +++ b/WEB-INF/lib/ttBehalfUser.class.php @@ -23,7 +23,7 @@ // | // +----------------------------------------------------------------------+ // | Contributors: -// | https://www.anuko.com/time_tracker/credits.htm +// | https://www.anuko.com/time-tracker/credits.htm // +----------------------------------------------------------------------+ import('ttConfigHelper'); diff --git a/WEB-INF/lib/ttChartHelper.class.php b/WEB-INF/lib/ttChartHelper.class.php index 60c9897cb..6550f5fa2 100644 --- a/WEB-INF/lib/ttChartHelper.class.php +++ b/WEB-INF/lib/ttChartHelper.class.php @@ -1,33 +1,11 @@ getGroup(); + $org_id = $user->org_id; + $period = null; switch ($interval_type) { case INTERVAL_THIS_DAY: - $period = new Period(INTERVAL_THIS_DAY, new DateAndTime(DB_DATEFORMAT, $selected_date)); + $period = new ttPeriod(new ttDate($selected_date), INTERVAL_THIS_DAY); break; case INTERVAL_THIS_WEEK: - $period = new Period(INTERVAL_THIS_WEEK, new DateAndTime(DB_DATEFORMAT, $selected_date)); + $period = new ttPeriod(new ttDate($selected_date), INTERVAL_THIS_WEEK); break; case INTERVAL_THIS_MONTH: - $period = new Period(INTERVAL_THIS_MONTH, new DateAndTime(DB_DATEFORMAT, $selected_date)); + $period = new ttPeriod(new ttDate($selected_date), INTERVAL_THIS_MONTH); + break; + + case INTERVAL_PREVIOUS_MONTH: + $period = new ttPeriod(new ttDate($selected_date), INTERVAL_PREVIOUS_MONTH); break; case INTERVAL_THIS_YEAR: - $period = new Period(INTERVAL_THIS_YEAR, new DateAndTime(DB_DATEFORMAT, $selected_date)); + $period = new ttPeriod(new ttDate($selected_date), INTERVAL_THIS_YEAR); break; } $result = array(); $mdb2 = getConnection(); + $userIdPart = ''; + if ($user_id > 0) { + // -1 here means "all users in group" both active and inactive. + // Therefore, we will not be using user id. + $userIdPart = "and l.user_id = $user_id"; + } + $q_period = ''; if ($period != null) { - $q_period = " and date >= '".$period->getStartDate(DB_DATEFORMAT)."' and date <= '".$period->getEndDate(DB_DATEFORMAT)."'"; + $q_period = "and date >= '".$period->getStartDate(DB_DATEFORMAT)."' and date <= '".$period->getEndDate(DB_DATEFORMAT)."'"; + } + if (CHART_PROJECTS == $chart_type) { + // Data for projects. + $sql = "select p.name as name, sum(time_to_sec(l.duration)) as time from tt_log l + left join tt_projects p on (p.id = l.project_id) + where l.status = 1 $userIdPart and l.group_id = $group_id and l.org_id = $org_id $q_period group by l.project_id"; + } elseif (CHART_TASKS == $chart_type) { + // Data for tasks. + $sql = "select t.name as name, sum(time_to_sec(l.duration)) as time from tt_log l + left join tt_tasks t on (t.id = l.task_id) + where l.status = 1 $userIdPart and l.group_id = $group_id and l.org_id = $org_id $q_period group by l.task_id"; + } elseif (CHART_CLIENTS == $chart_type) { + // Data for clients. + $sql = "select c.name as name, sum(time_to_sec(l.duration)) as time from tt_log l + left join tt_clients c on (c.id = l.client_id) + where l.status = 1 $userIdPart and l.group_id = $group_id and l.org_id = $org_id $q_period group by l.client_id"; + } + + $res = $mdb2->query($sql); + if (!is_a($res, 'PEAR_Error')) { + while ($val = $res->fetchRow()) { + if ($val['time'] > 0) // Only positive totals make sense in pie charts. Skip negatives entirely. + $result[] = array('name'=>$val['name'],'time'=>$val['time']); // name - entity name, time - total for entity in seconds. + } + } + + // Get total time. We'll need it to calculate percentages (for labels to the right of diagram). + $total = 0; + foreach ($result as $one_val) { + $total += $one_val['time']; + } + // Add a string representation of time + percentage to names. Example: "Time Tracker (1:15 - 6%)". + foreach ($result as &$one_val) { + $percent = round(100*$one_val['time']/$total).'%'; + $one_val['name'] .= ' ('.ttTimeHelper::minutesToDuration($one_val['time'] / 60).' - '.$percent.')'; + } + + // Note: the remaining code here is needed to display labels on the side of the diagram. + // We print labels ourselves (not using libchart.php) because quality of libchart labels is not good. + + // Note: Optimize this sorting and reversing. + $result = mu_sort($result, 'time'); + $result = array_reverse($result); // This is to assign correct colors to labels. + + // Add color to array items. This is used in labels on the side of a chart. + $colors = array( + array(2, 78, 0), + array(148, 170, 36), + array(233, 191, 49), + array(240, 127, 41), + array(243, 63, 34), + array(190, 71, 47), + array(135, 81, 60), + array(128, 78, 162), + array(121, 75, 255), + array(142, 165, 250), + array(162, 254, 239), + array(137, 240, 166), + array(104, 221, 71), + array(98, 174, 35), + array(93, 129, 1) + ); + for ($i = 0; $i < count($result); $i++) { + $color = $colors[$i%count($colors)]; + $result[$i]['color_html'] = sprintf('#%02x%02x%02x', $color[0], $color[1], $color[2]); } + + return $result; + } + + + // getTotals - returns total times by project, task, or client for a selected fav report. + static function getTotalsForFavReport($fav_report_id, $chart_type) { + + global $user; + + // Use custom fields plugin if it is enabled. + if ($user->isPluginEnabled('cf')) { + require_once(APP_DIR.'/plugins/CustomFields.class.php'); + $custom_fields = new CustomFields(); + } + + $result = array(); + $mdb2 = getConnection(); + + // Get favorite report details. + $options = ttFavReportHelper::getReportOptions($fav_report_id); + if (!$options) + return $result; // Return empty array if something went wrong. + + $options = ttFavReportHelper::adjustOptions($options); + + // Obtain the where clause for fav report. + $where = ttReportHelper::getWhere($options); + + // Build sql query according to selected chart type. if (CHART_PROJECTS == $chart_type) { // Data for projects. $sql = "select p.name as name, sum(time_to_sec(l.duration)) as time from tt_log l left join tt_projects p on (p.id = l.project_id) - where l.status = 1 and l.user_id = $user_id $q_period group by l.project_id"; + $where group by l.project_id"; } elseif (CHART_TASKS == $chart_type) { // Data for tasks. $sql = "select t.name as name, sum(time_to_sec(l.duration)) as time from tt_log l left join tt_tasks t on (t.id = l.task_id) - where l.status = 1 and l.user_id = $user_id $q_period group by l.task_id"; + $where group by l.task_id"; } elseif (CHART_CLIENTS == $chart_type) { // Data for clients. $sql = "select c.name as name, sum(time_to_sec(l.duration)) as time from tt_log l left join tt_clients c on (c.id = l.client_id) - where l.status = 1 and l.user_id = $user_id $q_period group by l.client_id"; + $where group by l.client_id"; } $res = $mdb2->query($sql); @@ -135,6 +224,7 @@ static function getTotals($user_id, $chart_type, $selected_date, $interval_type) return $result; } + // adjustType - adjust chart type to something that is available for a group. static function adjustType($requested_type) { global $user; diff --git a/WEB-INF/lib/ttClientHelper.class.php b/WEB-INF/lib/ttClientHelper.class.php index 3c5f0d646..5c9dff349 100644 --- a/WEB-INF/lib/ttClientHelper.class.php +++ b/WEB-INF/lib/ttClientHelper.class.php @@ -150,7 +150,14 @@ static function delete($id, $delete_client_entries) { $sql = "update tt_clients set status = null". " where id = $id and group_id = $group_id and org_id = $org_id"; $affected = $mdb2->exec($sql); - return (!is_a($affected, 'PEAR_Error')); + if (is_a($affected, 'PEAR_Error')) + return false; + + // Update entities_modified, too. + if (!ttGroupHelper::updateEntitiesModified()) + return false; + + return true; } // The insert function inserts a new client record into the clients table. @@ -190,6 +197,10 @@ static function insert($fields) return false; } + // Update entities_modified, too. + if (!ttGroupHelper::updateEntitiesModified()) + return false; + return $last_id; } @@ -232,7 +243,14 @@ static function update($fields) ", tax = $tax, projects = ".$mdb2->quote($comma_separated).", status = $status". " where id = $id and group_id = $group_id and org_id = $org_id"; $affected = $mdb2->exec($sql); - return (!is_a($affected, 'PEAR_Error')); + if (is_a($affected, 'PEAR_Error')) + return false; + + // Update entities_modified, too. + if (!ttGroupHelper::updateEntitiesModified()) + return false; + + return true; } // The fillBean function fills the ActionForm object with client data. @@ -340,4 +358,49 @@ static function deleteProjectFromClient($project_id, $client_id) { $affected = $mdb2->exec($sql); return (!is_a($affected, 'PEAR_Error')); } + + // unassignProjectFromAllClients - removes a project reference from all clients in tt_clients table + // and also from tt_client_project_binds. + static function unassignProjectFromAllClients($project_id) { + global $user; + $mdb2 = getConnection(); + + $group_id = $user->getGroup(); + $org_id = $user->org_id; + + $project_id = (int) $project_id; // Cast for sql injection protection, just in case. + + // Start with cleaning up tt_client_project_binds table. + $sql = "delete from tt_client_project_binds". + " where project_id = $project_id and group_id = $group_id and org_id = $org_id"; + $affected = $mdb2->exec($sql); + if (is_a($affected, 'PEAR_Error')) + return false; + + // Continue with tt_clients table. + $sql = "select id, projects from tt_clients where projects like '%$project_id%' and group_id = $group_id and org_id = $org_id"; + $res = $mdb2->query($sql); + while ($val = $res->fetchRow()) { + + $client_id = (int) $val['id']; + $projectListed = false; + + $projects = explode(',', $val['projects']); + if (($key = array_search($project_id, $projects)) !== false) { + unset($projects[$key]); + $projectListed = true; + } + if (!$projectListed) + continue; // Project not listed, continue iterating. + + // If we are here, project is listed and we need to remove it. + $comma_separated = implode(',', $projects); + $sql = "update tt_clients set projects = ".$mdb2->quote($comma_separated). + " where id = $client_id and group_id = $group_id and org_id = $org_id"; + $affected = $mdb2->exec($sql); + if (is_a($affected, 'PEAR_Error')) + return false; + } + return true; + } } diff --git a/WEB-INF/lib/ttConfigHelper.class.php b/WEB-INF/lib/ttConfigHelper.class.php index 33e94e513..95bc1e0f1 100644 --- a/WEB-INF/lib/ttConfigHelper.class.php +++ b/WEB-INF/lib/ttConfigHelper.class.php @@ -1,30 +1,6 @@ config = trim($config, ' ,'); + if (!is_null($config)) + $this->config = trim($config, ' ,'); if ($this->config) $this->config_array = explode(',', $this->config); } @@ -120,6 +97,6 @@ function setIntValue($name, $value) { // The getConfig returns the config string. function getConfig() { - return trim($this->config, ' ,'); + return (is_null($this->config) ? null : trim($this->config, ' ,')); } } diff --git a/WEB-INF/lib/ttCronJobHelper.class.php b/WEB-INF/lib/ttCronJobHelper.class.php index 8c44756c7..153938951 100644 --- a/WEB-INF/lib/ttCronJobHelper.class.php +++ b/WEB-INF/lib/ttCronJobHelper.class.php @@ -23,7 +23,7 @@ // | // +----------------------------------------------------------------------+ // | Contributors: -// | https://www.anuko.com/time_tracker/credits.htm +// | https://www.anuko.com/time-tracker/credits.htm // +----------------------------------------------------------------------+ import('ttRoleHelper'); diff --git a/WEB-INF/lib/ttDate.class.php b/WEB-INF/lib/ttDate.class.php new file mode 100644 index 000000000..25dc4874a --- /dev/null +++ b/WEB-INF/lib/ttDate.class.php @@ -0,0 +1,196 @@ +dateInDbDateFormat = $dateInDbDateFormat; + } else { + $today = date_create(); + $this->dateInDbDateFormat = date_format($today, 'Y-m-d'); + } + + $this->unixTimestamp = strtotime($this->dateInDbDateFormat); + $this->year = date('Y', $this->unixTimestamp); + $this->month = date('m', $this->unixTimestamp); + $this->day = date('d', $this->unixTimestamp); + $this->dayOfWeek = date('w', $this->unixTimestamp); + } + + + // Returns unix timestamp. + function getTimestamp() { + return $this->unixTimestamp; + } + + + // Resets the object properties from a passed in unix timestamp. + function setFromUnixTimestamp($unixTimestamp) { + $this->unixTimestamp = $unixTimestamp; + + $this->year = date('Y', $this->unixTimestamp); + $this->month = date('m', $this->unixTimestamp); + $this->day = date('d', $this->unixTimestamp); + $this->dayOfWeek = date('w', $this->unixTimestamp); + + $this->dateInDbDateFormat = $this->year.'-'.$this->month.'-'.$this->day; + } + + + // toString returns a date in specified format. + function toString($format = null) { + if ($format == null || $format == DB_DATEFORMAT) + return $this->dateInDbDateFormat; + else { + return $this->formatDate($format); + } + } + + + // formatDate returns a date string in specified format. + function formatDate($format) { + global $i18n; + + $formattedDate = $format; // Start with unmodified format string. + + // Replace all found elements with data. + $formattedDate = str_replace('%Y', $this->year, $formattedDate); + $formattedDate = str_replace('%m', $this->month, $formattedDate); + $formattedDate = str_replace('%d', $this->day, $formattedDate); + // Replace locale-dependent days of week. + $formattedDate = str_replace('%a', mb_substr($i18n->getWeekDayName($this->dayOfWeek), 0, 3, 'utf-8'), $formattedDate); + return $formattedDate; + } + + + function before(/*ttDate*/ $obj) { + if ($this->getTimestamp() < $obj->getTimestamp()) return true; + return false; + } + + + function after(/*ttDate*/ $obj) { + if ($this->getTimestamp() > $obj->getTimestamp()) return true; + return false; + } + + + function compare(/*ttDate*/ $obj) { + $ts1 = $this->getTimestamp(); + $ts2 = $obj->getTimestamp(); + if ($ts1 < $ts2) return -1; + if ($ts1 == $ts2) return 0; + if ($ts1 > $ts2) return 1; + } + + + // Getters. + function getYear() { return $this->year; } + function getMonth() { return $this->month; } + function getDay() { return $this->day; } + function getDayOfWeek() { return $this->dayOfWeek; } + + + // incrementDay increments our date by a number of days. + function incrementDay(/*int*/ $days = 1) { + $this->setFromUnixTimestamp(@mktime(0, 0, 0, $this->month, $this->day + $days, $this->year)); + } + + + // decrementDay decrements our date by a number of days. + function decrementDay(/*int*/ $days = 1) { + $this->setFromUnixTimestamp(@mktime(0, 0, 0, $this->month, $this->day - $days, $this->year)); + } + + + // A static function to obtain a date in DB_DATEFORMAT from a Unix timestamp. + static function dateFromUnixTimestamp($unixTimestamp = null) { + if ($unixTimestamp == null) { + $today = date_create(); + return date_format($today, 'Y-m-d'); + } + + $year = date('Y', $unixTimestamp); + $month = date('m', $unixTimestamp); + $day = date('d', $unixTimestamp); + $dateInDbFormat = $year.'-'.$month.'-'.$day; + return $dateInDbFormat; + } +} diff --git a/WEB-INF/lib/ttDebugTracer.class.php b/WEB-INF/lib/ttDebugTracer.class.php index 46469a908..33921e9dd 100644 --- a/WEB-INF/lib/ttDebugTracer.class.php +++ b/WEB-INF/lib/ttDebugTracer.class.php @@ -23,7 +23,7 @@ // | // +----------------------------------------------------------------------+ // | Contributors: -// | https://www.anuko.com/time_tracker/credits.htm +// | https://www.anuko.com/time-tracker/credits.htm // +----------------------------------------------------------------------+ import('ttConfigHelper'); diff --git a/WEB-INF/lib/ttFavReportHelper.class.php b/WEB-INF/lib/ttFavReportHelper.class.php index b4f917a4a..2b2470439 100644 --- a/WEB-INF/lib/ttFavReportHelper.class.php +++ b/WEB-INF/lib/ttFavReportHelper.class.php @@ -3,6 +3,7 @@ License: See license.txt */ import('ttGroupHelper'); +import('ttDate'); // Class ttFavReportHelper is used to help with favorite report related tasks. class ttFavReportHelper { @@ -96,8 +97,8 @@ static function insertReport($fields) { $org_id = $user->org_id; $sql = "insert into tt_fav_reports". - " (name, user_id, group_id, org_id, report_spec, client_id, project_id, task_id,". - " billable, approved, invoice, timesheet, paid_status, users, period, period_start,". + " (name, user_id, group_id, org_id, report_spec, client_id, task_id,". + " billable, approved, invoice, timesheet, paid_status, note_containing, users, period, period_start,". " period_end, show_client, show_invoice, show_paid, show_ip,". " show_project, show_timesheet, show_start, show_duration, show_cost,". " show_task, show_end, show_note, show_approved, show_work_units,". @@ -106,10 +107,10 @@ static function insertReport($fields) { $mdb2->quote($fields['name']).", $user_id, $group_id, $org_id, ". $mdb2->quote($fields['report_spec']).", ". $mdb2->quote($fields['client']).", ". - $mdb2->quote($fields['project']).", ".$mdb2->quote($fields['task']).", ". + $mdb2->quote($fields['task']).", ". $mdb2->quote($fields['billable']).", ".$mdb2->quote($fields['approved']).", ". $mdb2->quote($fields['invoice']).", ".$mdb2->quote($fields['timesheet']).", ". - $mdb2->quote($fields['paid_status']).", ". + $mdb2->quote($fields['paid_status']).", ".$mdb2->quote($fields['note_containing']).",". $mdb2->quote($fields['users']).", ".$mdb2->quote($fields['period']).", ". $mdb2->quote($fields['from']).", ".$mdb2->quote($fields['to']).", ". $fields['chclient'].", ".$fields['chinvoice'].", ".$fields['chpaid'].", ".$fields['chip'].", ". @@ -138,13 +139,14 @@ static function updateReport($fields) { "name = ".$mdb2->quote($fields['name']).", ". "report_spec = ".$mdb2->quote($fields['report_spec']).", ". "client_id = ".$mdb2->quote($fields['client']).", ". - "project_id = ".$mdb2->quote($fields['project']).", ". + //"project_id = ".$mdb2->quote($fields['project']).", ". "task_id = ".$mdb2->quote($fields['task']).", ". "billable = ".$mdb2->quote($fields['billable']).", ". "approved = ".$mdb2->quote($fields['approved']).", ". "invoice = ".$mdb2->quote($fields['invoice']).", ". "timesheet = ".$mdb2->quote($fields['timesheet']).", ". "paid_status = ".$mdb2->quote($fields['paid_status']).", ". + "note_containing = ".$mdb2->quote($fields['note_containing']).", ". "users = ".$mdb2->quote($fields['users']).", ". "period = ".$mdb2->quote($fields['period']).", ". "period_start = ".$mdb2->quote($fields['from']).", ". @@ -212,12 +214,12 @@ static function saveReport($bean) { } if ($bean->getAttribute('start_date')) { - $dt = new DateAndTime($user->getDateFormat(), $bean->getAttribute('start_date')); - $from = $dt->toString(DB_DATEFORMAT); + $dt = new ttDate($bean->getAttribute('start_date'), $user->getDateFormat()); + $from = $dt->toString(); } if ($bean->getAttribute('end_date')) { - $dt = new DateAndTime($user->getDateFormat(), $bean->getAttribute('end_date')); - $to = $dt->toString(DB_DATEFORMAT); + $dt = new ttDate($bean->getAttribute('end_date'), $user->getDateFormat()); + $to = $dt->toString(); } $fields = array( @@ -225,13 +227,14 @@ static function saveReport($bean) { 'report_spec'=>ttFavReportHelper::makeReportSpec($bean), 'client'=>$bean->getAttribute('client'), 'option'=>$bean->getAttribute('option'), - 'project'=>$bean->getAttribute('project'), + //'project'=>$bean->getAttribute('project'), 'task'=>$bean->getAttribute('task'), 'billable'=>$bean->getAttribute('include_records'), 'approved'=>$bean->getAttribute('approved'), 'paid_status'=>$bean->getAttribute('paid_status'), 'invoice'=>$bean->getAttribute('invoice'), 'timesheet'=>$bean->getAttribute('timesheet'), + 'note_containing'=>$bean->getAttribute('note_containing'), 'users'=>$users, 'period'=>$bean->getAttribute('period'), 'from'=>$from, @@ -304,7 +307,7 @@ static function loadReport(&$bean) { if ($val['report_spec']) { $report_spec = $val['report_spec']; // Time custom field settings. - if ($custom_fields && $custom_fields->timeFields) { + 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; @@ -315,7 +318,7 @@ static function loadReport(&$bean) { } } // User custom field settings. - if ($custom_fields && $custom_fields->userFields) { + 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; @@ -325,25 +328,44 @@ static function loadReport(&$bean) { $bean->setAttribute($checkbox_field_name, $checkbox_value); } } + // Project custom field settings. + if (isset($custom_fields) && $custom_fields->projectFields) { + foreach ($custom_fields->projectFields as $projectField) { + $field_name = 'project_field_'.$projectField['id']; + $checkbox_field_name = 'show_'.$field_name; + $field_value = ttFavReportHelper::getFieldSettingFromReportSpec($field_name, $report_spec); + $bean->setAttribute($field_name, $field_value); + $checkbox_value = ttFavReportHelper::getFieldSettingFromReportSpec($checkbox_field_name, $report_spec); + $bean->setAttribute($checkbox_field_name, $checkbox_value); + } + } + + // Project ids. + $project_ids = ttFavReportHelper::getFieldSettingFromReportSpec('project_ids', $report_spec); + if (isset($project_ids)) { + $projects = explode('&', $project_ids); + $bean->setAttribute('project', $projects); + } } $bean->setAttribute('client', $val['client_id']); - $bean->setAttribute('project', $val['project_id']); + // $bean->setAttribute('project', $val['project_id']); $bean->setAttribute('task', $val['task_id']); $bean->setAttribute('include_records', $val['billable']); $bean->setAttribute('approved', $val['approved']); $bean->setAttribute('invoice', $val['invoice']); $bean->setAttribute('paid_status', $val['paid_status']); $bean->setAttribute('timesheet', $val['timesheet']); + $bean->setAttribute('note_containing', $val['note_containing']); $bean->setAttribute('users_active', explode(',', $val['users'])); $bean->setAttribute('users_inactive', explode(',', $val['users'])); $bean->setAttribute('period', $val['period']); if ($val['period_start']) { - $dt = new DateAndTime(DB_DATEFORMAT, $val['period_start']); + $dt = new ttDate($val['period_start']); $bean->setAttribute('start_date', $dt->toString($user->getDateFormat())); } if ($val['period_end']) { - $dt = new DateAndTime(DB_DATEFORMAT, $val['period_end']); + $dt = new ttDate($val['period_end']); $bean->setAttribute('end_date', $dt->toString($user->getDateFormat())); } $bean->setAttribute('chclient', $val['show_client']); @@ -399,7 +421,7 @@ static function loadReport(&$bean) { 'chtotalsonly' => null, 'new_fav_report' => null)); // Time custom fields. - if ($custom_fields && $custom_fields->timeFields) { + 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; @@ -408,7 +430,7 @@ static function loadReport(&$bean) { } } // User custom fields. - if ($custom_fields && $custom_fields->userFields) { + 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; @@ -416,7 +438,7 @@ static function loadReport(&$bean) { $custom_field_attrs[$checkbox_field_name] = null; } } - if (is_array($custom_field_attrs)) + if (isset($custom_field_attrs)) $attrs = array_merge($attrs, $custom_field_attrs); $bean->setAttributes($attrs); } @@ -439,6 +461,47 @@ static function getReportOptions($id) { unset($options['id']); unset($options['status']); + // Cast to int some values similar to ttReportHelper::getReportOptions + // where we do it for extra protection against SQL injections. + // This step is redundant, though, as data validation is supposed to work. + // However, in case we missed something, casting to int reduces our risks. + $options['client_id'] = isset($options['client_id']) ? (int)$options['client_id'] : 0; + + // Obtain project ids from report_spec. + $project_ids = ttFavReportHelper::getFieldSettingFromReportSpec('project_ids', $options['report_spec']); + if (isset($project_ids)) { + $projects = explode('&', $project_ids); + $options['project_ids'] = join(',', $projects); + } + + $options['task_id'] = isset($options['task_id']) ? (int)$options['task_id'] : 0; + $options['billable'] = isset($options['billable']) ? (int)$options['billable'] : 0; + $options['invoice'] = isset($options['invoice']) ? (int)$options['invoice'] : 0; + $options['paid_status'] = isset($options['paid_status']) ? (int)$options['paid_status'] : 0; + $options['approved'] = isset($options['approved']) ? (int)$options['approved'] : 0; + $options['timesheet'] = isset($options['timesheet']) ? (int)$options['timesheet'] : 0; + $options['period'] = isset($options['period']) ? (int)$options['period'] : 0; + + $options['show_client'] = isset($options['show_client']) ? (int)$options['show_client'] : 0; + $options['show_invoice'] = isset($options['show_invoice']) ? (int)$options['show_invoice'] : 0; + $options['show_approved'] = isset($options['show_approved']) ? (int)$options['show_approved'] : 0; + $options['show_paid'] = isset($options['show_paid']) ? (int)$options['show_paid'] : 0; + $options['show_ip'] = isset($options['show_ip']) ? (int)$options['show_ip'] : 0; + $options['show_project'] = isset($options['show_project']) ? (int)$options['show_project'] : 0; + $options['show_start'] = isset($options['show_start']) ? (int)$options['show_start'] : 0; + $options['show_duration'] = isset($options['show_duration']) ? (int)$options['show_duration'] : 0; + $options['show_cost'] = isset($options['show_cost']) ? (int)$options['show_cost'] : 0; + $options['show_task'] = isset($options['show_task']) ? (int)$options['show_task'] : 0; + $options['show_end'] = isset($options['show_end']) ? (int)$options['show_end'] : 0; + $options['show_note'] = isset($options['show_note']) ? (int)$options['show_note'] : 0; + $options['show_work_units'] = isset($options['show_work_units']) ? (int)$options['show_work_units'] : 0; + $options['show_timesheet'] = isset($options['show_timesheet']) ? (int)$options['show_timesheet'] : 0; + $options['show_files'] = isset($options['show_files']) ? (int)$options['show_files'] : 0; + $options['show_totals_only'] = isset($options['show_totals_only']) ? (int)$options['show_totals_only'] : 0; + + // Note: We can't cast anything to int for custom fields because global $user object is not recycled + // yet for user id. See cron.php. Because of this we do it in adjustOptions instead. + // Note: special handling for NULL users field is done in cron.php // $options now is a subset of db fields from tt_fav_reports table. @@ -473,6 +536,7 @@ static function adjustOptions($options) { } } else { $users_to_adjust = explode(',', $options['users']); // Users to adjust. + // Adjust user list for a client. if ($user->isClient()) { $users = ttGroupHelper::getUsersForClient(); // Active and inactive users for clients. foreach ($users as $single_user) { @@ -485,7 +549,15 @@ static function adjustOptions($options) { } $options['users'] = implode(',', $adjusted_user_ids); } - // TODO: add checking the existing user list for potentially changed access rights for user. + // Reset user list for a role that no longer has view_reports or view_all_reports rights. + if (!($user->can('view_reports') || $user->can('view_all_reports'))) { + $options['users'] = null; + // Also remove grouping by user if we can't do it. + if (isset($options['group_by1']) && $options['group_by1'] == 'user') unset($options['group_by1']); + if (isset($options['group_by2']) && $options['group_by2'] == 'user') unset($options['group_by2']); + if (isset($options['group_by3']) && $options['group_by3'] == 'user') unset($options['group_by3']); + } + // TODO: improve checking the existing user list for potentially changed access rights for user. } if ($user->isPluginEnabled('ap') && $user->isClient() && !$user->can('view_client_unapproved')) @@ -501,9 +573,15 @@ static function adjustOptions($options) { $field_name = 'time_field_'.$timeField['id']; $checkbox_field_name = 'show_'.$field_name; $field_value = ttFavReportHelper::getFieldSettingFromReportSpec($field_name, $report_spec); - $options[$field_name] = $field_value; + if ($timeField['type'] == CustomFields::TYPE_DROPDOWN) { + // Cast to int for a dropdown for extra security. + $options[$field_name] = (int)$field_value; + } elseif ($timeField['type'] == CustomFields::TYPE_TEXT) { + // No need to cast to int for a text field. + $options[$field_name] = $field_value; + } $checkbox_value = ttFavReportHelper::getFieldSettingFromReportSpec($checkbox_field_name, $report_spec); - $options[$checkbox_field_name] = $checkbox_value; + $options[$checkbox_field_name] = (int)$checkbox_value; } } // User fields. @@ -512,12 +590,34 @@ static function adjustOptions($options) { $field_name = 'user_field_'.$userField['id']; $checkbox_field_name = 'show_'.$field_name; $field_value = ttFavReportHelper::getFieldSettingFromReportSpec($field_name, $report_spec); - $options[$field_name] = $field_value; + if ($userField['type'] == CustomFields::TYPE_DROPDOWN) { + // Cast to int for a dropdown for extra security. + $options[$field_name] = (int)$field_value; + } elseif ($userField['type'] == CustomFields::TYPE_TEXT) { + // No need to cast to int for a text field. + $options[$field_name] = $field_value; + } + $checkbox_value = ttFavReportHelper::getFieldSettingFromReportSpec($checkbox_field_name, $report_spec); + $options[$checkbox_field_name] = (int)$checkbox_value; + } + } + // Project fields. + if ($custom_fields && $custom_fields->projectFields) { + foreach ($custom_fields->projectFields as $projectField) { + $field_name = 'project_field_'.$projectField['id']; + $checkbox_field_name = 'show_'.$field_name; + $field_value = ttFavReportHelper::getFieldSettingFromReportSpec($field_name, $report_spec); + if ($projectField['type'] == CustomFields::TYPE_DROPDOWN) { + // Cast to int for a dropdown for extra security. + $options[$field_name] = (int)$field_value; + } elseif ($projectField['type'] == CustomFields::TYPE_TEXT) { + // No need to cast to int for a text field. + $options[$field_name] = $field_value; + } $checkbox_value = ttFavReportHelper::getFieldSettingFromReportSpec($checkbox_field_name, $report_spec); - $options[$checkbox_field_name] = $checkbox_value; + $options[$checkbox_field_name] = (int)$checkbox_value; } } - // TODO: add project fields here. } // Adjust period_start and period_end to user date format. @@ -543,7 +643,7 @@ static function makeReportSpec($bean) { } // Add time custom field settings. - if ($custom_fields && $custom_fields->timeFields) { + if (isset($custom_fields) && $custom_fields->timeFields) { foreach ($custom_fields->timeFields as $timeField) { $field_name = 'time_field_'.$timeField['id']; $field_value = str_replace(',',',',$bean->getAttribute($field_name)); @@ -555,7 +655,7 @@ static function makeReportSpec($bean) { } // Add user custom field settings. - if ($custom_fields && $custom_fields->userFields) { + if (isset($custom_fields) && $custom_fields->userFields) { foreach ($custom_fields->userFields as $userField) { $field_name = 'user_field_'.$userField['id']; $field_value = str_replace(',',',',$bean->getAttribute($field_name)); @@ -566,8 +666,30 @@ static function makeReportSpec($bean) { } } + // Add project custom field settings. + if (isset($custom_fields) && $custom_fields->projectFields) { + foreach ($custom_fields->projectFields as $projectField) { + $field_name = 'project_field_'.$projectField['id']; + $field_value = str_replace(',',',',$bean->getAttribute($field_name)); + $checkbox_field_name = 'show_'.$field_name; + $checkbox_field_value = (int) $bean->getAttribute($checkbox_field_name); + if ($field_value) $reportSpecArray[] = $field_name.':'.$field_value; + if ($checkbox_field_value) $reportSpecArray[] = $checkbox_field_name.':1'; + } + } + + // Add projects ids. Format: project_ids:25&756&567&1023. Use the & sign to join multiple project ids. + $selected_projects_in_bean = $bean->getAttribute('project'); + if (!is_array($selected_projects_in_bean)) { + $selectedProjects = $bean->getAttribute('project[]'); + } + if (is_array($selected_projects_in_bean) && !empty($selected_projects_in_bean[0])) { + $selectedProjectsSpec = join('&', $selected_projects_in_bean); + } + if (isset($selectedProjectsSpec)) $reportSpecArray[] = 'project_ids:'.$selectedProjectsSpec; + $reportSpec = null; - if (is_array($reportSpecArray)) + if (isset($reportSpecArray)) $reportSpec = implode(',', $reportSpecArray); return $reportSpec; } diff --git a/WEB-INF/lib/ttGroupExportHelper.class.php b/WEB-INF/lib/ttGroupExportHelper.class.php index a464f2e25..ecd4f80ac 100644 --- a/WEB-INF/lib/ttGroupExportHelper.class.php +++ b/WEB-INF/lib/ttGroupExportHelper.class.php @@ -137,7 +137,8 @@ function writeData() { $group_part .= " workday_minutes=\"".$group['workday_minutes']."\""; $group_part .= " custom_logo=\"".$group['custom_logo']."\""; $group_part .= " config=\"".$group['config']."\""; - $group_part .= " custom_css=\"".$group['custom_css']."\""; + $group_part .= " custom_css=\"".htmlspecialchars($group['custom_css'])."\""; + $group_part .= " custom_translation=\"".htmlspecialchars($group['custom_translation'])."\""; $group_part .= ">\n"; // Write group info. @@ -424,6 +425,7 @@ function writeData() { $custom_field_option_part = $this->indentation.' '."customFieldOptionMap[$option['id']]."\""; $custom_field_option_part .= " field_id=\"".$this->customFieldMap[$option['field_id']]."\""; $custom_field_option_part .= " value=\"".htmlspecialchars($option['value'])."\""; + $custom_field_option_part .= " status=\"".$option['status']."\""; $custom_field_option_part .= ">\n"; fwrite($this->file, $custom_field_option_part); } @@ -566,6 +568,7 @@ function writeData() { $fav_report_part .= " invoice=\"".$fav_report['invoice']."\""; $fav_report_part .= " timesheet=\"".$fav_report['timesheet']."\""; $fav_report_part .= " paid_status=\"".$fav_report['paid_status']."\""; + $fav_report_part .= " note_containing=\"".$fav_report['note_containing']."\""; $fav_report_part .= " users=\"".$user_list."\""; $fav_report_part .= " period=\"".$fav_report['period']."\""; $fav_report_part .= " period_start=\"".$fav_report['period_start']."\""; diff --git a/WEB-INF/lib/ttGroupHelper.class.php b/WEB-INF/lib/ttGroupHelper.class.php index 0dd3dd412..81d1af696 100644 --- a/WEB-INF/lib/ttGroupHelper.class.php +++ b/WEB-INF/lib/ttGroupHelper.class.php @@ -27,9 +27,13 @@ static function getGroupName($group_id) { static function getParentGroup($group_id) { global $user; + // Checking parameters for sanity is normally done in access check blocks on pages. + // This cast below is just in case we forgot to check $group_id to be an integer. + $groupId = (int) $group_id; // Protection against sql injection. + $mdb2 = getConnection(); - $sql = "select parent_id from tt_groups where id = $group_id and org_id = $user->org_id and status = 1"; + $sql = "select parent_id from tt_groups where id = $groupId and org_id = $user->org_id and status = 1"; $res = $mdb2->query($sql); if (!is_a($res, 'PEAR_Error')) { @@ -427,10 +431,9 @@ static function getActiveInvoices($sort_options = false) $res = $mdb2->query($sql); $result = array(); if (!is_a($res, 'PEAR_Error')) { - $dt = new DateAndTime(DB_DATEFORMAT); while ($val = $res->fetchRow()) { // Localize date. - $dt->parseVal($val['date']); + $dt = new ttDate($val['date']); $val['date'] = $dt->toString($user->getDateFormat()); if ($addPaidStatus) $val['paid'] = ttInvoiceHelper::isPaid($val['id']); @@ -675,7 +678,6 @@ static function getRecentInvoices($client_id) { $res = $mdb2->query($sql); $result = array(); if (!is_a($res, 'PEAR_Error')) { - $dt = new DateAndTime(DB_DATEFORMAT); while ($val = $res->fetchRow()) { $result[] = $val; } @@ -761,4 +763,17 @@ static function deleteGroupFiles($group_id) { // By design, we ignore such errors. return true; } + + // updateEntitiesModified updates the entities_modified field in tt_groups table + // with a current timestamp. + static function updateEntitiesModified() { + global $user; + $org_id = $user->org_id; + $group_id = $user->getGroup(); + $mdb2 = getConnection(); + + $sql = "update tt_groups set entities_modified = now() where id = $group_id and org_id = $org_id"; + $affected = $mdb2->exec($sql); + return (!is_a($affected, 'PEAR_Error')); + } } diff --git a/WEB-INF/lib/ttInvoiceHelper.class.php b/WEB-INF/lib/ttInvoiceHelper.class.php index 9a20730ae..44206fa0e 100644 --- a/WEB-INF/lib/ttInvoiceHelper.class.php +++ b/WEB-INF/lib/ttInvoiceHelper.class.php @@ -3,7 +3,7 @@ License: See license.txt */ import('ttClientHelper'); -import('DateAndTime'); +import('ttDate'); // Class ttInvoiceHelper is used for help with invoices. class ttInvoiceHelper { @@ -159,9 +159,8 @@ static function getInvoiceItems($invoice_id) { $res = $mdb2->query($sql); if (!is_a($res, 'PEAR_Error')) { - $dt = new DateAndTime(DB_DATEFORMAT); while ($val = $res->fetchRow()) { - $dt->parseVal($val['date']); + $dt = new ttDate($val['date']); $val['date'] = $dt->toString($user->getDateFormat()); $result[] = $val; } @@ -226,11 +225,11 @@ static function invoiceableItemsExist($fields) { $client_id = (int) $fields['client_id']; - $start_date = new DateAndTime($user->date_format, $fields['start_date']); - $start = $start_date->toString(DB_DATEFORMAT); + $start_date = new ttDate($fields['start_date'], $user->getDateFormat()); + $start = $start_date->toString(); - $end_date = new DateAndTime($user->date_format, $fields['end_date']); - $end = $end_date->toString(DB_DATEFORMAT); + $end_date = new ttDate($fields['end_date'], $user->getDateFormat()); + $end = $end_date->toString(); $project_id = null; $project_part = ''; @@ -299,19 +298,18 @@ static function createInvoice($fields) { $group_id = $user->getGroup(); $org_id = $user->org_id; - $name = $fields['name']; - if (!$name) return false; + $name = isset($fields['name']) ? $fields['name'] : null; $client_id = (int) $fields['client_id']; - $invoice_date = new DateAndTime($user->date_format, $fields['date']); - $date = $invoice_date->toString(DB_DATEFORMAT); + $invoice_date = new ttDate($fields['date'], $user->getDateFormat()); + $date = $invoice_date->toString(); - $start_date = new DateAndTime($user->date_format, $fields['start_date']); - $start = $start_date->toString(DB_DATEFORMAT); + $start_date = new ttDate($fields['start_date'], $user->getDateFormat()); + $start = $start_date->toString(); - $end_date = new DateAndTime($user->date_format, $fields['end_date']); - $end = $end_date->toString(DB_DATEFORMAT); + $end_date = new ttDate($fields['end_date'], $user->getDateFormat()); + $end = $end_date->toString(); $project_id = null; $project_part = ''; diff --git a/WEB-INF/lib/ttNotificationHelper.class.php b/WEB-INF/lib/ttNotificationHelper.class.php index 35c309040..8189e5a9b 100644 --- a/WEB-INF/lib/ttNotificationHelper.class.php +++ b/WEB-INF/lib/ttNotificationHelper.class.php @@ -23,7 +23,7 @@ // | // +----------------------------------------------------------------------+ // | Contributors: -// | https://www.anuko.com/time_tracker/credits.htm +// | https://www.anuko.com/time-tracker/credits.htm // +----------------------------------------------------------------------+ // Class ttNotificationHelper is used to help with notification related tasks. diff --git a/WEB-INF/lib/ttOrgExportHelper.class.php b/WEB-INF/lib/ttOrgExportHelper.class.php index 2dd54f1b3..9380231e4 100644 --- a/WEB-INF/lib/ttOrgExportHelper.class.php +++ b/WEB-INF/lib/ttOrgExportHelper.class.php @@ -1,30 +1,6 @@ $attrs['WORKDAY_MINUTES'], 'custom_logo' => $attrs['CUSTOM_LOGO'], 'config' => $attrs['CONFIG'], - 'custom_css' => $attrs['CUSTOM_CSS'])); + 'custom_css' => $attrs['CUSTOM_CSS'], + 'custom_translation' => $attrs['CUSTOM_TRANSLATION'])); // Special handling for top group. if (!$this->org_id && $this->current_group_id) { @@ -167,6 +168,7 @@ function startElement($parser, $name, $attrs) { // We get here when processing tags for the current group. // Prepare a list of task ids. + $mapped_tasks = null; if ($attrs['TASKS']) { $tasks = explode(',', $attrs['TASKS']); foreach ($tasks as $id) @@ -193,6 +195,7 @@ function startElement($parser, $name, $attrs) { // We get here when processing tags for the current group. // Prepare a list of project ids. + $mapped_projects = array(); if ($attrs['PROJECTS']) { $projects = explode(',', $attrs['PROJECTS']); foreach ($projects as $id) @@ -218,7 +221,6 @@ function startElement($parser, $name, $attrs) { if ($name == 'USER') { // We get here when processing tags for the current group. - $role_id = $attrs['ROLE_ID'] === '0' ? $this->top_role_id : $this->currentGroupRoleMap[$attrs['ROLE_ID']]; // 0 (not null) means top manager role. $user_id = $this->insertUser(array( @@ -348,7 +350,8 @@ function startElement($parser, $name, $attrs) { 'group_id' => $this->current_group_id, 'org_id' => $this->org_id, 'field_id' => $this->currentGroupCustomFieldMap[$attrs['FIELD_ID']], - 'value' => $attrs['VALUE'])); + 'value' => $attrs['VALUE'], + 'status' => $attrs['STATUS'])); if ($custom_field_option_id) { // Add a mapping. $this->currentGroupCustomFieldOptionMap[$attrs['ID']] = $custom_field_option_id; @@ -461,6 +464,7 @@ function startElement($parser, $name, $attrs) { 'invoice' => $attrs['INVOICE'], 'timesheet' => $attrs['TIMESHEET'], 'paid_status' => $attrs['PAID_STATUS'], + 'note_containing' => $attrs['NOTE_CONTAINING'], 'users' => $user_list, 'period' => $attrs['PERIOD'], 'from' => $attrs['PERIOD_START'], @@ -682,7 +686,7 @@ private function createGroup($fields) { $columns = '(parent_id, org_id, group_key, name, description, currency, decimal_mark, lang, date_format, time_format,'. ' week_start, tracking_mode, project_required, record_type, bcc_email,'. ' allow_ip, password_complexity, plugins, lock_spec,'. - ' workday_minutes, config, custom_css, created, created_ip, created_by)'; + ' workday_minutes, config, custom_css, custom_translation, created, created_ip, created_by)'; $values = ' values ('; $values .= $mdb2->quote($fields['parent_id']); @@ -707,6 +711,7 @@ private function createGroup($fields) { $values .= ', '.(int)$fields['workday_minutes']; $values .= ', '.$mdb2->quote($fields['config']); $values .= ', '.$mdb2->quote($fields['custom_css']); + $values .= ', '.$mdb2->quote($fields['custom_translation']); $values .= ', now(), '.$mdb2->quote($_SERVER['REMOTE_ADDR']).', '.$user->id; $values .= ')'; @@ -898,7 +903,7 @@ private function insertProject($fields) $org_id = (int) $fields['org_id']; $name = $fields['name']; $description = $fields['description']; - $tasks = $fields['tasks']; + $tasks = isset($fields['tasks']) ? $fields['tasks'] : array(); $comma_separated = implode(',', $tasks); // This is a comma-separated list of associated task ids. $status = $fields['status']; @@ -1013,6 +1018,7 @@ private function insertClient($fields) $address = $fields['address']; $tax = $fields['tax']; $projects = $fields['projects']; + $comma_separated = null; if ($projects) $comma_separated = implode(',', $projects); // This is a comma-separated list of associated projects ids. $status = $fields['status']; @@ -1049,7 +1055,7 @@ private function insertFavReport($fields) { $sql = "insert into tt_fav_reports". " (name, user_id, group_id, org_id, report_spec, client_id, project_id, task_id,". - " billable, approved, invoice, timesheet, paid_status, users, period, period_start, period_end,". + " billable, approved, invoice, timesheet, paid_status, note_containing, users, period, period_start, period_end,". " show_client, show_invoice, show_paid, show_ip,". " show_project, show_timesheet, show_start, show_duration, show_cost,". " show_task, show_end, show_note, show_approved, show_work_units,". @@ -1060,7 +1066,7 @@ private function insertFavReport($fields) { $mdb2->quote($fields['project']).", ".$mdb2->quote($fields['task']).", ". $mdb2->quote($fields['billable']).", ".$mdb2->quote($fields['approved']).", ". $mdb2->quote($fields['invoice']).", ".$mdb2->quote($fields['timesheet']).", ". - $mdb2->quote($fields['paid_status']).", ". + $mdb2->quote($fields['paid_status']).", ".$mdb2->quote($fields['note_containing']).", ". $mdb2->quote($fields['users']).", ".$mdb2->quote($fields['period']).", ". $mdb2->quote($fields['from']).", ".$mdb2->quote($fields['to']).", ". $fields['chclient'].", ".$fields['chinvoice'].", ".$fields['chpaid'].", ".$fields['chip'].", ". @@ -1150,9 +1156,10 @@ private function insertCustomFieldOption($fields) { $org_id = (int) $fields['org_id']; $field_id = (int) $fields['field_id']; $value = $fields['value']; + $status = $fields['status']; - $sql = "insert into tt_custom_field_options (group_id, org_id, field_id, value)". - " values ($group_id, $org_id, $field_id, ".$mdb2->quote($value).")"; + $sql = "insert into tt_custom_field_options (group_id, org_id, field_id, value, status)". + " values ($group_id, $org_id, $field_id, ".$mdb2->quote($value).", ".$mdb2->quote($status).")"; $affected = $mdb2->exec($sql); if (is_a($affected, 'PEAR_Error')) return false; diff --git a/WEB-INF/lib/ttPeriod.class.php b/WEB-INF/lib/ttPeriod.class.php new file mode 100644 index 000000000..0dcbf71c1 --- /dev/null +++ b/WEB-INF/lib/ttPeriod.class.php @@ -0,0 +1,85 @@ +getWeekStart(); + + $t_arr = localtime($ttDateInstance->getTimestamp()); + $t_arr[5] = $t_arr[5] + 1900; + $startWeekBias = ($t_arr[6] < $weekStartDay) ? $weekStartDay - 7 : $weekStartDay; + + $this->startDate = new ttDate(); + $this->endDate = new ttDate(); + + switch ($period_type) { + case INTERVAL_THIS_DAY: + $this->startDate->setFromUnixTimestamp($ttDateInstance->getTimestamp()); + $this->endDate->setFromUnixTimestamp($ttDateInstance->getTimestamp()); + break; + + case INTERVAL_PREVIOUS_DAY: + $this->startDate->setFromUnixTimestamp(mktime(0,0,0,$t_arr[4]+1,$t_arr[3]-1,$t_arr[5])); + $this->endDate->setFromUnixTimestamp(mktime(0,0,0,$t_arr[4]+1,$t_arr[3]-1,$t_arr[5])); + break; + + case INTERVAL_THIS_WEEK: + $this->startDate->setFromUnixTimestamp(mktime(0,0,0,$t_arr[4]+1,$t_arr[3]-$t_arr[6]+$startWeekBias,$t_arr[5])); + $this->endDate->setFromUnixTimestamp(mktime(0,0,0,$t_arr[4]+1,$t_arr[3]-$t_arr[6]+6+$startWeekBias,$t_arr[5])); + break; + + case INTERVAL_PREVIOUS_WEEK: + $this->startDate->setFromUnixTimestamp(mktime(0,0,0,$t_arr[4]+1,$t_arr[3]-$t_arr[6]-7+$startWeekBias,$t_arr[5])); + $this->endDate->setFromUnixTimestamp(mktime(0,0,0,$t_arr[4]+1,$t_arr[3]-$t_arr[6]-1+$startWeekBias,$t_arr[5])); + break; + + case INTERVAL_THIS_MONTH: + $this->startDate->setFromUnixTimestamp(mktime(0,0,0,$t_arr[4]+1,1,$t_arr[5])); + $this->endDate->setFromUnixTimestamp(mktime(0,0,0,$t_arr[4]+2,0,$t_arr[5])); + break; + + case INTERVAL_PREVIOUS_MONTH: + $this->startDate->setFromUnixTimestamp(mktime(0,0,0,$t_arr[4],1,$t_arr[5])); + $this->endDate->setFromUnixTimestamp(mktime(0,0,0,$t_arr[4]+1,0,$t_arr[5])); + break; + + case INTERVAL_THIS_YEAR: + $this->startDate->setFromUnixTimestamp(mktime(0,0,0,1,1,$t_arr[5])); + $this->endDate->setFromUnixTimestamp(mktime(0,0,0,12,31,$t_arr[5])); + break; + } + } + + // Sets period to designated start and and dates. + function setPeriod($start_date, $end_date) { + $this->startDate = $start_date; + $this->endDate = $end_date; + } + + // Returns start date in specified format. + function getStartDate($format = null) { + return $this->startDate->toString($format); + } + + + // Returns end date in specified format. + function getEndDate($format = null) { + return $this->endDate->toString($format); + } +} \ No newline at end of file diff --git a/WEB-INF/lib/ttPredefinedExpenseHelper.class.php b/WEB-INF/lib/ttPredefinedExpenseHelper.class.php index 8ec8ccfee..6d60d533e 100644 --- a/WEB-INF/lib/ttPredefinedExpenseHelper.class.php +++ b/WEB-INF/lib/ttPredefinedExpenseHelper.class.php @@ -23,7 +23,7 @@ // | // +----------------------------------------------------------------------+ // | Contributors: -// | https://www.anuko.com/time_tracker/credits.htm +// | https://www.anuko.com/time-tracker/credits.htm // +----------------------------------------------------------------------+ @@ -66,6 +66,10 @@ static function delete($id) { if (is_a($affected, 'PEAR_Error')) return false; + // Update entities_modified, too. + if (!ttGroupHelper::updateEntitiesModified()) + return false; + return true; } @@ -88,6 +92,10 @@ static function insert($fields) { if (is_a($affected, 'PEAR_Error')) return false; + // Update entities_modified, too. + if (!ttGroupHelper::updateEntitiesModified()) + return false; + return true; } @@ -108,6 +116,13 @@ static function update($fields) { $sql = "update tt_predefined_expenses set name = ".$mdb2->quote($name).", cost = ".$mdb2->quote($cost). " where id = $predefined_expense_id and group_id = $group_id and org_id = $org_id"; $affected = $mdb2->exec($sql); - return (!is_a($affected, 'PEAR_Error')); + if (is_a($affected, 'PEAR_Error')) + return false; + + // Update entities_modified, too. + if (!ttGroupHelper::updateEntitiesModified()) + return false; + + return true; } } diff --git a/WEB-INF/lib/ttProjectHelper.class.php b/WEB-INF/lib/ttProjectHelper.class.php index d456c3110..303289688 100644 --- a/WEB-INF/lib/ttProjectHelper.class.php +++ b/WEB-INF/lib/ttProjectHelper.class.php @@ -23,7 +23,7 @@ // | // +----------------------------------------------------------------------+ // | Contributors: -// | https://www.anuko.com/time_tracker/credits.htm +// | https://www.anuko.com/time-tracker/credits.htm // +----------------------------------------------------------------------+ import('ttUserHelper'); @@ -77,17 +77,23 @@ static function getRates($user_id) { return $result; } - // getProjects - returns an array of active and inactive projects in group. - static function getProjects() { + // getProjects - returns an array of projects for group. + static function getProjects($includeInactiveProjects = true) { global $user; $mdb2 = getConnection(); $group_id = $user->getGroup(); $org_id = $user->org_id; + // Construct status part. + $statusPart = 'status = 1'; + if ($includeInactiveProjects) { + $statusPart = '(status = 0 or '.$statusPart.')'; + } + $result = array(); $sql = "select id, name, tasks from tt_projects". - " where group_id = $group_id and org_id = $org_id and (status = 0 or status = 1) order by upper(name)"; + " where group_id = $group_id and org_id = $org_id and $statusPart order by upper(name)"; $res = $mdb2->query($sql); if (!is_a($res, 'PEAR_Error')) { while ($val = $res->fetchRow()) { @@ -160,7 +166,7 @@ static function getProjectByName($name) { } - // delete - deletes things associated with a project and marks the project as deleted. + // delete - deletes things associated with a project and marks the project and its custom fields as deleted. static function delete($id) { global $user; $mdb2 = getConnection(); @@ -210,9 +216,26 @@ static function delete($id) { if (is_a($affected, 'PEAR_Error')) return false; - // Finally, delete the project from the projects field in tt_clients table. - $result = ttClientHelper::deleteProject($id); - return $result; + // Delete the project from the projects field in tt_clients table. + if (!ttClientHelper::deleteProject($id)) + return false; + + // Mark project custom fields as deleted. + require_once(APP_DIR.'/plugins/CustomFields.class.php'); + $entity_type = CustomFields::ENTITY_PROJECT; + $modified_part = ', modified = now(), modified_ip = '.$mdb2->quote($_SERVER['REMOTE_ADDR']).', modified_by = '.$mdb2->quote($user->id); + $sql = "update tt_entity_custom_fields set status = null $modified_part". + " where entity_type = $entity_type and entity_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; } // insert function inserts a new project into database. @@ -263,6 +286,10 @@ static function insert($fields) } } + // Update entities_modified, too. + if (!ttGroupHelper::updateEntitiesModified()) + return false; + return $last_id; } @@ -341,14 +368,26 @@ static function update($fields) { return false; } // End of updating tt_project_task_binds table. - + + // If we are making the project inactive, unassign it from all clients. + if (constant('INACTIVE') == $status) { + ttClientHelper::unassignProjectFromAllClients($project_id); + } + // Update project name, description, tasks and status in tt_projects table. $comma_separated = implode(",", $tasks_to_bind); // This is a comma-separated list of associated task ids. $sql = "update tt_projects set name = ".$mdb2->quote($name).", description = ".$mdb2->quote($description). ", tasks = ".$mdb2->quote($comma_separated).", status = ".$mdb2->quote($status). " where id = $project_id and group_id = $group_id and org_id = $org_id"; $affected = $mdb2->exec($sql); - return (!is_a($affected, 'PEAR_Error')); + if (is_a($affected, 'PEAR_Error')) + return false; + + // Update entities_modified, too. + if (!ttGroupHelper::updateEntitiesModified()) + return false; + + return true; } // getAssignedUsers - returns an array of user ids assigned to a project. diff --git a/WEB-INF/lib/ttRegistrator.class.php b/WEB-INF/lib/ttRegistrator.class.php index ae499a9df..8afbf665b 100644 --- a/WEB-INF/lib/ttRegistrator.class.php +++ b/WEB-INF/lib/ttRegistrator.class.php @@ -41,13 +41,13 @@ function __construct($fields, &$err) { function validate() { global $i18n; - if (!ttValidString($this->group_name)) + if (!ttValidString($this->group_name, false, MAX_NAME_CHARS)) $this->err->add($i18n->get('error.field'), $i18n->get('label.group_name')); - if (!ttValidString($this->currency, true)) + if (!ttValidString($this->currency, true, MAX_CURRENCY_CHARS)) $this->err->add($i18n->get('error.field'), $i18n->get('label.currency')); - if (!ttValidString($this->user_name)) + if (!ttValidString($this->user_name, false, MAX_NAME_CHARS)) $this->err->add($i18n->get('error.field'), $i18n->get('label.manager_name')); - if (!ttValidString($this->login)) + if (!ttValidString($this->login, false, MAX_NAME_CHARS)) $this->err->add($i18n->get('error.field'), $i18n->get('label.manager_login')); if (!ttValidString($this->password1)) $this->err->add($i18n->get('error.field'), $i18n->get('label.password')); diff --git a/WEB-INF/lib/ttReportHelper.class.php b/WEB-INF/lib/ttReportHelper.class.php index d95348e8b..fb802f95e 100644 --- a/WEB-INF/lib/ttReportHelper.class.php +++ b/WEB-INF/lib/ttReportHelper.class.php @@ -3,12 +3,12 @@ License: See license.txt */ import('ttClientHelper'); -import('DateAndTime'); -import('Period'); +import('ttDate'); +import('ttPeriod'); import('ttTimeHelper'); import('ttConfigHelper'); -require_once(dirname(__FILE__).'/../../plugins/CustomFields.class.php'); +require_once(APP_DIR.'/plugins/CustomFields.class.php'); // Definitions of types for timesheet dropdown. define('TIMESHEET_ALL', 0); // Include all records. @@ -46,18 +46,22 @@ static function getWhere($options) { $dropdown_parts .= ' and l.client_id = '.$options['client_id']; elseif ($user->isClient() && $user->client_id) $dropdown_parts .= ' and l.client_id = '.$user->client_id; - if ($options['project_id']) $dropdown_parts .= ' and l.project_id = '.$options['project_id']; + + if (isset($options['project_ids'])) + $dropdown_parts .= ' and l.project_id in ('.$options['project_ids'].')'; + // if ($options['project_id']) $dropdown_parts .= ' and l.project_id = '.$options['project_id']; // This was here for a single select. + if ($options['task_id']) $dropdown_parts .= ' and l.task_id = '.$options['task_id']; - if ($options['billable']=='1') $dropdown_parts .= ' and l.billable = 1'; - if ($options['billable']=='2') $dropdown_parts .= ' and l.billable = 0'; - if ($options['invoice']=='1') $dropdown_parts .= ' and l.invoice_id is not null'; - if ($options['invoice']=='2') $dropdown_parts .= ' and l.invoice_id is null'; + if ($options['billable']==1) $dropdown_parts .= ' and l.billable = 1'; + if ($options['billable']==2) $dropdown_parts .= ' and l.billable = 0'; + if ($options['invoice']==1) $dropdown_parts .= ' and l.invoice_id is not null'; + if ($options['invoice']==2) $dropdown_parts .= ' and l.invoice_id is null'; if ($options['timesheet']==TIMESHEET_NOT_ASSIGNED) $dropdown_parts .= ' and l.timesheet_id is null'; if ($options['timesheet']==TIMESHEET_ASSIGNED) $dropdown_parts .= ' and l.timesheet_id is not null'; - if ($options['approved']=='1') $dropdown_parts .= ' and l.approved = 1'; - if ($options['approved']=='2') $dropdown_parts .= ' and l.approved = 0'; - if ($options['paid_status']=='1') $dropdown_parts .= ' and l.paid = 1'; - if ($options['paid_status']=='2') $dropdown_parts .= ' and l.paid = 0'; + if ($options['approved']==1) $dropdown_parts .= ' and l.approved = 1'; + if ($options['approved']==2) $dropdown_parts .= ' and l.approved = 0'; + if ($options['paid_status']==1) $dropdown_parts .= ' and l.paid = 1'; + if ($options['paid_status']==2) $dropdown_parts .= ' and l.paid = 0'; // Add time custom fields. if (isset($custom_fields) && $custom_fields->timeFields) { @@ -108,26 +112,56 @@ static function getWhere($options) { } } + // Add project custom fields. + if (isset($custom_fields) && $custom_fields->projectFields) { + foreach ($custom_fields->projectFields as $projectField) { + $field_name = 'project_field_'.$projectField['id']; + $field_value = $options[$field_name]; + if ($projectField['type'] == CustomFields::TYPE_DROPDOWN && $field_value) { + $cfoTable = 'cfo'.$projectField['id']; + $dropdown_parts .= " and $cfoTable.id = $field_value"; + } + } + } + + // Continue preparing part for text custom fields using LIKE operator. + if (isset($custom_fields) && $custom_fields->projectFields) { + foreach ($custom_fields->projectFields as $projectField) { + $field_name = 'project_field_'.$projectField['id']; + $field_value = $options[$field_name]; + if ($projectField['type'] == CustomFields::TYPE_TEXT && $field_value) { + $ecfTableName = 'ecf'.$projectField['id']; + $cf_text_parts .= " and $ecfTableName.value like ".$mdb2->quote("%$field_value%"); + } + } + } + // Prepare sql query part for user list. - $userlist = $options['users'] ? $options['users'] : '-1'; + $userlist = isset($options['users']) ? $options['users'] : '-1'; if ($user->can('view_reports') || $user->can('view_all_reports') || $user->isClient()) $user_list_part = " and l.user_id in ($userlist)"; else $user_list_part = " and l.user_id = ".$user->getUser(); $user_list_part .= " and l.group_id = $group_id and l.org_id = $org_id"; + // Prepare part for note_containing using LIKE operator. + $note_containing_part = ''; + if (isset($options['note_containing']) && !empty($options['note_containing'])) { + $note_containing_part = " and l.comment like ".$mdb2->quote('%'.$options['note_containing'].'%'); + } + // Prepare sql query part for where. $dateFormat = $user->getDateFormat(); - if ($options['period']) - $period = new Period($options['period'], new DateAndTime($dateFormat)); + if (isset($options['period']) && $options['period']) + $period = new ttPeriod(new ttDate(), $options['period']); else { - $period = new Period(); + $period = new ttPeriod(new ttDate()); $period->setPeriod( - new DateAndTime($dateFormat, $options['period_start']), - new DateAndTime($dateFormat, $options['period_end'])); + new ttDate($options['period_start'], $dateFormat), + new ttDate($options['period_end'], $dateFormat)); } - $where = " where l.status = 1 and l.date >= '".$period->getStartDate(DB_DATEFORMAT)."' and l.date <= '".$period->getEndDate(DB_DATEFORMAT)."'". - " $user_list_part $dropdown_parts $cf_text_parts"; + $where = " where l.status = 1 and l.date >= '".$period->getStartDate()."' and l.date <= '".$period->getEndDate()."'". + $user_list_part.$dropdown_parts.$cf_text_parts.$note_containing_part; return $where; } @@ -150,16 +184,20 @@ static function getExpenseWhere($options) { $dropdown_parts .= ' and ei.client_id = '.$options['client_id']; elseif ($user->isClient() && $user->client_id) $dropdown_parts .= ' and ei.client_id = '.$user->client_id; - if ($options['project_id']) $dropdown_parts .= ' and ei.project_id = '.$options['project_id']; - if ($options['invoice']=='1') $dropdown_parts .= ' and ei.invoice_id is not null'; - if ($options['invoice']=='2') $dropdown_parts .= ' and ei.invoice_id is null'; + + if (isset($options['project_ids'])) + $dropdown_parts .= ' and ei.project_id in ('.$options['project_ids'].')'; + // if ($options['project_id']) $dropdown_parts .= ' and l.project_id = '.$options['project_id']; // This was here for a single select. + + if ($options['invoice']==1) $dropdown_parts .= ' and ei.invoice_id is not null'; + if ($options['invoice']==2) $dropdown_parts .= ' and ei.invoice_id is null'; if (isset($options['timesheet']) && ($options['timesheet']!=TIMESHEET_ALL && $options['timesheet']!=TIMESHEET_NOT_ASSIGNED)) { $dropdown_parts .= ' and 0 = 1'; // Expense items do not have a timesheet_id. } - if ($options['approved']=='1') $dropdown_parts .= ' and ei.approved = 1'; - if ($options['approved']=='2') $dropdown_parts .= ' and ei.approved = 0'; - if ($options['paid_status']=='1') $dropdown_parts .= ' and ei.paid = 1'; - if ($options['paid_status']=='2') $dropdown_parts .= ' and ei.paid = 0'; + if ($options['approved']==1) $dropdown_parts .= ' and ei.approved = 1'; + if ($options['approved']==2) $dropdown_parts .= ' and ei.approved = 0'; + if ($options['paid_status']==1) $dropdown_parts .= ' and ei.paid = 1'; + if ($options['paid_status']==2) $dropdown_parts .= ' and ei.paid = 0'; // Not adding conditions for time custom fields by design because expenses are not associated with them. // Whether or not this is proper, we'll know eventually if users complain. @@ -190,26 +228,56 @@ static function getExpenseWhere($options) { } } + // Add project custom fields. + if (isset($custom_fields) && $custom_fields->projectFields) { + foreach ($custom_fields->projectFields as $projectField) { + $field_name = 'project_field_'.$projectField['id']; + $field_value = $options[$field_name]; + if ($projectField['type'] == CustomFields::TYPE_DROPDOWN && $field_value) { + $cfoTable = 'cfo'.$projectField['id']; + $dropdown_parts .= " and $cfoTable.id = $field_value"; + } + } + } + + // Continue preparing parts for text custom fields using LIKE operator. + if (isset($custom_fields) && $custom_fields->projectFields) { + foreach ($custom_fields->projectFields as $projectField) { + $field_name = 'project_field_'.$projectField['id']; + $field_value = $options[$field_name]; + if ($projectField['type'] == CustomFields::TYPE_TEXT && $field_value) { + $ecfTableName = 'ecf'.$projectField['id']; + $cf_text_parts .= " and $ecfTableName.value like ".$mdb2->quote("%$field_value%"); + } + } + } + // Prepare sql query part for user list. - $userlist = $options['users'] ? $options['users'] : '-1'; + $userlist = isset($options['users']) ? $options['users'] : '-1'; if ($user->can('view_reports') || $user->can('view_all_reports') || $user->isClient()) $user_list_part = " and ei.user_id in ($userlist)"; else $user_list_part = " and ei.user_id = ".$user->getUser(); $user_list_part .= " and ei.group_id = $group_id and ei.org_id = $org_id"; + // Prepare part for note_containing using LIKE operator. + $note_containing_part = ''; + if (isset($options['note_containing']) && !empty($options['note_containing'])) { + $note_containing_part = " and ei.name like ".$mdb2->quote('%'.$options['note_containing'].'%'); + } + // Prepare sql query part for where. $dateFormat = $user->getDateFormat(); - if ($options['period']) - $period = new Period($options['period'], new DateAndTime($dateFormat)); + if (isset($options['period']) && $options['period']) + $period = new ttPeriod(new ttDate(), $options['period']); else { - $period = new Period(); + $period = new ttPeriod(new ttDate()); $period->setPeriod( - new DateAndTime($dateFormat, $options['period_start']), - new DateAndTime($dateFormat, $options['period_end'])); + new ttDate($options['period_start'], $dateFormat), + new ttDate($options['period_end'], $dateFormat)); } - $where = " where ei.status = 1 and ei.date >= '".$period->getStartDate(DB_DATEFORMAT)."' and ei.date <= '".$period->getEndDate(DB_DATEFORMAT)."'". - " $user_list_part $dropdown_parts $cf_text_parts"; + $where = " where ei.status = 1 and ei.date >= '".$period->getStartDate()."' and ei.date <= '".$period->getEndDate()."'". + $user_list_part.$dropdown_parts.$cf_text_parts.$note_containing_part; return $where; } @@ -294,6 +362,22 @@ static function getItems($options) { } } } + // Add project custom fields. + if (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 ($options[$checkbox_field_name] || ttReportHelper::groupingBy($field_name, $options)) { + if ($projectField['type'] == CustomFields::TYPE_TEXT) { + $ecfTableName = 'ecf'.$projectField['id']; + array_push($fields, "$ecfTableName.value as $field_name"); + } elseif ($projectField['type'] == CustomFields::TYPE_DROPDOWN) { + $cfoTableName = 'cfo'.$projectField['id']; + array_push($fields, "$cfoTableName.value as $field_name"); + } + } + } + } // Add start time. if ($options['show_start']) { array_push($fields, "l.start as unformatted_start"); @@ -321,12 +405,19 @@ static function getItems($options) { // Handle cost. $includeCost = $options['show_cost']; if ($includeCost) { - if (MODE_TIME == $trackingMode) + $includeCostPerHour = $user->getConfigOption('report_cost_per_hour'); + if (MODE_TIME == $trackingMode) { + if ($includeCostPerHour) + array_push($fields, "cast(l.billable * coalesce(u.rate, 0) as decimal(10,2)) as cost_per_hour"); // Use default user rate. array_push($fields, "cast(l.billable * coalesce(u.rate, 0) * time_to_sec(l.duration)/3600 as decimal(10,2)) as cost"); // Use default user rate. - else + } else { + if ($includeCostPerHour) + array_push($fields, "cast(l.billable * coalesce(upb.rate, 0) as decimal(10,2)) as cost_per_hour"); // Use project rate for user. array_push($fields, "cast(l.billable * coalesce(upb.rate, 0) * time_to_sec(l.duration)/3600 as decimal(10,2)) as cost"); // Use project rate for user. + } array_push($fields, "null as expense"); } + // Add the fields used to determine if we show an edit icon for record. array_push($fields, 'l.approved'); array_push($fields, 'l.timesheet_id'); @@ -353,8 +444,7 @@ static function getItems($options) { // Prepare sql query part for left joins. $left_joins = null; - // Left joins for custom fields. - // 1 join is required for each text field, 2 joins for each dropdown. + // Left joins for user custom fields. if (isset($custom_fields) && $custom_fields->userFields) { foreach ($custom_fields->userFields as $userField) { $field_name = 'user_field_'.$userField['id']; @@ -374,6 +464,26 @@ static function getItems($options) { } } } + // Left joins for project custom fields. + if (isset($custom_fields) && $custom_fields->projectFields) { + foreach ($custom_fields->projectFields as $projectField) { + $field_name = 'project_field_'.$projectField['id']; + $checkbox_field_name = 'show_'.$field_name; + $entity_type = CustomFields::ENTITY_PROJECT; + if ($options[$field_name] || $options[$checkbox_field_name] || ttReportHelper::groupingBy($field_name, $options)) { + $ecfTable = 'ecf'.$projectField['id']; + if ($projectField['type'] == CustomFields::TYPE_TEXT) { + // Add one join for each text field. + $left_joins .= " left join tt_entity_custom_fields $ecfTable on ($ecfTable.entity_type = $entity_type and $ecfTable.entity_id = l.project_id and $ecfTable.field_id = ".$projectField['id'].")"; + } elseif ($projectField['type'] == CustomFields::TYPE_DROPDOWN) { + $cfoTable = 'cfo'.$projectField['id']; + // Add two joins for each dropdown field. + $left_joins .= " left join tt_entity_custom_fields $ecfTable on ($ecfTable.entity_type = $entity_type and $ecfTable.entity_id = l.project_id and $ecfTable.field_id = ".$projectField['id'].")"; + $left_joins .= " left join tt_custom_field_options $cfoTable on ($cfoTable.field_id = $ecfTable.field_id and $cfoTable.id = $ecfTable.option_id)"; + } + } + } + } if ($options['show_client'] || $grouping_by_client) $left_joins .= " left join tt_clients c on (c.id = l.client_id)"; if (($canViewReports || $isClient) && $options['show_invoice']) @@ -476,6 +586,22 @@ static function getItems($options) { } } } + // Add project custom fields. + if (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 ($options[$checkbox_field_name] || ttReportHelper::groupingBy($field_name, $options)) { + if ($projectField['type'] == CustomFields::TYPE_TEXT) { + $ecfTableName = 'ecf'.$projectField['id']; + array_push($fields, "$ecfTableName.value as $field_name"); + } elseif ($projectField['type'] == CustomFields::TYPE_DROPDOWN) { + $cfoTableName = 'cfo'.$projectField['id']; + array_push($fields, "$cfoTableName.value as $field_name"); + } + } + } + } if ($options['show_start']) { array_push($fields, 'null'); // null for unformatted_start. array_push($fields, 'null'); // null for start. @@ -489,6 +615,8 @@ static function getItems($options) { // Use the note field to print item name. if ($options['show_note']) array_push($fields, 'ei.name as note'); + if ($user->getConfigOption('report_cost_per_hour')) + array_push($fields, 'null as cost_per_hour'); // null for cost_per_hour. array_push($fields, 'ei.cost as cost'); array_push($fields, 'ei.cost as expense'); // Add the fields used to determine if we show an edit icon for record. @@ -536,6 +664,26 @@ static function getItems($options) { } } } + // Left joins for project custom fields. + if (isset($custom_fields) && $custom_fields->projectFields) { + foreach ($custom_fields->projectFields as $projectField) { + $field_name = 'project_field_'.$projectField['id']; + $checkbox_field_name = 'show_'.$field_name; + $entity_type = CustomFields::ENTITY_PROJECT; + if ($options[$field_name] || $options[$checkbox_field_name] || ttReportHelper::groupingBy($field_name, $options)) { + $ecfTable = 'ecf'.$projectField['id']; + if ($projectField['type'] == CustomFields::TYPE_TEXT) { + // Add one join for each text field. + $left_joins .= " left join tt_entity_custom_fields $ecfTable on ($ecfTable.entity_type = $entity_type and $ecfTable.entity_id = ei.project_id and $ecfTable.field_id = ".$projectField['id'].")"; + } elseif ($projectField['type'] == CustomFields::TYPE_DROPDOWN) { + $cfoTable = 'cfo'.$projectField['id']; + // Add two joins for each dropdown field. + $left_joins .= " left join tt_entity_custom_fields $ecfTable on ($ecfTable.entity_type = $entity_type and $ecfTable.entity_id = ei.project_id and $ecfTable.field_id = ".$projectField['id'].")"; + $left_joins .= " left join tt_custom_field_options $cfoTable on ($cfoTable.field_id = $ecfTable.field_id and $cfoTable.id = $ecfTable.option_id)"; + } + } + } + } if ($canViewReports || $isClient) $left_joins .= " left join tt_users u on (u.id = ei.user_id)"; if ($options['show_client'] || $grouping_by_client) @@ -571,7 +719,7 @@ static function getItems($options) { } else { $sort_part .= 'date'; } - if (($canViewReports || $isClient) && $options['users'] && !$grouping_by_user) + if (($canViewReports || $isClient) && isset($options['users']) && !$grouping_by_user) $sort_part .= ', user, type'; if ($options['show_start']) $sort_part .= ', unformatted_start'; @@ -584,6 +732,7 @@ static function getItems($options) { $res = $mdb2->query($sql); if (is_a($res, 'PEAR_Error')) die($res->getMessage()); + $report_items = array(); while ($val = $res->fetchRow()) { if ($convertTo12Hour) { if($val['start'] != '') @@ -591,6 +740,10 @@ static function getItems($options) { if($val['finish'] != '') $val['finish'] = ttTimeHelper::to12HourFormat($val['finish']); } + if (isset($val['cost_per_hour'])) { + if ('.' != $decimalMark) + $val['cost_per_hour'] = str_replace('.', $decimalMark, $val['cost_per_hour']); + } if (isset($val['cost'])) { if ('.' != $decimalMark) $val['cost'] = str_replace('.', $decimalMark, $val['cost']); @@ -669,7 +822,7 @@ static function getSubtotals($options) { // By now we have sql for time items. // However, when we have expenses, we need to do a union with a separate query for expense items from tt_expense_items table. - if ($options['show_cost'] && $user->isPluginEnabled('ex')) { // if ex(penses) plugin is enabled + if (isset($options['show_cost']) && $options['show_cost'] && $user->isPluginEnabled('ex')) { // if ex(penses) plugin is enabled $concat_part = ttReportHelper::makeConcatExpensesPart($options); $group_by_fields_part = ttReportHelper::makeGroupByFieldsExpensesPart($options); @@ -692,10 +845,12 @@ static function getSubtotals($options) { // Execute query. $res = $mdb2->query($sql); if (is_a($res, 'PEAR_Error')) die($res->getMessage()); + + $subtotals = array(); while ($val = $res->fetchRow()) { $time = ttTimeHelper::minutesToDuration($val['time'] / 60); $rowLabel = ttReportHelper::makeGroupByLabel($val['group_field'], $options); - if ($options['show_cost']) { + if (isset($options['show_cost']) && $options['show_cost']) { $decimalMark = $user->getDecimalMark(); if ('.' != $decimalMark) { $val['cost'] = str_replace('.', $decimalMark, $val['cost']); @@ -742,13 +897,13 @@ static function getTotals($options) // Prepare parts. $time_part = $units_part = $cost_part = ''; $time_part = "sum(time_to_sec(l.duration)) as time"; - if ($options['show_work_units']) { + if (isset($options['show_work_units']) && $options['show_work_units']) { $unitTotalsOnly = $user->getConfigOption('unit_totals_only'); $firstUnitThreshold = $user->getConfigInt('1st_unit_threshold', 0); $minutesInUnit = $user->getConfigInt('minutes_in_unit', 15); $units_part = $unitTotalsOnly ? ", null as units" : ", sum(if(l.billable = 0 or time_to_sec(l.duration)/60 < $firstUnitThreshold, 0, ceil(time_to_sec(l.duration)/60/$minutesInUnit))) as units"; } - if ($options['show_cost']) { + if (isset($options['show_cost']) && $options['show_cost']) { if (MODE_TIME == $trackingMode) $cost_part = ", sum(cast(l.billable * coalesce(u.rate, 0) * time_to_sec(l.duration)/3600 as decimal(10,2))) as cost, null as expenses"; else @@ -798,7 +953,27 @@ static function getTotals($options) } } } - if ($options['show_cost']) { + if (isset($custom_fields) && $custom_fields->projectFields) { + foreach ($custom_fields->projectFields as $projectField) { + $field_name = 'project_field_'.$projectField['id']; + $field_value = $options[$field_name]; + $entity_type = CustomFields::ENTITY_PROJECT; + if ($field_value) { + // We need to add left joins when input is not null. + $ecfTable = 'ecf'.$projectField['id']; + if ($projectField['type'] == CustomFields::TYPE_TEXT) { + // Add one join for each text field. + $left_joins .= " left join tt_entity_custom_fields $ecfTable on ($ecfTable.entity_type = $entity_type and $ecfTable.entity_id = l.project_id and $ecfTable.field_id = ".$projectField['id'].")"; + } elseif ($projectField['type'] == CustomFields::TYPE_DROPDOWN) { + $cfoTable = 'cfo'.$projectField['id']; + // Add two joins for each dropdown field. + $left_joins .= " left join tt_entity_custom_fields $ecfTable on ($ecfTable.entity_type = $entity_type and $ecfTable.entity_id = l.project_id and $ecfTable.field_id = ".$projectField['id'].")"; + $left_joins .= " left join tt_custom_field_options $cfoTable on ($cfoTable.field_id = $ecfTable.field_id and $cfoTable.id = $ecfTable.option_id)"; + } + } + } + } + if (isset($options['show_cost']) && $options['show_cost']) { if (MODE_TIME == $trackingMode) { $left_joins .= " left join tt_users u on (l.user_id = u.id)"; } else { @@ -807,7 +982,7 @@ static function getTotals($options) } // Prepare sql query part for inner joins. $inner_joins = null; - if ($user->isPluginEnabled('ts') && $options['timesheet']) { + if ($user->isPluginEnabled('ts') && isset($options['timesheet']) && $options['timesheet']) { $timesheet_option = $options['timesheet']; if ($timesheet_option == TIMESHEET_PENDING) $inner_joins .= " inner join tt_timesheets ts on (l.timesheet_id = ts.id and ts.submit_status = 1 and ts.approve_status is null)"; @@ -820,7 +995,7 @@ static function getTotals($options) $sql = "select $time_part $units_part $cost_part from tt_log l $left_joins $inner_joins $where"; // If we have expenses, query becomes a bit more complex. - if ($options['show_cost'] && $user->isPluginEnabled('ex')) { + if (isset($options['show_cost']) && $options['show_cost'] && $user->isPluginEnabled('ex')) { $where = ttReportHelper::getExpenseWhere($options); $sql_for_expenses = "select null as time"; if ($options['show_work_units']) $sql_for_expenses .= ", null as units"; @@ -848,6 +1023,26 @@ static function getTotals($options) } } } + if (isset($custom_fields) && $custom_fields->projectFields) { + foreach ($custom_fields->projectFields as $projectField) { + $field_name = 'project_field_'.$projectField['id']; + $field_value = $options[$field_name]; + $entity_type = CustomFields::ENTITY_PROJECT; + if ($field_value) { + // We need to add left joins when input is not null. + $ecfTable = 'ecf'.$projectField['id']; + if ($projectField['type'] == CustomFields::TYPE_TEXT) { + // Add one join for each text field. + $left_joins .= " left join tt_entity_custom_fields $ecfTable on ($ecfTable.entity_type = $entity_type and $ecfTable.entity_id = ei.project_id and $ecfTable.field_id = ".$projectField['id'].")"; + } elseif ($projectField['type'] == CustomFields::TYPE_DROPDOWN) { + $cfoTable = 'cfo'.$projectField['id']; + // Add two joins for each dropdown field. + $left_joins .= " left join tt_entity_custom_fields $ecfTable on ($ecfTable.entity_type = $entity_type and $ecfTable.entity_id = ei.project_id and $ecfTable.field_id = ".$projectField['id'].")"; + $left_joins .= " left join tt_custom_field_options $cfoTable on ($cfoTable.field_id = $ecfTable.field_id and $cfoTable.id = $ecfTable.option_id)"; + } + } + } + } $sql_for_expenses .= ", sum(cost) as cost, sum(cost) as expenses from tt_expense_items ei $left_joins $where"; @@ -865,7 +1060,7 @@ static function getTotals($options) $val = $res->fetchRow(); $total_time = ttTimeHelper::minutesToDuration($val['time'] / 60); $total_cost = $total_expenses = null; - if ($options['show_cost']) { + if (isset($options['show_cost']) && $options['show_cost']) { $total_cost = $val['cost']; if (!$total_cost) $total_cost = '0.00'; if ('.' != $decimalMark) @@ -877,17 +1072,17 @@ static function getTotals($options) } $dateFormat = $user->getDateFormat(); - if ($options['period']) - $period = new Period($options['period'], new DateAndTime($dateFormat)); + if (isset($options['period']) && $options['period']) + $period = new ttPeriod(new ttDate(), $options['period']); else { - $period = new Period(); + $period = new ttPeriod(new ttDate()); $period->setPeriod( - new DateAndTime($dateFormat, $options['period_start']), - new DateAndTime($dateFormat, $options['period_end'])); + new ttDate($options['period_start'], $dateFormat), + new ttDate($options['period_end'], $dateFormat)); } - $totals['start_date'] = $period->getStartDate(); - $totals['end_date'] = $period->getEndDate(); + $totals['start_date'] = $period->getStartDate($user->getDateFormat()); + $totals['end_date'] = $period->getEndDate($user->getDateFormat()); $totals['time'] = $total_time; $totals['minutes'] = $val['time'] / 60; $totals['units'] = isset($val['units']) ? $val['units'] : null; @@ -1009,11 +1204,11 @@ static function prepareReportBody($options, $comment = null) $config = new ttConfigHelper($user->getConfig()); $show_note_column = $options['show_note'] && !$config->getDefinedValue('report_note_on_separate_row'); $show_note_row = $options['show_note'] && $config->getDefinedValue('report_note_on_separate_row'); + $show_cost_per_hour = $options['show_cost'] && $config->getDefinedValue('report_cost_per_hour') && ($user->can('manage_invoices') || $user->isClient()); $items = ttReportHelper::getItems($options); $grouping = ttReportHelper::grouping($options); - if ($grouping) - $subtotals = ttReportHelper::getSubtotals($options); + $subtotals = $grouping ? ttReportHelper::getSubtotals($options) : array(); $totals = ttReportHelper::getTotals($options); // Use custom fields plugin if it is enabled. @@ -1045,6 +1240,14 @@ static function prepareReportBody($options, $comment = null) } if ($options['show_client']) $colspan++; if ($options['show_project']) $colspan++; + // Project custom fields. + if ($custom_fields && $custom_fields->projectFields) { + foreach ($custom_fields->projectFields as $projectField) { + $field_name = 'project_field_'.$projectField['id']; + $checkbox_control_name = 'show_'.$field_name; + if ($options[$checkbox_control_name]) $colspan++; + } + } if ($options['show_task']) $colspan++; // Time custom fields. if ($custom_fields && $custom_fields->timeFields) { @@ -1058,6 +1261,7 @@ static function prepareReportBody($options, $comment = null) if ($options['show_end']) $colspan++; if ($options['show_duration']) $colspan++; if ($options['show_work_units']) $colspan++; + if ($show_cost_per_hour) $colspan++; if ($options['show_cost']) $colspan++; if ($options['show_approved']) $colspan++; if ($options['show_paid']) $colspan++; @@ -1154,6 +1358,14 @@ static function prepareReportBody($options, $comment = null) $body .= ''.$i18n->get('label.client').''; if ($options['show_project']) $body .= ''.$i18n->get('label.project').''; + // Project custom fields. + if ($custom_fields && $custom_fields->projectFields) { + foreach ($custom_fields->projectFields as $projectField) { + $field_name = 'project_field_'.$projectField['id']; + $checkbox_control_name = 'show_'.$field_name; + if ($options[$checkbox_control_name]) $body .= ''.htmlspecialchars($projectField['label']).''; + } + } if ($options['show_task']) $body .= ''.$i18n->get('label.task').''; // Time custom fields. @@ -1174,6 +1386,8 @@ static function prepareReportBody($options, $comment = null) $body .= ''.$i18n->get('label.work_units_short').''; if ($show_note_column) $body .= ''.$i18n->get('label.note').''; + if ($show_cost_per_hour) + $body .= ''.$i18n->get('form.report.per_hour').''; if ($options['show_cost']) $body .= ''.$i18n->get('label.cost').''; if ($options['show_approved']) @@ -1222,6 +1436,14 @@ static function prepareReportBody($options, $comment = null) } if ($options['show_client']) $body .= ''.$subtotals[$prev_grouped_by]['client'].''; if ($options['show_project']) $body .= ''.$subtotals[$prev_grouped_by]['project'].''; + // Project custom fields. + if ($custom_fields && $custom_fields->projectFields) { + foreach ($custom_fields->projectFields as $projectField) { + $field_name = 'project_field_'.$projectField['id']; + $checkbox_control_name = 'show_'.$field_name; + if ($options[$checkbox_control_name]) $body .= ''; + } + } if ($options['show_task']) $body .= ''.$subtotals[$prev_grouped_by]['task'].''; // Time custom fields. if ($custom_fields && $custom_fields->timeFields) { @@ -1236,6 +1458,7 @@ static function prepareReportBody($options, $comment = null) if ($options['show_duration']) $body .= ''.$subtotals[$prev_grouped_by]['time'].''; if ($options['show_work_units']) $body .= ''.$subtotals[$prev_grouped_by]['units'].''; if ($show_note_column) $body .= ''; + if ($show_cost_per_hour) $body .= ''; if ($options['show_cost']) { $body .= ''; $body .= ($canViewReports || $isClient) ? $subtotals[$prev_grouped_by]['cost'] : $subtotals[$prev_grouped_by]['expenses']; @@ -1271,6 +1494,14 @@ static function prepareReportBody($options, $comment = null) $body .= ''.htmlspecialchars($record['client']).''; if ($options['show_project']) $body .= ''.htmlspecialchars($record['project']).''; + // Project custom fields. + if ($custom_fields && $custom_fields->projectFields) { + foreach ($custom_fields->projectFields as $projectField) { + $field_name = 'project_field_'.$projectField['id']; + $checkbox_control_name = 'show_'.$field_name; + if ($options[$checkbox_control_name]) $body .= ''.htmlspecialchars($record[$field_name]).''; + } + } if ($options['show_task']) $body .= ''.htmlspecialchars($record['task']).''; // Time custom fields. @@ -1291,6 +1522,8 @@ static function prepareReportBody($options, $comment = null) $body .= ''.$record['units'].''; if ($show_note_column) $body .= ''.htmlspecialchars($record['note']).''; + if ($show_cost_per_hour) + $body .= ''.$record['cost_per_hour'].''; if ($options['show_cost']) $body .= ''.$record['cost'].''; if ($options['show_approved']) { @@ -1311,7 +1544,7 @@ static function prepareReportBody($options, $comment = null) if ($options['show_invoice']) $body .= ''.htmlspecialchars($record['invoice']).''; if ($options['show_timesheet']) - $body .= ''.htmlspecialchars($record['timesheet']).''; + $body .= ''.htmlspecialchars($record['timesheet_name']).''; $body .= ''; if ($show_note_row && $record['note']) { $body .= ''; @@ -1341,6 +1574,14 @@ static function prepareReportBody($options, $comment = null) } if ($options['show_client']) $body .= ''.$subtotals[$prev_grouped_by]['client'].''; if ($options['show_project']) $body .= ''.$subtotals[$prev_grouped_by]['project'].''; + // Project custom fields. + if ($custom_fields && $custom_fields->projectFields) { + foreach ($custom_fields->projectFields as $projectField) { + $field_name = 'project_field_'.$projectField['id']; + $checkbox_control_name = 'show_'.$field_name; + if ($options[$checkbox_control_name]) $body .= ''; + } + } if ($options['show_task']) $body .= ''.$subtotals[$prev_grouped_by]['task'].''; // Time custom fields. if ($custom_fields && $custom_fields->timeFields) { @@ -1355,6 +1596,7 @@ static function prepareReportBody($options, $comment = null) if ($options['show_duration']) $body .= ''.$subtotals[$cur_grouped_by]['time'].''; if ($options['show_work_units']) $body .= ''.$subtotals[$cur_grouped_by]['units'].''; if ($show_note_column) $body .= ''; + if ($show_cost_per_hour) $body .= ''; if ($options['show_cost']) { $body .= ''; $body .= ($canViewReports || $isClient) ? $subtotals[$cur_grouped_by]['cost'] : $subtotals[$cur_grouped_by]['expenses']; @@ -1383,6 +1625,14 @@ static function prepareReportBody($options, $comment = null) } if ($options['show_client']) $body .= ''; if ($options['show_project']) $body .= ''; + // Project custom fields. + if ($custom_fields && $custom_fields->projectFields) { + foreach ($custom_fields->projectFields as $projectField) { + $field_name = 'project_field_'.$projectField['id']; + $checkbox_control_name = 'show_'.$field_name; + if ($options[$checkbox_control_name]) $body .= ''; + } + } if ($options['show_task']) $body .= ''; // Time custom fields. if ($custom_fields && $custom_fields->timeFields) { @@ -1397,6 +1647,7 @@ static function prepareReportBody($options, $comment = null) if ($options['show_duration']) $body .= ''.$totals['time'].''; if ($options['show_work_units']) $body .= ''.$totals['units'].''; if ($show_note_column) $body .= ''; + if ($show_cost_per_hour) $body .= ''; if ($options['show_cost']) { $body .= ''.htmlspecialchars($user->currency).' '; $body .= ($canViewReports || $isClient) ? $totals['cost'] : $totals['expenses']; @@ -1512,19 +1763,31 @@ static function getReportOptions($bean) { // Construct one by one. $options['name'] = null; // No name required. - $options['user_id'] = $user->id; // Not sure if we need user_id here. Fav reports use it to recycle $user object in cron.php. - $options['client_id'] = $bean->getAttribute('client'); - $options['project_id'] = $bean->getAttribute('project'); - $options['task_id'] = $bean->getAttribute('task'); - $options['billable'] = $bean->getAttribute('include_records'); - $options['invoice'] = $bean->getAttribute('invoice'); - $options['paid_status'] = $bean->getAttribute('paid_status'); - $options['approved'] = $bean->getAttribute('approved'); + // $options['user_id'] = $user->id; // We don't use user_id in regular reports. But fav reports use it to recycle $user object in cron.php. + $options['client_id'] = (int)$bean->getAttribute('client'); + + // Handle selected projects. Just in case check both "project[]" and "project". + $selected_projects_in_bean = $bean->getAttribute('project[]'); + if (is_array($selected_projects_in_bean) && !empty($selected_projects_in_bean[0])) { + $options['project_ids'] = join(',', $selected_projects_in_bean); + } + $selected_projects_in_bean = $bean->getAttribute('project'); + if (is_array($selected_projects_in_bean) && !empty($selected_projects_in_bean[0])) { + $options['project_ids'] = join(',', $selected_projects_in_bean); + } + // $options['project_id'] = (int)$bean->getAttribute('project'); // This was here for a single project select. + + $options['task_id'] = (int)$bean->getAttribute('task'); + $options['billable'] = (int)$bean->getAttribute('include_records'); + $options['invoice'] = (int)$bean->getAttribute('invoice'); + $options['paid_status'] = (int)$bean->getAttribute('paid_status'); + $options['approved'] = (int)$bean->getAttribute('approved'); if ($user->isPluginEnabled('ap') && $user->isClient() && !$user->can('view_client_unapproved')) $options['approved'] = 1; // Restrict clients to approved records only. - $options['timesheet'] = $bean->getAttribute('timesheet'); + $options['timesheet'] = (int)$bean->getAttribute('timesheet'); $active_users_in_bean = $bean->getAttribute('users_active'); + $users = ''; if ($active_users_in_bean && is_array($active_users_in_bean)) { $users = join(',', $active_users_in_bean); } @@ -1535,24 +1798,25 @@ static function getReportOptions($bean) { } if ($users) $options['users'] = $users; - $options['period'] = $bean->getAttribute('period'); + $options['period'] = (int)$bean->getAttribute('period'); $options['period_start'] = $bean->getAttribute('start_date'); $options['period_end'] = $bean->getAttribute('end_date'); - $options['show_client'] = $bean->getAttribute('chclient'); - $options['show_invoice'] = $bean->getAttribute('chinvoice'); - $options['show_approved'] = $bean->getAttribute('chapproved'); - $options['show_paid'] = $bean->getAttribute('chpaid'); - $options['show_ip'] = $bean->getAttribute('chip'); - $options['show_project'] = $bean->getAttribute('chproject'); - $options['show_start'] = $bean->getAttribute('chstart'); - $options['show_duration'] = $bean->getAttribute('chduration'); - $options['show_cost'] = $bean->getAttribute('chcost'); - $options['show_task'] = $bean->getAttribute('chtask'); - $options['show_end'] = $bean->getAttribute('chfinish'); - $options['show_note'] = $bean->getAttribute('chnote'); - $options['show_work_units'] = $bean->getAttribute('chunits'); - $options['show_timesheet'] = $bean->getAttribute('chtimesheet'); - $options['show_files'] = $bean->getAttribute('chfiles'); + $options['note_containing'] = $bean->getAttribute('note_containing'); + $options['show_client'] = (int)$bean->getAttribute('chclient'); + $options['show_invoice'] = (int)$bean->getAttribute('chinvoice'); + $options['show_approved'] = (int)$bean->getAttribute('chapproved'); + $options['show_paid'] = (int)$bean->getAttribute('chpaid'); + $options['show_ip'] = (int)$bean->getAttribute('chip'); + $options['show_project'] = (int)$bean->getAttribute('chproject'); + $options['show_start'] = (int)$bean->getAttribute('chstart'); + $options['show_duration'] = (int)$bean->getAttribute('chduration'); + $options['show_cost'] = (int)$bean->getAttribute('chcost'); + $options['show_task'] = (int)$bean->getAttribute('chtask'); + $options['show_end'] = (int)$bean->getAttribute('chfinish'); + $options['show_note'] = (int)$bean->getAttribute('chnote'); + $options['show_work_units'] = (int)$bean->getAttribute('chunits'); + $options['show_timesheet'] = (int)$bean->getAttribute('chtimesheet'); + $options['show_files'] = (int)$bean->getAttribute('chfiles'); // Prepare custom field options. if ($user->isPluginEnabled('cf')) { @@ -1564,8 +1828,14 @@ static function getReportOptions($bean) { foreach ($custom_fields->timeFields as $timeField) { $control_name = 'time_field_'.$timeField['id']; $checkbox_control_name = 'show_'.$control_name; - $options[$control_name] = $bean->getAttribute($control_name); - $options[$checkbox_control_name] = $bean->getAttribute($checkbox_control_name); + if ($timeField['type'] == CustomFields::TYPE_DROPDOWN) { + // Cast to int for a dropdown for extra security. + $options[$control_name] = (int)$bean->getAttribute($control_name); + } elseif ($timeField['type'] == CustomFields::TYPE_TEXT) { + // No need to cast to int for a text field. + $options[$control_name] = $bean->getAttribute($control_name); + } + $options[$checkbox_control_name] = (int)$bean->getAttribute($checkbox_control_name); } } @@ -1574,18 +1844,38 @@ static function getReportOptions($bean) { foreach ($custom_fields->userFields as $userField) { $control_name = 'user_field_'.$userField['id']; $checkbox_control_name = 'show_'.$control_name; - $options[$control_name] = $bean->getAttribute($control_name); - $options[$checkbox_control_name] = $bean->getAttribute($checkbox_control_name); + if ($userField['type'] == CustomFields::TYPE_DROPDOWN) { + // Cast to int for a dropdown for extra security. + $options[$control_name] = (int)$bean->getAttribute($control_name); + } elseif ($userField['type'] == CustomFields::TYPE_TEXT) { + // No need to cast to int for a text field. + $options[$control_name] = $bean->getAttribute($control_name); + } + $options[$checkbox_control_name] = (int)$bean->getAttribute($checkbox_control_name); } } - // TODO: add project fields here. + // Project fields. + if ($custom_fields->projectFields) { + foreach ($custom_fields->projectFields as $projectField) { + $control_name = 'project_field_'.$projectField['id']; + $checkbox_control_name = 'show_'.$control_name; + if ($projectField['type'] == CustomFields::TYPE_DROPDOWN) { + // Cast to int for a dropdown for extra security. + $options[$control_name] = (int)$bean->getAttribute($control_name); + } elseif ($projectField['type'] == CustomFields::TYPE_TEXT) { + // No need to cast to int for a text field. + $options[$control_name] = $bean->getAttribute($control_name); + } + $options[$checkbox_control_name] = (int)$bean->getAttribute($checkbox_control_name); + } + } } $options['group_by1'] = $bean->getAttribute('group_by1'); $options['group_by2'] = $bean->getAttribute('group_by2'); $options['group_by3'] = $bean->getAttribute('group_by3'); - $options['show_totals_only'] = $bean->getAttribute('chtotalsonly'); + $options['show_totals_only'] = (int)$bean->getAttribute('chtotalsonly'); return $options; } @@ -1593,6 +1883,9 @@ static function getReportOptions($bean) { static function verifyBean($bean) { global $user; + // Check period control. + if (!ttValidInteger($bean->getAttribute('period'), true)) return false; + // Check users. $active_users_in_bean = (array) $bean->getAttribute('users_active'); $inactive_users_in_bean = (array) $bean->getAttribute('users_inactive'); @@ -1613,10 +1906,162 @@ static function verifyBean($bean) { } } - // TODO: add additional checks here. Perhaps do it before saving the bean for consistency. + // Check fav report id. + $fav_report_id = $bean->getAttribute('favorite_report'); + if (!($fav_report_id == -1 || (ttValidInteger($fav_report_id) && ttFavReportHelper::get($fav_report_id)))) return false; + + // Check client id. + if (!ttValidInteger($bean->getAttribute('client'), true)) return false; + + // Check billable control. + if (!ttValidInteger($bean->getAttribute('include_records'), true)) return false; + + // Check invoiced / not invoiced control. + if (!ttValidInteger($bean->getAttribute('invoice'), true)) return false; + + // Check selected project ids. Just in case check both project[] and project. + $selectedProjects = $bean->getAttribute('project'); + if (is_array($selectedProjects)) { + foreach ($selectedProjects as $singleProjectId) { + if (!ttValidInteger($singleProjectId, true)) return false; + } + } + $selectedProjects = $bean->getAttribute('project[]'); + if (is_array($selectedProjects)) { + foreach ($selectedProjects as $singleProjectId) { + if (!ttValidInteger($singleProjectId, true)) return false; + } + } + + // Check task id. + if (!ttValidInteger($bean->getAttribute('task'), true)) return false; + + // Check paid status control. + if (!ttValidInteger($bean->getAttribute('paid_status'), true)) return false; + + // Check approved status control. + if (!ttValidInteger($bean->getAttribute('approved'), true)) return false; + + // Check timesheet status control. + if (!ttValidInteger($bean->getAttribute('timesheet'), true)) return false; + + // Validate checkboxes. + if (!ttValidCheckbox($bean->getAttribute('chclient'))) return false; + if (!ttValidCheckbox($bean->getAttribute('chproject'))) return false; + if (!ttValidCheckbox($bean->getAttribute('chtask'))) return false; + if (!ttValidCheckbox($bean->getAttribute('chstart'))) return false; + if (!ttValidCheckbox($bean->getAttribute('chfinish'))) return false; + if (!ttValidCheckbox($bean->getAttribute('chduration'))) return false; + if (!ttValidCheckbox($bean->getAttribute('chnote'))) return false; + if (!ttValidCheckbox($bean->getAttribute('chunits'))) return false; + if (!ttValidCheckbox($bean->getAttribute('chcost'))) return false; + if (!ttValidCheckbox($bean->getAttribute('chapproved'))) return false; + if (!ttValidCheckbox($bean->getAttribute('chpaid'))) return false; + if (!ttValidCheckbox($bean->getAttribute('chip'))) return false; + if (!ttValidCheckbox($bean->getAttribute('chinvoice'))) return false; + if (!ttValidCheckbox($bean->getAttribute('chtimesheet'))) return false; + if (!ttValidCheckbox($bean->getAttribute('chfiles'))) return false; + if (!ttValidCheckbox($bean->getAttribute('chtotalsonly'))) return false; + + // Verify custom field controls. + if ($user->isPluginEnabled('cf')) { + global $custom_fields; + if (!$custom_fields) $custom_fields = new CustomFields(); + + // Time fields. + if ($custom_fields->timeFields) { + foreach ($custom_fields->timeFields as $timeField) { + $control_name = 'time_field_'.$timeField['id']; + $checkbox_control_name = 'show_'.$control_name; + if ($timeField['type'] == CustomFields::TYPE_DROPDOWN) { + if (!ttValidInteger($bean->getAttribute($control_name), true)) + return false; + } + if (!ttValidCheckbox($bean->getAttribute($checkbox_control_name), true)) + return false; + } + } + + // User fields. + if ($custom_fields->userFields) { + foreach ($custom_fields->userFields as $userField) { + $control_name = 'user_field_'.$userField['id']; + $checkbox_control_name = 'show_'.$control_name; + if ($userField['type'] == CustomFields::TYPE_DROPDOWN) { + if (!ttValidInteger($bean->getAttribute($control_name), true)) + return false; + } + if (!ttValidCheckbox($bean->getAttribute($checkbox_control_name), true)) + return false; + } + } + + // Project fields. + if ($custom_fields->projectFields) { + foreach ($custom_fields->projectFields as $projectField) { + $control_name = 'project_field_'.$projectField['id']; + $checkbox_control_name = 'show_'.$control_name; + if ($projectField['type'] == CustomFields::TYPE_DROPDOWN) { + if (!ttValidInteger($bean->getAttribute($control_name), true)) + return false; + } + if (!ttValidCheckbox($bean->getAttribute($checkbox_control_name), true)) + return false; + } + } + } + + if (!ttReportHelper::validGroupByOption($bean->getAttribute('group_by1'))) return false; + if (!ttReportHelper::validGroupByOption($bean->getAttribute('group_by2'))) return false; + if (!ttReportHelper::validGroupByOption($bean->getAttribute('group_by3'))) return false; + return true; } + // validGroupByOption is a security function to verify selection options for group by controls. + static function validGroupByOption($groupByOption) { + + global $user; + + if ($groupByOption == 'no_grouping' || $groupByOption == 'date' || $groupByOption == 'user' || $groupByOption == 'client' || $groupByOption == 'project' || $groupByOption == 'task' || $groupByOption == null) + return true; + + // Verify custom field group by options. + if ($user->isPluginEnabled('cf')) { + global $custom_fields; + if (!$custom_fields) $custom_fields = new CustomFields(); + + // Time fields. + if ($custom_fields->timeFields) { + foreach ($custom_fields->timeFields as $timeField) { + $control_name = 'time_field_'.$timeField['id']; + if ($groupByOption == $control_name) + return true; + } + } + + // User fields. + if ($custom_fields->userFields) { + foreach ($custom_fields->userFields as $userField) { + $control_name = 'user_field_'.$userField['id']; + if ($groupByOption == $control_name) + return true; + } + } + + // Project fields. + if ($custom_fields->projectFields) { + foreach ($custom_fields->projectFields as $projectField) { + $control_name = 'project_field_'.$projectField['id']; + if ($groupByOption == $control_name) + return true; + } + } + } // if ($user->isPluginEnabled('cf')) { + + return false; + } + // makeGroupByKey builds a combined group by key from group_by1, group_by2 and group_by3 values // (passed in $options) and a row of data ($row obtained from a db query). static function makeGroupByKey($options, $row) { @@ -1674,9 +2119,12 @@ static function makeGroupByPart($options) { if (!ttReportHelper::grouping($options)) return null; $group_by_parts = ''; - $group_by_parts .= ttReportHelper::makeSingleDropdownGroupByPart($options['group_by1'], $options); - $group_by_parts .= ttReportHelper::makeSingleDropdownGroupByPart($options['group_by2'], $options); - $group_by_parts .= ttReportHelper::makeSingleDropdownGroupByPart($options['group_by3'], $options); + if (isset($options['group_by1'])) + $group_by_parts .= ttReportHelper::makeSingleDropdownGroupByPart($options['group_by1'], $options); + if (isset($options['group_by2'])) + $group_by_parts .= ttReportHelper::makeSingleDropdownGroupByPart($options['group_by2'], $options); + if (isset($options['group_by3'])) + $group_by_parts .= ttReportHelper::makeSingleDropdownGroupByPart($options['group_by3'], $options); // Remove garbage from the beginning. $group_by_parts = ltrim($group_by_parts, ', '); $group_by_part = "group by $group_by_parts"; @@ -1754,6 +2202,19 @@ static function makeSingleDropdownConcatCustomFieldPart($dropdown_value) { } } } + // Iterate through project custom fields. + if ($custom_fields && $custom_fields->projectFields) { + foreach ($custom_fields->projectFields as $projectField) { + $field_name = 'project_field_'.$projectField['id']; + if ($dropdown_value == $field_name) { + if ($projectField['type'] == CustomFields::TYPE_TEXT) { + return (", ' - ', coalesce(ecf".$projectField['id'].".value, 'Null')"); + } elseif ($projectField['type'] == CustomFields::TYPE_DROPDOWN) { + return (", ' - ', coalesce(cfo".$projectField['id'].".value, 'Null')"); + } + } + } + } // Iterate through time custom fields. if ($custom_fields && $custom_fields->timeFields) { foreach ($custom_fields->timeFields as $timeField) { @@ -1788,6 +2249,19 @@ static function makeSingleDropdownConcatCustomFieldExpensesPart($dropdown_value) } } } + // Iterate through project custom fields. + if ($custom_fields && $custom_fields->projectFields) { + foreach ($custom_fields->projectFields as $projectField) { + $field_name = 'project_field_'.$projectField['id']; + if ($dropdown_value == $field_name) { + if ($projectField['type'] == CustomFields::TYPE_TEXT) { + return (", ' - ', coalesce(ecf".$projectField['id'].".value, 'Null')"); + } elseif ($projectField['type'] == CustomFields::TYPE_DROPDOWN) { + return (", ' - ', coalesce(cfo".$projectField['id'].".value, 'Null')"); + } + } + } + } // Iterate through time custom fields. if ($custom_fields && $custom_fields->timeFields) { foreach ($custom_fields->timeFields as $timeField) { @@ -1841,6 +2315,19 @@ static function makeSingleDropdownGroupByCustomFieldPart($dropdown_value) { } } } + // Iterate through project custom fields. + if ($custom_fields && $custom_fields->projectFields) { + foreach ($custom_fields->projectFields as $projectField) { + $field_name = 'project_field_'.$projectField['id']; + if ($dropdown_value == $field_name) { + if ($projectField['type'] == CustomFields::TYPE_TEXT) { + return (", ecf".$projectField['id'].".value as $field_name"); + } elseif ($projectField['type'] == CustomFields::TYPE_DROPDOWN) { + return (", cfo".$projectField['id'].".value as $field_name"); + } + } + } + } // Iterate through time custom fields. if ($custom_fields && $custom_fields->timeFields) { foreach ($custom_fields->timeFields as $timeField) { @@ -1875,6 +2362,19 @@ static function makeSingleDropdownGroupByCustomFieldExpensesPart($dropdown_value } } } + // Iterate through project custom fields. + if ($custom_fields && $custom_fields->projectFields) { + foreach ($custom_fields->projectFields as $projectField) { + $field_name = 'project_field_'.$projectField['id']; + if ($dropdown_value == $field_name) { + if ($projectField['type'] == CustomFields::TYPE_TEXT) { + return (", ecf".$projectField['id'].".value as $field_name"); + } elseif ($projectField['type'] == CustomFields::TYPE_DROPDOWN) { + return (", cfo".$projectField['id'].".value as $field_name"); + } + } + } + } // Iterate through time custom fields. if ($custom_fields && $custom_fields->timeFields) { foreach ($custom_fields->timeFields as $timeField) { @@ -1893,9 +2393,12 @@ static function makeGroupByFieldsPart($options) { if (!ttReportHelper::grouping($options)) return null; $group_by_fields_parts = ''; - $group_by_fields_parts .= ttReportHelper::makeSingleDropdownGroupByFieldPart($options['group_by1'], $options); - $group_by_fields_parts .= ttReportHelper::makeSingleDropdownGroupByFieldPart($options['group_by2'], $options); - $group_by_fields_parts .= ttReportHelper::makeSingleDropdownGroupByFieldPart($options['group_by3'], $options); + if (isset($options['group_by1'])) + $group_by_fields_parts .= ttReportHelper::makeSingleDropdownGroupByFieldPart($options['group_by1'], $options); + if (isset($options['group_by2'])) + $group_by_fields_parts .= ttReportHelper::makeSingleDropdownGroupByFieldPart($options['group_by2'], $options); + if (isset($options['group_by3'])) + $group_by_fields_parts .= ttReportHelper::makeSingleDropdownGroupByFieldPart($options['group_by3'], $options); return $group_by_fields_parts; } @@ -1905,9 +2408,12 @@ static function makeGroupByFieldsExpensesPart($options) { if (!ttReportHelper::grouping($options)) return null; $group_by_fields_parts = ''; - $group_by_fields_parts .= ttReportHelper::makeSingleDropdownGroupByFieldExpensesPart($options['group_by1'], $options); - $group_by_fields_parts .= ttReportHelper::makeSingleDropdownGroupByFieldExpensesPart($options['group_by2'], $options); - $group_by_fields_parts .= ttReportHelper::makeSingleDropdownGroupByFieldExpensesPart($options['group_by3'], $options); + if (isset($options['group_by1'])) + $group_by_fields_parts .= ttReportHelper::makeSingleDropdownGroupByFieldExpensesPart($options['group_by1'], $options); + if (isset($options['group_by2'])) + $group_by_fields_parts .= ttReportHelper::makeSingleDropdownGroupByFieldExpensesPart($options['group_by2'], $options); + if (isset($options['group_by3'])) + $group_by_fields_parts .= ttReportHelper::makeSingleDropdownGroupByFieldExpensesPart($options['group_by3'], $options); return $group_by_fields_parts; } @@ -1916,9 +2422,12 @@ static function makeConcatPart($options) { if (!ttReportHelper::grouping($options)) return null; $concat_part = ''; - $concat_part .= ttReportHelper::makeSingleDropdownConcatPart($options['group_by1'], $options); - $concat_part .= ttReportHelper::makeSingleDropdownConcatPart($options['group_by2'], $options); - $concat_part .= ttReportHelper::makeSingleDropdownConcatPart($options['group_by3'], $options); + if (isset($options['group_by1'])) + $concat_part .= ttReportHelper::makeSingleDropdownConcatPart($options['group_by1'], $options); + if (isset($options['group_by2'])) + $concat_part .= ttReportHelper::makeSingleDropdownConcatPart($options['group_by2'], $options); + if (isset($options['group_by3'])) + $concat_part .= ttReportHelper::makeSingleDropdownConcatPart($options['group_by3'], $options); // Remove garbage from the beginning. if (ttStartsWith($concat_part, ", ' - ', ")) $concat_part = substr($concat_part, 9); @@ -2058,7 +2567,7 @@ static function makeJoinPart($options) { if (ttReportHelper::groupingBy('task', $options)) { $left_joins .= ' left join tt_tasks t on (l.task_id = t.id)'; } - if ($options['show_cost'] && $trackingMode != MODE_TIME) { + if (isset($options['show_cost']) && $options['show_cost'] && $trackingMode != MODE_TIME) { $left_joins .= ' left join tt_user_project_binds upb on (l.user_id = upb.user_id and l.project_id = upb.project_id)'; } // Left joins for time custom fields. @@ -2101,10 +2610,31 @@ static function makeJoinPart($options) { } } } + // Left joins for project custom fields. + if (isset($custom_fields) && $custom_fields->projectFields) { + foreach ($custom_fields->projectFields as $projectField) { + $field_name = 'project_field_'.$projectField['id']; + $field_value = $options[$field_name]; + $entity_type = CustomFields::ENTITY_PROJECT; + if ($field_value || ttReportHelper::groupingBy($field_name, $options)) { + // We need to add left joins when input is not null. + $ecfTable = 'ecf'.$projectField['id']; + if ($projectField['type'] == CustomFields::TYPE_TEXT) { + // Add one join for each text field. + $left_joins .= " left join tt_entity_custom_fields $ecfTable on ($ecfTable.entity_type = $entity_type and $ecfTable.entity_id = l.project_id and $ecfTable.field_id = ".$projectField['id'].")"; + } elseif ($projectField['type'] == CustomFields::TYPE_DROPDOWN) { + $cfoTable = 'cfo'.$projectField['id']; + // Add two joins for each dropdown field. + $left_joins .= " left join tt_entity_custom_fields $ecfTable on ($ecfTable.entity_type = $entity_type and $ecfTable.entity_id = l.project_id and $ecfTable.field_id = ".$projectField['id'].")"; + $left_joins .= " left join tt_custom_field_options $cfoTable on ($cfoTable.field_id = $ecfTable.field_id and $cfoTable.id = $ecfTable.option_id)"; + } + } + } + } // Prepare inner joins. $inner_joins = null; - if ($user->isPluginEnabled('ts') && $options['timesheet']) { + if ($user->isPluginEnabled('ts') && isset($options['timesheet']) && $options['timesheet']) { $timesheet_option = $options['timesheet']; if ($timesheet_option == TIMESHEET_PENDING) $inner_joins .= " inner join tt_timesheets ts on (l.timesheet_id = ts.id and ts.submit_status = 1 and ts.approve_status is null)"; @@ -2123,8 +2653,7 @@ static function makeWorkUnitPart($options) { global $user; $work_unit_part = ''; - $workUnits = $options['show_work_units']; - if ($workUnits) { + if (isset($options['show_work_units']) && $options['show_work_units']) { $unitTotalsOnly = $user->getConfigOption('unit_totals_only'); $firstUnitThreshold = $user->getConfigInt('1st_unit_threshold', 0); $minutesInUnit = $user->getConfigInt('minutes_in_unit', 15); @@ -2141,7 +2670,7 @@ static function makeCostPart($options) { global $user; $cost_part = ''; - if ($options['show_cost']) { + if (isset($options['show_cost']) && $options['show_cost']) { if (MODE_TIME == $user->getTrackingMode()) $cost_part = ", sum(cast(l.billable * coalesce(u.rate, 0) * time_to_sec(l.duration)/3600 as decimal(10, 2))) as cost"; else @@ -2191,6 +2720,27 @@ static function makeJoinExpensesPart($options) { } } } + // Left joins for project custom fields. + if (isset($custom_fields) && $custom_fields->projectFields) { + foreach ($custom_fields->projectFields as $projectField) { + $field_name = 'project_field_'.$projectField['id']; + $field_value = $options[$field_name]; + $entity_type = CustomFields::ENTITY_PROJECT; + if ($field_value || ttReportHelper::groupingBy($field_name, $options)) { + // We need to add left joins when input is not null. + $ecfTable = 'ecf'.$projectField['id']; + if ($projectField['type'] == CustomFields::TYPE_TEXT) { + // Add one join for each text field. + $left_joins .= " left join tt_entity_custom_fields $ecfTable on ($ecfTable.entity_type = $entity_type and $ecfTable.entity_id = ei.project_id and $ecfTable.field_id = ".$projectField['id'].")"; + } elseif ($projectField['type'] == CustomFields::TYPE_DROPDOWN) { + $cfoTable = 'cfo'.$projectField['id']; + // Add two joins for each dropdown field. + $left_joins .= " left join tt_entity_custom_fields $ecfTable on ($ecfTable.entity_type = $entity_type and $ecfTable.entity_id = ei.project_id and $ecfTable.field_id = ".$projectField['id'].")"; + $left_joins .= " left join tt_custom_field_options $cfoTable on ($cfoTable.field_id = $ecfTable.field_id and $cfoTable.id = $ecfTable.option_id)"; + } + } + } + } return $left_joins; } @@ -2208,7 +2758,10 @@ static function grouping($options) { // ('date', 'user', 'project', etc.) by checking group_by1, group_by2, // and group_by3 values passed in $options. static function groupingBy($what, $options) { - $grouping = ($options['group_by1'] == $what) || ($options['group_by2'] == $what) || ($options['group_by3'] == $what); + $grouping_by1 = isset($options['group_by1']) && $options['group_by1'] == $what; + $grouping_by2 = isset($options['group_by2']) && $options['group_by2'] == $what; + $grouping_by3 = isset($options['group_by3']) && $options['group_by3'] == $what; + $grouping = $grouping_by1 || $grouping_by2 || $grouping_by3; return $grouping; } @@ -2220,7 +2773,7 @@ static function makeGroupByHeaderPart($dropdown_value) { // First, try to get a label from a translation file, which is the most likely scenario // such as grouping by date, user, project, or task. - if (!ttStartsWith($dropdown_value, 'time_field_') && !ttStartsWith($dropdown_value, 'user_field_')) { + if (!ttContains($dropdown_value, '_field_')) { $key = 'label.'.$dropdown_value; $part = $i18n->get($key); if ($part) return $part; @@ -2247,6 +2800,15 @@ static function makeGroupByHeaderPart($dropdown_value) { } } } + // Process project custom fields. + if ($custom_fields && $custom_fields->projectFields) { + foreach ($custom_fields->projectFields as $projectField) { + $field_name = 'project_field_'.$projectField['id']; + if ($dropdown_value == $field_name) { + return $projectField['label']; + } + } + } // Return null if nothing is found. return null; @@ -2261,17 +2823,17 @@ static function makeGroupByHeader($options) { if ($no_grouping) return null; $group_by_header = ''; - if ($options['group_by1'] != null && $options['group_by1'] != 'no_grouping') { + if (isset($options['group_by1']) && $options['group_by1'] != 'no_grouping') { // We have group_by1. $group_by1 = $options['group_by1']; $group_by_header .= ' - '.ttReportHelper::makeGroupByHeaderPart($group_by1); } - if ($options['group_by2'] != null && $options['group_by2'] != 'no_grouping') { + if (isset($options['group_by2']) && $options['group_by2'] != 'no_grouping') { // We have group_by2. $group_by2 = $options['group_by2']; $group_by_header .= ' - '.ttReportHelper::makeGroupByHeaderPart($group_by2); } - if ($options['group_by3'] != null && $options['group_by3'] != 'no_grouping') { + if (isset($options['group_by3']) && $options['group_by3'] != 'no_grouping') { // We have group_by3. $group_by3 = $options['group_by3']; $group_by_header .= ' - '.ttReportHelper::makeGroupByHeaderPart($group_by3); @@ -2283,6 +2845,13 @@ static function makeGroupByHeader($options) { // makeGroupByXmlTag creates an xml tag for a totals only report using group_by1, // group_by2, and group_by3 values passed in $options. static function makeGroupByXmlTag($options) { + // Note: there is a problem with this function for custom fields. + // Returned xml tags are like "project_field_1134" and not their user entered labels. + // This is on purpose because we are trying to avoid potential XML parsing problems. + // See https://stackoverflow.com/questions/1478486/using-in-xml-element-name + // Perhaps review this if someone complains, but there are W3C standard (section 2.3) rules + // on names (including starting character). + $tag = ''; if ($options['group_by1'] != null && $options['group_by1'] != 'no_grouping') { // We have group_by1. diff --git a/WEB-INF/lib/ttRoleHelper.class.php b/WEB-INF/lib/ttRoleHelper.class.php index a3753d238..fa6739a5b 100644 --- a/WEB-INF/lib/ttRoleHelper.class.php +++ b/WEB-INF/lib/ttRoleHelper.class.php @@ -1,30 +1,6 @@ fetchRow(); - if ($val['id']) + if (isset($val['id']) && $val['id']) return $val; } return false; @@ -115,7 +91,7 @@ static function getRoleByRank($rank) { if (!is_a($res, 'PEAR_Error')) { $val = $res->fetchRow(); - if ($val['id']) + if (isset($val['id']) && $val['id']) return $val['id']; } return false; @@ -130,6 +106,7 @@ static function update($fields) { $org_id = $user->org_id; $id = (int)$fields['id']; + $name_part = $rank_part = $descr_part = $status_part = $rights_part = ''; if (isset($fields['name'])) $name_part = 'name = '.$mdb2->quote($fields['name']); if (isset($fields['rank'])) $rank_part = ', `rank` = '.(int)$fields['rank']; if (isset($fields['description'])) $descr_part = ', description = '.$mdb2->quote($fields['description']); @@ -191,8 +168,8 @@ static function createPredefinedRoles($group_id, $lang) $rights_client = 'view_client_reports,view_client_invoices,manage_own_settings'; $rights_user = 'track_own_time,track_own_expenses,view_own_reports,view_own_charts,view_own_projects,view_own_tasks,manage_own_settings,view_users'; - $rights_supervisor = $rights_user.',track_time,track_expenses,view_reports,approve_reports,approve_timesheets,view_charts,view_own_clients,override_punch_mode,override_date_lock,override_own_date_lock,swap_roles,update_work'; - $rights_comanager = $rights_supervisor.',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_work,bid_on_work'; + $rights_supervisor = $rights_user.',track_time,track_expenses,view_reports,approve_reports,approve_timesheets,view_charts,view_own_clients,override_punch_mode,override_date_lock,override_own_date_lock,swap_roles'; + $rights_comanager = $rights_supervisor.',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'; $rights_manager = $rights_comanager.',manage_features,manage_advanced_settings,manage_roles,export_data,approve_all_reports,approve_own_timesheets,manage_subgroups'; // Active roles. diff --git a/WEB-INF/lib/ttTaskHelper.class.php b/WEB-INF/lib/ttTaskHelper.class.php index 71a520025..e93d3d669 100644 --- a/WEB-INF/lib/ttTaskHelper.class.php +++ b/WEB-INF/lib/ttTaskHelper.class.php @@ -107,7 +107,14 @@ static function delete($task_id) { // Mark the task as deleted. $sql = "update tt_tasks set status = NULL where id = $task_id"; $affected = $mdb2->exec($sql); - return (!is_a($affected, 'PEAR_Error')); + if (is_a($affected, 'PEAR_Error')) + return false; + + // Update entities_modified, too. + if (!ttGroupHelper::updateEntitiesModified()) + return false; + + return true; } // insert function inserts a new task into database. @@ -161,6 +168,11 @@ static function insert($fields) return false; } } + + // Update entities_modified, too. + if (!ttGroupHelper::updateEntitiesModified()) + return false; + return $last_id; } @@ -242,6 +254,11 @@ static function update($fields) } } } + + // Update entities_modified, too. + if (!ttGroupHelper::updateEntitiesModified()) + return false; + return true; } diff --git a/WEB-INF/lib/ttTeamHelper.class.php b/WEB-INF/lib/ttTeamHelper.class.php index 9bb538912..e12c2575b 100644 --- a/WEB-INF/lib/ttTeamHelper.class.php +++ b/WEB-INF/lib/ttTeamHelper.class.php @@ -1,33 +1,8 @@ query($sql); $result = array(); if (!is_a($res, 'PEAR_Error')) { - $dt = new DateAndTime(DB_DATEFORMAT); while ($val = $res->fetchRow()) { $result[] = $val; } diff --git a/WEB-INF/lib/ttTemplateHelper.class.php b/WEB-INF/lib/ttTemplateHelper.class.php index 6e6ad0a02..0b9fcf92a 100644 --- a/WEB-INF/lib/ttTemplateHelper.class.php +++ b/WEB-INF/lib/ttTemplateHelper.class.php @@ -48,6 +48,10 @@ static function delete($id) { if (is_a($affected, 'PEAR_Error')) return false; + // Update entities_modified, too. + if (!ttGroupHelper::updateEntitiesModified()) + return false; + return true; } @@ -87,6 +91,11 @@ static function insert($fields) { return false; } } + + // Update entities_modified, too. + if (!ttGroupHelper::updateEntitiesModified()) + return false; + return $last_id; } @@ -129,6 +138,10 @@ static function update($fields) { die($affected->getMessage()); } + // Update entities_modified, too. + if (!ttGroupHelper::updateEntitiesModified()) + return false; + return true; } diff --git a/WEB-INF/lib/ttTimeHelper.class.php b/WEB-INF/lib/ttTimeHelper.class.php index 58f2f4222..6019b2f12 100644 --- a/WEB-INF/lib/ttTimeHelper.class.php +++ b/WEB-INF/lib/ttTimeHelper.class.php @@ -2,8 +2,6 @@ /* Copyright (c) Anuko International Ltd. https://www.anuko.com License: See license.txt */ -import('DateAndTime'); - // The ttTimeHelper is a class to help with time-related values. class ttTimeHelper { @@ -119,13 +117,13 @@ static function isValidDuration($value) { } // postedDurationToMinutes - converts a value representing a duration - // (usually enetered in a form by a user) to an integer number of minutes. + // (usually entered in a form by a user) to an integer number of minutes. // // Parameters: // $duration - user entered duration string. Valid strings are: // 3 or 3h - means 3 hours. Note: h and m letters are not localized. // 0.25 or 0.25h or .25 or .25h - means a quarter of hour. - // 0,25 or 0,25h or ,25 or ,25h - same as above for users with comma ad decimal mark. + // 0,25 or 0,25h or ,25 or ,25h - same as above for users with comma as decimal mark. // 1:30 - means 1 hour 30 minutes. // 25m - means 25 minutes. // $max - maximum number of minutes that is valid. @@ -200,6 +198,9 @@ static function minutesToDuration($minutes, $abbreviate = false) { // toMinutes - converts a time string in format 00:00 to a number of minutes. static function toMinutes($value) { + if (is_null($value)) + return 0; + $signMultiplier = ttStartsWith($value, '-') ? -1 : 1; if ($signMultiplier == -1) $value = ltrim($value, '-'); @@ -414,8 +415,8 @@ static function insert($fields) $org_id = $user->org_id; $date = $fields['date']; - $start = $fields['start']; - $finish = $fields['finish']; + $start = isset($fields['start']) ? $fields['start'] : null; + $finish = isset($fields['finish']) ? $fields['finish'] : null ; $duration = isset($fields['duration']) ? $fields['duration'] : null; if ($duration) { $minutes = ttTimeHelper::postedDurationToMinutes($duration); @@ -439,8 +440,7 @@ static function insert($fields) if (!$billable) $billable = 0; if (!$paid) $paid = 0; - - if ($duration) { + if (!is_null($duration)) { $sql = "insert into tt_log (user_id, group_id, org_id, date, duration, client_id, project_id, task_id, invoice_id, comment, billable, paid, created, created_ip, created_by) ". "values ($user_id, $group_id, $org_id, ".$mdb2->quote($date).", '$duration', ".$mdb2->quote($client).", ".$mdb2->quote($project).", ".$mdb2->quote($task).", ".$mdb2->quote($invoice).", ".$mdb2->quote($note).", $billable, $paid $created_v)"; $affected = $mdb2->exec($sql); @@ -449,7 +449,6 @@ static function insert($fields) } else { $duration = ttTimeHelper::toDuration($start, $finish); if ($duration === false) $duration = 0; - if (!$duration && ttTimeHelper::getUncompleted($user_id)) return false; $sql = "insert into tt_log (user_id, group_id, org_id, date, start, duration, client_id, project_id, task_id, invoice_id, comment, billable, paid, created, created_ip, created_by) ". "values ($user_id, $group_id, $org_id, ".$mdb2->quote($date).", '$start', '$duration', ".$mdb2->quote($client).", ".$mdb2->quote($project).", ".$mdb2->quote($task).", ".$mdb2->quote($invoice).", ".$mdb2->quote($note).", $billable, $paid $created_v)"; @@ -500,9 +499,9 @@ static function update($fields) $finish = ttTimeHelper::to24HourFormat($finish); if ('00:00' == $finish) $finish = '24:00'; - if ($start) $duration = ''; + if ($start) $duration = null; - if ($duration) { + if (!is_null($duration)) { $sql = "UPDATE tt_log set start = NULL, duration = '$duration', client_id = ".$mdb2->quote($client).", project_id = ".$mdb2->quote($project).", task_id = ".$mdb2->quote($task).", ". "comment = ".$mdb2->quote($note)."$billable_part $paid_part $modified_part, date = '$date' WHERE id = $id and user_id = $user_id and group_id = $group_id and org_id = $org_id"; $affected = $mdb2->exec($sql); @@ -512,9 +511,6 @@ static function update($fields) $duration = ttTimeHelper::toDuration($start, $finish); if ($duration === false) $duration = 0; - $uncompleted = ttTimeHelper::getUncompleted($user_id); - if (!$duration && $uncompleted && ($uncompleted['id'] != $id)) - return false; $sql = "UPDATE tt_log SET start = '$start', duration = '$duration', client_id = ".$mdb2->quote($client).", project_id = ".$mdb2->quote($project).", task_id = ".$mdb2->quote($task).", ". "comment = ".$mdb2->quote($note)."$billable_part $paid_part $modified_part, date = '$date' WHERE id = $id and user_id = $user_id and group_id = $group_id and org_id = $org_id"; @@ -570,7 +566,8 @@ static function getTimeForDay($date) { $org_id = $user->org_id; $sql = "select sum(time_to_sec(duration)) as sm from tt_log". - " where user_id = $user_id and group_id = $group_id and org_id = $org_id and date = '$date' and status = 1"; + " where user_id = $user_id and group_id = $group_id and org_id = $org_id". + " and date = ".$mdb2->quote($date)." and status = 1"; $res = $mdb2->query($sql); if (!is_a($res, 'PEAR_Error')) { $val = $res->fetchRow(); @@ -582,17 +579,17 @@ static function getTimeForDay($date) { // getTimeForWeek - gets total time for a user for a given week. static function getTimeForWeek($date) { global $user; - import('Period'); + import('ttPeriod'); $mdb2 = getConnection(); $user_id = $user->getUser(); $group_id = $user->getGroup(); $org_id = $user->org_id; - $period = new Period(INTERVAL_THIS_WEEK, $date); + $period = new ttPeriod($date, INTERVAL_THIS_WEEK); $sql = "select sum(time_to_sec(duration)) as sm from tt_log". " where user_id = $user_id and group_id = $group_id and org_id = $org_id". - " and date >= '".$period->getStartDate(DB_DATEFORMAT)."' and date <= '".$period->getEndDate(DB_DATEFORMAT)."' and status = 1"; + " and date >= '".$period->getStartDate()."' and date <= '".$period->getEndDate()."' and status = 1"; $res = $mdb2->query($sql); if (!is_a($res, 'PEAR_Error')) { $val = $res->fetchRow(); @@ -604,17 +601,17 @@ static function getTimeForWeek($date) { // getTimeForMonth - gets total time for a user for a given month. static function getTimeForMonth($date) { global $user; - import('Period'); + import('ttPeriod'); $mdb2 = getConnection(); $user_id = $user->getUser(); $group_id = $user->getGroup(); $org_id = $user->org_id; - $period = new Period(INTERVAL_THIS_MONTH, $date); + $period = new ttPeriod($date, INTERVAL_THIS_MONTH); $sql = "select sum(time_to_sec(duration)) as sm from tt_log". " where user_id = $user_id and group_id = $group_id and org_id = $org_id". - " and date >= '".$period->getStartDate(DB_DATEFORMAT)."' and date <= '".$period->getEndDate(DB_DATEFORMAT)."' and status = 1"; + " and date >= '".$period->getStartDate()."' and date <= '".$period->getEndDate()."' and status = 1"; $res = $mdb2->query($sql); if (!is_a($res, 'PEAR_Error')) { $val = $res->fetchRow(); @@ -625,10 +622,45 @@ static function getTimeForMonth($date) { // getUncompleted - retrieves an uncompleted record for user, if one exists. static function getUncompleted($user_id) { + + $user_id = (int) $user_id; // Protection against sql injection. + + global $user; + $group_id = $user->getGroup(); + $org_id = $user->org_id; + $mdb2 = getConnection(); $sql = "select id, start, date from tt_log". - " where user_id = $user_id and start is not null and time_to_sec(duration) = 0 and status = 1"; + " where user_id = $user_id and start is not null and time_to_sec(duration) = 0 and status = 1". + " and group_id = $group_id and org_id = $org_id"; + $res = $mdb2->query($sql); + if (!is_a($res, 'PEAR_Error')) { + if (!$res->numRows()) { + return false; + } + if ($val = $res->fetchRow()) { + return $val; + } + } + return false; + } + + // getFirstUncompletedForDate - retrieves first found uncompleted record for user for a specific date, if one exists. + static function getFirstUncompletedForDate($user_id, $date) { + + $user_id = (int) $user_id; // Protection against sql injection. + + global $user; + $group_id = $user->getGroup(); + $org_id = $user->org_id; + + $mdb2 = getConnection(); + + $sql = "select id, start, date from tt_log". + " where user_id = $user_id and start is not null and time_to_sec(duration) = 0 and status = 1". + " and group_id = $group_id and org_id = $org_id and date = ".$mdb2->quote($date). + " order by start"; // Ordering by start time to get the earliest uncompleted for date. $res = $mdb2->query($sql); if (!is_a($res, 'PEAR_Error')) { if (!$res->numRows()) { @@ -692,6 +724,8 @@ static function overlaps($user_id, $date, $start, $finish, $record_id = null) { // getRecord - retrieves a time record identified by its id. static function getRecord($id) { global $user; + + $id = (int) $id; // Protection against sql injections. $user_id = $user->getUser(); $group_id = $user->getGroup(); diff --git a/WEB-INF/lib/ttTimesheetHelper.class.php b/WEB-INF/lib/ttTimesheetHelper.class.php index 17119edac..16d740291 100644 --- a/WEB-INF/lib/ttTimesheetHelper.class.php +++ b/WEB-INF/lib/ttTimesheetHelper.class.php @@ -1,33 +1,11 @@ date_format, $fields['start_date']); - $start = $start_date->toString(DB_DATEFORMAT); + $start_date = new ttDate($fields['start_date'], $user->getDateFormat()); + $start = $start_date->toString(); - $end_date = new DateAndTime($user->date_format, $fields['end_date']); - $end = $end_date->toString(DB_DATEFORMAT); + $end_date = new ttDate($fields['end_date'], $user->getDateFormat()); + $end = $end_date->toString(); $created_part = ', now(), '.$mdb2->quote($_SERVER['REMOTE_ADDR']).', '.$user->id; @@ -88,9 +66,11 @@ static function createTimesheet($fields) $last_id = $mdb2->lastInsertID('tt_timesheets', 'id'); // Associate tt_log items with timesheet. + $client_id = $project_id = null; if (isset($fields['client'])) $client_id = (int) $fields['client_id']; if (isset($fields['project_id'])) $project_id = (int) $fields['project_id']; // sql parts. + $client_part = $project_part = ''; if ($client_id) $client_part = " and client_id = $client_id"; if ($project_id) $project_part = " and project_id = $project_id"; @@ -115,6 +95,7 @@ static function getActiveTimesheets() $group_id = $user->getGroup(); $org_id = $user->org_id; + $filePart = $fileJoin = ''; if ($user->isPluginEnabled('at')) { $filePart = ', if(Sub1.entity_id is null, 0, 1) as has_files'; $fileJoin = " left join (select distinct entity_id from tt_files". @@ -148,6 +129,7 @@ static function getInactiveTimesheets() $group_id = $user->getGroup(); $org_id = $user->org_id; + $filePart = $fileJoin = ''; if ($user->isPluginEnabled('at')) { $filePart = ', if(Sub1.entity_id is null, 0, 1) as has_files'; $fileJoin = " left join (select distinct entity_id from tt_files". @@ -462,16 +444,18 @@ static function timesheetItemsExist($fields) { $group_id = $user->getGroup(); $org_id = $user->org_id; + $client_id = $project_id = null; if (isset($fields['client_id'])) $client_id = (int) $fields['client_id']; if (isset($fields['project_id'])) $project_id = (int) $fields['project_id']; - $start_date = new DateAndTime($user->date_format, $fields['start_date']); - $start = $start_date->toString(DB_DATEFORMAT); + $start_date = new ttDate($fields['start_date'], $user->getDateFormat()); + $start = $start_date->toString(); - $end_date = new DateAndTime($user->date_format, $fields['end_date']); - $end = $end_date->toString(DB_DATEFORMAT); + $end_date = new ttDate($fields['end_date'], $user->getDateFormat()); + $end = $end_date->toString(); // sql parts. + $client_part = $project_part = ''; if ($client_id) $client_part = " and client_id = $client_id"; if ($project_id) $project_part = " and project_id = $project_id"; @@ -500,18 +484,20 @@ static function overlaps($fields) { $group_id = $user->getGroup(); $org_id = $user->org_id; + $client_id = $project_id = null; if (isset($fields['client_id'])) $client_id = (int) $fields['client_id']; if (isset($fields['project_id'])) $project_id = (int) $fields['project_id']; - $start_date = new DateAndTime($user->date_format, $fields['start_date']); - $start = $start_date->toString(DB_DATEFORMAT); + $start_date = new ttDate($fields['start_date'], $user->getDateFormat()); + $start = $start_date->toString(); $quoted_start = $mdb2->quote($start); - $end_date = new DateAndTime($user->date_format, $fields['end_date']); - $end = $end_date->toString(DB_DATEFORMAT); + $end_date = new ttDate($fields['end_date'], $user->getDateFormat()); + $end = $end_date->toString(); $quoted_end = $mdb2->quote($end); // sql parts. + $client_part = $project_part = ''; if ($client_id) $client_part = " and client_id = $client_id"; if ($project_id) $project_part = " and project_id = $project_id"; @@ -523,7 +509,7 @@ static function overlaps($fields) { $res = $mdb2->query($sql); if (!is_a($res, 'PEAR_Error')) { $val = $res->fetchRow(); - if ($val['id']) { + if (isset($val['id']) && $val['id']) { return true; } } @@ -560,16 +546,16 @@ static function getMatchingTimesheets($options) { // Determine start and end dates. $dateFormat = $user->getDateFormat(); - if ($options['period']) - $period = new Period($options['period'], new DateAndTime($dateFormat)); + if (isset($options['period']) && $options['period']) + $period = new ttPeriod(new ttDate(), $options['period']); else { - $period = new Period(); + $period = new ttPeriod(new ttDate()); $period->setPeriod( - new DateAndTime($dateFormat, $options['period_start']), - new DateAndTime($dateFormat, $options['period_end'])); + new ttDate($options['period_start'], $dateFormat), + new ttDate($options['period_end'], $dateFormat)); } - $start = $period->getStartDate(DB_DATEFORMAT); - $end = $period->getEndDate(DB_DATEFORMAT); + $start = $period->getStartDate(); + $end = $period->getEndDate(); $result = false; $sql = "select id, name from tt_timesheets". diff --git a/WEB-INF/lib/ttUser.class.php b/WEB-INF/lib/ttUser.class.php index cf1065f06..fedf3dafb 100644 --- a/WEB-INF/lib/ttUser.class.php +++ b/WEB-INF/lib/ttUser.class.php @@ -9,6 +9,7 @@ import('form.Form'); import('form.ActionForm'); import('ttTemplateHelper'); +import('ttDate'); class ttUser { var $login = null; // User login. @@ -49,6 +50,7 @@ class ttUser { var $config = null; // Comma-separated list of miscellaneous config options. var $configHelper = null; // An instance of ttConfigHelper class. var $custom_css = null; // Custom css. + var $custom_translation = null; // Custom translation. var $custom_logo = 0; // Whether to use a custom logo for group. var $lock_spec = null; // Cron specification for record locking. @@ -60,6 +62,9 @@ class ttUser { var $behalfUser = null; // A ttBehalfUser instance with on behalf user attributes. var $behalfGroup = null; // A ttGroup instance with on behalf group attributes. + var $initialized = false; // Set to true when an object is initialized. + + // Constructor. function __construct($login, $id = null) { if (!$login && !$id) { @@ -67,90 +72,99 @@ function __construct($login, $id = null) { return; } - $mdb2 = getConnection(); - - $sql = "SELECT u.id, u.login, u.name, u.group_id, u.role_id, r.rank, r.name as role_name, r.rights, u.client_id,". - " u.quota_percent, u.email, g.org_id, g.group_key, g.name as group_name, g.currency, g.lang, g.decimal_mark, g.date_format,". - " g.time_format, g.week_start, g.tracking_mode, g.project_required, g.record_type,". - " g.bcc_email, g.allow_ip, g.password_complexity, g.plugins, g.config, g.lock_spec, g.custom_css, g.holidays, g.workday_minutes, g.custom_logo". - " FROM tt_users u LEFT JOIN tt_groups g ON (u.group_id = g.id) LEFT JOIN tt_roles r on (r.id = u.role_id) WHERE "; - if ($id) - $sql .= "u.id = $id"; - else - $sql .= "u.login = ".$mdb2->quote($login); - $sql .= " AND u.status = 1"; + try { + $mdb2 = getConnection(); + + $sql = "SELECT u.id, u.login, u.name, u.group_id, u.role_id, r.rank, r.name as role_name, r.rights, u.client_id,". + " u.quota_percent, u.email, g.org_id, g.group_key, g.name as group_name, g.currency, g.lang, g.decimal_mark, g.date_format,". + " g.time_format, g.week_start, g.tracking_mode, g.project_required, g.record_type,". + " g.bcc_email, g.allow_ip, g.password_complexity, g.plugins, g.config, g.lock_spec, g.custom_css, g.custom_translation,". + " g.holidays, g.workday_minutes, g.custom_logo". + " FROM tt_users u LEFT JOIN tt_groups g ON (u.group_id = g.id) LEFT JOIN tt_roles r on (r.id = u.role_id) WHERE "; + if ($id) + $sql .= "u.id = $id"; + else + $sql .= "u.login = ".$mdb2->quote($login); + $sql .= " AND u.status = 1"; + + $res = $mdb2->query($sql); + if (is_a($res, 'PEAR_Error')) { + return; + } - $res = $mdb2->query($sql); - if (is_a($res, 'PEAR_Error')) { - return; - } + $val = $res->fetchRow(); + if ($val['id'] > 0) { + $this->login = $val['login']; + $this->name = $val['name']; + $this->id = $val['id']; + $this->org_id = $val['org_id']; + $this->group_id = $val['group_id']; + $this->group_key = $val['group_key']; + if ($this->org_id == $this->group_key) $this->org_key = $val['group_key']; + $this->role_id = $val['role_id']; + $this->role_name = $val['role_name']; + $this->rights = explode(',', $val['rights']); + $this->rank = $val['rank']; + $this->client_id = $val['client_id']; + $this->is_client = $this->client_id && !in_array('track_own_time', $this->rights); + if ($val['quota_percent']) $this->quota_percent = $val['quota_percent']; + $this->email = $val['email']; + if ($val['lang']) $this->lang = $val['lang']; + if ($val['decimal_mark']) $this->decimal_mark = $val['decimal_mark']; + $this->date_format = $val['date_format']; + $this->time_format = $val['time_format']; + $this->week_start = $val['week_start']; + $this->tracking_mode = $val['tracking_mode']; + $this->project_required = $val['project_required']; + $this->record_type = $val['record_type']; + $this->bcc_email = $val['bcc_email']; + $this->allow_ip = $val['allow_ip']; + $this->password_complexity = $val['password_complexity']; + $this->group_name = $val['group_name']; + $this->currency = $val['currency']; + $this->plugins = $val['plugins']; + $this->lock_spec = $val['lock_spec']; + $this->holidays = $val['holidays']; + $this->workday_minutes = $val['workday_minutes']; + $this->custom_logo = $val['custom_logo']; + + // TODO: refactor this. + $this->config = $val['config']; + $this->configHelper = new ttConfigHelper($val['config']); + + // Set user config options. + $this->punch_mode = $this->configHelper->getDefinedValue('punch_mode'); + $this->allow_overlap = $this->configHelper->getDefinedValue('allow_overlap'); + + $this->custom_css = $val['custom_css']; + $this->custom_translation = $val['custom_translation']; + + // Set "on behalf" id and name (user). + if (isset($_SESSION['behalf_id'])) { + $this->behalf_id = $_SESSION['behalf_id']; + $this->behalf_name = $_SESSION['behalf_name']; + + $this->behalfUser = new ttBehalfUser($this->behalf_id, $this->org_id); + } + // Set "on behalf" id and name (group). + if (isset($_SESSION['behalf_group_id'])) { + $this->behalf_group_id = $_SESSION['behalf_group_id']; + $this->behalf_group_name = $_SESSION['behalf_group_name']; - $val = $res->fetchRow(); - if ($val['id'] > 0) { - $this->login = $val['login']; - $this->name = $val['name']; - $this->id = $val['id']; - $this->org_id = $val['org_id']; - $this->group_id = $val['group_id']; - $this->group_key = $val['group_key']; - if ($this->org_id == $this->group_key) $this->org_key = $val['group_key']; - $this->role_id = $val['role_id']; - $this->role_name = $val['role_name']; - $this->rights = explode(',', $val['rights']); - $this->rank = $val['rank']; - $this->client_id = $val['client_id']; - $this->is_client = $this->client_id && !in_array('track_own_time', $this->rights); - if ($val['quota_percent']) $this->quota_percent = $val['quota_percent']; - $this->email = $val['email']; - if ($val['lang']) $this->lang = $val['lang']; - if ($val['decimal_mark']) $this->decimal_mark = $val['decimal_mark']; - $this->date_format = $val['date_format']; - $this->time_format = $val['time_format']; - $this->week_start = $val['week_start']; - $this->tracking_mode = $val['tracking_mode']; - $this->project_required = $val['project_required']; - $this->record_type = $val['record_type']; - $this->bcc_email = $val['bcc_email']; - $this->allow_ip = $val['allow_ip']; - $this->password_complexity = $val['password_complexity']; - $this->group_name = $val['group_name']; - $this->currency = $val['currency']; - $this->plugins = $val['plugins']; - $this->lock_spec = $val['lock_spec']; - $this->holidays = $val['holidays']; - $this->workday_minutes = $val['workday_minutes']; - $this->custom_logo = $val['custom_logo']; - - // TODO: refactor this. - $this->config = $val['config']; - $this->configHelper = new ttConfigHelper($val['config']); - - // Set user config options. - $this->punch_mode = $this->configHelper->getDefinedValue('punch_mode'); - $this->allow_overlap = $this->configHelper->getDefinedValue('allow_overlap'); - - $this->custom_css = $val['custom_css']; - - // Set "on behalf" id and name (user). - if (isset($_SESSION['behalf_id'])) { - $this->behalf_id = $_SESSION['behalf_id']; - $this->behalf_name = $_SESSION['behalf_name']; - - $this->behalfUser = new ttBehalfUser($this->behalf_id, $this->org_id); + $this->behalfGroup = new ttGroup($this->behalf_group_id, $this->org_id); + } } - // Set "on behalf" id and name (group). - if (isset($_SESSION['behalf_group_id'])) { - $this->behalf_group_id = $_SESSION['behalf_group_id']; - $this->behalf_group_name = $_SESSION['behalf_group_name']; - $this->behalfGroup = new ttGroup($this->behalf_group_id, $this->org_id); - } + $this->initialized = true; + } + catch (Exception $e) { + error_log("Exception raised in ttUser constructor: ".$e->getMessage()); } } // getUser returns user id on behalf of whom the current user is operating. function getUser() { - return ($this->behalfUser ? $this->behalfUser->id : $this->id); + return (isset($this->behalfUser) ? $this->behalfUser->id : $this->id); } // getName returns user name on behalf of whom the current user is operating. @@ -168,6 +182,11 @@ function getEmail() { return ($this->behalfUser ? $this->behalfUser->email : $this->email); } + // getPasswordComplexity returns password complexity for active user. + function getPasswordComplexity() { + return ($this->behalfUser ? $this->behalfUser->password_complexity : $this->password_complexity); + } + // The getGroup returns group id on behalf of which the current user is operating. function getGroup() { return ($this->behalfGroup ? $this->behalfGroup->id : $this->group_id); @@ -284,6 +303,11 @@ function getCustomCss() { return ($this->behalfGroup ? $this->behalfGroup->custom_css : $this->custom_css); } + // getCustomTranslation returns custom translation for active group. + function getCustomTranslation() { + return ($this->behalfGroup ? $this->behalfGroup->custom_translation : $this->custom_translation); + } + // can - determines whether user has a right to do something. function can($do_something) { return in_array($do_something, $this->rights); @@ -297,7 +321,7 @@ function isClient() { // isPluginEnabled checks whether a plugin is enabled for user. function isPluginEnabled($plugin) { - return in_array($plugin, explode(',', $this->getPlugins())); + return in_array($plugin, explode(',', $this->getPlugins() ? $this->getPlugins() : '')); } // isOptionEnabled checks whether a config option is enabled for user. @@ -335,7 +359,7 @@ function getAssignedProjects($options = null) // Do a query with inner join to get assigned projects. $sql = "select p.id, p.name, p.description, p.tasks, upb.rate $filePart from tt_projects p $fileJoin". " inner join tt_user_project_binds upb on (upb.user_id = $user_id and upb.project_id = p.id and upb.status = 1)". - " where p.group_id = $group_id and p.org_id = $org_id and p.status = 1 order by p.name"; + " where p.group_id = $group_id and p.org_id = $org_id and p.status = 1 order by upper(p.name)"; $res = $mdb2->query($sql); if (!is_a($res, 'PEAR_Error')) { $bindTemplatesWithProjects = isset($options['include_templates']) && $options['include_templates']; @@ -436,7 +460,7 @@ function isDateLocked($date) // Calculate the last occurrence of a lock. $last = tdCron::getLastOccurrence($this->getLockSpec(), time()); - $lockdate = new DateAndTime(DB_DATEFORMAT, strftime('%Y-%m-%d', $last)); + $lockdate = new ttDate(strftime('%Y-%m-%d', $last)); if ($date->before($lockdate)) return true; @@ -554,9 +578,10 @@ function getGroupsForDropdown() { // addGroupToDropdown is a recursive function to populate a tree of groups, used with getGroupsForDropdown(). function addGroupToDropdown(&$groups, $group_id, $subgroup_level) { $name = ''; - // Add indentation markup to indicate subdirectory level. + // Add indentation markup to indicate a subdirectory level. for ($i = 0; $i < $subgroup_level; $i++) { - $name .= '🛑'; // Unicode stop sign. + $name .= '*'; + // $name .= '🛑'; // Unicode stop sign. Does not display properly in Chrome 98. } if ($subgroup_level) $name .= ' '; // Add an extra space. $name .= ttGroupHelper::getGroupName($group_id); @@ -587,11 +612,8 @@ function getSubgroups($group_id = null) { return $groups; } - // getUserDetails function is used to manage users in group and returns user details. - // At the moment, the function is used for user edits and deletes. - function getUserDetails($user_id) { - if (!$this->can('manage_users')) return false; - + // getUserDetails function returns user details. + function getUserDetails($user_id) { $mdb2 = getConnection(); $group_id = $this->getGroup(); $org_id = $this->org_id; @@ -599,7 +621,7 @@ function getUserDetails($user_id) { // Determine max rank. If we are searching in on behalf group // then rank restriction does not apply. - $max_rank = $this->behalfGroup ? MAX_RANK : $this->rank; + $max_rank = $this->behalfGroup ? MAX_RANK + 1 : $this->rank; $sql = "select u.id, u.name, u.login, u.role_id, u.client_id, u.status, u.rate, u.quota_percent, u.email from tt_users u". " left join tt_roles r on (u.role_id = r.id)". @@ -678,7 +700,8 @@ function updateGroup($fields) { $name_part = $description_part = $currency_part = $lang_part = $decimal_mark_part = $date_format_part = $time_format_part = $week_start_part = $tracking_mode_part = $project_required_part = $record_type_part = $bcc_email_part = $allow_ip_part = - $plugins_part = $config_part = $custom_css_part = $lock_spec_part = $holidays_part = $workday_minutes_part = ''; + $password_complexity_part = $plugins_part = $config_part = $custom_css_part = $custom_translation_part = $lock_spec_part = + $holidays_part = $workday_minutes_part = ''; if (isset($fields['name'])) $name_part = ', name = '.$mdb2->quote($fields['name']); if (isset($fields['description'])) $description_part = ', description = '.$mdb2->quote($fields['description']); if (isset($fields['currency'])) $currency_part = ', currency = '.$mdb2->quote($fields['currency']); @@ -694,9 +717,11 @@ function updateGroup($fields) { if (isset($fields['record_type'])) $record_type_part = ', record_type = '.(int) $fields['record_type']; if (isset($fields['bcc_email'])) $bcc_email_part = ', bcc_email = '.$mdb2->quote($fields['bcc_email']); if (isset($fields['allow_ip'])) $allow_ip_part = ', allow_ip = '.$mdb2->quote($fields['allow_ip']); + if (isset($fields['password_complexity'])) $password_complexity_part = ', password_complexity = '.$mdb2->quote($fields['password_complexity']); if (isset($fields['plugins'])) $plugins_part = ', plugins = '.$mdb2->quote($fields['plugins']); if (isset($fields['config'])) $config_part = ', config = '.$mdb2->quote($fields['config']); if (isset($fields['custom_css'])) $custom_css_part = ', custom_css = '.$mdb2->quote($fields['custom_css']); + if (isset($fields['custom_translation'])) $custom_translation_part = ', custom_translation = '.$mdb2->quote($fields['custom_translation']); if (isset($fields['lock_spec'])) $lock_spec_part = ', lock_spec = '.$mdb2->quote($fields['lock_spec']); if (isset($fields['holidays'])) $holidays_part = ', holidays = '.$mdb2->quote($fields['holidays']); if (isset($fields['workday_minutes'])) $workday_minutes_part = ', workday_minutes = '.$mdb2->quote($fields['workday_minutes']); @@ -704,11 +729,17 @@ function updateGroup($fields) { $parts = trim($name_part.$description_part.$currency_part.$lang_part.$decimal_mark_part.$date_format_part. $time_format_part.$week_start_part.$tracking_mode_part.$project_required_part.$record_type_part. - $bcc_email_part.$allow_ip_part.$plugins_part.$config_part.$custom_css_part.$lock_spec_part.$holidays_part.$workday_minutes_part.$modified_part, ','); + $bcc_email_part.$allow_ip_part.$password_complexity_part.$plugins_part.$config_part.$custom_css_part. + $custom_translation_part.$lock_spec_part.$holidays_part.$workday_minutes_part.$modified_part, ','); $sql = "update tt_groups set $parts where id = $group_id and org_id = $this->org_id"; $affected = $mdb2->exec($sql); - if (is_a($affected, 'PEAR_Error')) return false; + if (is_a($affected, 'PEAR_Error')) + return false; + + // Update entities_modified, too. + if (!ttGroupHelper::updateEntitiesModified()) + return false; return true; } @@ -740,8 +771,8 @@ function markUserDeleted($user_id) { if (is_a($affected, 'PEAR_Error')) return false; - // Mark user custom fields as deleted, - require_once('plugins/CustomFields.class.php'); + // Mark user custom fields as deleted. + require_once(APP_DIR.'/plugins/CustomFields.class.php'); $entity_type = CustomFields::ENTITY_USER; $modified_part = ', modified = now(), modified_ip = '.$mdb2->quote($_SERVER['REMOTE_ADDR']).', modified_by = '.$mdb2->quote($this->id); $sql = "update tt_entity_custom_fields set status = null $modified_part". @@ -758,7 +789,9 @@ function markUserDeleted($user_id) { if (is_a($affected, 'PEAR_Error')) return false; - + // Update entities_modified, too. + if (!ttGroupHelper::updateEntitiesModified()) + return false; return true; } @@ -828,7 +861,7 @@ function getUserPartForHeader() { $user_part .= ', '.htmlspecialchars($this->behalf_group_name).''; } else { if ($this->group_name) // Note: we did not require group names in the past. - $user_part .= ', '.$this->group_name; + $user_part .= ', '.htmlspecialchars($this->group_name); } return $user_part; } diff --git a/WEB-INF/lib/ttUserHelper.class.php b/WEB-INF/lib/ttUserHelper.class.php index 95be4fbb5..2aeada43c 100644 --- a/WEB-INF/lib/ttUserHelper.class.php +++ b/WEB-INF/lib/ttUserHelper.class.php @@ -68,7 +68,8 @@ static function getUserIdByTmpRef($ref) { if (!is_a($res, 'PEAR_Error')) { $val = $res->fetchRow(); - return $val['user_id']; + if ($val) + return $val['user_id']; } return false; } @@ -95,27 +96,31 @@ static function insert($fields, $hash = true) { $mdb2->quote($fields['name']).", ".$mdb2->quote($fields['login']). ", $password, $group_id, $org_id, ".$mdb2->quote($fields['role_id']).", ".$mdb2->quote($fields['client_id']).", $rate, $quota_percent, ".$mdb2->quote($email).", now() $created_ip_v $created_by_v)"; $affected = $mdb2->exec($sql); + if (is_a($affected, 'PEAR_Error')) + return false; // Now deal with project assignment. - if (!is_a($affected, 'PEAR_Error')) { - $last_id = $mdb2->lastInsertID('tt_users', 'id'); - $projects = isset($fields['projects']) ? $fields['projects'] : array(); - if (count($projects) > 0) { - // We have at least one project assigned. Insert corresponding entries in tt_user_project_binds table. - foreach($projects as $p) { - if(!isset($p['rate'])) - $p['rate'] = 0; - else - $p['rate'] = str_replace(',', '.', $p['rate']); - - $sql = "insert into tt_user_project_binds (project_id, user_id, group_id, org_id, rate, status)". - " values(".$p['id'].", $last_id, $group_id, $org_id, ".$p['rate'].", 1)"; - $affected = $mdb2->exec($sql); - } + $last_id = $mdb2->lastInsertID('tt_users', 'id'); + $projects = isset($fields['projects']) ? $fields['projects'] : array(); + if (count($projects) > 0) { + // We have at least one project assigned. Insert corresponding entries in tt_user_project_binds table. + foreach($projects as $p) { + if(!isset($p['rate'])) + $p['rate'] = 0; + else + $p['rate'] = str_replace(',', '.', $p['rate']); + + $sql = "insert into tt_user_project_binds (project_id, user_id, group_id, org_id, rate, status)". + " values(".$p['id'].", $last_id, $group_id, $org_id, ".$p['rate'].", 1)"; + $affected = $mdb2->exec($sql); } - return $last_id; } - return false; + + // Update entities_modified, too. + if (!ttGroupHelper::updateEntitiesModified()) + return false; + + return $last_id; } // update - updates a user in database. @@ -232,6 +237,11 @@ static function update($user_id, $fields) { } } } + + // Update entities_modified, too. + if (!ttGroupHelper::updateEntitiesModified()) + return false; + return true; } @@ -282,6 +292,10 @@ static function delete($user_id) { if (is_a($affected, 'PEAR_Error')) return false; + // Update entities_modified, too. + if (!ttGroupHelper::updateEntitiesModified()) + return false; + return true; } diff --git a/WEB-INF/lib/ttWeekViewHelper.class.php b/WEB-INF/lib/ttWeekViewHelper.class.php index aee8c8d61..cd1e4a952 100644 --- a/WEB-INF/lib/ttWeekViewHelper.class.php +++ b/WEB-INF/lib/ttWeekViewHelper.class.php @@ -1,30 +1,6 @@ isOptionEnabled('weekends'); $includeNotes = $user->isOptionEnabled('week_notes'); // Construct the first row for a brand new entry. $dataArray[] = array('row_id' => null,'label' => $i18n->get('form.week.new_entry').':'); // Insert row. // Insert empty cells with proper control ids. - for ($i = 0; $i < 7; $i++) { - $control_id = '0_'. $dayHeaders[$i]; - $dataArray[0][$dayHeaders[$i]] = array('control_id' => $control_id, 'tt_log_id' => null,'duration' => null); + if ($includeWeekends) { + for ($i = 0; $i < 7; $i++) { + $control_id = '0_'. $dayHeaders[$i]; + $dataArray[0][$dayHeaders[$i]] = array('control_id' => $control_id, 'tt_log_id' => null,'duration' => null); + } + } else { + for ($i = 0; $i < 5; $i++) { + $control_id = '0_'. $dayHeaders[$i]; + $dataArray[0][$dayHeaders[$i]] = array('control_id' => $control_id, 'tt_log_id' => null,'duration' => null); + } } if ($includeNotes) { // Construct the second row for daily comments for a brand new entry. $dataArray[] = array('row_id' => null,'label' => $i18n->get('label.notes').':'); // Insert row. // Insert empty cells with proper control ids. - for ($i = 0; $i < 7; $i++) { - $control_id = '1_'. $dayHeaders[$i]; - $dataArray[1][$dayHeaders[$i]] = array('control_id' => $control_id, 'tt_log_id' => null,'note' => null); + if ($includeWeekends) { + for ($i = 0; $i < 7; $i++) { + $control_id = '1_'. $dayHeaders[$i]; + $dataArray[1][$dayHeaders[$i]] = array('control_id' => $control_id, 'tt_log_id' => null,'note' => null); + } + } else { + for ($i = 0; $i < 5; $i++) { + $control_id = '1_'. $dayHeaders[$i]; + $dataArray[1][$dayHeaders[$i]] = array('control_id' => $control_id, 'tt_log_id' => null,'note' => null); + } } } @@ -229,18 +220,32 @@ static function getDataForWeekView($records, $dayHeaders) { $dataArray[] = array('row_id' => $row_id,'label' => ttWeekViewHelper::makeRowLabel($record)); $pos = ttWeekViewHelper::findRow($row_id, $dataArray); // Insert empty cells with proper control ids. - for ($i = 0; $i < 7; $i++) { - $control_id = $pos.'_'. $dayHeaders[$i]; - $dataArray[$pos][$dayHeaders[$i]] = array('control_id' => $control_id, 'tt_log_id' => null,'duration' => null); + if ($includeWeekends) { + for ($i = 0; $i < 7; $i++) { + $control_id = $pos.'_'. $dayHeaders[$i]; + $dataArray[$pos][$dayHeaders[$i]] = array('control_id' => $control_id, 'tt_log_id' => null,'duration' => null); + } + } else { + for ($i = 0; $i < 5; $i++) { + $control_id = $pos.'_'. $dayHeaders[$i]; + $dataArray[$pos][$dayHeaders[$i]] = array('control_id' => $control_id, 'tt_log_id' => null,'duration' => null); + } } // Insert row for comments. if ($includeNotes) { $dataArray[] = array('row_id' => $row_id.'_notes','label' => $i18n->get('label.notes').':'); $pos++; // Insert empty cells with proper control ids. - for ($i = 0; $i < 7; $i++) { - $control_id = $pos.'_'. $dayHeaders[$i]; - $dataArray[$pos][$dayHeaders[$i]] = array('control_id' => $control_id, 'tt_log_id' => null,'note' => null); + if ($includeWeekends) { + for ($i = 0; $i < 7; $i++) { + $control_id = $pos.'_'. $dayHeaders[$i]; + $dataArray[$pos][$dayHeaders[$i]] = array('control_id' => $control_id, 'tt_log_id' => null,'note' => null); + } + } else { + for ($i = 0; $i < 5; $i++) { + $control_id = $pos.'_'. $dayHeaders[$i]; + $dataArray[$pos][$dayHeaders[$i]] = array('control_id' => $control_id, 'tt_log_id' => null,'note' => null); + } } $pos--; } @@ -266,23 +271,23 @@ static function prePopulateFromPastWeeks($startDate, $dayHeaders) { global $i18n; // First, determine past week start and end dates. - $objDate = new DateAndTime(DB_DATEFORMAT, $startDate); - $objDate->decDay(7); - $pastWeekStartDate = $objDate->toString(DB_DATEFORMAT); - $objDate->incDay(6); - $pastWeekEndDate = $objDate->toString(DB_DATEFORMAT); + $objDate = new ttDate($startDate); + $objDate->decrementDay(7); + $pastWeekStartDate = $objDate->toString(); + $objDate->incrementDay(6); + $pastWeekEndDate = $objDate->toString(); unset($objDate); // Obtain past week(s) records. $records = ttWeekViewHelper::getRecordsForInterval($pastWeekStartDate, $pastWeekEndDate); - // Handle potential situation of no records by re-trying for up to 4 more previous weeks (after a long vacation, etc.). + // Handle a potential situation of no records by re-trying for up to 4 more previous weeks (after a long vacation, etc.). if (!$records) { for ($i = 0; $i < 4; $i++) { - $objDate = new DateAndTime(DB_DATEFORMAT, $pastWeekStartDate); - $objDate->decDay(7); - $pastWeekStartDate = $objDate->toString(DB_DATEFORMAT); - $objDate->incDay(6); - $pastWeekEndDate = $objDate->toString(DB_DATEFORMAT); + $objDate = new ttDate($pastWeekStartDate); + $objDate->decrementDay(7); + $pastWeekStartDate = $objDate->toString(); + $objDate->incrementDay(6); + $pastWeekEndDate = $objDate->toString(); unset($objDate); $records = ttWeekViewHelper::getRecordsForInterval($pastWeekStartDate, $pastWeekEndDate); @@ -291,21 +296,38 @@ static function prePopulateFromPastWeeks($startDate, $dayHeaders) { } } + // We will need this for iterations below. + $includeWeekends = $user->isOptionEnabled('weekends'); + // Construct the first row for a brand new entry. $dataArray[] = array('row_id' => null,'label' => $i18n->get('form.week.new_entry').':'); // Insert row. // Insert empty cells with proper control ids. - for ($i = 0; $i < 7; $i++) { - $control_id = '0_'. $dayHeaders[$i]; - $dataArray[0][$dayHeaders[$i]] = array('control_id' => $control_id, 'tt_log_id' => null,'duration' => null); + if ($includeWeekends) { + for ($i = 0; $i < 7; $i++) { + $control_id = '0_'. $dayHeaders[$i]; + $dataArray[0][$dayHeaders[$i]] = array('control_id' => $control_id, 'tt_log_id' => null,'duration' => null); + } + } else { + for ($i = 0; $i < 5; $i++) { + $control_id = '0_'. $dayHeaders[$i]; + $dataArray[0][$dayHeaders[$i]] = array('control_id' => $control_id, 'tt_log_id' => null,'duration' => null); + } } $includeNotes = $user->isOptionEnabled('week_notes'); if ($includeNotes) { // Construct the second row for daily comments for a brand new entry. $dataArray[] = array('row_id' => null,'label' => $i18n->get('label.notes').':'); // Insert row. // Insert empty cells with proper control ids. - for ($i = 0; $i < 7; $i++) { - $control_id = '1_'. $dayHeaders[$i]; - $dataArray[1][$dayHeaders[$i]] = array('control_id' => $control_id, 'tt_log_id' => null,'note' => null); + if ($includeWeekends) { + for ($i = 0; $i < 7; $i++) { + $control_id = '1_'. $dayHeaders[$i]; + $dataArray[1][$dayHeaders[$i]] = array('control_id' => $control_id, 'tt_log_id' => null,'note' => null); + } + } else { + for ($i = 0; $i < 5; $i++) { + $control_id = '1_'. $dayHeaders[$i]; + $dataArray[1][$dayHeaders[$i]] = array('control_id' => $control_id, 'tt_log_id' => null,'note' => null); + } } } @@ -320,18 +342,32 @@ static function prePopulateFromPastWeeks($startDate, $dayHeaders) { $dataArray[] = array('row_id' => $row_id,'label' => ttWeekViewHelper::makeRowLabel($record)); $pos = ttWeekViewHelper::findRow($row_id, $dataArray); // Insert empty cells with proper control ids. - for ($i = 0; $i < 7; $i++) { - $control_id = $pos.'_'. $dayHeaders[$i]; - $dataArray[$pos][$dayHeaders[$i]] = array('control_id' => $control_id, 'tt_log_id' => null,'duration' => null); + if ($includeWeekends) { + for ($i = 0; $i < 7; $i++) { + $control_id = $pos.'_'. $dayHeaders[$i]; + $dataArray[$pos][$dayHeaders[$i]] = array('control_id' => $control_id, 'tt_log_id' => null,'duration' => null); + } + } else { + for ($i = 0; $i < 5; $i++) { + $control_id = $pos.'_'. $dayHeaders[$i]; + $dataArray[$pos][$dayHeaders[$i]] = array('control_id' => $control_id, 'tt_log_id' => null,'duration' => null); + } } // Insert row for comments. if ($includeNotes) { $dataArray[] = array('row_id' => $row_id.'_notes','label' => $i18n->get('label.notes').':'); $pos++; // Insert empty cells with proper control ids. - for ($i = 0; $i < 7; $i++) { - $control_id = $pos.'_'. $dayHeaders[$i]; - $dataArray[$pos][$dayHeaders[$i]] = array('control_id' => $control_id, 'tt_log_id' => null,'note' => null); + if ($includeWeekends) { + for ($i = 0; $i < 7; $i++) { + $control_id = $pos.'_'. $dayHeaders[$i]; + $dataArray[$pos][$dayHeaders[$i]] = array('control_id' => $control_id, 'tt_log_id' => null,'note' => null); + } + } else { + for ($i = 0; $i < 5; $i++) { + $control_id = $pos.'_'. $dayHeaders[$i]; + $dataArray[$pos][$dayHeaders[$i]] = array('control_id' => $control_id, 'tt_log_id' => null,'note' => null); + } } $pos--; } @@ -369,11 +405,13 @@ static function getDayTotals($dataArray, $dayHeaders) { // Insert label. global $i18n; $dayTotals['label'] = $i18n->get('label.day_total').':'; - + foreach($dayHeaders as $dayHeader) { + $dayTotals[$dayHeader] = 0; + } foreach ($dataArray as $row) { foreach($dayHeaders as $dayHeader) { if (array_key_exists($dayHeader, $row)) { - $minutes = ttTimeHelper::toMinutes($row[$dayHeader]['duration']); + $minutes = ttTimeHelper::toMinutes(@$row[$dayHeader]['duration']); $dayTotals[$dayHeader] += $minutes; } } @@ -388,23 +426,11 @@ static function getDayTotals($dataArray, $dayHeaders) { // getDayHeadersForWeek - obtains day column headers for week view, which are simply day numbers in month. static function getDayHeadersForWeek($start_date) { $dayHeaders = array(); - $objDate = new DateAndTime(DB_DATEFORMAT, $start_date); - $dayHeaders[] = (string) $objDate->getDate(); // It returns an int on first call. - if (strlen($dayHeaders[0]) == 1) // Which is an implementation detail of DateAndTime class. - $dayHeaders[0] = '0'.$dayHeaders[0]; // Add a 0 for single digit day. - $objDate->incDay(); - $dayHeaders[] = $objDate->getDate(); // After incDay it returns a string with leading 0, when necessary. - $objDate->incDay(); - $dayHeaders[] = $objDate->getDate(); - $objDate->incDay(); - $dayHeaders[] = $objDate->getDate(); - $objDate->incDay(); - $dayHeaders[] = $objDate->getDate(); - $objDate->incDay(); - $dayHeaders[] = $objDate->getDate(); - $objDate->incDay(); - $dayHeaders[] = $objDate->getDate(); - unset($objDate); + $objDate = new ttDate($start_date); + for ($i = 0; $i < 7; $i++) { + $dayHeaders[] = $objDate->getDay(); + $objDate->incrementDay(); + } return $dayHeaders; } @@ -412,12 +438,11 @@ static function getDayHeadersForWeek($start_date) { static function getLockedDaysForWeek($start_date) { global $user; $lockedDays = array(); - $objDate = new DateAndTime(DB_DATEFORMAT, $start_date); + $objDate = new ttDate($start_date); for ($i = 0; $i < 7; $i++) { $lockedDays[] = $user->isDateLocked($objDate); - $objDate->incDay(); + $objDate->incrementDay(); } - unset($objDate); return $lockedDays; } @@ -428,8 +453,9 @@ static function getLockedDaysForWeek($start_date) { static function makeRowIdentifier($record) { global $user; // Start with client. + $row_identifier = ''; if ($user->isPluginEnabled('cl')) - $row_identifier = $record['client_id'] ? 'cl:'.$record['client_id'] : ''; + $row_identifier .= $record['client_id'] ? 'cl:'.$record['client_id'] : ''; // Add billable flag. if (!empty($row_identifier)) $row_identifier .= ','; $row_identifier .= 'bl:'.$record['billable']; @@ -439,7 +465,7 @@ static function makeRowIdentifier($record) { $row_identifier .= $record['task_id'] ? ',ts:'.$record['task_id'] : ''; // Add custom field parts. global $custom_fields; - if ($custom_fields && $custom_fields->timeFields) { + 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) @@ -460,8 +486,9 @@ static function makeRowIdentifier($record) { static function makeRowLabel($record) { global $user; // Start with client. + $label = ''; if ($user->isPluginEnabled('cl')) - $label = $record['client']; + $label .= $record['client']; // Add project. if (!empty($label) && !empty($record['project'])) $label .= ' - '; @@ -473,7 +500,7 @@ static function makeRowLabel($record) { // Add custom field parts. global $custom_fields; - if ($custom_fields && $custom_fields->timeFields) { + if (isset($custom_fields) && $custom_fields->timeFields) { foreach ($custom_fields->timeFields as $timeField) { $field_name = 'time_field_'.$timeField['id']; $field_value = $record[$field_name]; @@ -513,18 +540,15 @@ static function parseFromWeekViewRow($row_id, $field_label, $base64decode = fals // dateFromDayHeader calculates date from start date and day header in week view. static function dateFromDayHeader($start_date, $day_header) { - $objDate = new DateAndTime(DB_DATEFORMAT, $start_date); - $currentDayHeader = (string) $objDate->getDate(); // It returns an int on first call. - if (strlen($currentDayHeader) == 1) // Which is an implementation detail of DateAndTime class. - $currentDayHeader = '0'.$currentDayHeader; // Add a 0 for single digit day. - $i = 1; - while ($currentDayHeader != $day_header && $i < 7) { - // Iterate through remaining days to find a match. - $objDate->incDay(); - $currentDayHeader = $objDate->getDate(); // After incDay it returns a string with leading 0, when necessary. - $i++; + $objDate = new ttDate($start_date); + $currentDayHeader = $objDate->getDay(); + for ($i = 0; $i < 7; $i++) { + if ($currentDayHeader == $day_header) + break; + $objDate->incrementDay(); + $currentDayHeader = $objDate->getDay(); } - return $objDate->toString(DB_DATEFORMAT); + return $objDate->toString(); } // insertDurationFromWeekView - inserts a new record in log tables from a week view post. @@ -534,12 +558,14 @@ static function insertDurationFromWeekView($fields, $custom_fields, $err) { // Determine date for a new entry. $entry_date = ttWeekViewHelper::dateFromDayHeader($fields['start_date'], $fields['day_header']); - $objEntryDate = new DateAndTime(DB_DATEFORMAT, $entry_date); + $objEntryDate = new ttDate($entry_date); // Prohibit creating entries in future. if (!$user->isOptionEnabled('future_entries') && $fields['browser_today']) { - $objBrowserToday = new DateAndTime(DB_DATEFORMAT, $fields['browser_today']); - if ($objEntryDate->after($objBrowserToday)) { + $objBrowserToday = new ttDate($fields['browser_today']); + $server_tomorrow = new ttDate(); + $server_tomorrow->incrementDay(); + if ($objEntryDate->after($objBrowserToday) || $objEntryDate->after($server_tomorrow)) { $err->add($i18n->get('error.future_date')); return false; } @@ -553,7 +579,7 @@ static function insertDurationFromWeekView($fields, $custom_fields, $err) { $fields4insert['billable'] = ttWeekViewHelper::parseFromWeekViewRow($fields['row_id'], 'bl'); $fields4insert['project'] = ttWeekViewHelper::parseFromWeekViewRow($fields['row_id'], 'pr'); $fields4insert['task'] = ttWeekViewHelper::parseFromWeekViewRow($fields['row_id'], 'ts'); - $fields4insert['note'] = $fields['note']; + $fields4insert['note'] = isset($fields['note']) ? $fields['note'] : null; // Try to insert a record. $id = ttTimeHelper::insert($fields4insert); diff --git a/WEB-INF/lib/ttWorkHelper.class.php b/WEB-INF/lib/ttWorkHelper.class.php deleted file mode 100644 index 6ddf87513..000000000 --- a/WEB-INF/lib/ttWorkHelper.class.php +++ /dev/null @@ -1,1617 +0,0 @@ -errors = &$errors; - - $this->work_server_uri = defined('WORK_SERVER_URI') ? WORK_SERVER_URI : "https://www.anuko.com/work/"; - - // Note: at some point a need will arise for API versioning. - // When this happens, we will append an API version number to the end of URI, - // for example: register_0_1 instead of register. - // This should theoretically allow a remote work server to be able to work with - // a complete variety of deployed clients, including those without versions. - $this->register_uri = $this->work_server_uri.'register'; // register_0_0 - $this->put_own_work_item_uri = $this->work_server_uri.'putownworkitem'; - $this->get_own_work_item_uri = $this->work_server_uri.'getownworkitem'; - $this->get_own_work_item_offer_uri = $this->work_server_uri.'getownworkitemoffer'; - $this->get_own_work_item_offers_uri = $this->work_server_uri.'getownworkitemoffers'; - $this->get_own_work_items_uri = $this->work_server_uri.'getownworkitems'; - $this->get_available_work_items_uri = $this->work_server_uri.'getavailableworkitems'; - $this->get_available_work_item_uri = $this->work_server_uri.'getavailableworkitem'; - $this->delete_own_work_item_uri = $this->work_server_uri.'deleteownworkitem'; - $this->accept_own_work_item_offer_uri = $this->work_server_uri.'acceptownworkitemoffer'; - $this->decline_own_work_item_offer_uri = $this->work_server_uri.'declineownworkitemoffer'; - $this->update_own_work_item_uri = $this->work_server_uri.'updateownworkitem'; - $this->update_own_work_item_on_offer_uri = $this->work_server_uri.'updateownworkitemonoffer'; - $this->put_own_offer_uri = $this->work_server_uri.'putownoffer'; - $this->get_own_offer_uri = $this->work_server_uri.'getownoffer'; - $this->get_own_offers_uri = $this->work_server_uri.'getownoffers'; - - $this->get_available_offers_uri = $this->work_server_uri.'getavailableoffers'; - $this->get_available_offer_uri = $this->work_server_uri.'getavailableoffer'; - $this->get_group_items_uri = $this->work_server_uri.'getgroupitems'; - $this->delete_own_offer_uri = $this->work_server_uri.'deleteownoffer'; - $this->update_own_offer_uri = $this->work_server_uri.'updateownoffer'; - $this->send_message_to_work_owner_uri = $this->work_server_uri.'sendmessagetoworkowner'; - $this->send_message_to_offer_owner_uri = $this->work_server_uri.'sendmessagetoofferowner'; - $this->checkSiteRegistration(); - } - - // checkSiteRegistration - obtains site id and key from local database. - // If not found, it tries to register with remote work server. - function checkSiteRegistration() { - global $i18n; - global $user; - $mdb2 = getConnection(); - - // Obtain site id. - $sql = "select param_value as id from tt_site_config where param_name = 'worksite_id'"; - $res = $mdb2->query($sql); - $val = $res->fetchRow(); - if (!$val) { - // No site id found, need to register. - $group_id = $user->getGroup(); - $org_id = $user->org_id; - $fields = array('lang' => urlencode($user->lang), - 'name' => urlencode('time tracker'), - 'origin' => urlencode('time tracker source'), - 'org_id' => urlencode($org_id), - 'group_id' => urlencode($group_id), - 'group_name' => urlencode(base64_encode($user->getGroupName())), - 'user_id' => urlencode($user->getUser()), - 'user_name' => urlencode(base64_encode($user->getName())), - 'user_email' => urlencode(base64_encode($user->getEmail())), - 'user_ip' => urlencode($_SERVER['REMOTE_ADDR'])); - - // Urlify the data for the POST. - foreach($fields as $key=>$value) { $fields_string .= $key.'='.$value.'&'; } - $fields_string = rtrim($fields_string, '&'); - - // Open connection. - $ch = curl_init(); - - // Set the url, number of POST vars, POST data. - curl_setopt($ch, CURLOPT_URL, $this->register_uri); - curl_setopt($ch, CURLOPT_POST, true); - curl_setopt($ch, CURLOPT_POSTFIELDS, $fields_string); - curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); - - // Execute a post request. - $result = curl_exec($ch); - - // Close connection. - curl_close($ch); - - if (!$result) { - $this->errors->add($i18n->get('error.remote_work')); - return false; - } - - $result_array = json_decode($result, true); - - // Check for errors. - $call_status = $result_array['call_status']; - if (!$call_status) { - $this->errors->add($i18n->get('error.remote_work')); - return false; - } - if ($call_status['code'] != TT_CURL_SUCCESS) { - $this->errors->add($call_status['error']); - return false; - } - - $reg_status = $result_array['reg_status']; // Registration status. - if ($reg_status['site_id'] && $reg_status['site_key']) { - $this->site_id = $reg_status['site_id']; - $this->site_key = $reg_status['site_key']; - - // Registration successful. Store id and key locally for future use. - $sql = "insert into tt_site_config values('worksite_id', $this->site_id, now(), null)"; - $mdb2->exec($sql); - $sql = "insert into tt_site_config values('worksite_key', ".$mdb2->quote($this->site_key).", now(), null)"; - $mdb2->exec($sql); - } else { - $this->errors->add($i18n->get('error.remote_work')); - } - } else { - // Site id found. - $this->site_id = $val['id']; - - // Obtain site key. - $sql = "select param_value as site_key from tt_site_config where param_name = 'worksite_key'"; - $res = $mdb2->query($sql); - $val = $res->fetchRow(); - $this->site_key = $val['site_key']; - } - } - - // putOwnWorkItem - publishes a work item in remote work server. - // - // Note about some fields using additional base64 encoding. - // There is a problem with data posted by curl calls on the server side for - // some languages. Data arrives corrupted. - // - // For example: consider a case for a single Russian letter ф in the subject field. - // UTF-8 encoding for ф: 0xD1 0x84 (2 bytes). - // urlencoded ф: %D1%84 - a string of 6 characters. - // If we use a curl call like here with only urlencoded ф, what arrives on the server in POST is "C3 91 C2 84" - // no idea what it is. - // - // A workaround for now is to use use an additional base64 encoding for all text fields, - // which are decoded back to utf-8 strings on the server side. - function putOwnWorkItem($fields) { - global $i18n; - global $user; - $mdb2 = getConnection(); - - $group_id = $user->getGroup(); - $org_id = $user->org_id; - $curl_fields = array('lang' => urlencode($user->lang), - 'site_id' => urlencode($this->site_id), - 'site_key' => urlencode($this->site_key), - 'org_id' => urlencode($org_id), - 'org_key' => urlencode($user->getOrgKey()), - 'group_id' => urlencode($group_id), - 'group_name' => urlencode(base64_encode($user->getGroupName())), - 'group_key' => urlencode($user->getGroupKey()), - 'offer_id' => urlencode($fields['offer_id']), - 'type' => urlencode($fields['type']), - 'subject' => urlencode(base64_encode($fields['subject'])), - 'descr_short' => urlencode(base64_encode($fields['descr_short'])), - 'descr_long' => urlencode(base64_encode($fields['descr_long'])), - 'currency' => urlencode($fields['currency']), - 'amount' => urlencode($fields['amount']), - 'created_ip' => urlencode($_SERVER['REMOTE_ADDR']), - 'created_by' => urlencode($user->getUser()), - 'created_by_name' => urlencode(base64_encode($user->getName())), - 'created_by_email' => urlencode(base64_encode($user->getEmail())) - ); - - // url-ify the data for the POST. - foreach($curl_fields as $key=>$value) { $fields_string .= $key.'='.$value.'&'; } - $fields_string = rtrim($fields_string, '&'); - - // Open connection. - $ch = curl_init(); - - // Set the url, number of POST vars, POST data. - curl_setopt($ch, CURLOPT_URL, $this->put_own_work_item_uri); - curl_setopt($ch, CURLOPT_POST, true); - curl_setopt($ch, CURLOPT_POSTFIELDS, $fields_string); - curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); - - // Execute a post request. - $result = curl_exec($ch); - - // Close connection. - curl_close($ch); - - if (!$result) { - $this->errors->add($i18n->get('error.remote_work')); - return false; - } - - $result_array = json_decode($result, true); - - // Check for errors. - $call_status = $result_array['call_status']; - if (!$call_status) { - $this->errors->add($i18n->get('error.remote_work')); - return false; - } - if ($call_status['code'] != TT_CURL_SUCCESS) { - $this->errors->add($call_status['error']); - return false; - } - - return true; - } - - // updateOwnWorkItem - updates a work item in remote work server. - function updateOwnWorkItem($fields) { - global $i18n; - global $user; - $mdb2 = getConnection(); - - $group_id = $user->getGroup(); - $org_id = $user->org_id; - - $curl_fields = array('lang' => urlencode($user->lang), - 'site_id' => urlencode($this->site_id), - 'site_key' => urlencode($this->site_key), - 'org_id' => urlencode($org_id), - 'org_key' => urlencode($user->getOrgKey()), - 'group_id' => urlencode($group_id), - 'group_name' => urlencode(base64_encode($user->getGroupName())), - 'group_key' => urlencode($user->getGroupKey()), - 'work_id' => urlencode($fields['work_id']), - 'type' => urlencode($fields['type']), - 'subject' => urlencode(base64_encode($fields['subject'])), - 'descr_short' => urlencode(base64_encode($fields['descr_short'])), - 'descr_long' => urlencode(base64_encode($fields['descr_long'])), - 'currency' => urlencode($fields['currency']), - 'amount' => urlencode($fields['amount']), - 'modified_ip' => urlencode($_SERVER['REMOTE_ADDR']), - 'modified_by' => urlencode($user->getUser()), - 'modified_by_name' => urlencode(base64_encode($user->getName())), - 'modified_by_email' => urlencode(base64_encode($user->getEmail())) - ); - - // url-ify the data for the POST. - foreach($curl_fields as $key=>$value) { $fields_string .= $key.'='.$value.'&'; } - $fields_string = rtrim($fields_string, '&'); - - // Open connection. - $ch = curl_init(); - - // Set the url, number of POST vars, POST data. - curl_setopt($ch, CURLOPT_URL, $this->update_own_work_item_uri); - curl_setopt($ch, CURLOPT_POST, true); - curl_setopt($ch, CURLOPT_POSTFIELDS, $fields_string); - curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); - - // Execute a post request. - $result = curl_exec($ch); - - // Close connection. - curl_close($ch); - - if (!$result) { - $this->errors->add($i18n->get('error.remote_work')); - return false; - } - - $result_array = json_decode($result, true); - - // Check for errors. - $call_status = $result_array['call_status']; - if (!$call_status) { - $this->errors->add($i18n->get('error.remote_work')); - return false; - } - if ($call_status['code'] != TT_CURL_SUCCESS) { - $this->errors->add($call_status['error']); - return false; - } - - return true; - } - - // updateOwnWorkItemOnOffer - updates own work item posted on offer in remote work server. - function updateOwnWorkItemOnOffer($fields) { - global $i18n; - global $user; - $mdb2 = getConnection(); - - $group_id = $user->getGroup(); - $org_id = $user->org_id; - - $curl_fields = array('lang' => urlencode($user->lang), - 'site_id' => urlencode($this->site_id), - 'site_key' => urlencode($this->site_key), - 'org_id' => urlencode($org_id), - 'org_key' => urlencode($user->getOrgKey()), - 'group_id' => urlencode($group_id), - 'group_name' => urlencode(base64_encode($user->getGroupName())), - 'group_key' => urlencode($user->getGroupKey()), - 'work_id' => urlencode($fields['work_id']), - 'descr_short' => urlencode(base64_encode($fields['descr_short'])), - 'descr_long' => urlencode(base64_encode($fields['descr_long'])), - 'modified_ip' => urlencode($_SERVER['REMOTE_ADDR']), - 'modified_by' => urlencode($user->getUser()), - 'modified_by_name' => urlencode(base64_encode($user->getName())), - 'modified_by_email' => urlencode(base64_encode($user->getEmail())) - ); - - // url-ify the data for the POST. - foreach($curl_fields as $key=>$value) { $fields_string .= $key.'='.$value.'&'; } - $fields_string = rtrim($fields_string, '&'); - - // Open connection. - $ch = curl_init(); - - // Set the url, number of POST vars, POST data. - curl_setopt($ch, CURLOPT_URL, $this->update_own_work_item_on_offer_uri); - curl_setopt($ch, CURLOPT_POST, true); - curl_setopt($ch, CURLOPT_POSTFIELDS, $fields_string); - curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); - - // Execute a post request. - $result = curl_exec($ch); - - // Close connection. - curl_close($ch); - - if (!$result) { - $this->errors->add($i18n->get('error.remote_work')); - return false; - } - - $result_array = json_decode($result, true); - - // Check for errors. - $call_status = $result_array['call_status']; - if (!$call_status) { - $this->errors->add($i18n->get('error.remote_work')); - return false; - } - if ($call_status['code'] != TT_CURL_SUCCESS) { - $this->errors->add($call_status['error']); - return false; - } - - return true; - } - - // getOwnWorkItems - obtains a list of work items this group is currently outsourcing. - function getOwnWorkItems() { - global $i18n; - global $user; - $mdb2 = getConnection(); - - $group_id = $user->getGroup(); - $org_id = $user->org_id; - - $curl_fields = array('lang' => urlencode($user->lang), - 'site_id' => urlencode($this->site_id), - 'site_key' => urlencode($this->site_key), - 'org_id' => urlencode($org_id), - 'org_key' => urlencode($user->getOrgKey()), - 'group_id' => urlencode($group_id), - 'group_key' => urlencode($user->getGroupKey()) - ); - - // url-ify the data for the POST. - foreach($curl_fields as $key=>$value) { $fields_string .= $key.'='.$value.'&'; } - $fields_string = rtrim($fields_string, '&'); - - // Open connection. - $ch = curl_init(); - - // Set the url, number of POST vars, POST data. - curl_setopt($ch, CURLOPT_URL, $this->get_own_work_items_iri); - curl_setopt($ch, CURLOPT_POST, true); - curl_setopt($ch, CURLOPT_POSTFIELDS, $fields_string); - curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); - - // Execute a post request. - $result = curl_exec($ch); - - // Close connection. - curl_close($ch); - - if (!$result) { - $this->errors->add($i18n->get('error.remote_work')); - return false; - } - - $result_array = json_decode($result, true); - - // Check for errors. - $call_status = $result_array['call_status']; - if (!$call_status) { - $this->errors->add($i18n->get('error.remote_work')); - return false; - } - if ($call_status['code'] != TT_CURL_SUCCESS) { - $this->errors->add($call_status['error']); - return false; - } - - $active_work = $result_array['active_work']; - return $active_work; - } - - // getAvailableWorkItems - obtains a list of available work items this group can bid on. - function getAvailableWorkItems() { - global $i18n; - global $user; - $mdb2 = getConnection(); - - $group_id = $user->getGroup(); - $org_id = $user->org_id; - - $curl_fields = array('lang' => urlencode($user->lang), - 'site_id' => urlencode($this->site_id), - 'site_key' => urlencode($this->site_key), - 'org_id' => urlencode($org_id) - ); - - // url-ify the data for the POST. - foreach($curl_fields as $key=>$value) { $fields_string .= $key.'='.$value.'&'; } - $fields_string = rtrim($fields_string, '&'); - - // Open connection. - $ch = curl_init(); - - // Set the url, number of POST vars, POST data. - curl_setopt($ch, CURLOPT_URL, $this->get_available_work_items_uri); - curl_setopt($ch, CURLOPT_POST, true); - curl_setopt($ch, CURLOPT_POSTFIELDS, $fields_string); - curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); - - // Execute a post request. - $result = curl_exec($ch); - - // Close connection. - curl_close($ch); - - if (!$result) { - $this->errors->add($i18n->get('error.remote_work')); - return false; - } - - $result_array = json_decode($result, true); - - // Check for errors. - $call_status = $result_array['call_status']; - if (!$call_status) { - $this->errors->add($i18n->get('error.remote_work')); - return false; - } - if ($call_status['code'] != TT_CURL_SUCCESS) { - $this->errors->add($call_status['error']); - return false; - } - - $available_work = $result_array['available_work']; - return $available_work; - } - - // getOwnWorkItem - gets work item details from remote work server. - function getOwnWorkItem($work_id) { - global $i18n; - global $user; - $mdb2 = getConnection(); - - $group_id = $user->getGroup(); - $org_id = $user->org_id; - - $curl_fields = array('lang' => urlencode($user->lang), - 'site_id' => urlencode($this->site_id), - 'site_key' => urlencode($this->site_key), - 'org_id' => urlencode($org_id), - 'org_key' => urlencode($user->getOrgKey()), - 'group_id' => urlencode($group_id), - 'group_key' => urlencode($user->getGroupKey()), - 'work_id' => urlencode($work_id)); - - // url-ify the data for the POST. - foreach($curl_fields as $key=>$value) { $fields_string .= $key.'='.$value.'&'; } - $fields_string = rtrim($fields_string, '&'); - - // Open connection. - $ch = curl_init(); - - // Set the url, number of POST vars, POST data. - curl_setopt($ch, CURLOPT_URL, $this->get_own_work_item_uri); - curl_setopt($ch, CURLOPT_POST, true); - curl_setopt($ch, CURLOPT_POSTFIELDS, $fields_string); - curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); - - // Execute a post request. - $result = curl_exec($ch); - - // Close connection. - curl_close($ch); - - if (!$result) { - $this->errors->add($i18n->get('error.remote_work')); - return false; - } - - $result_array = json_decode($result, true); - - // Check for errors. - $call_status = $result_array['call_status']; - if (!$call_status) { - $this->errors->add($i18n->get('error.remote_work')); - return false; - } - if ($call_status['code'] != TT_CURL_SUCCESS) { - $this->errors->add($call_status['error']); - return false; - } - - $work_item = $result_array['work_item']; - return $work_item; - } - - // getOwnWorkItemOffers - gets offers on own work item from remote work server. - function getOwnWorkItemOffers($work_id) { - global $i18n; - global $user; - $mdb2 = getConnection(); - - $group_id = $user->getGroup(); - $org_id = $user->org_id; - - $curl_fields = array('lang' => urlencode($user->lang), - 'site_id' => urlencode($this->site_id), - 'site_key' => urlencode($this->site_key), - 'org_id' => urlencode($org_id), - 'org_key' => urlencode($user->getOrgKey()), - 'group_id' => urlencode($group_id), - 'group_key' => urlencode($user->getGroupKey()), - 'work_id' => urlencode($work_id)); - - // url-ify the data for the POST. - foreach($curl_fields as $key=>$value) { $fields_string .= $key.'='.$value.'&'; } - $fields_string = rtrim($fields_string, '&'); - - // Open connection. - $ch = curl_init(); - - // Set the url, number of POST vars, POST data. - curl_setopt($ch, CURLOPT_URL, $this->get_own_work_item_offers_uri); - curl_setopt($ch, CURLOPT_POST, true); - curl_setopt($ch, CURLOPT_POSTFIELDS, $fields_string); - curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); - - // Execute a post request. - $result = curl_exec($ch); - - // Close connection. - curl_close($ch); - - if (!$result) { - $this->errors->add($i18n->get('error.remote_work')); - return false; - } - - $result_array = json_decode($result, true); - - // Check for errors. - $call_status = $result_array['call_status']; - if (!$call_status) { - $this->errors->add($i18n->get('error.remote_work')); - return false; - } - if ($call_status['code'] != TT_CURL_SUCCESS) { - $this->errors->add($call_status['error']); - return false; - } - - $work_item_offers = $result_array['work_item_offers']; - return $work_item_offers; - } - - // deleteOwnWorkItem - deletes work item from remote work server. - function deleteOwnWorkItem($work_id) { - global $i18n; - global $user; - $mdb2 = getConnection(); - - $group_id = $user->getGroup(); - $org_id = $user->org_id; - - $curl_fields = array('lang' => urlencode($user->lang), - 'site_id' => urlencode($this->site_id), - 'site_key' => urlencode($this->site_key), - 'org_id' => urlencode($org_id), - 'org_key' => urlencode($user->getOrgKey()), - 'group_id' => urlencode($group_id), - 'group_key' => urlencode($user->getGroupKey()), - 'work_id' => urlencode($work_id), - 'modified_ip' => urlencode($_SERVER['REMOTE_ADDR']), - 'modified_by' => urlencode($user->getUser()), - 'modified_by_name' => urlencode(base64_encode($user->getName())), - 'modified_by_email' => urlencode(base64_encode($user->getEmail()))); - - // url-ify the data for the POST. - foreach($curl_fields as $key=>$value) { $fields_string .= $key.'='.$value.'&'; } - $fields_string = rtrim($fields_string, '&'); - - // Open connection. - $ch = curl_init(); - - // Set the url, number of POST vars, POST data. - curl_setopt($ch, CURLOPT_URL, $this->delete_own_work_item_uri); - curl_setopt($ch, CURLOPT_POST, true); - curl_setopt($ch, CURLOPT_POSTFIELDS, $fields_string); - curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); - - // Execute a post request. - $result = curl_exec($ch); - - // Close connection. - curl_close($ch); - - if (!$result) { - $this->errors->add($i18n->get('error.remote_work')); - return false; - } - - $result_array = json_decode($result, true); - - // Check for errors. - $call_status = $result_array['call_status']; - if (!$call_status) { - $this->errors->add($i18n->get('error.remote_work')); - return false; - } - if ($call_status['code'] != TT_CURL_SUCCESS) { - $this->errors->add($call_status['error']); - return false; - } - - return true; - } - - // putOwnOffer - publishes an offer in remote work server. - function putOwnOffer($fields) { - global $i18n; - global $user; - $mdb2 = getConnection(); - - $group_id = $user->getGroup(); - $org_id = $user->org_id; - $curl_fields = array('lang' => urlencode($user->lang), - 'site_id' => urlencode($this->site_id), - 'site_key' => urlencode($this->site_key), - 'org_id' => urlencode($org_id), - 'org_key' => urlencode($user->getOrgKey()), - 'group_id' => urlencode($group_id), - 'group_name' => urlencode(base64_encode($user->getGroupName())), - 'group_key' => urlencode($user->getGroupKey()), - 'work_id' => urlencode($fields['work_id']), - 'subject' => urlencode(base64_encode($fields['subject'])), - 'descr_short' => urlencode(base64_encode($fields['descr_short'])), - 'descr_long' => urlencode(base64_encode($fields['descr_long'])), - 'currency' => urlencode($fields['currency']), - 'amount' => urlencode($fields['amount']), - 'payment_info' => urlencode(base64_encode($fields['payment_info'])), - 'created_ip' => urlencode($_SERVER['REMOTE_ADDR']), - 'created_by' => urlencode($user->getUser()), - 'created_by_name' => urlencode(base64_encode($user->getName())), - 'created_by_email' => urlencode(base64_encode($user->getEmail())) - ); - - // url-ify the data for the POST. - foreach($curl_fields as $key=>$value) { $fields_string .= $key.'='.$value.'&'; } - $fields_string = rtrim($fields_string, '&'); - - // Open connection. - $ch = curl_init(); - - // Set the url, number of POST vars, POST data. - curl_setopt($ch, CURLOPT_URL, $this->put_own_offer_uri); - curl_setopt($ch, CURLOPT_POST, true); - curl_setopt($ch, CURLOPT_POSTFIELDS, $fields_string); - curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); - - // Execute a post request. - $result = curl_exec($ch); - - // Close connection. - curl_close($ch); - - if (!$result) { - $this->errors->add($i18n->get('error.remote_work')); - return false; - } - - $result_array = json_decode($result, true); - - // Check for errors. - $call_status = $result_array['call_status']; - if (!$call_status) { - $this->errors->add($i18n->get('error.remote_work')); - return false; - } - if ($call_status['code'] != TT_CURL_SUCCESS) { - $this->errors->add($call_status['error']); - return false; - } - - return true; - } - - // getOwnOffers - obtains a list of offers this group made available to other groups. - function getOwnOffers() { - global $i18n; - global $user; - $mdb2 = getConnection(); - - $group_id = $user->getGroup(); - $org_id = $user->org_id; - - $curl_fields = array('lang' => urlencode($user->lang), - 'site_id' => urlencode($this->site_id), - 'site_key' => urlencode($this->site_key), - 'org_id' => urlencode($org_id), - 'org_key' => urlencode($user->getOrgKey()), - 'group_id' => urlencode($group_id), - 'group_key' => urlencode($user->getGroupKey()) - ); - - // url-ify the data for the POST. - foreach($curl_fields as $key=>$value) { $fields_string .= $key.'='.$value.'&'; } - $fields_string = rtrim($fields_string, '&'); - - // Open connection. - $ch = curl_init(); - - // Set the url, number of POST vars, POST data. - curl_setopt($ch, CURLOPT_URL, $this->get_own_offers_uri); - curl_setopt($ch, CURLOPT_POST, true); - curl_setopt($ch, CURLOPT_POSTFIELDS, $fields_string); - curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); - - // Execute a post request. - $result = curl_exec($ch); - - // Close connection. - curl_close($ch); - - if (!$result) { - $this->errors->add($i18n->get('error.remote_work')); - return false; - } - - $result_array = json_decode($result, true); - - // Check for errors. - $call_status = $result_array['call_status']; - if (!$call_status) { - $this->errors->add($i18n->get('error.remote_work')); - return false; - } - if ($call_status['code'] != TT_CURL_SUCCESS) { - $this->errors->add($call_status['error']); - return false; - } - - $active_offers = $result_array['active_offers']; - return $active_offers; - } - - // getAvailableOffers - obtains a list of available offers from other organizations. - function getAvailableOffers() { - global $i18n; - global $user; - $mdb2 = getConnection(); - - $group_id = $user->getGroup(); - $org_id = $user->org_id; - - $curl_fields = array('lang' => urlencode($user->lang), - 'site_id' => urlencode($this->site_id), - 'site_key' => urlencode($this->site_key), - 'org_id' => urlencode($org_id) - ); - - // url-ify the data for the POST. - foreach($curl_fields as $key=>$value) { $fields_string .= $key.'='.$value.'&'; } - $fields_string = rtrim($fields_string, '&'); - - // Open connection. - $ch = curl_init(); - - // Set the url, number of POST vars, POST data. - curl_setopt($ch, CURLOPT_URL, $this->get_available_offers_uri); - curl_setopt($ch, CURLOPT_POST, true); - curl_setopt($ch, CURLOPT_POSTFIELDS, $fields_string); - curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); - - // Execute a post request. - $result = curl_exec($ch); - - // Close connection. - curl_close($ch); - - if (!$result) { - $this->errors->add($i18n->get('error.remote_work')); - return false; - } - - $result_array = json_decode($result, true); - - // Check for errors. - $call_status = $result_array['call_status']; - if (!$call_status) { - $this->errors->add($i18n->get('error.remote_work')); - return false; - } - if ($call_status['code'] != TT_CURL_SUCCESS) { - $this->errors->add($call_status['error']); - return false; - } - - $available_offers = $result_array['available_offers']; - return $available_offers; - } - - // getOwnOffer - gets offer details from remote work server. - function getOwnOffer($offer_id) { - global $i18n; - global $user; - $mdb2 = getConnection(); - - $group_id = $user->getGroup(); - $org_id = $user->org_id; - - $curl_fields = array('lang' => urlencode($user->lang), - 'site_id' => urlencode($this->site_id), - 'site_key' => urlencode($this->site_key), - 'org_id' => urlencode($org_id), - 'org_key' => urlencode($user->getOrgKey()), - 'group_id' => urlencode($group_id), - 'group_key' => urlencode($user->getGroupKey()), - 'offer_id' => urlencode($offer_id)); - - // url-ify the data for the POST. - foreach($curl_fields as $key=>$value) { $fields_string .= $key.'='.$value.'&'; } - $fields_string = rtrim($fields_string, '&'); - - // Open connection. - $ch = curl_init(); - - // Set the url, number of POST vars, POST data. - curl_setopt($ch, CURLOPT_URL, $this->get_own_offer_uri); - curl_setopt($ch, CURLOPT_POST, true); - curl_setopt($ch, CURLOPT_POSTFIELDS, $fields_string); - curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); - - // Execute a post request. - $result = curl_exec($ch); - - // Close connection. - curl_close($ch); - - if (!$result) { - $this->errors->add($i18n->get('error.remote_work')); - return false; - } - - $result_array = json_decode($result, true); - - // Check for errors. - $call_status = $result_array['call_status']; - if (!$call_status) { - $this->errors->add($i18n->get('error.remote_work')); - return false; - } - if ($call_status['code'] != TT_CURL_SUCCESS) { - $this->errors->add($call_status['error']); - return false; - } - - $offer = $result_array['offer']; - return $offer; - } - - // updateOwnOffer - updates an offer in remote work server. - function updateOwnOffer($fields) { - global $i18n; - global $user; - $mdb2 = getConnection(); - - $group_id = $user->getGroup(); - $org_id = $user->org_id; - - $curl_fields = array('lang' => urlencode($user->lang), - 'site_id' => urlencode($this->site_id), - 'site_key' => urlencode($this->site_key), - 'org_id' => urlencode($org_id), - 'org_key' => urlencode($user->getOrgKey()), - 'group_id' => urlencode($group_id), - 'group_name' => urlencode(base64_encode($user->getGroupName())), - 'group_key' => urlencode($user->getGroupKey()), - 'offer_id' => urlencode($fields['offer_id']), - 'subject' => urlencode(base64_encode($fields['subject'])), - 'descr_short' => urlencode(base64_encode($fields['descr_short'])), - 'descr_long' => urlencode(base64_encode($fields['descr_long'])), - 'currency' => urlencode($fields['currency']), - 'amount' => urlencode($fields['amount']), - 'payment_info' => urlencode(base64_encode($fields['payment_info'])), - 'modified_ip' => urlencode($_SERVER['REMOTE_ADDR']), - 'modified_by' => urlencode($user->getUser()), - 'modified_by_name' => urlencode(base64_encode($user->getName())), - 'modified_by_email' => urlencode(base64_encode($user->getEmail())) - ); - - // url-ify the data for the POST. - foreach($curl_fields as $key=>$value) { $fields_string .= $key.'='.$value.'&'; } - $fields_string = rtrim($fields_string, '&'); - - // Open connection. - $ch = curl_init(); - - // Set the url, number of POST vars, POST data. - curl_setopt($ch, CURLOPT_URL, $this->update_own_offer_uri); - curl_setopt($ch, CURLOPT_POST, true); - curl_setopt($ch, CURLOPT_POSTFIELDS, $fields_string); - curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); - - // Execute a post request. - $result = curl_exec($ch); - - // Close connection. - curl_close($ch); - - if (!$result) { - $this->errors->add($i18n->get('error.remote_work')); - return false; - } - - $result_array = json_decode($result, true); - - // Check for errors. - $call_status = $result_array['call_status']; - if (!$call_status) { - $this->errors->add($i18n->get('error.remote_work')); - return false; - } - if ($call_status['code'] != TT_CURL_SUCCESS) { - $this->errors->add($call_status['error']); - return false; - } - - return true; - } - - // deleteOwnOffer - deletes an offer from remote work server. - function deleteOwnOffer($offer_id) { - global $i18n; - global $user; - $mdb2 = getConnection(); - - $group_id = $user->getGroup(); - $org_id = $user->org_id; - - $curl_fields = array('lang' => urlencode($user->lang), - 'site_id' => urlencode($this->site_id), - 'site_key' => urlencode($this->site_key), - 'org_id' => urlencode($org_id), - 'org_key' => urlencode($user->getOrgKey()), - 'group_id' => urlencode($group_id), - 'group_key' => urlencode($user->getGroupKey()), - 'offer_id' => urlencode($offer_id), - 'modified_ip' => urlencode($_SERVER['REMOTE_ADDR']), - 'modified_by' => urlencode($user->getUser()), - 'modified_by_name' => urlencode(base64_encode($user->getName())), - 'modified_by_email' => urlencode(base64_encode($user->getEmail()))); - - // url-ify the data for the POST. - foreach($curl_fields as $key=>$value) { $fields_string .= $key.'='.$value.'&'; } - $fields_string = rtrim($fields_string, '&'); - - // Open connection. - $ch = curl_init(); - - // Set the url, number of POST vars, POST data. - curl_setopt($ch, CURLOPT_URL, $this->delete_own_offer_uri); - curl_setopt($ch, CURLOPT_POST, true); - curl_setopt($ch, CURLOPT_POSTFIELDS, $fields_string); - curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); - - // Execute a post request. - $result = curl_exec($ch); - - // Close connection. - curl_close($ch); - - if (!$result) { - $this->errors->add($i18n->get('error.remote_work')); - return false; - } - - $result_array = json_decode($result, true); - - // Check for errors. - $call_status = $result_array['call_status']; - if (!$call_status) { - $this->errors->add($i18n->get('error.remote_work')); - return false; - } - if ($call_status['code'] != TT_CURL_SUCCESS) { - $this->errors->add($call_status['error']); - return false; - } - - return true; - } - - // getOwnWorkItemOffer - gets offer details from remote work server - // for an offer posted on own work item. - function getOwnWorkItemOffer($offer_id) { - global $i18n; - global $user; - $mdb2 = getConnection(); - - $group_id = $user->getGroup(); - $org_id = $user->org_id; - - $curl_fields = array('lang' => urlencode($user->lang), - 'site_id' => urlencode($this->site_id), - 'site_key' => urlencode($this->site_key), - 'org_id' => urlencode($org_id), - 'org_key' => urlencode($user->getOrgKey()), - 'group_id' => urlencode($group_id), - 'group_key' => urlencode($user->getGroupKey()), - 'offer_id' => urlencode($offer_id)); - - // url-ify the data for the POST. - foreach($curl_fields as $key=>$value) { $fields_string .= $key.'='.$value.'&'; } - $fields_string = rtrim($fields_string, '&'); - - // Open connection. - $ch = curl_init(); - - // Set the url, number of POST vars, POST data. - curl_setopt($ch, CURLOPT_URL, $this->get_own_work_item_offer_uri); - curl_setopt($ch, CURLOPT_POST, true); - curl_setopt($ch, CURLOPT_POSTFIELDS, $fields_string); - curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); - - // Execute a post request. - $result = curl_exec($ch); - - // Close connection. - curl_close($ch); - - if (!$result) { - $this->errors->add($i18n->get('error.remote_work')); - return false; - } - - $result_array = json_decode($result, true); - - // Check for errors. - $call_status = $result_array['call_status']; - if (!$call_status) { - $this->errors->add($i18n->get('error.remote_work')); - return false; - } - if ($call_status['code'] != TT_CURL_SUCCESS) { - $this->errors->add($call_status['error']); - return false; - } - - $offer = $result_array['offer']; - return $offer; - } - - // declineOwnWorkItemOffer - declines an offer posted on own work item in remote work server. - function declineOwnWorkItemOffer($fields) { - global $i18n; - global $user; - $mdb2 = getConnection(); - - $group_id = $user->getGroup(); - $org_id = $user->org_id; - - $curl_fields = array('lang' => urlencode($user->lang), - 'site_id' => urlencode($this->site_id), - 'site_key' => urlencode($this->site_key), - 'org_id' => urlencode($org_id), - 'org_key' => urlencode($user->getOrgKey()), - 'group_id' => urlencode($group_id), - 'group_key' => urlencode($user->getGroupKey()), - 'offer_id' => urlencode($fields['offer_id']), - 'client_comment' => urlencode(base64_encode($fields['client_comment']))); - //'modified_ip' => urlencode($_SERVER['REMOTE_ADDR']), - //'modified_by' => urlencode($user->getUser()), - //'modified_by_name' => urlencode(base64_encode($user->getName())), - //'modified_by_email' => urlencode(base64_encode($user->getEmail()))); - - // url-ify the data for the POST. - foreach($curl_fields as $key=>$value) { $fields_string .= $key.'='.$value.'&'; } - $fields_string = rtrim($fields_string, '&'); - - // Open connection. - $ch = curl_init(); - - // Set the url, number of POST vars, POST data. - curl_setopt($ch, CURLOPT_URL, $this->decline_own_work_item_offer_uri); - curl_setopt($ch, CURLOPT_POST, true); - curl_setopt($ch, CURLOPT_POSTFIELDS, $fields_string); - curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); - - // Execute a post request. - $result = curl_exec($ch); - - // Close connection. - curl_close($ch); - - if (!$result) { - $this->errors->add($i18n->get('error.remote_work')); - return false; - } - - $result_array = json_decode($result, true); - - // Check for errors. - $call_status = $result_array['call_status']; - if (!$call_status) { - $this->errors->add($i18n->get('error.remote_work')); - return false; - } - if ($call_status['code'] != TT_CURL_SUCCESS) { - $this->errors->add($call_status['error']); - return false; - } - - return true; - } - - // acceptOwnWorkItemOffer - accepts an offer posted on own work item in remote work server. - function acceptOwnWorkItemOffer($fields) { - global $i18n; - global $user; - $mdb2 = getConnection(); - - $group_id = $user->getGroup(); - $org_id = $user->org_id; - - $curl_fields = array('lang' => urlencode($user->lang), - 'site_id' => urlencode($this->site_id), - 'site_key' => urlencode($this->site_key), - 'org_id' => urlencode($org_id), - 'org_key' => urlencode($user->getOrgKey()), - 'group_id' => urlencode($group_id), - 'group_key' => urlencode($user->getGroupKey()), - 'offer_id' => urlencode($fields['offer_id']), - 'client_comment' => urlencode(base64_encode($fields['client_comment'])), - 'accepted_ip' => urlencode($_SERVER['REMOTE_ADDR']), - 'accepted_by' => urlencode($user->getUser()), - 'accepted_by_name' => urlencode(base64_encode($user->getName())), - 'accepted_by_email' => urlencode(base64_encode($user->getEmail()))); - - // url-ify the data for the POST. - foreach($curl_fields as $key=>$value) { $fields_string .= $key.'='.$value.'&'; } - $fields_string = rtrim($fields_string, '&'); - - // Open connection. - $ch = curl_init(); - - // Set the url, number of POST vars, POST data. - curl_setopt($ch, CURLOPT_URL, $this->accept_own_work_item_offer_uri); - curl_setopt($ch, CURLOPT_POST, true); - curl_setopt($ch, CURLOPT_POSTFIELDS, $fields_string); - curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); - - // Execute a post request. - $result = curl_exec($ch); - - // Close connection. - curl_close($ch); - - if (!$result) { - $this->errors->add($i18n->get('error.remote_work')); - return false; - } - - $result_array = json_decode($result, true); - - // Check for errors. - $call_status = $result_array['call_status']; - if (!$call_status) { - $this->errors->add($i18n->get('error.remote_work')); - return false; - } - if ($call_status['code'] != TT_CURL_SUCCESS) { - $this->errors->add($call_status['error']); - return false; - } - - return true; - } - - // getCurrencies - obtains a list of supported currencies. - static function getCurrencies() { - $mdb2 = getConnection(); - - $sql = "select id, name from tt_work_currencies order by id"; - $res = $mdb2->query($sql); - if (is_a($res, 'PEAR_Error')) return false; - - while ($val = $res->fetchRow()) { - $result[] = array('id'=>$val['id'], 'name'=>$val['name']); - } - return $result; - } - - // getCurrencyID - obtains an ID for currency. - static function getCurrencyID($currency) { - $mdb2 = getConnection(); - - $currency_id = null; - $sql = "select id from tt_work_currencies where name = ".$mdb2->quote($currency); - $res = $mdb2->query($sql); - if (is_a($res, 'PEAR_Error')) return null; - - if($val = $res->fetchRow()) { - $currency_id = $val['id']; - } - return $currency_id; - } - - // getCurrencyName - obtains currency name using its id. - static function getCurrencyName($currency_id) { - $mdb2 = getConnection(); - - $currency_name = null; - $sql = "select name from tt_work_currencies where id = $currency_id"; - $res = $mdb2->query($sql); - if (is_a($res, 'PEAR_Error')) return null; - - if($val = $res->fetchRow()) { - $currency_name = $val['name']; - } - return $currency_name; - } - - // getGroupItems - obtains a list of all items relevant to group in one API call to Remote Work Server. - function getGroupItems() { - global $i18n; - global $user; - $mdb2 = getConnection(); - - $group_id = $user->getGroup(); - $org_id = $user->org_id; - - $curl_fields = array('lang' => urlencode($user->lang), - 'site_id' => urlencode($this->site_id), - 'site_key' => urlencode($this->site_key), - 'org_id' => urlencode($org_id), - 'org_key' => urlencode($user->getOrgKey()), - 'group_id' => urlencode($group_id), - 'group_key' => urlencode($user->getGroupKey()) - ); - - // url-ify the data for the POST. - foreach($curl_fields as $key=>$value) { $fields_string .= $key.'='.$value.'&'; } - $fields_string = rtrim($fields_string, '&'); - - // Open connection. - $ch = curl_init(); - - // Set the url, number of POST vars, POST data. - curl_setopt($ch, CURLOPT_URL, $this->get_group_items_uri); - curl_setopt($ch, CURLOPT_POST, true); - curl_setopt($ch, CURLOPT_POSTFIELDS, $fields_string); - curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); - - // Execute a post request. - $result = curl_exec($ch); - - // Close connection. - curl_close($ch); - - if (!$result) { - $this->errors->add($i18n->get('error.remote_work')); - return false; - } - - $result_array = json_decode($result, true); - - // Check for errors. - $call_status = $result_array['call_status']; - if (!$call_status) { - $this->errors->add($i18n->get('error.remote_work')); - return false; - } - if ($call_status['code'] != TT_CURL_SUCCESS) { - $this->errors->add($call_status['error']); - return false; - } - - unset($result_array['call_status']); // Remove call_status element, - return $result_array; - } - - // getAvailableWorkItem - gets available work item details from remote work server. - function getAvailableWorkItem($work_id) { - global $i18n; - global $user; - $mdb2 = getConnection(); - - $group_id = $user->getGroup(); - $org_id = $user->org_id; - - $curl_fields = array('lang' => urlencode($user->lang), - 'site_id' => urlencode($this->site_id), - 'site_key' => urlencode($this->site_key), - 'org_id' => urlencode($org_id), - 'group_id' => urlencode($group_id), - 'group_name' => urlencode(base64_encode($user->getGroupName())), - 'user_id' => urlencode($user->getUser()), - 'user_name' => urlencode(base64_encode($user->getName())), - 'user_email' => urlencode(base64_encode($user->getEmail())), - 'user_ip' => urlencode($_SERVER['REMOTE_ADDR']), - 'work_id' => urlencode($work_id)); - - // url-ify the data for the POST. - foreach($curl_fields as $key=>$value) { $fields_string .= $key.'='.$value.'&'; } - $fields_string = rtrim($fields_string, '&'); - - // Open connection. - $ch = curl_init(); - - // Set the url, number of POST vars, POST data. - curl_setopt($ch, CURLOPT_URL, $this->get_available_work_item_uri); - curl_setopt($ch, CURLOPT_POST, true); - curl_setopt($ch, CURLOPT_POSTFIELDS, $fields_string); - curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); - - // Execute a post request. - $result = curl_exec($ch); - - // Close connection. - curl_close($ch); - - if (!$result) { - $this->errors->add($i18n->get('error.remote_work')); - return false; - } - - $result_array = json_decode($result, true); - - // Check for errors. - $call_status = $result_array['call_status']; - if (!$call_status) { - $this->errors->add($i18n->get('error.remote_work')); - return false; - } - if ($call_status['code'] != TT_CURL_SUCCESS) { - $this->errors->add($call_status['error']); - return false; - } - - $work_item = $result_array['work_item']; - return $work_item; - } - - // getAvailableOffer - gets available offer details from remote work server. - function getAvailableOffer($offer_id) { - global $i18n; - global $user; - $mdb2 = getConnection(); - - $group_id = $user->getGroup(); - $org_id = $user->org_id; - - $curl_fields = array('lang' => urlencode($user->lang), - 'site_id' => urlencode($this->site_id), - 'site_key' => urlencode($this->site_key), - 'org_id' => urlencode($org_id), - 'group_id' => urlencode($group_id), - 'group_name' => urlencode(base64_encode($user->getGroupName())), - 'user_id' => urlencode($user->getUser()), - 'user_name' => urlencode(base64_encode($user->getName())), - 'user_email' => urlencode(base64_encode($user->getEmail())), - 'user_ip' => urlencode($_SERVER['REMOTE_ADDR']), - 'offer_id' => urlencode($offer_id)); - - // url-ify the data for the POST. - foreach($curl_fields as $key=>$value) { $fields_string .= $key.'='.$value.'&'; } - $fields_string = rtrim($fields_string, '&'); - - // Open connection. - $ch = curl_init(); - - // Set the url, number of POST vars, POST data. - curl_setopt($ch, CURLOPT_URL, $this->get_available_offer_uri); - curl_setopt($ch, CURLOPT_POST, true); - curl_setopt($ch, CURLOPT_POSTFIELDS, $fields_string); - curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); - - // Execute a post request. - $result = curl_exec($ch); - - // Close connection. - curl_close($ch); - - if (!$result) { - $this->errors->add($i18n->get('error.remote_work')); - return false; - } - - $result_array = json_decode($result, true); - - // Check for errors. - $call_status = $result_array['call_status']; - if (!$call_status) { - $this->errors->add($i18n->get('error.remote_work')); - return false; - } - if ($call_status['code'] != TT_CURL_SUCCESS) { - $this->errors->add($call_status['error']); - return false; - } - - $offer = $result_array['offer']; - return $offer; - } - - // sendMessageToWorkOwner - sends a message to work owner via remote work server. - function sendMessageToWorkOwner($fields) { - global $i18n; - global $user; - $mdb2 = getConnection(); - - $group_id = $user->getGroup(); - $org_id = $user->org_id; - - $curl_fields = array('lang' => urlencode($user->lang), - 'site_id' => urlencode($this->site_id), - 'site_key' => urlencode($this->site_key), - 'org_id' => urlencode($org_id), - 'org_key' => urlencode($user->getOrgKey()), - 'group_id' => urlencode($group_id), - 'group_name' => urlencode(base64_encode($user->getGroupName())), - 'group_key' => urlencode($user->getGroupKey()), - 'work_id' => urlencode($fields['work_id']), - 'sender_ip' => urlencode($_SERVER['REMOTE_ADDR']), - 'sender_user_id' => urlencode($user->getUser()), - 'sender_name' => urlencode(base64_encode($user->getName())), - 'sender_email' => urlencode(base64_encode($user->getEmail())), - 'message_body' => urlencode(base64_encode($fields['message_body']))); - - // url-ify the data for the POST. - foreach($curl_fields as $key=>$value) { $fields_string .= $key.'='.$value.'&'; } - $fields_string = rtrim($fields_string, '&'); - - // Open connection. - $ch = curl_init(); - - // Set the url, number of POST vars, POST data. - curl_setopt($ch, CURLOPT_URL, $this->send_message_to_work_owner_uri); - curl_setopt($ch, CURLOPT_POST, true); - curl_setopt($ch, CURLOPT_POSTFIELDS, $fields_string); - curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); - - // Execute a post request. - $result = curl_exec($ch); - - // Close connection. - curl_close($ch); - - if (!$result) { - $this->errors->add($i18n->get('error.remote_work')); - return false; - } - - $result_array = json_decode($result, true); - - // Check for errors. - $call_status = $result_array['call_status']; - if (!$call_status) { - $this->errors->add($i18n->get('error.remote_work')); - return false; - } - if ($call_status['code'] != TT_CURL_SUCCESS) { - $this->errors->add($call_status['error']); - return false; - } - - return true; - } - - // sendMessageToOfferOwner - sends a message to offer owner via remote work server. - function sendMessageToOfferOwner($fields) { - global $i18n; - global $user; - $mdb2 = getConnection(); - - $group_id = $user->getGroup(); - $org_id = $user->org_id; - - $curl_fields = array('lang' => urlencode($user->lang), - 'site_id' => urlencode($this->site_id), - 'site_key' => urlencode($this->site_key), - 'org_id' => urlencode($org_id), - 'org_key' => urlencode($user->getOrgKey()), - 'group_id' => urlencode($group_id), - 'group_name' => urlencode(base64_encode($user->getGroupName())), - 'group_key' => urlencode($user->getGroupKey()), - 'offer_id' => urlencode($fields['offer_id']), - 'sender_ip' => urlencode($_SERVER['REMOTE_ADDR']), - 'sender_user_id' => urlencode($user->getUser()), - 'sender_name' => urlencode(base64_encode($user->getName())), - 'sender_email' => urlencode(base64_encode($user->getEmail())), - 'message_body' => urlencode(base64_encode($fields['message_body']))); - - // url-ify the data for the POST. - foreach($curl_fields as $key=>$value) { $fields_string .= $key.'='.$value.'&'; } - $fields_string = rtrim($fields_string, '&'); - - // Open connection. - $ch = curl_init(); - - // Set the url, number of POST vars, POST data. - curl_setopt($ch, CURLOPT_URL, $this->send_message_to_offer_owner_uri); - curl_setopt($ch, CURLOPT_POST, true); - curl_setopt($ch, CURLOPT_POSTFIELDS, $fields_string); - curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); - - // Execute a post request. - $result = curl_exec($ch); - - // Close connection. - curl_close($ch); - - if (!$result) { - $this->errors->add($i18n->get('error.remote_work')); - return false; - } - - $result_array = json_decode($result, true); - - // Check for errors. - $call_status = $result_array['call_status']; - if (!$call_status) { - $this->errors->add($i18n->get('error.remote_work')); - return false; - } - if ($call_status['code'] != TT_CURL_SUCCESS) { - $this->errors->add($call_status['error']); - return false; - } - - return true; - } -} diff --git a/WEB-INF/resources/ca.lang.php b/WEB-INF/resources/ca.lang.php index 34421e73d..327132ed8 100644 --- a/WEB-INF/resources/ca.lang.php +++ b/WEB-INF/resources/ca.lang.php @@ -2,7 +2,8 @@ /* Copyright (c) Anuko International Ltd. https://www.anuko.com License: See license.txt */ -// Note: escape apostrophes with THREE backslashes, like here: choisir l\\\'option. +// Note: escape apostrophes with THREE backslashes, like here: 'choisir l\\\'option'. +// Alternatively: use one backslash and surround by double-quotes: "choisir l\'option". // Other characters (such as double-quotes in http links, etc.) do not have to be escaped. $i18n_language = 'Catalan (Català)'; @@ -27,6 +28,7 @@ // 'menu.plugins' => 'Plugins', 'menu.time' => 'Temps', // TODO: translate the following. +// 'menu.puncher' => 'Punch', // 'menu.week' => 'Week', // 'menu.expenses' => 'Expenses', 'menu.reports' => 'Informes', @@ -39,7 +41,6 @@ 'menu.users' => 'Usuaris', 'menu.groups' => 'Grups', // TODO: translate the following. -// 'menu.subgroups' => 'Subgroups', // 'menu.export' => 'Export', 'menu.clients' => 'Clients', 'menu.options' => 'Opcions', @@ -74,6 +75,8 @@ // 'error.record' => 'Select record.', 'error.auth' => 'Usuari o parula de pas incorrecta.', // TODO: translate the following. +// 'error.2fa_code' => 'Invalid 2FA code.', +// 'error.weak_password' => 'Weak password.', // 'error.user_exists' => 'User with this login already exists.', // 'error.object_exists' => 'Object with this name already exists.', // 'error.invoice_exists' => 'Invoice with this number already exists.', @@ -97,7 +100,6 @@ // 'error.user_count' => 'Limit on user count.', // 'error.expired' => 'Expiration date reached.', // 'error.file_storage' => 'File storage server error.', // See comment in English file. -// 'error.remote_work' => 'Remote work server error.', // See comment in English file. // Warning messages. // TODO: translate the following. @@ -127,9 +129,11 @@ 'button.import' => 'Importar grup', // TODO: translate the following. // 'button.close' => 'Close', +// 'button.start' => 'Start', // 'button.stop' => 'Stop', // 'button.approve' => 'Approve', // 'button.disapprove' => 'Disapprove', +// 'button.sync' => 'Sync', // Used in Android app. The meaning is to synchronize offline time records with server. // Labels for controls on forms. Labels in this section are used on multiple forms. // TODO: translate the following. @@ -192,6 +196,7 @@ // TODO: translate the following. // 'label.day_view' => 'Day view', // 'label.week_view' => 'Week view', +// 'label.puncher' => 'Puncher', // 'label.id' => 'ID', // 'label.language' => 'Language', // 'label.decimal_mark' => 'Decimal mark', @@ -212,9 +217,6 @@ // 'label.ldap_hint' => 'Type your Windows login and password in the fields below.', 'label.required_fields' => '* camps requerits', 'label.on_behalf' => 'a nom de', -'label.role_manager' => '(manejador)', -'label.role_comanager' => '(auxiliar del manejador)', -'label.role_admin' => '(administrador)', // TODO: translate the following. // 'label.page' => 'Page', // 'label.condition' => 'Condition', @@ -240,9 +242,9 @@ // 'label.paid_status' => 'Paid status', // 'label.paid' => 'Paid', // 'label.mark_paid' => 'Mark paid', -// 'label.week_menu' => 'Week menu', // 'label.week_note' => 'Week note', // 'label.week_list' => 'Week list', +// 'label.weekends' => 'Weekends', // 'label.work_units' => 'Work units', // 'label.work_units_short' => 'Units', 'label.totals_only' => 'Només totals', @@ -259,20 +261,8 @@ // 'label.attachments' => 'Attachments', // 'label.files' => 'Files', // 'label.file' => 'File', -// 'label.image' => 'Image', -// 'label.download' => 'Download', // 'label.active_users' => 'Active Users', // 'label.inactive_users' => 'Inactive Users', -// 'label.details' => 'Details', -// 'label.budget' => 'Budget', -// 'label.work' => 'Work', // Table column header for work items, see https://www.anuko.com/time_tracker/what_is/work_plugin.htm -// 'label.offer' => 'Offer', // Table column header for offers, see https://www.anuko.com/time_tracker/what_is/work_plugin.htm -// 'label.contractor' => 'Contractor', // Table column header for offers (contractor is someone who offers to do work). - // Technically, it is either an org name or a combination of org and group names - // because both work items and offers are owned by Time Tracker groups of users. -// 'label.how_to_pay' => 'How to pay', // Label for the "How to pay" field on offers, which allows contractors to specify - // how to pay them, for example: paypal email, check by mail to a specific address, etc. -// 'label.moderator_comment' => 'Moderator comment', // Label for "Moderator comment" field that explains something. // Entity names. We use lower case (in English) because they are used in dropdowns, too. // They are used to associate a custom field with an entity type. @@ -290,9 +280,10 @@ // 'title.error' => 'Error', // 'title.success' => 'Success', 'title.login' => 'Sessió iniciada', +// TODO: translate the follolwing. +// 'title.2fa' => 'Two Factor Authentication', 'title.groups' => 'Grups', // TODO: translate the following. -// 'title.subgroups' => 'Subgroups', // 'title.add_group' => 'Adding Group', // 'title.edit_group' => 'Editing Group', 'title.delete_group' => 'Eliminar grup', @@ -304,6 +295,7 @@ 'title.delete_time_record' => 'Eliminant l\\\'historial de temps', // TODO: translate the following. // 'title.time_files' => 'Time Record Files', +// 'title.puncher' => 'Puncher', // 'title.expenses' => 'Expenses', // 'title.edit_expense' => 'Editing Expense Item', // 'title.delete_expense' => 'Deleting Expense Item', @@ -389,22 +381,6 @@ // 'title.edit_file' => 'Editing File', // 'title.delete_file' => 'Deleting File', // 'title.download_file' => 'Downloading File', -// 'title.work' => 'Work', -// 'title.add_work' => 'Adding Work', -// 'title.edit_work' => 'Editing Work', -// 'title.delete_work' => 'Deleting Work', -// 'title.active_work' => 'Active Work', // Active work items this group outsources to other groups. -// 'title.available_work' => 'Available Work', // Available work items from other organizations. -// 'title.inactive_work' => 'Inactive Work', // Inactive work items this group was outsourcing to other groups. -// 'title.pending_work' => 'Pending Work', // Work items pending moderator approval. -// 'title.offer' => 'Offer', -// 'title.add_offer' => 'Adding Offer', -// 'title.edit_offer' => 'Editing Offer', -// 'title.delete_offer' => 'Deleting Offer', -// 'title.active_offers' => 'Active Offers', // Active offers this group makes available to other groups. -// 'title.available_offers' => 'Available Offers', // Available offers from other organizations. -// 'title.inactive_offers' => 'Inactive Offers', // Inactive offers for group. -// 'title.pending_offers' => 'Pending Offers', // Offers pending moderator approval. // Section for common strings inside combo boxes on forms. Strings shared between forms shall be placed here. // Strings that are used in a single form must go to the specific form section. @@ -437,7 +413,6 @@ // 'dropdown.status_inactive' => 'inactive', // 'dropdown.delete' => 'delete', // 'dropdown.do_not_delete' => 'do not delete', -// 'dropdown.pending_approval' => 'pending approval', // 'dropdown.approved' => 'approved', // 'dropdown.not_approved' => 'not approved', // 'dropdown.paid' => 'paid', @@ -450,6 +425,16 @@ // TODO: translate the following. // 'form.login.about' => 'Anuko Time Tracker is an open source time tracking system.', +// Email subject and body for two-factor authentication. +// TODO: translate the following. +// 'email.2fa_code.subject' => 'Anuko Time Tracker two-factor authentication code', +// 'email.2fa_code.body' => "Dear User,\n\nYour two-factor authentication code is:\n\n%s\n\n", + +// Two-factor authentication form. See example at https://timetracker.anuko.com/2fa.php. +// TODO: translate the following. +// 'form.2fa.hint' => 'Check your email for 2FA code and enter it here.', +// 'form.2fa.2fa_code' => '2FA code', + // Resetting Password form. See example at https://timetracker.anuko.com/password_reset.php. 'form.reset_password.message' => 'S\\\'ha enviat la petició de restablir paraula de pas.', // TODO: add "by email" to match the English string. 'form.reset_password.email_subject' => 'Sol·licitud de restabliment de la paraula de pas de Anuko Time Tracker', @@ -490,10 +475,13 @@ // 'form.reports.include_pending' => 'pending', 'form.reports.select_period' => 'Seleccionar període de temps', 'form.reports.set_period' => 'o establir dates', +// TODO: translate the following. +// 'form.reports.note_containing' => 'Note containing', 'form.reports.show_fields' => 'Mostrar camps', // TODO: translate the following. // 'form.reports.time_fields' => 'Time fields', // 'form.reports.user_fields' => 'User fields', +// 'form.reports.project_fields' => 'Project fields', 'form.reports.group_by' => 'Agrupar per', 'form.reports.group_by_no' => '--- no agrupar ---', 'form.reports.group_by_date' => 'data', @@ -508,6 +496,7 @@ // (after generating a report at https://timetracker.anuko.com/reports.php). 'form.report.export' => 'Exportar', // TODO: translate the following. +// 'form.report.per_hour' => 'Per hour', // 'form.report.assign_to_invoice' => 'Assign to invoice', // 'form.report.assign_to_timesheet' => 'Assign to timesheet', @@ -521,8 +510,8 @@ // 'form.templates.active_templates' => 'Active Templates', // 'form.templates.inactive_templates' => 'Inactive Templates', -// Invoice form. See example at https://timetracker.anuko.com/invoice.php -// (you can get to this form after generating a report). +// Invoice form. See example at https://timetracker.anuko.com/invoice_view.php +// (you can get to this form after generating an invoice). 'form.invoice.number' => 'Número de factura', 'form.invoice.person' => 'Persona', @@ -549,6 +538,7 @@ // Users form. See example at https://timetracker.anuko.com/users.php // TODO: translate the following. +// 'form.users.uncompleted_entry_today' => 'User has an uncompleted time entry today', // 'form.users.uncompleted_entry' => 'User has an uncompleted time entry', 'form.users.role' => 'Rol', 'form.users.manager' => 'Manejador', @@ -610,13 +600,19 @@ // 'form.group_edit.type_start_finish' => 'start and finish', // 'form.group_edit.type_duration' => 'duration', // 'form.group_edit.punch_mode' => 'Punch mode', +// 'form.group_edit.one_uncompleted' => 'One uncompleted', // 'form.group_edit.allow_overlap' => 'Allow overlap', // 'form.group_edit.future_entries' => 'Future entries', // 'form.group_edit.uncompleted_indicators' => 'Uncompleted indicators', // 'form.group_edit.confirm_save' => 'Confirm saving', -// 'form.group_edit.allow_ip' => 'Allow IP', // 'form.group_edit.advanced_settings' => 'Advanced settings', +// Advanced Group Settings form. See example at https://timetracker.anuko.com/group_advanced_edit.php. +// TODO: Translate the following. +// 'form.group_advanced_edit.allow_ip' => 'Allow IP', +// 'form.group_advanced_edit.password_complexity' => 'Password complexity', +// 'form.group_advanced_edit.2fa' => 'Two factor authentication', + // Deleting Group form. See example at https://timetracker.anuko.com/delete_group.php // TODO: translate the following. // 'form.group_delete.hint' => 'Are you sure you want to delete the entire group?', @@ -683,21 +679,8 @@ // TODO: translate the following. // 'form.display_options.note_on_separate_row' => 'Note on separate row', // 'form.display_options.not_complete_days' => 'Not complete days', +// 'form.display_options.inactive_projects' => 'Inactive projects', +// 'form.display_options.cost_per_hour' => 'Cost per hour', // 'form.display_options.custom_css' => 'Custom CSS', - -// Work plugin strings. See example at https://timetracker.anuko.com/work.php -// TODO: translate the following. -// 'work.error.work_not_available' => 'Work item is not available.', -// 'work.error.offer_not_available' => 'Offer is not available.', -// 'work.type.one_time' => 'one time', // Work type is "one time job" for well defined work ("do exactly this"). -// 'work.type.ongoing' => 'ongoing', // Work type is "ongoing" for complex jobs (billed by the hour, multiple contractors, etc.) -// 'work.label.own_work' => 'Own work', -// 'work.label.own_offers' => 'Own offers', -// 'work.label.offers' => 'Offers', -// 'work.button.send_message' => 'Send message', -// 'work.button.make_offer' => 'Make offer', -// 'work.button.accept' => 'Accept', -// 'work.button.decline' => 'Decline', -// 'work.title.send_message' => 'Sending Message', -// 'work.msg.message_sent' => 'Message sent.', +// 'form.display_options.custom_translation' => 'Custom translation', ); diff --git a/WEB-INF/resources/cs.lang.php b/WEB-INF/resources/cs.lang.php index 199a96faf..c89457e6d 100644 --- a/WEB-INF/resources/cs.lang.php +++ b/WEB-INF/resources/cs.lang.php @@ -2,7 +2,8 @@ /* Copyright (c) Anuko International Ltd. https://www.anuko.com License: See license.txt */ -// Note: escape apostrophes with THREE backslashes, like here: choisir l\\\'option. +// Note: escape apostrophes with THREE backslashes, like here: 'choisir l\\\'option'. +// Alternatively: use one backslash and surround by double-quotes: "choisir l\'option". // Other characters (such as double-quotes in http links, etc.) do not have to be escaped. // Note to translators: Use proper capitalization rules for your language. @@ -28,6 +29,7 @@ // 'menu.group' => 'Group', // 'menu.plugins' => 'Plugins', // 'menu.time' => 'Time', +// 'menu.puncher' => 'Punch', // 'menu.week' => 'Week', // 'menu.expenses' => 'Expenses', 'menu.reports' => 'Sestavy', @@ -40,7 +42,6 @@ 'menu.users' => 'Uživatelů', // TODO: translate the following. // 'menu.groups' => 'Groups', -// 'menu.subgroups' => 'Subgroups', 'menu.export' => 'Export', 'menu.clients' => 'Zákazníci', // TODO: translate the following. @@ -76,6 +77,8 @@ // 'error.record' => 'Select record.', 'error.auth' => 'Nesprávné jméno nebo heslo.', // TODO: translate the following. +// 'error.2fa_code' => 'Invalid 2FA code.', +// 'error.weak_password' => 'Weak password.', // 'error.user_exists' => 'User with this login already exists.', // 'error.object_exists' => 'Object with this name already exists.', // 'error.invoice_exists' => 'Invoice with this number already exists.', @@ -99,7 +102,6 @@ // 'error.user_count' => 'Limit on user count.', // 'error.expired' => 'Expiration date reached.', // 'error.file_storage' => 'File storage server error.', // See comment in English file. -// 'error.remote_work' => 'Remote work server error.', // See comment in English file. // Warning messages. // TODO: translate the following. @@ -129,9 +131,11 @@ 'button.import' => 'Importovat tým', // TODO: replace "team" with "group". // TODO: translate the following. // 'button.close' => 'Close', +// 'button.start' => 'Start', // 'button.stop' => 'Stop', // 'button.approve' => 'Approve', // 'button.disapprove' => 'Disapprove', +// 'button.sync' => 'Sync', // Used in Android app. The meaning is to synchronize offline time records with server. // Labels for controls on forms. Labels in this section are used on multiple forms. // TODO: translate the following. @@ -200,6 +204,7 @@ // TODO: translate the following. // 'label.day_view' => 'Day view', // 'label.week_view' => 'Week view', +// 'label.puncher' => 'Puncher', 'label.id' => 'ID', 'label.language' => 'Jazyk', // TODO: translate the following. @@ -222,10 +227,6 @@ 'label.required_fields' => '* nutno vyplnit', // TODO: translate the following. // 'label.on_behalf' => 'on behalf of', -'label.role_manager' => '(manažer)', -'label.role_comanager' => '(spolumanažer)', -'label.role_admin' => '(administrator)', -// TODO: translate the following. // 'label.page' => 'Page', // 'label.condition' => 'Condition', // 'label.yes' => 'yes', @@ -250,9 +251,9 @@ // 'label.paid_status' => 'Paid status', // 'label.paid' => 'Paid', // 'label.mark_paid' => 'Mark paid', -// 'label.week_menu' => 'Week menu', // 'label.week_note' => 'Week note', // 'label.week_list' => 'Week list', +// 'label.weekends' => 'Weekends', // 'label.work_units' => 'Work units', // 'label.work_units_short' => 'Units', 'label.totals_only' => 'Pouze součty', @@ -269,20 +270,8 @@ // 'label.attachments' => 'Attachments', // 'label.files' => 'Files', // 'label.file' => 'File', -// 'label.image' => 'Image', -// 'label.download' => 'Download', // 'label.active_users' => 'Active Users', // 'label.inactive_users' => 'Inactive Users', -// 'label.details' => 'Details', -// 'label.budget' => 'Budget', -// 'label.work' => 'Work', // Table column header for work items, see https://www.anuko.com/time_tracker/what_is/work_plugin.htm -// 'label.offer' => 'Offer', // Table column header for offers, see https://www.anuko.com/time_tracker/what_is/work_plugin.htm -// 'label.contractor' => 'Contractor', // Table column header for offers (contractor is someone who offers to do work). - // Technically, it is either an org name or a combination of org and group names - // because both work items and offers are owned by Time Tracker groups of users. -// 'label.how_to_pay' => 'How to pay', // Label for the "How to pay" field on offers, which allows contractors to specify - // how to pay them, for example: paypal email, check by mail to a specific address, etc. -// 'label.moderator_comment' => 'Moderator comment', // Label for "Moderator comment" field that explains something. // Entity names. We use lower case (in English) because they are used in dropdowns, too. // They are used to associate a custom field with an entity type. @@ -300,9 +289,10 @@ // 'title.error' => 'Error', // 'title.success' => 'Success', 'title.login' => 'Přihlásit', +// TODO: translate the follolwing. +// 'title.2fa' => 'Two Factor Authentication', 'title.groups' => 'Týmy', // TODO: change "teams" to "groups". // TODO: translate the following. -// 'title.subgroups' => 'Subgroups', // 'title.add_group' => 'Adding Group', // 'title.edit_group' => 'Editing Group', 'title.delete_group' => 'Smazat tým', // TODO: change "team" to "group". @@ -314,6 +304,7 @@ 'title.delete_time_record' => 'Smazat časový záznam', // TODO: translate the following. // 'title.time_files' => 'Time Record Files', +// 'title.puncher' => 'Puncher', // 'title.expenses' => 'Expenses', // 'title.edit_expense' => 'Editing Expense Item', // 'title.delete_expense' => 'Deleting Expense Item', @@ -399,22 +390,6 @@ // 'title.edit_file' => 'Editing File', // 'title.delete_file' => 'Deleting File', // 'title.download_file' => 'Downloading File', -// 'title.work' => 'Work', -// 'title.add_work' => 'Adding Work', -// 'title.edit_work' => 'Editing Work', -// 'title.delete_work' => 'Deleting Work', -// 'title.active_work' => 'Active Work', // Active work items this group outsources to other groups. -// 'title.available_work' => 'Available Work', // Available work items from other organizations. -// 'title.inactive_work' => 'Inactive Work', // Inactive work items this group was outsourcing to other groups. -// 'title.pending_work' => 'Pending Work', // Work items pending moderator approval. -// 'title.offer' => 'Offer', -// 'title.add_offer' => 'Adding Offer', -// 'title.edit_offer' => 'Editing Offer', -// 'title.delete_offer' => 'Deleting Offer', -// 'title.active_offers' => 'Active Offers', // Active offers this group makes available to other groups. -// 'title.available_offers' => 'Available Offers', // Available offers from other organizations. -// 'title.inactive_offers' => 'Inactive Offers', // Inactive offers for group. -// 'title.pending_offers' => 'Pending Offers', // Offers pending moderator approval. // Section for common strings inside combo boxes on forms. Strings shared between forms shall be placed here. // Strings that are used in a single form must go to the specific form section. @@ -445,7 +420,6 @@ // 'dropdown.status_inactive' => 'inactive', // 'dropdown.delete' => 'delete', // 'dropdown.do_not_delete' => 'do not delete', -// 'dropdown.pending_approval' => 'pending approval', // 'dropdown.approved' => 'approved', // 'dropdown.not_approved' => 'not approved', // 'dropdown.paid' => 'paid', @@ -458,6 +432,16 @@ // TODO: translate the following. // 'form.login.about' => 'Anuko Time Tracker is an open source time tracking system.', +// Email subject and body for two-factor authentication. +// TODO: translate the following. +// 'email.2fa_code.subject' => 'Anuko Time Tracker two-factor authentication code', +// 'email.2fa_code.body' => "Dear User,\n\nYour two-factor authentication code is:\n\n%s\n\n", + +// Two-factor authentication form. See example at https://timetracker.anuko.com/2fa.php. +// TODO: translate the following. +// 'form.2fa.hint' => 'Check your email for 2FA code and enter it here.', +// 'form.2fa.2fa_code' => '2FA code', + // Resetting Password form. See example at https://timetracker.anuko.com/password_reset.php. 'form.reset_password.message' => 'Zaslán požadavek k vymazání hesla.', // TODO: add "by email" to match the English string. 'form.reset_password.email_subject' => 'Anuko Time Tracker požadavek na vymazání hesla', @@ -499,10 +483,13 @@ // 'form.reports.include_pending' => 'pending', 'form.reports.select_period' => 'Výberte období', 'form.reports.set_period' => 'nebo určete dny', +// TODO: translate the following. +// 'form.reports.note_containing' => 'Note containing', 'form.reports.show_fields' => 'Zobrazit pole', // TODO: translate the following. // 'form.reports.time_fields' => 'Time fields', // 'form.reports.user_fields' => 'User fields', +// 'form.reports.project_fields' => 'Project fields', 'form.reports.group_by' => 'Seskupit podle', // TODO: translate the following. // 'form.reports.group_by_no' => '--- no grouping ---', @@ -517,6 +504,7 @@ // (after generating a report at https://timetracker.anuko.com/reports.php). 'form.report.export' => 'Exportovat', // TODO: translate the following. +// 'form.report.per_hour' => 'Per hour', // 'form.report.assign_to_invoice' => 'Assign to invoice', // 'form.report.assign_to_timesheet' => 'Assign to timesheet', @@ -530,8 +518,8 @@ // 'form.templates.active_templates' => 'Active Templates', // 'form.templates.inactive_templates' => 'Inactive Templates', -// Invoice form. See example at https://timetracker.anuko.com/invoice.php -// (you can get to this form after generating a report). +// Invoice form. See example at https://timetracker.anuko.com/invoice_view.php +// (you can get to this form after generating an invoice). 'form.invoice.number' => 'Faktura číslo', 'form.invoice.person' => 'Osoba', @@ -558,6 +546,7 @@ // Users form. See example at https://timetracker.anuko.com/users.php // TODO: translate the following. +// 'form.users.uncompleted_entry_today' => 'User has an uncompleted time entry today', // 'form.users.uncompleted_entry' => 'User has an uncompleted time entry', 'form.users.role' => 'Role', 'form.users.manager' => 'Manažer', @@ -623,13 +612,19 @@ // 'form.group_edit.type_start_finish' => 'start and finish', // 'form.group_edit.type_duration' => 'duration', // 'form.group_edit.punch_mode' => 'Punch mode', +// 'form.group_edit.one_uncompleted' => 'One uncompleted', // 'form.group_edit.allow_overlap' => 'Allow overlap', // 'form.group_edit.future_entries' => 'Future entries', // 'form.group_edit.uncompleted_indicators' => 'Uncompleted indicators', // 'form.group_edit.confirm_save' => 'Confirm saving', -// 'form.group_edit.allow_ip' => 'Allow IP', // 'form.group_edit.advanced_settings' => 'Advanced settings', +// Advanced Group Settings form. See example at https://timetracker.anuko.com/group_advanced_edit.php. +// TODO: Translate the following. +// 'form.group_advanced_edit.allow_ip' => 'Allow IP', +// 'form.group_advanced_edit.password_complexity' => 'Password complexity', +// 'form.group_advanced_edit.2fa' => 'Two factor authentication', + // Deleting Group form. See example at https://timetracker.anuko.com/delete_group.php // TODO: translate the following. // 'form.group_delete.hint' => 'Are you sure you want to delete the entire group?', @@ -696,21 +691,8 @@ // TODO: translate the following. // 'form.display_options.note_on_separate_row' => 'Note on separate row', // 'form.display_options.not_complete_days' => 'Not complete days', +// 'form.display_options.inactive_projects' => 'Inactive projects', +// 'form.display_options.cost_per_hour' => 'Cost per hour', // 'form.display_options.custom_css' => 'Custom CSS', - -// Work plugin strings. See example at https://timetracker.anuko.com/work.php -// TODO: translate the following. -// 'work.error.work_not_available' => 'Work item is not available.', -// 'work.error.offer_not_available' => 'Offer is not available.', -// 'work.type.one_time' => 'one time', // Work type is "one time job" for well defined work ("do exactly this"). -// 'work.type.ongoing' => 'ongoing', // Work type is "ongoing" for complex jobs (billed by the hour, multiple contractors, etc.) -// 'work.label.own_work' => 'Own work', -// 'work.label.own_offers' => 'Own offers', -// 'work.label.offers' => 'Offers', -// 'work.button.send_message' => 'Send message', -// 'work.button.make_offer' => 'Make offer', -// 'work.button.accept' => 'Accept', -// 'work.button.decline' => 'Decline', -// 'work.title.send_message' => 'Sending Message', -// 'work.msg.message_sent' => 'Message sent.', +// 'form.display_options.custom_translation' => 'Custom translation', ); diff --git a/WEB-INF/resources/da.lang.php b/WEB-INF/resources/da.lang.php index efdacc370..6fe93c261 100644 --- a/WEB-INF/resources/da.lang.php +++ b/WEB-INF/resources/da.lang.php @@ -2,7 +2,8 @@ /* Copyright (c) Anuko International Ltd. https://www.anuko.com License: See license.txt */ -// Note: escape apostrophes with THREE backslashes, like here: choisir l\\\'option. +// Note: escape apostrophes with THREE backslashes, like here: 'choisir l\\\'option'. +// Alternatively: use one backslash and surround by double-quotes: "choisir l\'option". // Other characters (such as double-quotes in http links, etc.) do not have to be escaped. $i18n_language = 'Danish (Dansk)'; @@ -26,6 +27,7 @@ 'menu.plugins' => 'Plugins', 'menu.time' => 'Tid', // TODO: translate the following. +// 'menu.puncher' => 'Punch', // 'menu.week' => 'Week', 'menu.expenses' => 'Udgifter', 'menu.reports' => 'Rapporter', @@ -37,7 +39,6 @@ 'menu.users' => 'Brugere', // TODO: translate the following. // 'menu.groups' => 'Groups', -// 'menu.subgroups' => 'Subgroups', 'menu.export' => 'Eksport', 'menu.clients' => 'Kunder', 'menu.options' => 'Indstillinger', @@ -66,6 +67,9 @@ // TODO: translate the following. // 'error.record' => 'Select record.', 'error.auth' => 'Forkert brugernavn eller adgangskode.', +// TODO: translate the following. +// 'error.2fa_code' => 'Invalid 2FA code.', +// 'error.weak_password' => 'Weak password.', 'error.user_exists' => 'Brugernavn eksistere allerede.', // TODO: translate the following. // 'error.object_exists' => 'Object with this name already exists.', @@ -94,7 +98,6 @@ // 'error.user_count' => 'Limit on user count.', // 'error.expired' => 'Expiration date reached.', // 'error.file_storage' => 'File storage server error.', // See comment in English file. -// 'error.remote_work' => 'Remote work server error.', // See comment in English file. // Warning messages. // TODO: translate the following. @@ -122,10 +125,13 @@ 'button.export' => 'Eksporter team', // TODO: replace "team" with "group". 'button.import' => 'Importer team', // TODO: replace "team" with "group". 'button.close' => 'Luk', +// TODO: translate the following. +// 'button.start' => 'Start', 'button.stop' => 'Stop', // TODO: translate the following. // 'button.approve' => 'Approve', // 'button.disapprove' => 'Disapprove', +// 'button.sync' => 'Sync', // Used in Android app. The meaning is to synchronize offline time records with server. // Labels for controls on forms. Labels in this section are used on multiple forms. // TODO: translate the following. @@ -186,6 +192,7 @@ // TODO: translate the following. // 'label.day_view' => 'Day view', // 'label.week_view' => 'Week view', +// 'label.puncher' => 'Puncher', 'label.id' => 'ID', 'label.language' => 'Sprog', 'label.decimal_mark' => 'Decimal tegn', @@ -204,9 +211,6 @@ 'label.ldap_hint' => 'Skriv dit Windows brugernavn eller adgangskode i felterne her under.', 'label.required_fields' => '* - obligatorisk felt', 'label.on_behalf' => 'på vegne af', -'label.role_manager' => '(Manager)', -'label.role_comanager' => '(Co-Manager)', -'label.role_admin' => '(Administrator)', 'label.page' => 'Side', 'label.condition' => 'Betingelse', // TODO: translate the following. @@ -231,9 +235,9 @@ // 'label.paid_status' => 'Paid status', // 'label.paid' => 'Paid', // 'label.mark_paid' => 'Mark paid', -// 'label.week_menu' => 'Week menu', // 'label.week_note' => 'Week note', // 'label.week_list' => 'Week list', +// 'label.weekends' => 'Weekends', // 'label.work_units' => 'Work units', // 'label.work_units_short' => 'Units', 'label.totals_only' => 'Kun Total', @@ -250,21 +254,8 @@ // 'label.attachments' => 'Attachments', // 'label.files' => 'Files', // 'label.file' => 'File', -// 'label.image' => 'Image', -// 'label.download' => 'Download', 'label.active_users' => 'Aktive Brugere', 'label.inactive_users' => 'Inaktive Brugere', -// TODO: translate the following. -// 'label.details' => 'Details', -// 'label.budget' => 'Budget', -// 'label.work' => 'Work', // Table column header for work items, see https://www.anuko.com/time_tracker/what_is/work_plugin.htm -// 'label.offer' => 'Offer', // Table column header for offers, see https://www.anuko.com/time_tracker/what_is/work_plugin.htm -// 'label.contractor' => 'Contractor', // Table column header for offers (contractor is someone who offers to do work). - // Technically, it is either an org name or a combination of org and group names - // because both work items and offers are owned by Time Tracker groups of users. -// 'label.how_to_pay' => 'How to pay', // Label for the "How to pay" field on offers, which allows contractors to specify - // how to pay them, for example: paypal email, check by mail to a specific address, etc. -// 'label.moderator_comment' => 'Moderator comment', // Label for "Moderator comment" field that explains something. // Entity names. We use lower case (in English) because they are used in dropdowns, too. // They are used to associate a custom field with an entity type. @@ -278,9 +269,10 @@ // TODO: Translate the following. // 'title.success' => 'Success', 'title.login' => 'Login', +// TODO: translate the follolwing. +// 'title.2fa' => 'Two Factor Authentication', 'title.groups' => 'Teams', // TODO: change "teams" to "groups". // TODO: translate the following. -// 'title.subgroups' => 'Subgroups', // 'title.add_group' => 'Adding Group', 'title.edit_group' => 'Redigér Team', // TODO: change "team" to "group". 'title.delete_group' => 'Slet Team', // TODO: change "team" to "group". @@ -291,6 +283,7 @@ 'title.delete_time_record' => 'Slet Tidsregistrering', // TODO: Translate the following. // 'title.time_files' => 'Time Record Files', +// 'title.puncher' => 'Puncher', 'title.expenses' => 'Udgifter', 'title.edit_expense' => 'Redigér Udgift', 'title.delete_expense' => 'Slet Udgift', @@ -373,22 +366,6 @@ // 'title.edit_file' => 'Editing File', // 'title.delete_file' => 'Deleting File', // 'title.download_file' => 'Downloading File', -// 'title.work' => 'Work', -// 'title.add_work' => 'Adding Work', -// 'title.edit_work' => 'Editing Work', -// 'title.delete_work' => 'Deleting Work', -// 'title.active_work' => 'Active Work', // Active work items this group outsources to other groups. -// 'title.available_work' => 'Available Work', // Available work items from other organizations. -// 'title.inactive_work' => 'Inactive Work', // Inactive work items this group was outsourcing to other groups. -// 'title.pending_work' => 'Pending Work', // Work items pending moderator approval. -// 'title.offer' => 'Offer', -// 'title.add_offer' => 'Adding Offer', -// 'title.edit_offer' => 'Editing Offer', -// 'title.delete_offer' => 'Deleting Offer', -// 'title.active_offers' => 'Active Offers', // Active offers this group makes available to other groups. -// 'title.available_offers' => 'Available Offers', // Available offers from other organizations. -// 'title.inactive_offers' => 'Inactive Offers', // Inactive offers for group. -// 'title.pending_offers' => 'Pending Offers', // Offers pending moderator approval. // Section for common strings inside combo boxes on forms. Strings shared between forms shall be placed here. // Strings that are used in a single form must go to the specific form section. @@ -421,7 +398,6 @@ 'dropdown.delete' => 'Slet', 'dropdown.do_not_delete' => 'Slet ikke', // TODO: translate the following. -// 'dropdown.pending_approval' => 'pending approval', // 'dropdown.approved' => 'approved', // 'dropdown.not_approved' => 'not approved', // 'dropdown.paid' => 'paid', @@ -433,6 +409,16 @@ 'form.login.forgot_password' => 'Har du glemt din adgangskode?', 'form.login.about' => 'Anuko Time Tracker er et open source tidsregistrerings system.', +// Email subject and body for two-factor authentication. +// TODO: translate the following. +// 'email.2fa_code.subject' => 'Anuko Time Tracker two-factor authentication code', +// 'email.2fa_code.body' => "Dear User,\n\nYour two-factor authentication code is:\n\n%s\n\n", + +// Two-factor authentication form. See example at https://timetracker.anuko.com/2fa.php. +// TODO: translate the following. +// 'form.2fa.hint' => 'Check your email for 2FA code and enter it here.', +// 'form.2fa.2fa_code' => '2FA code', + // Resetting Password form. See example at https://timetracker.anuko.com/password_reset.php. 'form.reset_password.message' => 'Nulstilling af adgangskode er sendt på email.', 'form.reset_password.email_subject' => 'Anuko Time Tracker - Anmodning om nulstilling af adgangskode', @@ -474,10 +460,13 @@ // 'form.reports.include_pending' => 'pending', 'form.reports.select_period' => 'Vælg en periode', 'form.reports.set_period' => 'eller sæt datoer', +// TODO: translate the following. +// 'form.reports.note_containing' => 'Note containing', 'form.reports.show_fields' => 'Vis felter', // TODO: translate the following. // 'form.reports.time_fields' => 'Time fields', // 'form.reports.user_fields' => 'User fields', +// 'form.reports.project_fields' => 'Project fields', 'form.reports.group_by' => 'Gruppér ved', 'form.reports.group_by_no' => '--- Ingen gruppereing ---', 'form.reports.group_by_date' => 'Dato', @@ -490,6 +479,7 @@ // (after generating a report at https://timetracker.anuko.com/reports.php). 'form.report.export' => 'Eksport', // TODO: translate the following. +// 'form.report.per_hour' => 'Per hour', // 'form.report.assign_to_invoice' => 'Assign to invoice', // 'form.report.assign_to_timesheet' => 'Assign to timesheet', @@ -503,8 +493,8 @@ // 'form.templates.active_templates' => 'Active Templates', // 'form.templates.inactive_templates' => 'Inactive Templates', -// Invoice form. See example at https://timetracker.anuko.com/invoice.php -// (you can get to this form after generating a report). +// Invoice form. See example at https://timetracker.anuko.com/invoice_view.php +// (you can get to this form after generating an invoice). 'form.invoice.number' => 'Fakturanummer', 'form.invoice.person' => 'Person', @@ -527,6 +517,8 @@ 'form.tasks.inactive_tasks' => 'Inaktive Opgaver', // Users form. See example at https://timetracker.anuko.com/users.php +// TODO: translate the following. +// 'form.users.uncompleted_entry_today' => 'User has an uncompleted time entry today', 'form.users.uncompleted_entry' => 'Bruger har en uafsluttet tidsregistrering', 'form.users.role' => 'Rolle', 'form.users.manager' => 'Manager', @@ -587,14 +579,20 @@ 'form.group_edit.type_duration' => 'Varighed', // TODO: translate the following. // 'form.group_edit.punch_mode' => 'Punch mode', +// 'form.group_edit.one_uncompleted' => 'One uncompleted', // 'form.group_edit.allow_overlap' => 'Allow overlap', // 'form.group_edit.future_entries' => 'Future entries', 'form.group_edit.uncompleted_indicators' => 'Uafsluttede indikatore', // TODO: translate the following. // 'form.group_edit.confirm_save' => 'Confirm saving', -// 'form.group_edit.allow_ip' => 'Allow IP', // 'form.group_edit.advanced_settings' => 'Advanced settings', +// Advanced Group Settings form. See example at https://timetracker.anuko.com/group_advanced_edit.php. +// TODO: Translate the following. +// 'form.group_advanced_edit.allow_ip' => 'Allow IP', +// 'form.group_advanced_edit.password_complexity' => 'Password complexity', +// 'form.group_advanced_edit.2fa' => 'Two factor authentication', + // Deleting Group form. See example at https://timetracker.anuko.com/delete_group.php // TODO: translate the following. // 'form.group_delete.hint' => 'Are you sure you want to delete the entire group?', @@ -659,21 +657,8 @@ // TODO: translate the following. // 'form.display_options.note_on_separate_row' => 'Note on separate row', // 'form.display_options.not_complete_days' => 'Not complete days', +// 'form.display_options.inactive_projects' => 'Inactive projects', +// 'form.display_options.cost_per_hour' => 'Cost per hour', // 'form.display_options.custom_css' => 'Custom CSS', - -// Work plugin strings. See example at https://timetracker.anuko.com/work.php -// TODO: translate the following. -// 'work.error.work_not_available' => 'Work item is not available.', -// 'work.error.offer_not_available' => 'Offer is not available.', -// 'work.type.one_time' => 'one time', // Work type is "one time job" for well defined work ("do exactly this"). -// 'work.type.ongoing' => 'ongoing', // Work type is "ongoing" for complex jobs (billed by the hour, multiple contractors, etc.) -// 'work.label.own_work' => 'Own work', -// 'work.label.own_offers' => 'Own offers', -// 'work.label.offers' => 'Offers', -// 'work.button.send_message' => 'Send message', -// 'work.button.make_offer' => 'Make offer', -// 'work.button.accept' => 'Accept', -// 'work.button.decline' => 'Decline', -// 'work.title.send_message' => 'Sending Message', -// 'work.msg.message_sent' => 'Message sent.', +// 'form.display_options.custom_translation' => 'Custom translation', ); diff --git a/WEB-INF/resources/de.lang.php b/WEB-INF/resources/de.lang.php index e41d92b09..9e9ad0a3d 100644 --- a/WEB-INF/resources/de.lang.php +++ b/WEB-INF/resources/de.lang.php @@ -2,7 +2,8 @@ /* Copyright (c) Anuko International Ltd. https://www.anuko.com License: See license.txt */ -// Note: escape apostrophes with THREE backslashes, like here: choisir l\\\'option. +// Note: escape apostrophes with THREE backslashes, like here: 'choisir l\\\'option'. +// Alternatively: use one backslash and surround by double-quotes: "choisir l\'option". // Other characters (such as double-quotes in http links, etc.) do not have to be escaped. $i18n_language = 'German (Deutsch)'; @@ -19,24 +20,21 @@ 'menu.logout' => 'Abmelden', 'menu.forum' => 'Forum', 'menu.help' => 'Hilfe', -// TODO: translate the following. -// 'menu.register' => 'Register', +'menu.register' => 'Registrieren', 'menu.profile' => 'Profil', 'menu.group' => 'Gruppe', 'menu.plugins' => 'Erweiterungen', 'menu.time' => 'Zeiten', +'menu.puncher' => 'Stempeluhr', 'menu.week' => 'Woche', 'menu.expenses' => 'Kosten', 'menu.reports' => 'Berichte', -// TODO: translate the following. -// 'menu.timesheets' => 'Timesheets', +'menu.timesheets' => 'Arbeitszeittabellen', 'menu.charts' => 'Diagramme', 'menu.projects' => 'Projekte', 'menu.tasks' => 'Aufgaben', 'menu.users' => 'Personen', 'menu.groups' => 'Gruppen', -// TODO: translate the following. -// 'menu.subgroups' => 'Subgroups', 'menu.export' => 'Exportieren', 'menu.clients' => 'Kunden', 'menu.options' => 'Optionen', @@ -51,8 +49,7 @@ 'error.access_denied' => 'Zugriff verweigert.', 'error.sys' => 'Systemfehler.', 'error.db' => 'Datenbankfehler.', -// TODO: translate the following. -// 'error.registered_recently' => 'Registered recently.', +'error.registered_recently' => 'Kürzlich registriert.', 'error.feature_disabled' => 'Funktion ist deaktiviert.', 'error.field' => 'Ungültige "{0}" Daten.', 'error.empty' => 'Feld "{0}" ist leer.', @@ -64,38 +61,34 @@ 'error.report' => 'Bericht auswählen.', 'error.record' => 'Eintrag auswählen.', 'error.auth' => 'Benutzername oder Passwort ungültig.', +'error.2fa_code' => 'Ungültiger 2FA-Code.', +'error.weak_password' => 'Schwaches Passwort.', 'error.user_exists' => 'Benutzer mit diesem Konto ist bereits vorhanden.', 'error.object_exists' => 'Objekt mit diesem Namen ist bereits vorhanden.', 'error.invoice_exists' => 'Rechnung mit dieser Nummer existiert bereits.', 'error.role_exists' => 'Rolle mit diesem Rang existiert bereits.', 'error.no_invoiceable_items' => 'Keine Einträge zur Rechnungsstellung gefunden.', -// TODO: translate the following. -// 'error.no_records' => 'There are no records.', +'error.no_records' => 'Es gibt keine Einträge.', 'error.no_login' => 'Benutzer mit diesen Anmeldedaten nicht vorhanden.', 'error.no_groups' => 'Die Datenbank ist leer. Als Administrator anmelden und ein neues Gruppe erzeugen.', 'error.upload' => 'Fehler beim hochladen einer Datei.', -'error.range_locked' => 'Zeitinterval ist gesperrt.', -'error.mail_send' => 'Fehler beim versenden einer E-Mail.', -// TODO: improve the translation above by adding MAIL_SMTP_DEBUG part. -// 'error.mail_send' => 'Error sending mail. Use MAIL_SMTP_DEBUG for diagnostics.', +'error.range_locked' => 'Zeitintervall ist gesperrt.', +'error.mail_send' => 'Fehler beim versenden einer E-Mail. Verwenden Sie MAIL_SMTP_DEBUG für die Diagnose.', 'error.no_email' => 'Dieser Benutzer besitzt keine e-Mail Adresse.', 'error.uncompleted_exists' => 'Unvollständiger Eintrag bereits vorhanden. Schließen oder Löschen.', 'error.goto_uncompleted' => 'Zum unvollständigen Eintrag gehen.', -'error.overlap' => 'Der Zeitinterval überschneidet sich mit vorhandenen Einträgen.', +'error.overlap' => 'Der Zeitintervall überschneidet sich mit vorhandenen Einträgen.', 'error.future_date' => 'Datum ist in der Zukunft.', -// TODO: translate the following. -// 'error.xml' => 'Error in XML file at line %d: %s.', -// 'error.cannot_import' => 'Cannot import: %s.', -// 'error.format' => 'Invalid file format.', -// 'error.user_count' => 'Limit on user count.', -// 'error.expired' => 'Expiration date reached.', -// 'error.file_storage' => 'File storage server error.', // See comment in English file. -// 'error.remote_work' => 'Remote work server error.', // See comment in English file. +'error.xml' => 'Fehler in der XML-Datei in der Zeile %d: %s.', +'error.cannot_import' => 'Kann nicht importiert werden: %s.', +'error.format' => 'Ungültiges Dateiformat.', +'error.user_count' => 'Begrenzung der Benutzeranzahl erreicht.', +'error.expired' => 'Ablaufdatum erreicht.', +'error.file_storage' => 'Dateispeicherserver-Fehler.', // Warning messages. 'warn.sure' => 'Sind Sie sicher?', -// TODO: translate the following. -// 'warn.confirm_save' => 'Date has changed. Confirm saving, not copying this item.', +'warn.confirm_save' => 'Das Datum hat sich geändert. Bestätigen Sie das Speichern, nicht das Kopieren dieses Elements.', // Success messages. 'msg.success' => 'Operation vollständig abgeschlossen.', @@ -117,16 +110,14 @@ 'button.export' => 'Gruppe exportieren', 'button.import' => 'Gruppe importieren', 'button.close' => 'Schließen', +'button.start' => 'Start', 'button.stop' => 'Stop', -// TODO: translate the following. -// (PR#81 suggested 'Freigeben / Genehmigen' for 'Approve' and 'Freigabe zurücknehmen' for 'Disapprove'. -// The problem is they do not appear precise, deviate from the meaning of approval / disaproval of report items.) -// 'button.approve' => 'Approve', (suggested 'Freigeben / Genehmigen' does not appear precise) -// 'button.disapprove' => 'Disapprove', +'button.approve' => 'Annehmen', +'button.disapprove' => 'Ablehnen', +'button.sync' => 'Sync', // Labels for controls on forms. Labels in this section are used on multiple forms. -// TODO: translate the following. -// 'label.menu' => 'Menu', +'label.menu' => 'Menü', 'label.group_name' => 'Gruppenname', 'label.address' => 'Adresse', 'label.currency' => 'Währung', @@ -146,9 +137,8 @@ 'label.end_date' => 'Enddatum', 'label.user' => 'Benutzer', 'label.users' => 'Personen', -// TODO: translate the following. -// 'label.group' => 'Group', -// 'label.subgroups' => 'Subgroups', +'label.group' => 'Gruppe', +'label.subgroups' => 'Untergruppen', 'label.roles' => 'Rollen', 'label.client' => 'Kunde', 'label.clients' => 'Kunden', @@ -179,6 +169,7 @@ 'label.select_none' => 'Alle abwählen', 'label.day_view' => 'Tagesansicht', 'label.week_view' => 'Wochenansicht', +'label.puncher' => 'Stempeluhr', 'label.id' => 'ID', 'label.language' => 'Sprache', 'label.decimal_mark' => 'Dezimaltrennzeichen', @@ -197,9 +188,6 @@ 'label.ldap_hint' => 'Geben Sie unten Ihren Windows Benutzernamen und Ihr Passwort ein.', 'label.required_fields' => '* - Pflichtfelder', 'label.on_behalf' => 'für', -'label.role_manager' => '(Manager)', -'label.role_comanager' => '(Co-Manager)', -'label.role_admin' => '(Administrator)', 'label.page' => 'Seite', 'label.condition' => 'Bedingung', 'label.yes' => 'Ja', @@ -208,8 +196,7 @@ // Labels for plugins (extensions to Time Tracker that provide additional features). 'label.custom_fields' => 'Benutzerfelder', 'label.monthly_quotas' => 'Monatliche Quoten', -// TODO: translate the following. -// 'label.entity' => 'Entity', +'label.entity' => 'Einheit', 'label.type' => 'Typ', 'label.type_dropdown' => 'Ausklappen', 'label.type_text' => 'Text', @@ -222,59 +209,39 @@ 'label.paid_status' => 'Bezahlstatus', 'label.paid' => 'Bezahlt', 'label.mark_paid' => 'Als bezahlt setzen', -'label.week_menu' => 'Wochenansicht im Menü', 'label.week_note' => 'Wochennotiz', 'label.week_list' => 'Wochenliste', +'label.weekends' => 'Wochenenden', 'label.work_units' => 'Arbeitseinheiten', 'label.work_units_short' => 'Einheiten', 'label.totals_only' => 'Nur Gesamtstunden', 'label.quota' => 'Quote', -// TODO: translate the following. -// 'label.timesheet' => 'Timesheet', -// 'label.submitted' => 'Submitted', -// 'label.approved' => 'Approved', -// 'label.approval' => 'Report approval', -// 'label.mark_approved' => 'Mark approved', -// 'label.template' => 'Template', -// 'label.bind_templates_with_projects' => 'Bind templates with projects', -// 'label.prepopulate_note' => 'Prepopulate Note field', -// 'label.attachments' => 'Attachments', -// 'label.files' => 'Files', -// 'label.file' => 'File', -// 'label.image' => 'Image', -// 'label.download' => 'Download', +'label.timesheet' => 'Arbeitszeittabelle', +'label.submitted' => 'Eingereicht', +'label.approved' => 'Genehmigt', +'label.approval' => 'Genehmigung des Berichts', +'label.mark_approved' => 'Als genehmigt markieren', +'label.template' => 'Vorlage', +'label.bind_templates_with_projects' => 'Vorlagen mit Projekten verbinden', +'label.prepopulate_note' => 'Notizfeld vorab ausfüllen', +'label.attachments' => 'Anhänge', +'label.files' => 'Dateien', +'label.file' => 'Datei', 'label.active_users' => 'Aktive Nutzer', 'label.inactive_users' => 'Inaktive Nutzer', -// TODO: translate the following or confirm that "Details" is also correct for German (exactly as the English string). -// label.details is used to identify a field for LONG DESCRIPTION of a work item used in Remote Work plugin. -// For example, a work item could be "Design a logo", and the Details hold EXACT anfd PRECISE specs of what a customer needs. -// Another use is with offers with Remote Work plugin, where details hold a long, precise, and complete description of the offer. -// 'label.details' => 'Details', -'label.budget' => 'Budget', -// TODO: translate the following. -// 'label.work' => 'Work', // Table column header for work items, see https://www.anuko.com/time_tracker/what_is/work_plugin.htm -// 'label.offer' => 'Offer', // Table column header for offers, see https://www.anuko.com/time_tracker/what_is/work_plugin.htm -// 'label.contractor' => 'Contractor', // Table column header for offers (contractor is someone who offers to do work). - // Technically, it is either an org name or a combination of org and group names - // because both work items and offers are owned by Time Tracker groups of users. -// 'label.how_to_pay' => 'How to pay', // Label for the "How to pay" field on offers, which allows contractors to specify - // how to pay them, for example: paypal email, check by mail to a specific address, etc. -// 'label.moderator_comment' => 'Moderator comment', // Label for "Moderator comment" field that explains something. // Entity names. We use lower case (in English) because they are used in dropdowns, too. // They are used to associate a custom field with an entity type. -// TODO: translate the following. -// 'entity.time' => 'time', -// 'entity.user' => 'user', -// 'entity.project' => 'project', +'entity.time' => 'Zeit', +'entity.user' => 'Benutzer', +'entity.project' => 'Projekt', // Form titles. 'title.error' => 'Fehler', -'title.success' => 'Erfol', +'title.success' => 'Erfolg', 'title.login' => 'Anmelden', +'title.2fa' => 'Zwei-Faktor-Authentifizierung', 'title.groups' => 'Gruppen', -// TODO: translate the following. -// 'title.subgroups' => 'Subgroups', 'title.add_group' => 'Gruppe anlegen', 'title.edit_group' => 'Gruppe bearbeiten', 'title.delete_group' => 'Gruppe löschen', @@ -283,20 +250,18 @@ 'title.time' => 'Zeiten', 'title.edit_time_record' => 'Bearbeiten des Stundeneintrags', 'title.delete_time_record' => 'Eintrag löschen', -// TODO: Translate the following. -// 'title.time_files' => 'Time Record Files', +'title.time_files' => 'Zeiterfassungsdateien', +'title.puncher' => 'Stempeluhr', 'title.expenses' => 'Kosten', 'title.edit_expense' => 'Kostenposition ändern', 'title.delete_expense' => 'Kostenposition löschen', -// TODO: translate the following. -// 'title.expense_files' => 'Expense Item Files', +'title.expense_files' => 'Ausgabenartikel-Dateien', 'title.reports' => 'Berichte', 'title.report' => 'Bericht', 'title.send_report' => 'Bericht senden', -// TODO: Translate the following. -// 'title.timesheets' => 'Timesheets', -// 'title.timesheet' => 'Timesheet', -// 'title.timesheet_files' => 'Timesheet Files', +'title.timesheets' => 'Arbeitszeittabellen', +'title.timesheet' => 'Arbeitszeittabelle', +'title.timesheet_files' => 'Arbeitszeittabellendateien', 'title.invoice' => 'Rechnung', 'title.send_invoice' => 'Rechnung senden', 'title.charts' => 'Diagramme', @@ -329,10 +294,9 @@ 'title.add_notification' => 'Benachrichtigung hinzufügen', 'title.edit_notification' => 'Benachrichtigung bearbeiten', 'title.delete_notification' => 'Benachrichtigung löschen', -// TODO: translate the following. -// 'title.add_timesheet' => 'Adding Timesheet', -// 'title.edit_timesheet' => 'Editing Timesheet', -// 'title.delete_timesheet' => 'Deleting Timesheet', +'title.add_timesheet' => 'Arbeitszeittabelle hinzufügen', +'title.edit_timesheet' => 'Arbeitszeittabelle bearbeiten', +'title.delete_timesheet' => 'Arbeitszeittabelle löschen', 'title.monthly_quotas' => 'Monatliche Quoten', 'title.export' => 'Daten exportieren', 'title.import' => 'Daten importieren', @@ -352,30 +316,13 @@ 'title.week_view' => 'Wochenansicht', 'title.swap_roles' => 'Tausche Rollen', 'title.work_units' => 'Arbeitseinheiten', -// TODO: translate the following. -// 'title.templates' => 'Templates', -// 'title.add_template' => 'Adding Template', -// 'title.edit_template' => 'Editing Template', -// 'title.delete_template' => 'Deleting Template', -// 'title.edit_file' => 'Editing File', -// 'title.delete_file' => 'Deleting File', -// 'title.download_file' => 'Downloading File', -// 'title.work' => 'Work', -// 'title.add_work' => 'Adding Work', -// 'title.edit_work' => 'Editing Work', -// 'title.delete_work' => 'Deleting Work', -// 'title.active_work' => 'Active Work', // Active work items this group outsources to other groups. -// 'title.available_work' => 'Available Work', // Available work items from other organizations. -// 'title.inactive_work' => 'Inactive Work', // Inactive work items this group was outsourcing to other groups. -// 'title.pending_work' => 'Pending Work', // Work items pending moderator approval. -// 'title.offer' => 'Offer', -// 'title.add_offer' => 'Adding Offer', -// 'title.edit_offer' => 'Editing Offer', -// 'title.delete_offer' => 'Deleting Offer', -// 'title.active_offers' => 'Active Offers', // Active offers this group makes available to other groups. -// 'title.available_offers' => 'Available Offers', // Available offers from other organizations. -// 'title.inactive_offers' => 'Inactive Offers', // Inactive offers for group. -// 'title.pending_offers' => 'Pending Offers', // Offers pending moderator approval. +'title.templates' => 'Vorlagen', +'title.add_template' => 'Vorlage hinzufügen', +'title.edit_template' => 'Vorlage bearbeiten', +'title.delete_template' => 'Vorlage löschen', +'title.edit_file' => 'Datei bearbeiten', +'title.delete_file' => 'Datei löschen', +'title.download_file' => 'Datei herunterladen', // Section for common strings inside combo boxes on forms. Strings shared between forms shall be placed here. // Strings that are used in a single form must go to the specific form section. @@ -399,21 +346,17 @@ 'dropdown.clients' => 'Kunden', 'dropdown.select' => '--- auswählen ---', 'dropdown.select_invoice' => '--- Rechnung auswählen ---', -// TODO: translate the following. -// 'dropdown.select_timesheet' => '--- select timesheet ---', +'dropdown.select_timesheet' => '--- Arbeitszeittabelle auswählen ---', 'dropdown.status_active' => 'aktiv', 'dropdown.status_inactive' => 'inaktiv', 'dropdown.delete' => 'löschen', 'dropdown.do_not_delete' => 'nicht löschen', -// TODO: translate the following. -// 'dropdown.pending_approval' => 'pending approval', -// 'dropdown.approved' => 'approved', -// 'dropdown.not_approved' => 'not approved', +'dropdown.approved' => 'genehmigt', +'dropdown.not_approved' => 'abgelehnt', 'dropdown.paid' => 'bezahlt', 'dropdown.not_paid' => 'nicht bezahlt', -// TODO: translate the following. -// 'dropdown.ascending' => 'ascending', -// 'dropdown.descending' => 'descending', +'dropdown.ascending' => 'aufsteigend', +'dropdown.descending' => 'absteigend', // Below is a section for strings that are used on individual forms. When a string is used only on one form it should be placed here. // One exception is for closely related forms such as "Time" and "Editing Time Record" with similar controls. In such cases @@ -424,6 +367,14 @@ 'form.login.forgot_password' => 'Passwort vergessen?', 'form.login.about' => 'Anuko Time Tracker ist ein Open-Source Zeiterfassungssystem.', +// Email subject and body for two-factor authentication. +'email.2fa_code.subject' => 'Anuko Time Tracker Zwei-Faktor-Authentifizierungscode', +'email.2fa_code.body' => "Sehr geehrter Benutzer,\n\nIhr Zwei-Faktor-Authentifizierungscode lautet:\n\n%s\n\n", + +// Two-factor authentication form. See example at https://timetracker.anuko.com/2fa.php. +'form.2fa.hint' => 'Überprüfen Sie Ihre E-Mails auf den 2FA-Code und geben Sie ihn hier ein.', +'form.2fa.2fa_code' => '2FA-Code', + // Resetting Password form. See example at https://timetracker.anuko.com/password_reset.php. 'form.reset_password.message' => 'Anfrage zur Zurücksetzung des Passwortes wurde per E-mail gesendet.', 'form.reset_password.email_subject' => 'Anuko Time Tracker Anfrage zur Zurücksetzung des Passwortes', @@ -454,16 +405,14 @@ 'form.reports.include_not_billable' => 'nicht in Rechnung stellen', 'form.reports.include_invoiced' => 'berechnet', 'form.reports.include_not_invoiced' => 'nicht berechnet', -// TODO: translate the following. -// 'form.reports.include_assigned' => 'assigned', -// 'form.reports.include_not_assigned' => 'not assigned', -// 'form.reports.include_pending' => 'pending', +'form.reports.include_pending' => 'Ausstehend', 'form.reports.select_period' => 'Zeitraum auswählen', 'form.reports.set_period' => 'oder Datum eingeben', +'form.reports.note_containing' => 'Notiz enthält', 'form.reports.show_fields' => 'Felder anzeigen', -// TODO: translate the following. -// 'form.reports.time_fields' => 'Time fields', -// 'form.reports.user_fields' => 'User fields', +'form.reports.time_fields' => 'Zeitfelder', +'form.reports.user_fields' => 'Benutzerfelder', +'form.reports.project_fields' => 'Projektfelder', 'form.reports.group_by' => 'Gruppieren nach', 'form.reports.group_by_no' => '--- keine Gruppierung ---', 'form.reports.group_by_date' => 'Datum', @@ -475,22 +424,20 @@ // Report form. See example at https://timetracker.anuko.com/report.php // (after generating a report at https://timetracker.anuko.com/reports.php). 'form.report.export' => 'Exportiere', +'form.report.per_hour' => 'Stundensatz', 'form.report.assign_to_invoice' => 'Zu Rechnung hinzufügen', -// TODO: translate the following. -// 'form.report.assign_to_timesheet' => 'Assign to timesheet', +'form.report.assign_to_timesheet' => 'Zu Arbeitszeittabelle zuweisen', // Timesheets form. See example at https://timetracker.anuko.com/timesheets.php -// TODO: translate the following. -// 'form.timesheets.active_timesheets' => 'Active Timesheets', -// 'form.timesheets.inactive_timesheets' => 'Inactive Timesheets', +'form.timesheets.active_timesheets' => 'Aktive Arbeitszeittabellen', +'form.timesheets.inactive_timesheets' => 'Inaktive Arbeitszeittabellen', // Templates form. See example at https://timetracker.anuko.com/templates.php -// TODO: translate the following. -// 'form.templates.active_templates' => 'Active Templates', -// 'form.templates.inactive_templates' => 'Inactive Templates', +'form.templates.active_templates' => 'Aktive Vorlagen', +'form.templates.inactive_templates' => 'Inaktive Vorlagen', -// Invoice form. See example at https://timetracker.anuko.com/invoice.php -// (you can get to this form after generating a report). +// Invoice form. See example at https://timetracker.anuko.com/invoice_view.php +// (you can get to this form after generating an invoice). 'form.invoice.number' => 'Rechnungsnummer', 'form.invoice.person' => 'Person', @@ -512,6 +459,7 @@ 'form.tasks.inactive_tasks' => 'Inaktive Tasks', // Users form. See example at https://timetracker.anuko.com/users.php +'form.users.uncompleted_entry_today' => 'Der Benutzer hat heute einen nicht abgeschlossenen Zeiteintrag', 'form.users.uncompleted_entry' => 'Nutzer hat einen unvollständigen Zeiteintrag', 'form.users.role' => 'Rolle', 'form.users.manager' => 'Manager', @@ -565,14 +513,18 @@ 'form.group_edit.type_all' => 'alle', 'form.group_edit.type_start_finish' => 'Start und Ende', 'form.group_edit.type_duration' => 'Dauer', -'form.group_edit.punch_mode' => 'Stechuhr-Modus', +'form.group_edit.punch_mode' => 'Stempeluhr-Modus', +'form.group_edit.one_uncompleted' => 'Eine unvollendete', 'form.group_edit.allow_overlap' => 'Erlaube Überschneidung', 'form.group_edit.future_entries' => 'Einträge in der Zukunft', 'form.group_edit.uncompleted_indicators' => 'Zeige unfertige Einträge', 'form.group_edit.confirm_save' => 'Speichern bestätigen', -'form.group_edit.allow_ip' => 'Erlaube IP', -// TODO: translate the following. -// 'form.group_edit.advanced_settings' => 'Advanced settings', +'form.group_edit.advanced_settings' => 'Erweiterte Einstellungen', + +// Advanced Group Settings form. See example at https://timetracker.anuko.com/group_advanced_edit.php. +'form.group_advanced_edit.allow_ip' => 'Erlaube IP', +'form.group_advanced_edit.password_complexity' => 'Komplexität des Passworts', +'form.group_advanced_edit.2fa' => 'Zwei-Faktor-Authentifizierung', // Deleting Group form. See example at https://timetracker.anuko.com/delete_group.php 'form.group_delete.hint' => 'Sind Sie sicher, dass Sie die gesamte Gruppe löschen möchten?', @@ -588,10 +540,10 @@ 'form.quota.year' => 'Jahr', 'form.quota.month' => 'Monat', 'form.quota.workday_hours' => 'Arbeitsstunden pro Tag', -'form.quota.hint' => 'Wenn leergelassen wird die Quote automatisch berechnet (Basierend auf Arbeitsstunden pro Tag und Feiertagen)', +'form.quota.hint' => 'Wenn leer gelassen wird die Quote automatisch berechnet (Basierend auf Arbeitsstunden pro Tag und Feiertagen)', // Swap roles form. See example at https://timetracker.anuko.com/swap_roles.php. -'form.swap.hint' => 'Stufen Sie ihre Rolle auf eine niedrigere indem Sie mit jemadem die Rollen tauschen. Dies kann nicht rückgängig gemacht werden.', +'form.swap.hint' => 'Stufen Sie ihre Rolle auf eine niedrigere indem Sie mit jemandem die Rollen tauschen. Dies kann nicht rückgängig gemacht werden.', 'form.swap.swap_with' => 'Tausche Rolle mit', // Work Units configuration form. See example at https://timetracker.anuko.com/work_units.php after enabling Work units plugin. @@ -604,9 +556,7 @@ 'role.user.description' => 'Ein normaler Benutzer ohne Administrationsrechte.', 'role.client.label' => 'Kunde', 'role.client.low_case_label' => 'Kunde', -// TODO: translate the following. -// 'role.client.description' => 'A client can view its own data.', -'role.client.description' => 'Ein Kunde kann zu ihm gehörende Berichte und Rechnungen ansehen.', +'role.client.description' => 'Ein Kunde kann seine eigenen Daten einsehen.', 'role.supervisor.label' => 'Dienstvorgesetzter', 'role.supervisor.low_case_label' => 'Dienstvorgesetzter', 'role.supervisor.description' => 'Eine Person mit ein paar Administrationsrechten.', @@ -621,36 +571,21 @@ 'role.top_manager.description' => 'Top Gruppen-Manager. Kann alles innerhalb eines Gruppenbaums administrieren', 'role.admin.label' => 'Administrator', 'role.admin.low_case_label' => 'Administrator', -'role.admin.description' => 'Aadminsitrator der Seite.', +'role.admin.description' => 'Administrator der Seite.', // Timesheet View form. See example at https://timetracker.anuko.com/timesheet_view.php. -// TODO: translate the following. -// 'form.timesheet_view.submit_subject' => 'Timesheet approval request', -// 'form.timesheet_view.submit_body' => "A new timesheet requires approval.

User: %s.", -// 'form.timesheet_view.approve_subject' => 'Timesheet approved', -// 'form.timesheet_view.approve_body' => "Your timesheet %s was approved.

%s", -// 'form.timesheet_view.disapprove_subject' => 'Timesheet not approved', -// 'form.timesheet_view.disapprove_body' => "Your timesheet %s was not approved.

%s", +'form.timesheet_view.submit_subject' => 'Antrag auf Genehmigung der Arbeitszeittabelle', +'form.timesheet_view.submit_body' => "Eine neue Arbeitszeittabelle muss genehmigt werden.

Benutzer: %s.", +'form.timesheet_view.approve_subject' => 'Arbeitszeittabelle genehmigt', +'form.timesheet_view.approve_body' => "Ihre Arbeitszeittabelle %s wurde genehmigt.

%s", +'form.timesheet_view.disapprove_subject' => 'Arbeitszeittabelle nicht genehmigt', +'form.timesheet_view.disapprove_body' => "Ihre Arbeitszeittabelle %s wurde nicht genehmigt.

%s", // Display Options form. See example at https://timetracker.anuko.com/display_options.php. 'form.display_options.note_on_separate_row' => 'Beschreibung in separater Zeile', -// TODO: translate the following. -// 'form.display_options.not_complete_days' => 'Not complete days', -// 'form.display_options.custom_css' => 'Custom CSS', - -// Work plugin strings. See example at https://timetracker.anuko.com/work.php -// TODO: translate the following. -// 'work.error.work_not_available' => 'Work item is not available.', -// 'work.error.offer_not_available' => 'Offer is not available.', -// 'work.type.one_time' => 'one time', // Work type is "one time job" for well defined work ("do exactly this"). -// 'work.type.ongoing' => 'ongoing', // Work type is "ongoing" for complex jobs (billed by the hour, multiple contractors, etc.) -// 'work.label.own_work' => 'Own work', -// 'work.label.own_offers' => 'Own offers', -// 'work.label.offers' => 'Offers', -// 'work.button.send_message' => 'Send message', -// 'work.button.make_offer' => 'Make offer', -// 'work.button.accept' => 'Accept', -// 'work.button.decline' => 'Decline', -// 'work.title.send_message' => 'Sending Message', -// 'work.msg.message_sent' => 'Message sent.', +'form.display_options.not_complete_days' => 'Nicht vollständige Tage', +'form.display_options.inactive_projects' => 'Inaktive Projekte', +'form.display_options.cost_per_hour' => 'Kosten pro Stunde', +'form.display_options.custom_css' => 'Benutzerdefiniertes CSS', +'form.display_options.custom_translation' => 'Benutzerdefinierte Übersetzung', ); diff --git a/WEB-INF/resources/en.lang.php b/WEB-INF/resources/en.lang.php index 37aae4cae..5858b63d4 100644 --- a/WEB-INF/resources/en.lang.php +++ b/WEB-INF/resources/en.lang.php @@ -2,7 +2,8 @@ /* Copyright (c) Anuko International Ltd. https://www.anuko.com License: See license.txt */ -// Note: escape apostrophes with THREE backslashes, like here: choisir l\\\'option. +// Note: escape apostrophes with THREE backslashes, like here: 'choisir l\\\'option'. +// Alternatively: use one backslash and surround by double-quotes: "choisir l\'option". // Other characters (such as double-quotes in http links, etc.) do not have to be escaped. $i18n_language = 'English'; @@ -21,9 +22,9 @@ 'menu.register' => 'Register', 'menu.profile' => 'Profile', 'menu.group' => 'Group', -'menu.subgroups' => 'Subgroups', 'menu.plugins' => 'Plugins', 'menu.time' => 'Time', +'menu.puncher' => 'Punch', 'menu.week' => 'Week', 'menu.expenses' => 'Expenses', 'menu.reports' => 'Reports', @@ -60,6 +61,8 @@ 'error.report' => 'Select report.', 'error.record' => 'Select record.', 'error.auth' => 'Incorrect login or password.', +'error.2fa_code' => 'Invalid 2FA code.', +'error.weak_password' => 'Weak password.', 'error.user_exists' => 'User with this login already exists.', 'error.object_exists' => 'Object with this name already exists.', 'error.invoice_exists' => 'Invoice with this number already exists.', @@ -86,11 +89,6 @@ // "something went wrong" when trying to do some operation with attachments. // For example, File Storage server could be offline, or Time Tracker config option is wrong, etc. 'error.file_storage' => 'File storage server error.', -// Meaning of error.remote_work: an (unspecified) error occurred when trying to communicate with -// "Remote Work" server, the one that supports the "Work" plugin, see https://www.anuko.com/time_tracker/what_is/work_plugin.htm -// It is a generic message telling us that "something went wrong" when trying to do some operation with Work plugin. -// For example, Remote Work server could be offline, among other things. -'error.remote_work' => 'Remote work server error.', // Warning messages. 'warn.sure' => 'Are you sure?', @@ -116,9 +114,11 @@ 'button.export' => 'Export group', 'button.import' => 'Import group', 'button.close' => 'Close', +'button.start' => 'Start', 'button.stop' => 'Stop', 'button.approve' => 'Approve', 'button.disapprove' => 'Disapprove', +'button.sync' => 'Sync', // Used in Android app. The meaning is to synchronize offline time records with server. // Labels for controls on forms. Labels in this section are used on multiple forms. 'label.menu' => 'Menu', @@ -173,6 +173,7 @@ 'label.select_none' => 'Deselect all', 'label.day_view' => 'Day view', 'label.week_view' => 'Week view', +'label.puncher' => 'Puncher', 'label.id' => 'ID', 'label.language' => 'Language', 'label.decimal_mark' => 'Decimal mark', @@ -191,9 +192,6 @@ 'label.ldap_hint' => 'Type your Windows login and password in the fields below.', 'label.required_fields' => '* - required fields', 'label.on_behalf' => 'on behalf of', -'label.role_manager' => '(manager)', -'label.role_comanager' => '(co-manager)', -'label.role_admin' => '(administrator)', 'label.page' => 'Page', 'label.condition' => 'Condition', 'label.yes' => 'yes', @@ -215,9 +213,9 @@ 'label.paid_status' => 'Paid status', 'label.paid' => 'Paid', 'label.mark_paid' => 'Mark paid', -'label.week_menu' => 'Week menu', 'label.week_note' => 'Week note', 'label.week_list' => 'Week list', +'label.weekends' => 'Weekends', 'label.work_units' => 'Work units', 'label.work_units_short' => 'Units', 'label.totals_only' => 'Totals only', @@ -233,20 +231,8 @@ 'label.attachments' => 'Attachments', 'label.files' => 'Files', 'label.file' => 'File', -'label.image' => 'Image', -'label.download' => 'Download', 'label.active_users' => 'Active Users', 'label.inactive_users' => 'Inactive Users', -'label.details' => 'Details', -'label.budget' => 'Budget', -'label.work' => 'Work', // Table column header for work items, see https://www.anuko.com/time_tracker/what_is/work_plugin.htm -'label.offer' => 'Offer', // Table column header for offers, see https://www.anuko.com/time_tracker/what_is/work_plugin.htm -'label.contractor' => 'Contractor', // Table column header for offers (contractor is someone who offers to do work). - // Technically, it is either an org name or a combination of org and group names - // because both work items and offers are owned by Time Tracker groups of users. -'label.how_to_pay' => 'How to pay', // Label for the "How to pay" field on offers, which allows contractors to specify - // how to pay them, for example: paypal email, check by mail to a specific address, etc. -'label.moderator_comment' => 'Moderator comment', // Label for "Moderator comment" field that explains something. // Entity names. We use lower case (in English) because they are used in dropdowns, too. // They are used to associate a custom field with an entity type. @@ -258,8 +244,8 @@ 'title.error' => 'Error', 'title.success' => 'Success', 'title.login' => 'Login', +'title.2fa' => 'Two Factor Authentication', 'title.groups' => 'Groups', -'title.subgroups' => 'Subgroups', 'title.add_group' => 'Adding Group', 'title.edit_group' => 'Editing Group', 'title.delete_group' => 'Deleting Group', @@ -269,6 +255,7 @@ 'title.edit_time_record' => 'Editing Time Record', 'title.delete_time_record' => 'Deleting Time Record', 'title.time_files' => 'Time Record Files', +'title.puncher' => 'Puncher', 'title.expenses' => 'Expenses', 'title.edit_expense' => 'Editing Expense Item', 'title.delete_expense' => 'Deleting Expense Item', @@ -346,22 +333,6 @@ 'title.edit_file' => 'Editing File', 'title.delete_file' => 'Deleting File', 'title.download_file' => 'Downloading File', -'title.work' => 'Work', -'title.add_work' => 'Adding Work', -'title.edit_work' => 'Editing Work', -'title.delete_work' => 'Deleting Work', -'title.active_work' => 'Active Work', // Active work items this group outsources to other groups. -'title.available_work' => 'Available Work', // Available work items from other organizations. -'title.inactive_work' => 'Inactive Work', // Inactive work items for group. -'title.pending_work' => 'Pending Work', // Work items pending moderator approval. -'title.offer' => 'Offer', -'title.add_offer' => 'Adding Offer', -'title.edit_offer' => 'Editing Offer', -'title.delete_offer' => 'Deleting Offer', -'title.active_offers' => 'Active Offers', // Active offers this group makes available to other groups. -'title.available_offers' => 'Available Offers', // Available offers from other organizations. -'title.inactive_offers' => 'Inactive Offers', // Inactive offers for group. -'title.pending_offers' => 'Pending Offers', // Offers pending moderator approval. // Section for common strings inside combo boxes on forms. Strings shared between forms shall be placed here. // Strings that are used in a single form must go to the specific form section. @@ -390,7 +361,6 @@ 'dropdown.status_inactive' => 'inactive', 'dropdown.delete' => 'delete', 'dropdown.do_not_delete' => 'do not delete', -'dropdown.pending_approval' => 'pending approval', 'dropdown.approved' => 'approved', 'dropdown.not_approved' => 'not approved', 'dropdown.paid' => 'paid', @@ -407,6 +377,14 @@ 'form.login.forgot_password' => 'Forgot password?', 'form.login.about' => 'Anuko Time Tracker is an open source time tracking system.', +// Email subject and body for two-factor authentication. +'email.2fa_code.subject' => 'Anuko Time Tracker two-factor authentication code', +'email.2fa_code.body' => "Dear User,\n\nYour two-factor authentication code is:\n\n%s\n\n", + +// Two-factor authentication form. See example at https://timetracker.anuko.com/2fa.php. +'form.2fa.hint' => 'Check your email for 2FA code and enter it here.', +'form.2fa.2fa_code' => '2FA code', + // Resetting Password form. See example at https://timetracker.anuko.com/password_reset.php. 'form.reset_password.message' => 'Password reset request sent by email.', 'form.reset_password.email_subject' => 'Anuko Time Tracker password reset request', @@ -448,9 +426,11 @@ 'form.reports.include_pending' => 'pending', 'form.reports.select_period' => 'Select time period', 'form.reports.set_period' => 'or set dates', +'form.reports.note_containing' => 'Note containing', 'form.reports.show_fields' => 'Show fields', 'form.reports.time_fields' => 'Time fields', 'form.reports.user_fields' => 'User fields', +'form.reports.project_fields' => 'Project fields', 'form.reports.group_by' => 'Group by', 'form.reports.group_by_no' => '--- no grouping ---', 'form.reports.group_by_date' => 'date', @@ -462,6 +442,7 @@ // Report form. See example at https://timetracker.anuko.com/report.php // (after generating a report at https://timetracker.anuko.com/reports.php). 'form.report.export' => 'Export', +'form.report.per_hour' => 'Per hour', 'form.report.assign_to_invoice' => 'Assign to invoice', 'form.report.assign_to_timesheet' => 'Assign to timesheet', @@ -473,8 +454,8 @@ 'form.templates.active_templates' => 'Active Templates', 'form.templates.inactive_templates' => 'Inactive Templates', -// Invoice form. See example at https://timetracker.anuko.com/invoice.php -// (you can get to this form after generating a report). +// Invoice form. See example at https://timetracker.anuko.com/invoice_view.php +// (you can get to this form after generating an invoice). 'form.invoice.number' => 'Invoice number', 'form.invoice.person' => 'Person', @@ -496,6 +477,7 @@ 'form.tasks.inactive_tasks' => 'Inactive Tasks', // Users form. See example at https://timetracker.anuko.com/users.php +'form.users.uncompleted_entry_today' => 'User has an uncompleted time entry today', 'form.users.uncompleted_entry' => 'User has an uncompleted time entry', 'form.users.role' => 'Role', 'form.users.manager' => 'Manager', @@ -550,13 +532,19 @@ 'form.group_edit.type_start_finish' => 'start and finish', 'form.group_edit.type_duration' => 'duration', 'form.group_edit.punch_mode' => 'Punch mode', +'form.group_edit.one_uncompleted' => 'One uncompleted', 'form.group_edit.allow_overlap' => 'Allow overlap', 'form.group_edit.future_entries' => 'Future entries', 'form.group_edit.uncompleted_indicators' => 'Uncompleted indicators', 'form.group_edit.confirm_save' => 'Confirm saving', -'form.group_edit.allow_ip' => 'Allow IP', +'form.group_edit.password_complexity' => 'Password complexity', 'form.group_edit.advanced_settings' => 'Advanced settings', +// Advanced Group Settings form. See example at https://timetracker.anuko.com/group_advanced_edit.php. +'form.group_advanced_edit.allow_ip' => 'Allow IP', +'form.group_advanced_edit.password_complexity' => 'Password complexity', +'form.group_advanced_edit.2fa' => 'Two factor authentication', + // Deleting Group form. See example at https://timetracker.anuko.com/delete_group.php 'form.group_delete.hint' => 'Are you sure you want to delete the entire group?', @@ -615,21 +603,8 @@ // Display Options form. See example at https://timetracker.anuko.com/display_options.php. 'form.display_options.note_on_separate_row' => 'Note on separate row', 'form.display_options.not_complete_days' => 'Not complete days', +'form.display_options.inactive_projects' => 'Inactive projects', +'form.display_options.cost_per_hour' => 'Cost per hour', 'form.display_options.custom_css' => 'Custom CSS', - -// Work plugin strings. See example at https://timetracker.anuko.com/work.php -'work.error.work_not_available' => 'Work item is not available.', -'work.error.offer_not_available' => 'Offer is not available.', -'work.type.one_time' => 'one time', // Work type is "one time job" for well defined work ("do exactly this"). -'work.type.ongoing' => 'ongoing', // Work type is "ongoing" for complex jobs (billed by the hour, multiple contractors, etc.) -'work.label.own_work' => 'Own work', -'work.label.own_offers' => 'Own offers', -'work.label.offers' => 'Offers', -'work.label.message' => 'Message', -'work.button.send_message' => 'Send message', -'work.button.make_offer' => 'Make offer', -'work.button.accept' => 'Accept', -'work.button.decline' => 'Decline', -'work.title.send_message' => 'Sending Message', -'work.msg.message_sent' => 'Message sent.', +'form.display_options.custom_translation' => 'Custom translation', ); diff --git a/WEB-INF/resources/es.lang.php b/WEB-INF/resources/es.lang.php index 9df814845..b302de1c4 100644 --- a/WEB-INF/resources/es.lang.php +++ b/WEB-INF/resources/es.lang.php @@ -2,7 +2,8 @@ /* Copyright (c) Anuko International Ltd. https://www.anuko.com License: See license.txt */ -// Note: escape apostrophes with THREE backslashes, like here: choisir l\\\'option. +// Note: escape apostrophes with THREE backslashes, like here: 'choisir l\\\'option'. +// Alternatively: use one backslash and surround by double-quotes: "choisir l\'option". // Other characters (such as double-quotes in http links, etc.) do not have to be escaped. $i18n_language = 'Spanish (Español)'; @@ -26,6 +27,7 @@ // 'menu.plugins' => 'Plugins', 'menu.time' => 'Tiempo', // TODO: translate the following. +// 'menu.puncher' => 'Punch', // 'menu.week' => 'Week', // 'menu.expenses' => 'Expenses', 'menu.reports' => 'Reportes', @@ -38,7 +40,6 @@ 'menu.users' => 'Personas', // TODO: translate the following. // 'menu.groups' => 'Groups', -// 'menu.subgroups' => 'Subgroups', // 'menu.export' => 'Export', 'menu.clients' => 'Clientes', 'menu.options' => 'Opciones', @@ -72,6 +73,8 @@ // 'error.record' => 'Select record.', 'error.auth' => 'Usuario o contraseña incorrecta.', // TODO: translate the following. +// 'error.2fa_code' => 'Invalid 2FA code.', +// 'error.weak_password' => 'Weak password.', // 'error.user_exists' => 'User with this login already exists.', // 'error.object_exists' => 'Object with this name already exists.', // 'error.invoice_exists' => 'Invoice with this number already exists.', @@ -95,7 +98,6 @@ // 'error.user_count' => 'Limit on user count.', // 'error.expired' => 'Expiration date reached.', // 'error.file_storage' => 'File storage server error.', // See comment in English file. -// 'error.remote_work' => 'Remote work server error.', // See comment in English file. // Warning messages. // TODO: translate the following. @@ -126,9 +128,11 @@ 'button.import' => 'Importar grupo', // TODO: translate the following. // 'button.close' => 'Close', +// 'button.start' => 'Start', // 'button.stop' => 'Stop', // 'button.approve' => 'Approve', // 'button.disapprove' => 'Disapprove', +// 'button.sync' => 'Sync', // Used in Android app. The meaning is to synchronize offline time records with server. // Labels for controls on forms. Labels in this section are used on multiple forms. // TODO: translate the following. @@ -193,6 +197,7 @@ // TODO: translate the following. // 'label.day_view' => 'Day view', // 'label.week_view' => 'Week view', +// 'label.puncher' => 'Puncher', 'label.id' => 'Identificación', // TODO: translate the following. // 'label.language' => 'Language', @@ -216,9 +221,6 @@ // 'label.ldap_hint' => 'Type your Windows login and password in the fields below.', 'label.required_fields' => '* - campos requeridos', 'label.on_behalf' => 'a nombre de', -'label.role_manager' => '(manejador)', -'label.role_comanager' => '(auxiliar del manejador)', -'label.role_admin' => '(administrador)', // TODO: translate the following. // 'label.page' => 'Page', // 'label.condition' => 'Condition', @@ -243,9 +245,9 @@ // 'label.paid_status' => 'Paid status', // 'label.paid' => 'Paid', // 'label.mark_paid' => 'Mark paid', -// 'label.week_menu' => 'Week menu', // 'label.week_note' => 'Week note', // 'label.week_list' => 'Week list', +// 'label.weekends' => 'Weekends', // 'label.work_units' => 'Work units', // 'label.work_units_short' => 'Units', 'label.totals_only' => 'Solo totales', @@ -262,20 +264,8 @@ // 'label.attachments' => 'Attachments', // 'label.files' => 'Files', // 'label.file' => 'File', -// 'label.image' => 'Image', -// 'label.download' => 'Download', // 'label.active_users' => 'Active Users', // 'label.inactive_users' => 'Inactive Users', -// 'label.details' => 'Details', -// 'label.budget' => 'Budget', -// 'label.work' => 'Work', // Table column header for work items, see https://www.anuko.com/time_tracker/what_is/work_plugin.htm -// 'label.offer' => 'Offer', // Table column header for offers, see https://www.anuko.com/time_tracker/what_is/work_plugin.htm -// 'label.contractor' => 'Contractor', // Table column header for offers (contractor is someone who offers to do work). - // Technically, it is either an org name or a combination of org and group names - // because both work items and offers are owned by Time Tracker groups of users. -// 'label.how_to_pay' => 'How to pay', // Label for the "How to pay" field on offers, which allows contractors to specify - // how to pay them, for example: paypal email, check by mail to a specific address, etc. -// 'label.moderator_comment' => 'Moderator comment', // Label for "Moderator comment" field that explains something. // Entity names. We use lower case (in English) because they are used in dropdowns, too. // They are used to associate a custom field with an entity type. @@ -289,9 +279,10 @@ // 'title.error' => 'Error', // 'title.success' => 'Success', 'title.login' => 'Sesión iniciada', +// TODO: translate the follolwing. +// 'title.2fa' => 'Two Factor Authentication', 'title.groups' => 'Grupos', // TODO: translate the following. -// 'title.subgroups' => 'Subgroups', // 'title.add_group' => 'Adding Group', // 'title.edit_group' => 'Editing Group', // 'title.delete_group' => 'Deleting Group', @@ -303,6 +294,7 @@ 'title.delete_time_record' => 'Eliminando el historial de tiempo', // TODO: Translate the following. // 'title.time_files' => 'Time Record Files', +// 'title.puncher' => 'Puncher', // 'title.expenses' => 'Expenses', // 'title.edit_expense' => 'Editing Expense Item', // 'title.delete_expense' => 'Deleting Expense Item', @@ -388,22 +380,6 @@ // 'title.edit_file' => 'Editing File', // 'title.delete_file' => 'Deleting File', // 'title.download_file' => 'Downloading File', -// 'title.work' => 'Work', -// 'title.add_work' => 'Adding Work', -// 'title.edit_work' => 'Editing Work', -// 'title.delete_work' => 'Deleting Work', -// 'title.active_work' => 'Active Work', // Active work items this group outsources to other groups. -// 'title.available_work' => 'Available Work', // Available work items from other organizations. -// 'title.inactive_work' => 'Inactive Work', // Inactive work items this group was outsourcing to other groups. -// 'title.pending_work' => 'Pending Work', // Work items pending moderator approval. -// 'title.offer' => 'Offer', -// 'title.add_offer' => 'Adding Offer', -// 'title.edit_offer' => 'Editing Offer', -// 'title.delete_offer' => 'Deleting Offer', -// 'title.active_offers' => 'Active Offers', // Active offers this group makes available to other groups. -// 'title.available_offers' => 'Available Offers', // Available offers from other organizations. -// 'title.inactive_offers' => 'Inactive Offers', // Inactive offers for group. -// 'title.pending_offers' => 'Pending Offers', // Offers pending moderator approval. // Section for common strings inside combo boxes on forms. Strings shared between forms shall be placed here. // Strings that are used in a single form must go to the specific form section. @@ -436,7 +412,6 @@ // 'dropdown.status_inactive' => 'inactive', // 'dropdown.delete' => 'delete', // 'dropdown.do_not_delete' => 'do not delete', -// 'dropdown.pending_approval' => 'pending approval', // 'dropdown.approved' => 'approved', // 'dropdown.not_approved' => 'not approved', // 'dropdown.paid' => 'paid', @@ -454,6 +429,16 @@ // TODO: translate the following. // 'form.login.about' => 'Anuko Time Tracker is an open source time tracking system.', +// Email subject and body for two-factor authentication. +// TODO: translate the following. +// 'email.2fa_code.subject' => 'Anuko Time Tracker two-factor authentication code', +// 'email.2fa_code.body' => "Dear User,\n\nYour two-factor authentication code is:\n\n%s\n\n", + +// Two-factor authentication form. See example at https://timetracker.anuko.com/2fa.php. +// TODO: translate the following. +// 'form.2fa.hint' => 'Check your email for 2FA code and enter it here.', +// 'form.2fa.2fa_code' => '2FA code', + // Resetting Password form. See example at https://timetracker.anuko.com/password_reset.php. // TODO: check / improve translation of form.reset_password.message. // English form is: 'form.reset_password.message' => 'Password reset request sent by email.', @@ -503,10 +488,13 @@ // 'form.reports.include_pending' => 'pending', 'form.reports.select_period' => 'Seleccionar período de tiempo', 'form.reports.set_period' => 'o establecer fechas', +// TODO: translate the following. +// 'form.reports.note_containing' => 'Note containing', 'form.reports.show_fields' => 'Mostrar campos', // TODO: translate the following. // 'form.reports.time_fields' => 'Time fields', // 'form.reports.user_fields' => 'User fields', +// 'form.reports.project_fields' => 'Project fields', 'form.reports.group_by' => 'Agrupar por', 'form.reports.group_by_no' => '--- no agrupar ---', 'form.reports.group_by_date' => 'fecha', @@ -521,6 +509,7 @@ // (after generating a report at https://timetracker.anuko.com/reports.php). 'form.report.export' => 'Exportar', // TODO: translate the following. +// 'form.report.per_hour' => 'Per hour', // 'form.report.assign_to_invoice' => 'Assign to invoice', // 'form.report.assign_to_timesheet' => 'Assign to timesheet', @@ -534,8 +523,8 @@ // 'form.templates.active_templates' => 'Active Templates', // 'form.templates.inactive_templates' => 'Inactive Templates', -// Invoice form. See example at https://timetracker.anuko.com/invoice.php -// (you can get to this form after generating a report). +// Invoice form. See example at https://timetracker.anuko.com/invoice_view.php +// (you can get to this form after generating an invoice). 'form.invoice.number' => 'Número de factura', 'form.invoice.person' => 'Persona', @@ -561,6 +550,7 @@ // Users form. See example at https://timetracker.anuko.com/users.php // TODO: translate the following. +// 'form.users.uncompleted_entry_today' => 'User has an uncompleted time entry today', // 'form.users.uncompleted_entry' => 'User has an uncompleted time entry', 'form.users.role' => 'Rol', 'form.users.manager' => 'Manejador', @@ -624,13 +614,19 @@ // 'form.group_edit.type_start_finish' => 'start and finish', // 'form.group_edit.type_duration' => 'duration', // 'form.group_edit.punch_mode' => 'Punch mode', +// 'form.group_edit.one_uncompleted' => 'One uncompleted', // 'form.group_edit.allow_overlap' => 'Allow overlap', // 'form.group_edit.future_entries' => 'Future entries', // 'form.group_edit.uncompleted_indicators' => 'Uncompleted indicators', // 'form.group_edit.confirm_save' => 'Confirm saving', -// 'form.group_edit.allow_ip' => 'Allow IP', // 'form.group_edit.advanced_settings' => 'Advanced settings', +// Advanced Group Settings form. See example at https://timetracker.anuko.com/group_advanced_edit.php. +// TODO: Translate the following. +// 'form.group_advanced_edit.allow_ip' => 'Allow IP', +// 'form.group_advanced_edit.password_complexity' => 'Password complexity', +// 'form.group_advanced_edit.2fa' => 'Two factor authentication', + // Deleting Group form. See example at https://timetracker.anuko.com/delete_group.php // TODO: translate the following. // 'form.group_delete.hint' => 'Are you sure you want to delete the entire group?', @@ -698,21 +694,8 @@ // TODO: translate the following. // 'form.display_options.note_on_separate_row' => 'Note on separate row', // 'form.display_options.not_complete_days' => 'Not complete days', +// 'form.display_options.inactive_projects' => 'Inactive projects', +// 'form.display_options.cost_per_hour' => 'Cost per hour', // 'form.display_options.custom_css' => 'Custom CSS', - -// Work plugin strings. See example at https://timetracker.anuko.com/work.php -// TODO: translate the following. -// 'work.error.work_not_available' => 'Work item is not available.', -// 'work.error.offer_not_available' => 'Offer is not available.', -// 'work.type.one_time' => 'one time', // Work type is "one time job" for well defined work ("do exactly this"). -// 'work.type.ongoing' => 'ongoing', // Work type is "ongoing" for complex jobs (billed by the hour, multiple contractors, etc.) -// 'work.label.own_work' => 'Own work', -// 'work.label.own_offers' => 'Own offers', -// 'work.label.offers' => 'Offers', -// 'work.button.send_message' => 'Send message', -// 'work.button.make_offer' => 'Make offer', -// 'work.button.accept' => 'Accept', -// 'work.button.decline' => 'Decline', -// 'work.title.send_message' => 'Sending Message', -// 'work.msg.message_sent' => 'Message sent.', +// 'form.display_options.custom_translation' => 'Custom translation', ); diff --git a/WEB-INF/resources/et.lang.php b/WEB-INF/resources/et.lang.php index 7638ade63..19a74b4cd 100644 --- a/WEB-INF/resources/et.lang.php +++ b/WEB-INF/resources/et.lang.php @@ -2,7 +2,8 @@ /* Copyright (c) Anuko International Ltd. https://www.anuko.com License: See license.txt */ -// Note: escape apostrophes with THREE backslashes, like here: choisir l\\\'option. +// Note: escape apostrophes with THREE backslashes, like here: 'choisir l\\\'option'. +// Alternatively: use one backslash and surround by double-quotes: "choisir l\'option". // Other characters (such as double-quotes in http links, etc.) do not have to be escaped. // Note to translators: Use proper capitalization rules for your language. @@ -27,6 +28,7 @@ 'menu.plugins' => 'Lisad', 'menu.time' => 'Ajaarvestus', // TODO: translate the following. +// 'menu.puncher' => 'Punch', // 'menu.week' => 'Week', 'menu.expenses' => 'Kulud', 'menu.reports' => 'Raportid', @@ -37,7 +39,6 @@ 'menu.tasks' => 'Tööülesanded', 'menu.users' => 'Kasutajad', 'menu.groups' => 'Grupid', -'menu.subgroups' => 'Alamgrupid', 'menu.export' => 'Eksport', // TODO: is this a correct term as an opposite of "Import"? 'menu.clients' => 'Kliendid', 'menu.options' => 'Suvandid', @@ -65,6 +66,9 @@ 'error.report' => 'Vali raport.', 'error.record' => 'Vali kirje.', 'error.auth' => 'Autentimine ebaõnnestus.', +// TODO: translate the following. +// 'error.2fa_code' => 'Invalid 2FA code.', +// 'error.weak_password' => 'Weak password.', 'error.user_exists' => 'Selle nimega kasutaja on juba kasutusel.', 'error.object_exists' => 'Sellise nimega objekt on juba olemas.', 'error.invoice_exists' => 'Arve number on juba kasutusel.', @@ -107,7 +111,6 @@ 'error.expired' => 'Kehtivusaeg on lõppenud.', // TODO: translate the following. // 'error.file_storage' => 'File storage server error.', // See comment in English file. -// 'error.remote_work' => 'Remote work server error.', // See comment in English file. // Warning messages. 'warn.sure' => 'Oled kindel?', @@ -133,10 +136,13 @@ 'button.export' => 'Ekspordi grupp', 'button.import' => 'Impordi grupp', 'button.close' => 'Sulge', +// TODO: translate the following. +// 'button.start' => 'Start', 'button.stop' => 'Stopp', // TODO: translate the following. // 'button.approve' => 'Approve', // 'button.disapprove' => 'Disapprove', +// 'button.sync' => 'Sync', // Used in Android app. The meaning is to synchronize offline time records with server. // Labels for controls on forms. Labels in this section are used on multiple forms. @@ -193,6 +199,8 @@ 'label.select_none' => 'Märgi kõik mittevalituks', 'label.day_view' => 'Päeva vaade', 'label.week_view' => 'Nädala vaade', +// TODO: translate the following: +// 'label.puncher' => 'Puncher', 'label.id' => 'ID', 'label.language' => 'Keel', 'label.decimal_mark' => 'Koma märk', @@ -212,9 +220,6 @@ 'label.required_fields' => '* nõutud väljad', // TODO: Translate label.on_behalf, perhaps trying as "instead of". // 'label.on_behalf' => 'on behalf of', -'label.role_manager' => '(haldur)', -'label.role_comanager' => '(kaashaldur)', -'label.role_admin' => '(administraator)', 'label.page' => 'Lehekülg', 'label.condition' => 'Tingimus', 'label.yes' => 'jah', @@ -238,10 +243,10 @@ 'label.paid_status' => 'Makse olek', 'label.paid' => 'Makstud', 'label.mark_paid' => 'Märgi makstuks', -// TODO: translate the following. -// 'label.week_menu' => 'Week menu', 'label.week_note' => 'Nädala märge', 'label.week_list' => 'Nädala nimekiri', +// TODO: translate the following: +// 'label.weekends' => 'Weekends', 'label.work_units' => 'Töö ühikud', 'label.work_units_short' => 'Ühikud', 'label.totals_only' => 'Ainult summad', @@ -258,21 +263,8 @@ // 'label.attachments' => 'Attachments', // 'label.files' => 'Files', // 'label.file' => 'File', -// 'label.image' => 'Image', -// 'label.download' => 'Download', 'label.active_users' => 'Aktiivsed kasutajad', 'label.inactive_users' => 'Mitteaktiivsed kasutajad', -// TODO: translate the following. -// 'label.details' => 'Details', -// 'label.budget' => 'Budget', -// 'label.work' => 'Work', // Table column header for work items, see https://www.anuko.com/time_tracker/what_is/work_plugin.htm -// 'label.offer' => 'Offer', // Table column header for offers, see https://www.anuko.com/time_tracker/what_is/work_plugin.htm -// 'label.contractor' => 'Contractor', // Table column header for offers (contractor is someone who offers to do work). - // Technically, it is either an org name or a combination of org and group names - // because both work items and offers are owned by Time Tracker groups of users. -// 'label.how_to_pay' => 'How to pay', // Label for the "How to pay" field on offers, which allows contractors to specify - // how to pay them, for example: paypal email, check by mail to a specific address, etc. -// 'label.moderator_comment' => 'Moderator comment', // Label for "Moderator comment" field that explains something. // Entity names. We use lower case (in English) because they are used in dropdowns, too. // They are used to associate a custom field with an entity type. @@ -289,8 +281,9 @@ 'title.error' => 'Viga', 'title.success' => 'Õnnestumine', 'title.login' => 'Sisene', +// TODO: translate the follolwing. +// 'title.2fa' => 'Two Factor Authentication', 'title.groups' => 'Grupid', -'title.subgroups' => 'Alamgrupid', 'title.add_group' => 'Lisa grupp', 'title.edit_group' => 'Muuda gruppi', 'title.delete_group' => 'Kustuta grupp', @@ -301,6 +294,7 @@ 'title.delete_time_record' => 'Ajakande kustutamine', // TODO: Translate the following. // 'title.time_files' => 'Time Record Files', +// 'title.puncher' => 'Puncher', 'title.expenses' => 'Kulud', 'title.edit_expense' => 'Kulukirje muutmine', 'title.delete_expense' => 'Kulukirje kustutamine', @@ -382,22 +376,6 @@ // 'title.edit_file' => 'Editing File', // 'title.delete_file' => 'Deleting File', // 'title.download_file' => 'Downloading File', -// 'title.work' => 'Work', -// 'title.add_work' => 'Adding Work', -// 'title.edit_work' => 'Editing Work', -// 'title.delete_work' => 'Deleting Work', -// 'title.active_work' => 'Active Work', // Active work items this group outsources to other groups. -// 'title.available_work' => 'Available Work', // Available work items from other organizations. -// 'title.inactive_work' => 'Inactive Work', // Inactive work items this group was outsourcing to other groups. -// 'title.pending_work' => 'Pending Work', // Work items pending moderator approval. -// 'title.offer' => 'Offer', -// 'title.add_offer' => 'Adding Offer', -// 'title.edit_offer' => 'Editing Offer', -// 'title.delete_offer' => 'Deleting Offer', -// 'title.active_offers' => 'Active Offers', // Active offers this group makes available to other groups. -// 'title.available_offers' => 'Available Offers', // Available offers from other organizations. -// 'title.inactive_offers' => 'Inactive Offers', // Inactive offers for group. -// 'title.pending_offers' => 'Pending Offers', // Offers pending moderator approval. // Section for common strings inside combo boxes on forms. Strings shared between forms shall be placed here. // Strings that are used in a single form must go to the specific form section. @@ -428,7 +406,6 @@ 'dropdown.delete' => 'kustuta', 'dropdown.do_not_delete' => 'ära kustuta', // TODO: translate the following. -// 'dropdown.pending_approval' => 'pending approval', // 'dropdown.approved' => 'approved', // 'dropdown.not_approved' => 'not approved', 'dropdown.paid' => 'makstud', @@ -441,6 +418,16 @@ 'form.login.forgot_password' => 'Unustasid salasõna?', 'form.login.about' => 'Anuko Time Tracker on avatud lähtekoodiga ajaarvestussüsteem.', +// Email subject and body for two-factor authentication. +// TODO: translate the following. +// 'email.2fa_code.subject' => 'Anuko Time Tracker two-factor authentication code', +// 'email.2fa_code.body' => "Dear User,\n\nYour two-factor authentication code is:\n\n%s\n\n", + +// Two-factor authentication form. See example at https://timetracker.anuko.com/2fa.php. +// TODO: translate the following. +// 'form.2fa.hint' => 'Check your email for 2FA code and enter it here.', +// 'form.2fa.2fa_code' => '2FA code', + // Resetting Password form. See example at https://timetracker.anuko.com/password_reset.php. 'form.reset_password.message' => 'Salasõna tühistamise teade on saadetud e-postile.', 'form.reset_password.email_subject' => 'Anuko Time Tracker, parooli tühistamise nõue', @@ -486,10 +473,13 @@ // 'form.reports.include_pending' => 'pending', 'form.reports.select_period' => 'Vali ajaperiood', 'form.reports.set_period' => 'või märgi kuupäevad', +// TODO: translate the following. +// 'form.reports.note_containing' => 'Note containing', 'form.reports.show_fields' => 'Näita välju', // TODO: translate the following. // 'form.reports.time_fields' => 'Time fields', // 'form.reports.user_fields' => 'User fields', +// 'form.reports.project_fields' => 'Project fields', 'form.reports.group_by' => 'Grupeeri', 'form.reports.group_by_no' => '--- grupeerimata ---', 'form.reports.group_by_date' => 'kuupäev', @@ -501,6 +491,8 @@ // Report form. See example at https://timetracker.anuko.com/report.php // (after generating a report at https://timetracker.anuko.com/reports.php). 'form.report.export' => 'Eksport', +// TODO: translate the following. +// 'form.report.per_hour' => 'Per hour', 'form.report.assign_to_invoice' => 'Lisa arvele', // TODO: translate the following. // 'form.report.assign_to_timesheet' => 'Assign to timesheet', @@ -515,8 +507,8 @@ // 'form.templates.active_templates' => 'Active Templates', // 'form.templates.inactive_templates' => 'Inactive Templates', -// Invoice form. See example at https://timetracker.anuko.com/invoice.php -// (you can get to this form after generating a report). +// Invoice form. See example at https://timetracker.anuko.com/invoice_view.php +// (you can get to this form after generating an invoice). 'form.invoice.number' => 'Arve number', 'form.invoice.person' => 'Isik', @@ -538,6 +530,8 @@ 'form.tasks.inactive_tasks' => 'Mitteaktiivsed tööülesanded', // Users form. See example at https://timetracker.anuko.com/users.php +// TODO: translate the following. +// 'form.users.uncompleted_entry_today' => 'User has an uncompleted time entry today', 'form.users.uncompleted_entry' => 'Kasutajal on lõpetamata aja kanne', 'form.users.role' => 'Roll', 'form.users.manager' => 'Haldur', @@ -596,14 +590,21 @@ 'form.group_edit.type_start_finish' => 'algus ja lõpp', 'form.group_edit.type_duration' => 'vahemik', 'form.group_edit.punch_mode' => 'Kellast-kellani režiim', +// TODO: translate the following. +// 'form.group_edit.one_uncompleted' => 'One uncompleted', 'form.group_edit.allow_overlap' => 'Luba ajaline ülekate', 'form.group_edit.future_entries' => 'Tuleviku kirjed', 'form.group_edit.uncompleted_indicators' => 'Lõpetamata kirjete indikaator', // TODO: Fix this. Indicators (plural), not indicator. 'form.group_edit.confirm_save' => 'Kinnita salvestamine', -'form.group_edit.allow_ip' => 'Luba IP', // TODO: translate the following. // 'form.group_edit.advanced_settings' => 'Advanced settings', +// Advanced Group Settings form. See example at https://timetracker.anuko.com/group_advanced_edit.php. +'form.group_advanced_edit.allow_ip' => 'Luba IP', +// TODO: Translate the following. +// 'form.group_advanced_edit.password_complexity' => 'Password complexity', +// 'form.group_advanced_edit.2fa' => 'Two factor authentication', + // Deleting Group form. See example at https://timetracker.anuko.com/delete_group.php 'form.group_delete.hint' => 'Oled kindel, et soovid kogu gruppi kustutada?', @@ -667,21 +668,8 @@ // TODO: translate the following. // 'form.display_options.note_on_separate_row' => 'Note on separate row', // 'form.display_options.not_complete_days' => 'Not complete days', +// 'form.display_options.inactive_projects' => 'Inactive projects', +// 'form.display_options.cost_per_hour' => 'Cost per hour', // 'form.display_options.custom_css' => 'Custom CSS', - -// Work plugin strings. See example at https://timetracker.anuko.com/work.php -// TODO: translate the following. -// 'work.error.work_not_available' => 'Work item is not available.', -// 'work.error.offer_not_available' => 'Offer is not available.', -// 'work.type.one_time' => 'one time', // Work type is "one time job" for well defined work ("do exactly this"). -// 'work.type.ongoing' => 'ongoing', // Work type is "ongoing" for complex jobs (billed by the hour, multiple contractors, etc.) -// 'work.label.own_work' => 'Own work', -// 'work.label.own_offers' => 'Own offers', -// 'work.label.offers' => 'Offers', -// 'work.button.send_message' => 'Send message', -// 'work.button.make_offer' => 'Make offer', -// 'work.button.accept' => 'Accept', -// 'work.button.decline' => 'Decline', -// 'work.title.send_message' => 'Sending Message', -// 'work.msg.message_sent' => 'Message sent.', +// 'form.display_options.custom_translation' => 'Custom translation', ); diff --git a/WEB-INF/resources/fa.lang.php b/WEB-INF/resources/fa.lang.php index 0fca34586..32dacdf00 100644 --- a/WEB-INF/resources/fa.lang.php +++ b/WEB-INF/resources/fa.lang.php @@ -2,8 +2,10 @@ /* Copyright (c) Anuko International Ltd. https://www.anuko.com License: See license.txt */ -// Note: escape apostrophes with THREE backslashes, like here: choisir l\\\'option. +// Note: escape apostrophes with THREE backslashes, like here: 'choisir l\\\'option'. +// Alternatively: use one backslash and surround by double-quotes: "choisir l\'option". // Other characters (such as double-quotes in http links, etc.) do not have to be escaped. + $i18n_language = 'Persian (فارسی)'; // TODO: translate the following. $i18n_months = array('January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December'); @@ -27,6 +29,7 @@ 'menu.plugins' => 'پلاگین ها', 'menu.time' => 'زمان', // TODO: translate the following. +// 'menu.puncher' => 'Punch', // 'menu.week' => 'Week', 'menu.expenses' => 'هزينه ها', 'menu.reports' => 'گزارشات', @@ -38,7 +41,6 @@ 'menu.users' => 'کاربران', // TODO: translate the following. // 'menu.groups' => 'Groups', -// 'menu.subgroups' => 'Subgroups', 'menu.export' => 'پشتیبانی', 'menu.clients' => 'مشتری ها', 'menu.options' => 'تنظیمات', @@ -71,6 +73,9 @@ // 'error.report' => 'Select report.', // 'error.record' => 'Select record.', 'error.auth' => 'نام کاربری یا رمز عبور اشتباه است.', +// TODO: translate the following. +// 'error.2fa_code' => 'Invalid 2FA code.', +// 'error.weak_password' => 'Weak password.', 'error.user_exists' => 'کاربری با این نام کاربری موجود است.', // TODO: translate the following. // 'error.object_exists' => 'Object with this name already exists.', @@ -102,7 +107,6 @@ // 'error.expired' => 'Expiration date reached.', // TODO: translate the following. // 'error.file_storage' => 'File storage server error.', // See comment in English file. -// 'error.remote_work' => 'Remote work server error.', // See comment in English file. // Warning messages. // TODO: translate the following. @@ -130,10 +134,13 @@ 'button.export' => 'ایجاد پشتیبان از تیم', // TODO: replace "team" with "group". 'button.import' => 'وارد کردن تیم', // TODO: replace "team" with "group". 'button.close' => 'بستن', +// TODO: translate the following. +// 'button.start' => 'Start', 'button.stop' => 'توقف', // TODO: translate the following. // 'button.approve' => 'Approve', // 'button.disapprove' => 'Disapprove', +// 'button.sync' => 'Sync', // Used in Android app. The meaning is to synchronize offline time records with server. // Labels for controls on forms. Labels in this section are used on multiple forms. // TODO: translate the following. @@ -196,6 +203,7 @@ // TODO: translate the following. // 'label.day_view' => 'Day view', // 'label.week_view' => 'Week view', +// 'label.puncher' => 'Puncher', 'label.id' => 'شناسه', 'label.language' => 'زبان', // TODO: translate the following. @@ -215,9 +223,6 @@ 'label.ldap_hint' => 'نام کاربری ویندوزو رمزعبورخود را در فیلدهای زیر وارد کنید', 'label.required_fields' => '* - فیلد های اجباری', 'label.on_behalf' => 'از دیدگاه', -'label.role_manager' => '(مدیر)', -'label.role_comanager' => '(دستیار مدیر)', -'label.role_admin' => '(مدیر ارشد)', // Translate the following. // 'label.page' => 'Page', // 'label.condition' => 'Condition', @@ -243,9 +248,9 @@ // 'label.paid_status' => 'Paid status', // 'label.paid' => 'Paid', // 'label.mark_paid' => 'Mark paid', -// 'label.week_menu' => 'Week menu', // 'label.week_note' => 'Week note', // 'label.week_list' => 'Week list', +// 'label.weekends' => 'Weekends', // 'label.work_units' => 'Work units', // 'label.work_units_short' => 'Units', // 'label.totals_only' => 'Totals only', @@ -261,21 +266,8 @@ // 'label.attachments' => 'Attachments', // 'label.files' => 'Files', // 'label.file' => 'File', -// 'label.image' => 'Image', -// 'label.download' => 'Download', 'label.active_users' => 'کاربران فعال', 'label.inactive_users' => 'کاربران غیرفعال', -// TODO: translate the following. -// 'label.details' => 'Details', -// 'label.budget' => 'Budget', -// 'label.work' => 'Work', // Table column header for work items, see https://www.anuko.com/time_tracker/what_is/work_plugin.htm -// 'label.offer' => 'Offer', // Table column header for offers, see https://www.anuko.com/time_tracker/what_is/work_plugin.htm -// 'label.contractor' => 'Contractor', // Table column header for offers (contractor is someone who offers to do work). - // Technically, it is either an org name or a combination of org and group names - // because both work items and offers are owned by Time Tracker groups of users. -// 'label.how_to_pay' => 'How to pay', // Label for the "How to pay" field on offers, which allows contractors to specify - // how to pay them, for example: paypal email, check by mail to a specific address, etc. -// 'label.moderator_comment' => 'Moderator comment', // Label for "Moderator comment" field that explains something. // Entity names. We use lower case (in English) because they are used in dropdowns, too. // They are used to associate a custom field with an entity type. @@ -289,9 +281,10 @@ // TODO: Translate the following. // 'title.success' => 'Success', 'title.login' => 'ورود', +// TODO: translate the follolwing. +// 'title.2fa' => 'Two Factor Authentication', 'title.groups' => 'تیم ها', // TODO: change "teams" to "groups". // TODO: translate the following. -// 'title.subgroups' => 'Subgroups', // 'title.add_group' => 'Adding Group', // 'title.edit_group' => 'Editing Group', 'title.delete_group' => 'حذف تیم', // TODO: change "team" to "group". @@ -302,6 +295,7 @@ 'title.delete_time_record' => 'حذف رکورد زمان', // TODO: Translate the following. // 'title.time_files' => 'Time Record Files', +// 'title.puncher' => 'Puncher', 'title.expenses' => 'هزینه ها', 'title.edit_expense' => 'ویرایش آیتم هزینه ها', 'title.delete_expense' => 'حذف آیتم هزینه ها', @@ -382,22 +376,6 @@ // 'title.edit_file' => 'Editing File', // 'title.delete_file' => 'Deleting File', // 'title.download_file' => 'Downloading File', -// 'title.work' => 'Work', -// 'title.add_work' => 'Adding Work', -// 'title.edit_work' => 'Editing Work', -// 'title.delete_work' => 'Deleting Work', -// 'title.active_work' => 'Active Work', // Active work items this group outsources to other groups. -// 'title.available_work' => 'Available Work', // Available work items from other organizations. -// 'title.inactive_work' => 'Inactive Work', // Inactive work items this group was outsourcing to other groups. -// 'title.pending_work' => 'Pending Work', // Work items pending moderator approval. -// 'title.offer' => 'Offer', -// 'title.add_offer' => 'Adding Offer', -// 'title.edit_offer' => 'Editing Offer', -// 'title.delete_offer' => 'Deleting Offer', -// 'title.active_offers' => 'Active Offers', // Active offers this group makes available to other groups. -// 'title.available_offers' => 'Available Offers', // Available offers from other organizations. -// 'title.inactive_offers' => 'Inactive Offers', // Inactive offers for group. -// 'title.pending_offers' => 'Pending Offers', // Offers pending moderator approval. // Section for common strings inside combo boxes on forms. Strings shared between forms shall be placed here. // Strings that are used in a single form must go to the specific form section. @@ -433,7 +411,6 @@ // TODO: translate the following. // 'dropdown.delete' => 'delete', // 'dropdown.do_not_delete' => 'do not delete', -// 'dropdown.pending_approval' => 'pending approval', // 'dropdown.approved' => 'approved', // 'dropdown.not_approved' => 'not approved', // 'dropdown.paid' => 'paid', @@ -448,8 +425,18 @@ // Login form. See example at https://timetracker.anuko.com/login.php. 'form.login.forgot_password' => 'بازیابی رمز عبور؟', -// TODO: translate form.login.about. -'form.login.about' => 'Anuko Time Tracker is an open source time tracking system.', +// TODO: translate the following. +// 'form.login.about' => 'Anuko Time Tracker is an open source time tracking system.', + +// Email subject and body for two-factor authentication. +// TODO: translate the following. +// 'email.2fa_code.subject' => 'Anuko Time Tracker two-factor authentication code', +// 'email.2fa_code.body' => "Dear User,\n\nYour two-factor authentication code is:\n\n%s\n\n", + +// Two-factor authentication form. See example at https://timetracker.anuko.com/2fa.php. +// TODO: translate the following. +// 'form.2fa.hint' => 'Check your email for 2FA code and enter it here.', +// 'form.2fa.2fa_code' => '2FA code', // Resetting Password form. See example at https://timetracker.anuko.com/password_reset.php. 'form.reset_password.message' => 'درخواست بازیابی رمزعبور به ایمیل فرستاده شد.', @@ -495,10 +482,13 @@ // 'form.reports.include_pending' => 'pending', 'form.reports.select_period' => 'انتخاب بازه زمانی', 'form.reports.set_period' => 'یا تعیین تاریخ', +// TODO: translate the following. +// 'form.reports.note_containing' => 'Note containing', 'form.reports.show_fields' => 'نمایش فیلدها', // TODO: translate the following. // 'form.reports.time_fields' => 'Time fields', // 'form.reports.user_fields' => 'User fields', +// 'form.reports.project_fields' => 'Project fields', 'form.reports.group_by' => 'گروه بندی شده با', 'form.reports.group_by_no' => '--- بدون گروه ---', 'form.reports.group_by_date' => 'تاریخ', @@ -511,6 +501,7 @@ // (after generating a report at https://timetracker.anuko.com/reports.php). 'form.report.export' => 'پشتیبانی', // TODO: translate the following. +// 'form.report.per_hour' => 'Per hour', // 'form.report.assign_to_invoice' => 'Assign to invoice', // 'form.report.assign_to_timesheet' => 'Assign to timesheet', @@ -524,8 +515,8 @@ // 'form.templates.active_templates' => 'Active Templates', // 'form.templates.inactive_templates' => 'Inactive Templates', -// Invoice form. See example at https://timetracker.anuko.com/invoice.php -// (you can get to this form after generating a report). +// Invoice form. See example at https://timetracker.anuko.com/invoice_view.php +// (you can get to this form after generating an invoice). 'form.invoice.number' => 'شماره فاکتور', 'form.invoice.person' => 'شخص', @@ -549,6 +540,7 @@ // Users form. See example at https://timetracker.anuko.com/users.php // TODO: translate the following. + // 'form.users.uncompleted_entry_today' => 'User has an uncompleted time entry today', // 'form.users.uncompleted_entry' => 'User has an uncompleted time entry', 'form.users.role' => 'سمت', 'form.users.manager' => 'مدیر', @@ -610,13 +602,19 @@ 'form.group_edit.type_duration' => 'مدت زمان', // TODO: translate the following. // 'form.group_edit.punch_mode' => 'Punch mode', +// 'form.group_edit.one_uncompleted' => 'One uncompleted', // 'form.group_edit.allow_overlap' => 'Allow overlap', // 'form.group_edit.future_entries' => 'Future entries', // 'form.group_edit.uncompleted_indicators' => 'Uncompleted indicators', // 'form.group_edit.confirm_save' => 'Confirm saving', -// 'form.group_edit.allow_ip' => 'Allow IP', // 'form.group_edit.advanced_settings' => 'Advanced settings', +// Advanced Group Settings form. See example at https://timetracker.anuko.com/group_advanced_edit.php. +// TODO: Translate the following. +// 'form.group_advanced_edit.allow_ip' => 'Allow IP', +// 'form.group_advanced_edit.password_complexity' => 'Password complexity', +// 'form.group_advanced_edit.2fa' => 'Two factor authentication', + // Deleting Group form. See example at https://timetracker.anuko.com/delete_group.php // TODO: translate the following. // 'form.group_delete.hint' => 'Are you sure you want to delete the entire group?', @@ -683,21 +681,8 @@ // TODO: translate the following. // 'form.display_options.note_on_separate_row' => 'Note on separate row', // 'form.display_options.not_complete_days' => 'Not complete days', +// 'form.display_options.inactive_projects' => 'Inactive projects', +// 'form.display_options.cost_per_hour' => 'Cost per hour', // 'form.display_options.custom_css' => 'Custom CSS', - -// Work plugin strings. See example at https://timetracker.anuko.com/work.php -// TODO: translate the following. -// 'work.error.work_not_available' => 'Work item is not available.', -// 'work.error.offer_not_available' => 'Offer is not available.', -// 'work.type.one_time' => 'one time', // Work type is "one time job" for well defined work ("do exactly this"). -// 'work.type.ongoing' => 'ongoing', // Work type is "ongoing" for complex jobs (billed by the hour, multiple contractors, etc.) -// 'work.label.own_work' => 'Own work', -// 'work.label.own_offers' => 'Own offers', -// 'work.label.offers' => 'Offers', -// 'work.button.send_message' => 'Send message', -// 'work.button.make_offer' => 'Make offer', -// 'work.button.accept' => 'Accept', -// 'work.button.decline' => 'Decline', -// 'work.title.send_message' => 'Sending Message', -// 'work.msg.message_sent' => 'Message sent.', +// 'form.display_options.custom_translation' => 'Custom translation', ); diff --git a/WEB-INF/resources/fi.lang.php b/WEB-INF/resources/fi.lang.php index eaad8a293..cdfc4c5fe 100644 --- a/WEB-INF/resources/fi.lang.php +++ b/WEB-INF/resources/fi.lang.php @@ -2,7 +2,8 @@ /* Copyright (c) Anuko International Ltd. https://www.anuko.com License: See license.txt */ -// Note: escape apostrophes with THREE backslashes, like here: choisir l\\\'option. +// Note: escape apostrophes with THREE backslashes, like here: 'choisir l\\\'option'. +// Alternatively: use one backslash and surround by double-quotes: "choisir l\'option". // Other characters (such as double-quotes in http links, etc.) do not have to be escaped. $i18n_language = 'Finnish (Suomi)'; @@ -26,6 +27,7 @@ 'menu.plugins' => 'Lisäosat', 'menu.time' => 'Tunnit', // TODO: translate the following. +// 'menu.puncher' => 'Punch', // 'menu.week' => 'Week', 'menu.expenses' => 'Kulut', 'menu.reports' => 'Raportit', @@ -37,7 +39,6 @@ 'menu.users' => 'Käyttäjät', // TODO: translate the following. // 'menu.groups' => 'Groups', -// 'menu.subgroups' => 'Subgroups', 'menu.export' => 'Vie', 'menu.clients' => 'Asiakkaat', 'menu.options' => 'Optiot', @@ -68,6 +69,9 @@ // TODO: translate the following. // 'error.record' => 'Select record.', 'error.auth' => 'Virheellinen käyttäjänimi tai salasana.', +// TODO: translate the following. +// 'error.2fa_code' => 'Invalid 2FA code.', +// 'error.weak_password' => 'Weak password.', 'error.user_exists' => 'Tämä käyttäjänimi on jo olemassa.', // TODO: translate the following. // 'error.object_exists' => 'Object with this name already exists.', @@ -96,7 +100,6 @@ // 'error.user_count' => 'Limit on user count.', // 'error.expired' => 'Expiration date reached.', // 'error.file_storage' => 'File storage server error.', // See comment in English file. -// 'error.remote_work' => 'Remote work server error.', // See comment in English file. // Warning messages. // TODO: translate the following. @@ -124,10 +127,13 @@ 'button.export' => 'Vie tiimi', // TODO: replace "team" with "group". 'button.import' => 'Tuo tiimi', // TODO: replace "team" with "group". 'button.close' => 'Sulje', +// TODO: translate the following. +// 'button.start' => 'Start', 'button.stop' => 'Lopeta', // TODO: translate the following. // 'button.approve' => 'Approve', // 'button.disapprove' => 'Disapprove', +// 'button.sync' => 'Sync', // Used in Android app. The meaning is to synchronize offline time records with server. // Labels for controls on forms. Labels in this section are used on multiple forms. // TODO: translate the following. @@ -189,6 +195,7 @@ // TODO: translate the following. // 'label.day_view' => 'Day view', // 'label.week_view' => 'Week view', +// 'label.puncher' => 'Puncher', 'label.id' => 'ID', 'label.language' => 'Kieli', 'label.decimal_mark' => 'Desimaalierotin', @@ -207,9 +214,6 @@ 'label.ldap_hint' => 'Syötä Windows-käyttäjätunnuksesi ja salasanasi ao. kenttiin.', 'label.required_fields' => '* - pakolliset kentät', 'label.on_behalf' => 'puolesta', -'label.role_manager' => '(esimies)', -'label.role_comanager' => '(apu-esimies)', -'label.role_admin' => '(ylläpitäjä)', 'label.page' => 'Sivu', // TODO: translate the following. // 'label.condition' => 'Condition', @@ -235,9 +239,9 @@ // 'label.paid_status' => 'Paid status', // 'label.paid' => 'Paid', // 'label.mark_paid' => 'Mark paid', -// 'label.week_menu' => 'Week menu', // 'label.week_note' => 'Week note', // 'label.week_list' => 'Week list', +// 'label.weekends' => 'Weekends', // 'label.work_units' => 'Work units', // 'label.work_units_short' => 'Units', 'label.totals_only' => 'Vain yhteissummat', @@ -254,21 +258,8 @@ // 'label.attachments' => 'Attachments', // 'label.files' => 'Files', // 'label.file' => 'File', -// 'label.image' => 'Image', -// 'label.download' => 'Download', 'label.active_users' => 'Aktiiviset käyttäjät', 'label.inactive_users' => 'Ei-aktiiviset käyttäjät', -// TODO: translate the following. -// 'label.details' => 'Details', -// 'label.budget' => 'Budget', -// 'label.work' => 'Work', // Table column header for work items, see https://www.anuko.com/time_tracker/what_is/work_plugin.htm -// 'label.offer' => 'Offer', // Table column header for offers, see https://www.anuko.com/time_tracker/what_is/work_plugin.htm -// 'label.contractor' => 'Contractor', // Table column header for offers (contractor is someone who offers to do work). - // Technically, it is either an org name or a combination of org and group names - // because both work items and offers are owned by Time Tracker groups of users. -// 'label.how_to_pay' => 'How to pay', // Label for the "How to pay" field on offers, which allows contractors to specify - // how to pay them, for example: paypal email, check by mail to a specific address, etc. -// 'label.moderator_comment' => 'Moderator comment', // Label for "Moderator comment" field that explains something. // Entity names. We use lower case (in English) because they are used in dropdowns, too. // They are used to associate a custom field with an entity type. @@ -282,9 +273,10 @@ // TODO: Translate the following. // 'title.success' => 'Success', 'title.login' => 'Kirjautuminen', +// TODO: translate the follolwing. +// 'title.2fa' => 'Two Factor Authentication', 'title.groups' => 'Tiimit', // TODO: change "teams" to "groups". // TODO: translate the following. -// 'title.subgroups' => 'Subgroups', // 'title.add_group' => 'Adding Group', 'title.edit_group' => 'Tiimin muokkaus', // TODO: change "team" to "group". 'title.delete_group' => 'Tiimin poisto', // TODO: change "team" to "group". @@ -295,6 +287,7 @@ 'title.delete_time_record' => 'Tuntikirjausten poisto', // TODO: Translate the following. // 'title.time_files' => 'Time Record Files', +// 'title.puncher' => 'Puncher', 'title.expenses' => 'Kulut', 'title.edit_expense' => 'Kulutietojen muokkaus', 'title.delete_expense' => 'Kulutiedon poisto', @@ -373,22 +366,6 @@ // 'title.edit_file' => 'Editing File', // 'title.delete_file' => 'Deleting File', // 'title.download_file' => 'Downloading File', -// 'title.work' => 'Work', -// 'title.add_work' => 'Adding Work', -// 'title.edit_work' => 'Editing Work', -// 'title.delete_work' => 'Deleting Work', -// 'title.active_work' => 'Active Work', // Active work items this group outsources to other groups. -// 'title.available_work' => 'Available Work', // Available work items from other organizations. -// 'title.inactive_work' => 'Inactive Work', // Inactive work items this group was outsourcing to other groups. -// 'title.pending_work' => 'Pending Work', // Work items pending moderator approval. -// 'title.offer' => 'Offer', -// 'title.add_offer' => 'Adding Offer', -// 'title.edit_offer' => 'Editing Offer', -// 'title.delete_offer' => 'Deleting Offer', -// 'title.active_offers' => 'Active Offers', // Active offers this group makes available to other groups. -// 'title.available_offers' => 'Available Offers', // Available offers from other organizations. -// 'title.inactive_offers' => 'Inactive Offers', // Inactive offers for group. -// 'title.pending_offers' => 'Pending Offers', // Offers pending moderator approval. // Section for common strings inside combo boxes on forms. Strings shared between forms shall be placed here. // Strings that are used in a single form must go to the specific form section. @@ -421,7 +398,6 @@ 'dropdown.delete' => 'poista', 'dropdown.do_not_delete' => 'älä poista', // TODO: translate the following. -// 'dropdown.pending_approval' => 'pending approval', // 'dropdown.approved' => 'approved', // 'dropdown.not_approved' => 'not approved', // 'dropdown.paid' => 'paid', @@ -436,9 +412,19 @@ // Login form. See example at https://timetracker.anuko.com/login.php. 'form.login.forgot_password' => 'Salasana unohtunut?', - // TODO: check translation of form.login.about - is open source "vapaan koodin"? +// TODO: check translation of form.login.about - is open source "vapaan koodin"? 'form.login.about' => 'Anuko Time Tracker on vapaan koodin tuntiseurantaohjelmisto.', +// Email subject and body for two-factor authentication. +// TODO: translate the following. +// 'email.2fa_code.subject' => 'Anuko Time Tracker two-factor authentication code', +// 'email.2fa_code.body' => "Dear User,\n\nYour two-factor authentication code is:\n\n%s\n\n", + +// Two-factor authentication form. See example at https://timetracker.anuko.com/2fa.php. +// TODO: translate the following. +// 'form.2fa.hint' => 'Check your email for 2FA code and enter it here.', +// 'form.2fa.2fa_code' => '2FA code', + // Resetting Password form. See example at https://timetracker.anuko.com/password_reset.php. 'form.reset_password.message' => 'Salasanan nollauspyyntöviesti lähetetty.', 'form.reset_password.email_subject' => 'Anuko Time Tracker -salasanan nollauspyyntö', @@ -480,10 +466,13 @@ // 'form.reports.include_pending' => 'pending', 'form.reports.select_period' => 'Valitse ajanjakso', 'form.reports.set_period' => 'tai aseta päivät', +// TODO: translate the following. +// 'form.reports.note_containing' => 'Note containing', 'form.reports.show_fields' => 'Näytä kentät', // TODO: translate the following. // 'form.reports.time_fields' => 'Time fields', // 'form.reports.user_fields' => 'User fields', +// 'form.reports.project_fields' => 'Project fields', 'form.reports.group_by' => 'Ryhmittelyperuste', 'form.reports.group_by_no' => '--- ei ryhmitystä ---', 'form.reports.group_by_date' => 'päivä', @@ -496,6 +485,7 @@ // (after generating a report at https://timetracker.anuko.com/reports.php). 'form.report.export' => 'Vie', // TODO: translate the following. +// 'form.report.per_hour' => 'Per hour', // 'form.report.assign_to_invoice' => 'Assign to invoice', // 'form.report.assign_to_timesheet' => 'Assign to timesheet', @@ -509,8 +499,8 @@ // 'form.templates.active_templates' => 'Active Templates', // 'form.templates.inactive_templates' => 'Inactive Templates', -// Invoice form. See example at https://timetracker.anuko.com/invoice.php -// (you can get to this form after generating a report). +// Invoice form. See example at https://timetracker.anuko.com/invoice_view.php +// (you can get to this form after generating an invoice). 'form.invoice.number' => 'Laskun numero', 'form.invoice.person' => 'Henkilö', @@ -534,6 +524,7 @@ // Users form. See example at https://timetracker.anuko.com/users.php // TODO: translate the following. +// 'form.users.uncompleted_entry_today' => 'User has an uncompleted time entry today', // 'form.users.uncompleted_entry' => 'User has an uncompleted time entry', 'form.users.role' => 'Rooli', 'form.users.manager' => 'Esimies', @@ -594,13 +585,19 @@ 'form.group_edit.type_duration' => 'kesto', // TODO: translate the following. // 'form.group_edit.punch_mode' => 'Punch mode', +// 'form.group_edit.one_uncompleted' => 'One uncompleted', // 'form.group_edit.allow_overlap' => 'Allow overlap', // 'form.group_edit.future_entries' => 'Future entries', // 'form.group_edit.uncompleted_indicators' => 'Uncompleted indicators', // 'form.group_edit.confirm_save' => 'Confirm saving', -// 'form.group_edit.allow_ip' => 'Allow IP', // 'form.group_edit.advanced_settings' => 'Advanced settings', +// Advanced Group Settings form. See example at https://timetracker.anuko.com/group_advanced_edit.php. +// TODO: Translate the following. +// 'form.group_advanced_edit.allow_ip' => 'Allow IP', +// 'form.group_advanced_edit.password_complexity' => 'Password complexity', +// 'form.group_advanced_edit.2fa' => 'Two factor authentication', + // Deleting Group form. See example at https://timetracker.anuko.com/delete_group.php // TODO: translate the following. // 'form.group_delete.hint' => 'Are you sure you want to delete the entire group?', @@ -667,21 +664,8 @@ // TODO: translate the following. // 'form.display_options.note_on_separate_row' => 'Note on separate row', // 'form.display_options.not_complete_days' => 'Not complete days', +// 'form.display_options.inactive_projects' => 'Inactive projects', +// 'form.display_options.cost_per_hour' => 'Cost per hour', // 'form.display_options.custom_css' => 'Custom CSS', - -// Work plugin strings. See example at https://timetracker.anuko.com/work.php -// TODO: translate the following. -// 'work.error.work_not_available' => 'Work item is not available.', -// 'work.error.offer_not_available' => 'Offer is not available.', -// 'work.type.one_time' => 'one time', // Work type is "one time job" for well defined work ("do exactly this"). -// 'work.type.ongoing' => 'ongoing', // Work type is "ongoing" for complex jobs (billed by the hour, multiple contractors, etc.) -// 'work.label.own_work' => 'Own work', -// 'work.label.own_offers' => 'Own offers', -// 'work.label.offers' => 'Offers', -// 'work.button.send_message' => 'Send message', -// 'work.button.make_offer' => 'Make offer', -// 'work.button.accept' => 'Accept', -// 'work.button.decline' => 'Decline', -// 'work.title.send_message' => 'Sending Message', -// 'work.msg.message_sent' => 'Message sent.', +// 'form.display_options.custom_translation' => 'Custom translation', ); diff --git a/WEB-INF/resources/fr.lang.php b/WEB-INF/resources/fr.lang.php index 771ed18e3..b488be0bc 100644 --- a/WEB-INF/resources/fr.lang.php +++ b/WEB-INF/resources/fr.lang.php @@ -2,7 +2,8 @@ /* Copyright (c) Anuko International Ltd. https://www.anuko.com License: See license.txt */ -// Note: escape apostrophes with THREE backslashes, like here: choisir l\\\'option. +// Note: escape apostrophes with THREE backslashes, like here: 'choisir l\\\'option'. +// Alternatively: use one backslash and surround by double-quotes: "choisir l\'option". // Other characters (such as double-quotes in http links, etc.) do not have to be escaped. $i18n_language = 'French (Français)'; @@ -26,6 +27,7 @@ 'menu.plugins' => 'Plugins', 'menu.time' => 'Temps', // TODO: translate the following. +// 'menu.puncher' => 'Punch', // 'menu.week' => 'Week', 'menu.expenses' => 'Dépenses', 'menu.reports' => 'Rapports', @@ -37,7 +39,6 @@ 'menu.users' => 'Utilisateurs', // TODO: translate the following. // 'menu.groups' => 'Groups', -// 'menu.subgroups' => 'Subgroups', 'menu.export' => 'Exporter', 'menu.clients' => 'Clients', 'menu.options' => 'Options', @@ -66,6 +67,9 @@ // TODO: translate the following. // 'error.record' => 'Select record.', 'error.auth' => 'Nom d\\\'utilisateur ou mot de passe incorrect.', +// TODO: translate the following. +// 'error.2fa_code' => 'Invalid 2FA code.', +// 'error.weak_password' => 'Weak password.', 'error.user_exists' => 'Un utilisateur avec cet identifiant existe déjà.', // TODO: translate the following. // 'error.object_exists' => 'Object with this name already exists.', @@ -94,7 +98,6 @@ // 'error.user_count' => 'Limit on user count.', // 'error.expired' => 'Expiration date reached.', // 'error.file_storage' => 'File storage server error.', // See comment in English file. -// 'error.remote_work' => 'Remote work server error.', // See comment in English file. // Warning messages. // TODO: translate the following. @@ -122,10 +125,13 @@ 'button.export' => 'Exporter l\\\'équipe', // TODO: replace "team" with "group". 'button.import' => 'Importer une équipe', // TODO: replace "team" with "group". 'button.close' => 'Fermer', +// TODO: translate the following. +// 'button.start' => 'Start', 'button.stop' => 'Arrêter', // TODO: translate the following. // 'button.approve' => 'Approve', // 'button.disapprove' => 'Disapprove', +// 'button.sync' => 'Sync', // Used in Android app. The meaning is to synchronize offline time records with server. // Labels for controls on forms. Labels in this section are used on multiple forms. // TODO: translate the following. @@ -185,6 +191,7 @@ // TODO: translate the following. // 'label.day_view' => 'Day view', // 'label.week_view' => 'Week view', +// 'label.puncher' => 'Puncher', 'label.id' => 'ID', 'label.language' => 'Langage', 'label.decimal_mark' => 'Séparateur de décimal', @@ -203,9 +210,6 @@ 'label.ldap_hint' => 'Entrer votre Identifiant Windows et votre mot de passe dans les champs suivants.', 'label.required_fields' => '* - champs obligatoires', 'label.on_behalf' => 'de la part de', -'label.role_manager' => '(responsable)', -'label.role_comanager' => '(coresponsable)', -'label.role_admin' => '(administrateur)', 'label.page' => 'Page', 'label.condition' => 'Condition', // TODO: translate the following. @@ -229,9 +233,9 @@ // 'label.paid_status' => 'Paid status', // 'label.paid' => 'Paid', // 'label.mark_paid' => 'Mark paid', -// 'label.week_menu' => 'Week menu', // 'label.week_note' => 'Week note', // 'label.week_list' => 'Week list', +// 'label.weekends' => 'Weekends', // 'label.work_units' => 'Work units', // 'label.work_units_short' => 'Units', 'label.totals_only' => 'Totaux uniquement', @@ -248,21 +252,8 @@ // 'label.attachments' => 'Attachments', // 'label.files' => 'Files', // 'label.file' => 'File', -// 'label.image' => 'Image', -// 'label.download' => 'Download', 'label.active_users' => 'Utilisateurs actifs', 'label.inactive_users' => 'Utilisateurs inactifs', -// TODO: translate the following. -// 'label.details' => 'Details', -// 'label.budget' => 'Budget', -// 'label.work' => 'Work', // Table column header for work items, see https://www.anuko.com/time_tracker/what_is/work_plugin.htm -// 'label.offer' => 'Offer', // Table column header for offers, see https://www.anuko.com/time_tracker/what_is/work_plugin.htm -// 'label.contractor' => 'Contractor', // Table column header for offers (contractor is someone who offers to do work). - // Technically, it is either an org name or a combination of org and group names - // because both work items and offers are owned by Time Tracker groups of users. -// 'label.how_to_pay' => 'How to pay', // Label for the "How to pay" field on offers, which allows contractors to specify - // how to pay them, for example: paypal email, check by mail to a specific address, etc. -// 'label.moderator_comment' => 'Moderator comment', // Label for "Moderator comment" field that explains something. // Entity names. We use lower case (in English) because they are used in dropdowns, too. // They are used to associate a custom field with an entity type. @@ -276,9 +267,10 @@ // TODO: Translate the following. // 'title.success' => 'Success', 'title.login' => 'Connexion', +// TODO: translate the follolwing. +// 'title.2fa' => 'Two Factor Authentication', 'title.groups' => 'Équipes', // TODO: change "teams" to "groups". // TODO: translate the following. -// 'title.subgroups' => 'Subgroups', // 'title.add_group' => 'Adding Group', 'title.edit_group' => 'Modification d\\\'une équipe', // TODO: change "team" to "group". 'title.delete_group' => 'Suppression d\\\'une équipe', // TODO: change "team" to "group". @@ -289,6 +281,7 @@ 'title.delete_time_record' => 'Suppression de l\\\'entrée de temps', // TODO: Translate the following. // 'title.time_files' => 'Time Record Files', +// 'title.puncher' => 'Puncher', 'title.expenses' => 'Dépenses', 'title.edit_expense' => 'Modification d\\\'une dépense', 'title.delete_expense' => 'Suppression d\\\'une dépense', @@ -367,22 +360,6 @@ // 'title.edit_file' => 'Editing File', // 'title.delete_file' => 'Deleting File', // 'title.download_file' => 'Downloading File', -// 'title.work' => 'Work', -// 'title.add_work' => 'Adding Work', -// 'title.edit_work' => 'Editing Work', -// 'title.delete_work' => 'Deleting Work', -// 'title.active_work' => 'Active Work', // Active work items this group outsources to other groups. -// 'title.available_work' => 'Available Work', // Available work items from other organizations. -// 'title.inactive_work' => 'Inactive Work', // Inactive work items this group was outsourcing to other groups. -// 'title.pending_work' => 'Pending Work', // Work items pending moderator approval. -// 'title.offer' => 'Offer', -// 'title.add_offer' => 'Adding Offer', -// 'title.edit_offer' => 'Editing Offer', -// 'title.delete_offer' => 'Deleting Offer', -// 'title.active_offers' => 'Active Offers', // Active offers this group makes available to other groups. -// 'title.available_offers' => 'Available Offers', // Available offers from other organizations. -// 'title.inactive_offers' => 'Inactive Offers', // Inactive offers for group. -// 'title.pending_offers' => 'Pending Offers', // Offers pending moderator approval. // Section for common strings inside combo boxes on forms. Strings shared between forms shall be placed here. // Strings that are used in a single form must go to the specific form section. @@ -413,7 +390,6 @@ 'dropdown.delete' => 'supprimer', 'dropdown.do_not_delete' => 'ne pas supprimer', // TODO: translate the following. -// 'dropdown.pending_approval' => 'pending approval', // 'dropdown.approved' => 'approved', // 'dropdown.not_approved' => 'not approved', // 'dropdown.paid' => 'paid', @@ -430,6 +406,16 @@ 'form.login.forgot_password' => 'Mot de passe oublié?', 'form.login.about' => 'Anuko Time Tracker est un système de gestion du temps, open source.', +// Email subject and body for two-factor authentication. +// TODO: translate the following. +// 'email.2fa_code.subject' => 'Anuko Time Tracker two-factor authentication code', +// 'email.2fa_code.body' => "Dear User,\n\nYour two-factor authentication code is:\n\n%s\n\n", + +// Two-factor authentication form. See example at https://timetracker.anuko.com/2fa.php. +// TODO: translate the following. +// 'form.2fa.hint' => 'Check your email for 2FA code and enter it here.', +// 'form.2fa.2fa_code' => '2FA code', + // Resetting Password form. See example at https://timetracker.anuko.com/password_reset.php. 'form.reset_password.message' => 'Une demande de réinitialisation du mot de passe a été envoyé par courriel.', 'form.reset_password.email_subject' => 'Demande de réinitialisation de mot de passe Anuko Time Tracker', @@ -471,10 +457,13 @@ // 'form.reports.include_pending' => 'pending', 'form.reports.select_period' => 'Sélectionner la période de temps', 'form.reports.set_period' => 'ou dates indiquées', +// TODO: translate the following. +// 'form.reports.note_containing' => 'Note containing', 'form.reports.show_fields' => 'Afficher les champs', // TODO: translate the following. // 'form.reports.time_fields' => 'Time fields', // 'form.reports.user_fields' => 'User fields', +// 'form.reports.project_fields' => 'Project fields', 'form.reports.group_by' => 'Regroupés par', 'form.reports.group_by_no' => '--- Aucun regroupement ---', 'form.reports.group_by_date' => 'Date', @@ -487,6 +476,7 @@ // (after generating a report at https://timetracker.anuko.com/reports.php). 'form.report.export' => 'Exporter', // TODO: translate the following. +// 'form.report.per_hour' => 'Per hour', // 'form.report.assign_to_invoice' => 'Assign to invoice', // 'form.report.assign_to_timesheet' => 'Assign to timesheet', @@ -500,8 +490,8 @@ // 'form.templates.active_templates' => 'Active Templates', // 'form.templates.inactive_templates' => 'Inactive Templates', -// Invoice form. See example at https://timetracker.anuko.com/invoice.php -// (you can get to this form after generating a report). +// Invoice form. See example at https://timetracker.anuko.com/invoice_view.php +// (you can get to this form after generating an invoice). 'form.invoice.number' => 'Numéro de facture', 'form.invoice.person' => 'Personne', @@ -524,6 +514,8 @@ 'form.tasks.inactive_tasks' => 'Tâches inactives', // Users form. See example at https://timetracker.anuko.com/users.php +// TODO: translate the following. +// 'form.users.uncompleted_entry_today' => 'User has an uncompleted time entry today', 'form.users.uncompleted_entry' => 'L\\\'utilisateur a une entrée incomplète', 'form.users.role' => 'Rôle', 'form.users.manager' => 'Responsable', @@ -584,13 +576,19 @@ 'form.group_edit.type_duration' => 'Durée', // TODO: translate the following. // 'form.group_edit.punch_mode' => 'Punch mode', +// 'form.group_edit.one_uncompleted' => 'One uncompleted', // 'form.group_edit.allow_overlap' => 'Allow overlap', // 'form.group_edit.future_entries' => 'Future entries', // 'form.group_edit.uncompleted_indicators' => 'Uncompleted indicators', // 'form.group_edit.confirm_save' => 'Confirm saving', -// 'form.group_edit.allow_ip' => 'Allow IP', // 'form.group_edit.advanced_settings' => 'Advanced settings', +// Advanced Group Settings form. See example at https://timetracker.anuko.com/group_advanced_edit.php. +// TODO: Translate the following. +// 'form.group_advanced_edit.allow_ip' => 'Allow IP', +// 'form.group_advanced_edit.password_complexity' => 'Password complexity', +// 'form.group_advanced_edit.2fa' => 'Two factor authentication', + // Deleting Group form. See example at https://timetracker.anuko.com/delete_group.php // TODO: translate the following. // 'form.group_delete.hint' => 'Are you sure you want to delete the entire group?', @@ -655,21 +653,8 @@ // TODO: translate the following. // 'form.display_options.note_on_separate_row' => 'Note on separate row', // 'form.display_options.not_complete_days' => 'Not complete days', +// 'form.display_options.inactive_projects' => 'Inactive projects', +// 'form.display_options.cost_per_hour' => 'Cost per hour', // 'form.display_options.custom_css' => 'Custom CSS', - -// Work plugin strings. See example at https://timetracker.anuko.com/work.php -// TODO: translate the following. -// 'work.error.work_not_available' => 'Work item is not available.', -// 'work.error.offer_not_available' => 'Offer is not available.', -// 'work.type.one_time' => 'one time', // Work type is "one time job" for well defined work ("do exactly this"). -// 'work.type.ongoing' => 'ongoing', // Work type is "ongoing" for complex jobs (billed by the hour, multiple contractors, etc.) -// 'work.label.own_work' => 'Own work', -// 'work.label.own_offers' => 'Own offers', -// 'work.label.offers' => 'Offers', -// 'work.button.send_message' => 'Send message', -// 'work.button.make_offer' => 'Make offer', -// 'work.button.accept' => 'Accept', -// 'work.button.decline' => 'Decline', -// 'work.title.send_message' => 'Sending Message', -// 'work.msg.message_sent' => 'Message sent.', +// 'form.display_options.custom_translation' => 'Custom translation', ); diff --git a/WEB-INF/resources/gr.lang.php b/WEB-INF/resources/gr.lang.php index e9003045c..34f1890eb 100644 --- a/WEB-INF/resources/gr.lang.php +++ b/WEB-INF/resources/gr.lang.php @@ -2,7 +2,8 @@ /* Copyright (c) Anuko International Ltd. https://www.anuko.com License: See license.txt */ -// Note: escape apostrophes with THREE backslashes, like here: choisir l\\\'option. +// Note: escape apostrophes with THREE backslashes, like here: 'choisir l\\\'option'. +// Alternatively: use one backslash and surround by double-quotes: "choisir l\'option". // Other characters (such as double-quotes in http links, etc.) do not have to be escaped. $i18n_language = 'Greek (Ελληνικά)'; @@ -25,6 +26,7 @@ 'menu.plugins' => 'Πρόσθετα', 'menu.time' => 'Χρόνος', // TODO: translate the following. +// 'menu.puncher' => 'Punch', // 'menu.week' => 'Week', 'menu.expenses' => 'Έξοδα', 'menu.reports' => 'Αναφορές', @@ -35,8 +37,6 @@ 'menu.tasks' => 'Εργασίες', 'menu.users' => 'Χρήστες', 'menu.groups' => 'Ομάδες', -// TODO: translate the following. -// 'menu.subgroups' => 'Subgroups', 'menu.export' => 'Εξαγωγή', 'menu.clients' => 'Πελάτες', 'menu.options' => 'Επιλογές', @@ -64,6 +64,9 @@ 'error.report' => 'Επιλογή αναφοράς.', 'error.record' => 'Επιλογή εγγραφής.', 'error.auth' => 'Λανθασμένο όνομα εισόδου ή κωδικός.', +// TODO: translate the following. +// 'error.2fa_code' => 'Invalid 2FA code.', +// 'error.weak_password' => 'Weak password.', 'error.user_exists' => 'Ο χρήστης με αυτήν τη σύνδεση υπάρχει ήδη.', 'error.object_exists' => 'Το αντικείμενο με αυτό το όνομα υπάρχει ήδη.', 'error.invoice_exists' => 'Το τιμολόγιο με αυτόν τον αριθμό υπάρχει ήδη.', @@ -90,7 +93,6 @@ // 'error.user_count' => 'Limit on user count.', // 'error.expired' => 'Expiration date reached.', // 'error.file_storage' => 'File storage server error.', // See comment in English file. -// 'error.remote_work' => 'Remote work server error.', // See comment in English file. // Warning messages. // TODO: translate the following. @@ -118,10 +120,13 @@ 'button.export' => 'Εξαγωγη ομάδας', 'button.import' => 'Εισαγωγή ομάδας', 'button.close' => 'Κλείσιμο', +// TODO: translate the following. +// 'button.start' => 'Start', 'button.stop' => 'Τέλος', // TODO: translate the following. // 'button.approve' => 'Approve', // 'button.disapprove' => 'Disapprove', +// 'button.sync' => 'Sync', // Used in Android app. The meaning is to synchronize offline time records with server. // Labels for controls on forms. Labels in this section are used on multiple forms. // TODO: translate the following. @@ -179,6 +184,8 @@ 'label.select_none' => 'Μη επιλογή', 'label.day_view' => 'Προβολή ημέρας', 'label.week_view' => 'Προβολή εβδομάδας', +// TODO: translate the following: +// 'label.puncher' => 'Puncher', 'label.id' => 'ID', 'label.language' => 'Γλώσσα', 'label.decimal_mark' => 'Δεκαδική ένδειξη', @@ -197,9 +204,6 @@ 'label.ldap_hint' => 'Εισάγετε το όνομα σύνδεσης των Windows και κωδικό πρόσβασης στα παρακάτω πεδία.', 'label.required_fields' => '* - υποχρεωτικά πεδία', 'label.on_behalf' => 'εκ μέρους του', -'label.role_manager' => '(Διευθυντής)', -'label.role_comanager' => '(Υποδιευθυντής)', -'label.role_admin' => '(Διαχειριστής)', 'label.page' => 'Σελίδα', 'label.condition' => 'Κατάσταση', 'label.yes' => 'ναι', @@ -223,11 +227,10 @@ 'label.paid_status' => 'Κατάσταση πληρωμής', 'label.paid' => 'Πληρωμένο', 'label.mark_paid' => 'Σήμανση πληρωμένα', -// TODO: translate the following. -// 'label.week_menu' => 'Week menu', 'label.week_note' => 'Σημείωση εβδομάδας', 'label.week_list' => 'Λίστα εβδομάδων', // TODO: translate the following. +// 'label.weekends' => 'Weekends', // 'label.work_units' => 'Work units', // 'label.work_units_short' => 'Units', 'label.totals_only' => 'Σύνολα μόνο', @@ -244,21 +247,8 @@ // 'label.attachments' => 'Attachments', // 'label.files' => 'Files', // 'label.file' => 'File', -// 'label.image' => 'Image', -// 'label.download' => 'Download', 'label.active_users' => 'Ενεργοί χρήστες', 'label.inactive_users' => 'Ανενεργοί χρήστες', -// TODO: translate the following. -// 'label.details' => 'Details', -// 'label.budget' => 'Budget', -// 'label.work' => 'Work', // Table column header for work items, see https://www.anuko.com/time_tracker/what_is/work_plugin.htm -// 'label.offer' => 'Offer', // Table column header for offers, see https://www.anuko.com/time_tracker/what_is/work_plugin.htm -// 'label.contractor' => 'Contractor', // Table column header for offers (contractor is someone who offers to do work). - // Technically, it is either an org name or a combination of org and group names - // because both work items and offers are owned by Time Tracker groups of users. -// 'label.how_to_pay' => 'How to pay', // Label for the "How to pay" field on offers, which allows contractors to specify - // how to pay them, for example: paypal email, check by mail to a specific address, etc. -// 'label.moderator_comment' => 'Moderator comment', // Label for "Moderator comment" field that explains something. // Entity names. We use lower case (in English) because they are used in dropdowns, too. // They are used to associate a custom field with an entity type. @@ -272,9 +262,10 @@ // TODO: Translate the following. // 'title.success' => 'Success', 'title.login' => 'Σύνδεση', +// TODO: translate the follolwing. +// 'title.2fa' => 'Two Factor Authentication', 'title.groups' => 'Ομάδες', // TODO: translate the following. -// 'title.subgroups' => 'Subgroups', // 'title.add_group' => 'Adding Group', 'title.edit_group' => 'Επεξεργασία ομάδας', 'title.delete_group' => 'Διαγραφή ομάδας', @@ -285,6 +276,7 @@ 'title.delete_time_record' => 'Διαγραφή χρόνου', // TODO: Translate the following. // 'title.time_files' => 'Time Record Files', +// 'title.puncher' => 'Puncher', 'title.expenses' => 'Δαπάνες', 'title.edit_expense' => 'Επεξεργασία δαπάνης', 'title.delete_expense' => 'Διαγραφή δαπάνης', @@ -366,22 +358,6 @@ // 'title.edit_file' => 'Editing File', // 'title.delete_file' => 'Deleting File', // 'title.download_file' => 'Downloading File', -// 'title.work' => 'Work', -// 'title.add_work' => 'Adding Work', -// 'title.edit_work' => 'Editing Work', -// 'title.delete_work' => 'Deleting Work', -// 'title.active_work' => 'Active Work', // Active work items this group outsources to other groups. -// 'title.available_work' => 'Available Work', // Available work items from other organizations. -// 'title.inactive_work' => 'Inactive Work', // Inactive work items this group was outsourcing to other groups. -// 'title.pending_work' => 'Pending Work', // Work items pending moderator approval. -// 'title.offer' => 'Offer', -// 'title.add_offer' => 'Adding Offer', -// 'title.edit_offer' => 'Editing Offer', -// 'title.delete_offer' => 'Deleting Offer', -// 'title.active_offers' => 'Active Offers', // Active offers this group makes available to other groups. -// 'title.available_offers' => 'Available Offers', // Available offers from other organizations. -// 'title.inactive_offers' => 'Inactive Offers', // Inactive offers for group. -// 'title.pending_offers' => 'Pending Offers', // Offers pending moderator approval. // Section for common strings inside combo boxes on forms. Strings shared between forms shall be placed here. // Strings that are used in a single form must go to the specific form section. @@ -412,7 +388,6 @@ 'dropdown.delete' => 'διαγραφή', 'dropdown.do_not_delete' => 'μη το διαγράψετε', // TODO: translate the following. -// 'dropdown.pending_approval' => 'pending approval', // 'dropdown.approved' => 'approved', // 'dropdown.not_approved' => 'not approved', 'dropdown.paid' => 'εξοφλημένο', @@ -430,6 +405,16 @@ 'form.login.forgot_password' => 'Ξεχάσατε τον κωδικό πρόσβασης;', 'form.login.about' => 'Anuko Time Tracker είναι ένα ανοικτού κώδικα σύστημα παρακολούθησης χρόνου.', +// Email subject and body for two-factor authentication. +// TODO: translate the following. +// 'email.2fa_code.subject' => 'Anuko Time Tracker two-factor authentication code', +// 'email.2fa_code.body' => "Dear User,\n\nYour two-factor authentication code is:\n\n%s\n\n", + +// Two-factor authentication form. See example at https://timetracker.anuko.com/2fa.php. +// TODO: translate the following. +// 'form.2fa.hint' => 'Check your email for 2FA code and enter it here.', +// 'form.2fa.2fa_code' => '2FA code', + // Resetting Password form. See example at https://timetracker.anuko.com/password_reset.php. 'form.reset_password.message' => 'Το αίτημα επαναφοράς κωδικού πρόσβασης αποστέλλεται μέσω ηλεκτρονικού ταχυδρομείου.', 'form.reset_password.email_subject' => 'Αίτημα επαναφοράς κωδικού Anuko Time Tracker', @@ -467,10 +452,13 @@ // 'form.reports.include_pending' => 'pending', 'form.reports.select_period' => 'Επιλογή χρονικής περιόδου', 'form.reports.set_period' => 'ή εύρος ημερομηνιών', +// TODO: translate the following. +// 'form.reports.note_containing' => 'Note containing', 'form.reports.show_fields' => 'Εμφάνιση πεδίων', // TODO: translate the following. // 'form.reports.time_fields' => 'Time fields', // 'form.reports.user_fields' => 'User fields', +// 'form.reports.project_fields' => 'Project fields', 'form.reports.group_by' => 'Ομαδοποίηση με βάση', 'form.reports.group_by_no' => '--- χωρίς ομαδοποίηση ---', 'form.reports.group_by_date' => 'ημερομηνία', @@ -482,6 +470,8 @@ // Report form. See example at https://timetracker.anuko.com/report.php // (after generating a report at https://timetracker.anuko.com/reports.php). 'form.report.export' => 'Εξαγωγή', +// TODO: translate the following. +// 'form.report.per_hour' => 'Per hour', 'form.report.assign_to_invoice' => 'Ανάθεση στο τιμολόγιο', // TODO: translate the following. // 'form.report.assign_to_timesheet' => 'Assign to timesheet', @@ -496,8 +486,8 @@ // 'form.templates.active_templates' => 'Active Templates', // 'form.templates.inactive_templates' => 'Inactive Templates', -// Invoice form. See example at https://timetracker.anuko.com/invoice.php -// (you can get to this form after generating a report). +// Invoice form. See example at https://timetracker.anuko.com/invoice_view.php +// (you can get to this form after generating an invoice). 'form.invoice.number' => 'Αριθμός τιμολογίου', 'form.invoice.person' => 'Άτομο', @@ -519,6 +509,8 @@ 'form.tasks.inactive_tasks' => 'Ανενεργές εργασίες', // Users form. See example at https://timetracker.anuko.com/users.php +// TODO: translate the following. +// 'form.users.uncompleted_entry_today' => 'User has an uncompleted time entry today', 'form.users.uncompleted_entry' => 'Ο χρήστης έχει μια μη ολοκληρωμένη εισαγωγή χρόνου', 'form.users.role' => 'Ρόλος', 'form.users.manager' => 'Διευθυντής', @@ -575,14 +567,21 @@ 'form.group_edit.type_start_finish' => 'αρχή και τέλος', 'form.group_edit.type_duration' => 'διάρκεια', 'form.group_edit.punch_mode' => 'Λειτουργία διάτρησης', +// TODO: translate the following. +// 'form.group_edit.one_uncompleted' => 'One uncompleted', 'form.group_edit.allow_overlap' => 'Επικάλυψη επιτρεπτή', 'form.group_edit.future_entries' => 'Μελλοντικές καταχωρήσεις', 'form.group_edit.uncompleted_indicators' => 'Μη ολοκληρωμένες ενδείξεις', // TODO: translate the following. // 'form.group_edit.confirm_save' => 'Confirm saving', -// 'form.group_edit.allow_ip' => 'Allow IP', // 'form.group_edit.advanced_settings' => 'Advanced settings', +// Advanced Group Settings form. See example at https://timetracker.anuko.com/group_advanced_edit.php. +// TODO: Translate the following. +// 'form.group_advanced_edit.allow_ip' => 'Allow IP', +// 'form.group_advanced_edit.password_complexity' => 'Password complexity', +// 'form.group_advanced_edit.2fa' => 'Two factor authentication', + // Deleting Group form. See example at https://timetracker.anuko.com/delete_group.php // TODO: translate the following. // 'form.group_delete.hint' => 'Are you sure you want to delete the entire group?', @@ -648,21 +647,8 @@ // TODO: translate the following. // 'form.display_options.note_on_separate_row' => 'Note on separate row', // 'form.display_options.not_complete_days' => 'Not complete days', +// 'form.display_options.inactive_projects' => 'Inactive projects', +// 'form.display_options.cost_per_hour' => 'Cost per hour', // 'form.display_options.custom_css' => 'Custom CSS', - -// Work plugin strings. See example at https://timetracker.anuko.com/work.php -// TODO: translate the following. -// 'work.error.work_not_available' => 'Work item is not available.', -// 'work.error.offer_not_available' => 'Offer is not available.', -// 'work.type.one_time' => 'one time', // Work type is "one time job" for well defined work ("do exactly this"). -// 'work.type.ongoing' => 'ongoing', // Work type is "ongoing" for complex jobs (billed by the hour, multiple contractors, etc.) -// 'work.label.own_work' => 'Own work', -// 'work.label.own_offers' => 'Own offers', -// 'work.label.offers' => 'Offers', -// 'work.button.send_message' => 'Send message', -// 'work.button.make_offer' => 'Make offer', -// 'work.button.accept' => 'Accept', -// 'work.button.decline' => 'Decline', -// 'work.title.send_message' => 'Sending Message', -// 'work.msg.message_sent' => 'Message sent.', +// 'form.display_options.custom_translation' => 'Custom translation', ); diff --git a/WEB-INF/resources/he.lang.php b/WEB-INF/resources/he.lang.php index 032e01bf0..585a7ec2f 100644 --- a/WEB-INF/resources/he.lang.php +++ b/WEB-INF/resources/he.lang.php @@ -2,7 +2,8 @@ /* Copyright (c) Anuko International Ltd. https://www.anuko.com License: See license.txt */ -// Note: escape apostrophes with THREE backslashes, like here: choisir l\\\'option. +// Note: escape apostrophes with THREE backslashes, like here: 'choisir l\\\'option'. +// Alternatively: use one backslash and surround by double-quotes: "choisir l\'option". // Other characters (such as double-quotes in http links, etc.) do not have to be escaped. $i18n_language = 'Hebrew (עברית)'; @@ -27,6 +28,7 @@ 'menu.plugins' => 'תוספים', 'menu.time' => 'זמן', // TODO: translate the following. +// 'menu.puncher' => 'Punch', // 'menu.week' => 'Week', // 'menu.expenses' => 'Expenses', 'menu.reports' => 'דוחות', @@ -38,7 +40,6 @@ 'menu.users' => 'משתמשים', // TODO: translate the following. // 'menu.groups' => 'Groups', -// 'menu.subgroups' => 'Subgroups', 'menu.export' => 'ייצוא', 'menu.clients' => 'לקוחות', 'menu.options' => 'אפשרויות', @@ -81,6 +82,9 @@ // 'error.report' => 'Select report.' // 'error.record' => 'Select record.', 'error.auth' => 'שם משתמש או סיסמה שגויים', +// TODO: translate the following. +// 'error.2fa_code' => 'Invalid 2FA code.', +// 'error.weak_password' => 'Weak password.', 'error.user_exists' => 'שם משתמש כבר קיים', // TODO: translate the following. // 'error.object_exists' => 'Object with this name already exists.', @@ -110,7 +114,6 @@ // 'error.user_count' => 'Limit on user count.', // 'error.expired' => 'Expiration date reached.', // 'error.file_storage' => 'File storage server error.', // See comment in English file. -// 'error.remote_work' => 'Remote work server error.', // See comment in English file. // Warning messages. // TODO: translate the following. @@ -138,10 +141,13 @@ 'button.export' => 'ייצא צוות', // TODO: replace "team" with "group". 'button.import' => 'ייבא צוות', // TODO: replace "team" with "group". 'button.close' => 'סגור', +// TODO: translate the following. +// 'button.start' => 'Start', 'button.stop' => 'עצור', // TODO: translate the following. // 'button.approve' => 'Approve', // 'button.disapprove' => 'Disapprove', +// 'button.sync' => 'Sync', // Used in Android app. The meaning is to synchronize offline time records with server. // Labels for controls on forms. Labels in this section are used on multiple forms. // TODO: translate the following. @@ -204,6 +210,7 @@ // TODO: translate the following. // 'label.day_view' => 'Day view', // 'label.week_view' => 'Week view', +// 'label.puncher' => 'Puncher', 'label.id' => 'מזהה', 'label.language' => 'שפה', // TODO: translate the following. @@ -223,9 +230,6 @@ 'label.ldap_hint' => 'הכנס את שם המשתמש ואת הסיסמה של ווינדוז בשדות.', 'label.required_fields' => '* - שדות חובה', 'label.on_behalf' => 'מטעם', -'label.role_manager' => '(מנהל)', -'label.role_comanager' => '(מנהל משנה)', -'label.role_admin' => '(מנהל המערכת)', // Translate the following. // 'label.page' => 'Page', // 'label.condition' => 'Condition', @@ -250,9 +254,9 @@ // 'label.paid_status' => 'Paid status', // 'label.paid' => 'Paid', // 'label.mark_paid' => 'Mark paid', -// 'label.week_menu' => 'Week menu', // 'label.week_note' => 'Week note', // 'label.week_list' => 'Week list', +// 'label.weekends' => 'Weekends', // 'label.work_units' => 'Work units', // 'label.work_units_short' => 'Units', 'labl.totals_only' => 'סיכומים בלבד', @@ -269,21 +273,8 @@ // 'label.attachments' => 'Attachments', // 'label.files' => 'Files', // 'label.file' => 'File', -// 'label.image' => 'Image', -// 'label.download' => 'Download', 'label.active_users' => 'משתמשים פעילים', 'label.inactive_users' => 'משתמשים לא פעילים', -// TODO: translate the following. -// 'label.details' => 'Details', -// 'label.budget' => 'Budget', -// 'label.work' => 'Work', // Table column header for work items, see https://www.anuko.com/time_tracker/what_is/work_plugin.htm -// 'label.offer' => 'Offer', // Table column header for offers, see https://www.anuko.com/time_tracker/what_is/work_plugin.htm -// 'label.contractor' => 'Contractor', // Table column header for offers (contractor is someone who offers to do work). - // Technically, it is either an org name or a combination of org and group names - // because both work items and offers are owned by Time Tracker groups of users. -// 'label.how_to_pay' => 'How to pay', // Label for the "How to pay" field on offers, which allows contractors to specify - // how to pay them, for example: paypal email, check by mail to a specific address, etc. -// 'label.moderator_comment' => 'Moderator comment', // Label for "Moderator comment" field that explains something. // Entity names. We use lower case (in English) because they are used in dropdowns, too. // They are used to associate a custom field with an entity type. @@ -297,9 +288,10 @@ // TODO: Translate the following. // 'title.success' => 'Success', 'title.login' => 'כניסה', +// TODO: translate the follolwing. +// 'title.2fa' => 'Two Factor Authentication', 'title.groups' => 'צוותים', // TODO: change "teams" to "groups". // TODO: translate the following. -// 'title.subgroups' => 'Subgroups', // 'title.add_group' => 'Adding Group', // 'title.edit_group' => 'Editing Group', 'title.delete_group' => 'מחיקת צוות', // TODO: change "team" to "group". @@ -310,6 +302,7 @@ 'title.delete_time_record' => 'מחיקת רשומה', // TODO: Translate the following. // 'title.time_files' => 'Time Record Files', +// 'title.puncher' => 'Puncher', // 'title.expenses' => 'Expenses', // 'title.edit_expense' => 'Editing Expense Item', // 'title.delete_expense' => 'Deleting Expense Item', @@ -389,22 +382,6 @@ // 'title.edit_file' => 'Editing File', // 'title.delete_file' => 'Deleting File', // 'title.download_file' => 'Downloading File', -// 'title.work' => 'Work', -// 'title.add_work' => 'Adding Work', -// 'title.edit_work' => 'Editing Work', -// 'title.delete_work' => 'Deleting Work', -// 'title.active_work' => 'Active Work', // Active work items this group outsources to other groups. -// 'title.available_work' => 'Available Work', // Available work items from other organizations. -// 'title.inactive_work' => 'Inactive Work', // Inactive work items this group was outsourcing to other groups. -// 'title.pending_work' => 'Pending Work', // Work items pending moderator approval. -// 'title.offer' => 'Offer', -// 'title.add_offer' => 'Adding Offer', -// 'title.edit_offer' => 'Editing Offer', -// 'title.delete_offer' => 'Deleting Offer', -// 'title.active_offers' => 'Active Offers', // Active offers this group makes available to other groups. -// 'title.available_offers' => 'Available Offers', // Available offers from other organizations. -// 'title.inactive_offers' => 'Inactive Offers', // Inactive offers for group. -// 'title.pending_offers' => 'Pending Offers', // Offers pending moderator approval. // Section for common strings inside combo boxes on forms. Strings shared between forms shall be placed here. // Strings that are used in a single form must go to the specific form section. @@ -438,7 +415,6 @@ // TODO: translate the following. // 'dropdown.delete' => 'delete', // 'dropdown.do_not_delete' => 'do not delete', -// 'dropdown.pending_approval' => 'pending approval', // 'dropdown.approved' => 'approved', // 'dropdown.not_approved' => 'not approved', // 'dropdown.paid' => 'paid', @@ -457,6 +433,16 @@ // 'form.login.about' => 'Anuko Time Tracker is an open source time tracking system.', 'form.login.about' => 'Anuko Time Tracker הינה מערכת פשוטה, קלה לשימוש וחינמית לניהול זמן.', +// Email subject and body for two-factor authentication. +// TODO: translate the following. +// 'email.2fa_code.subject' => 'Anuko Time Tracker two-factor authentication code', +// 'email.2fa_code.body' => "Dear User,\n\nYour two-factor authentication code is:\n\n%s\n\n", + +// Two-factor authentication form. See example at https://timetracker.anuko.com/2fa.php. +// TODO: translate the following. +// 'form.2fa.hint' => 'Check your email for 2FA code and enter it here.', +// 'form.2fa.2fa_code' => '2FA code', + // Resetting Password form. See example at https://timetracker.anuko.com/password_reset.php. 'form.reset_password.message' => 'הבקשה לאיפוס בסיסמה נשלחה בדואר אלקטרוני.', 'form.reset_password.email_subject' => 'בקשה לאיפוס סיסמה למערכת Anuko Time Tracker', @@ -498,10 +484,13 @@ // 'form.reports.include_pending' => 'pending', 'form.reports.select_period' => 'בחר תקופת זמן', 'form.reports.set_period' => 'או הגדר תאריכים', +// TODO: translate the following. +// 'form.reports.note_containing' => 'Note containing', 'form.reports.show_fields' => 'הראה שדות', // TODO: translate the following. // 'form.reports.time_fields' => 'Time fields', // 'form.reports.user_fields' => 'User fields', +// 'form.reports.project_fields' => 'Project fields', 'form.reports.group_by' => 'סדר לפי', 'form.reports.group_by_no' => '--- ללא סדר ---', 'form.reports.group_by_date' => 'תאריך', @@ -515,6 +504,7 @@ // TODO: form.report.export is just "Export" now in the English file. Shorten this translation. 'form.report.export' => 'ייצא נתונים בתבנית', // TODO: translate the following. +// 'form.report.per_hour' => 'Per hour', // 'form.report.assign_to_invoice' => 'Assign to invoice', // 'form.report.assign_to_timesheet' => 'Assign to timesheet', @@ -528,8 +518,8 @@ // 'form.templates.active_templates' => 'Active Templates', // 'form.templates.inactive_templates' => 'Inactive Templates', -// Invoice form. See example at https://timetracker.anuko.com/invoice.php -// (you can get to this form after generating a report). +// Invoice form. See example at https://timetracker.anuko.com/invoice_view.php +// (you can get to this form after generating an invoice). 'form.invoice.number' => 'מספר חשבונית', 'form.invoice.person' => 'משתמש', @@ -553,6 +543,7 @@ // Users form. See example at https://timetracker.anuko.com/users.php // TODO: translate the following. +// 'form.users.uncompleted_entry_today' => 'User has an uncompleted time entry today', // 'form.users.uncompleted_entry' => 'User has an uncompleted time entry', 'form.users.role' => 'תפקיד', 'form.users.manager' => 'מנהל', @@ -614,13 +605,19 @@ 'form.group_edit.type_duration' => 'משך זמן', // TODO: translate the following. // 'form.group_edit.punch_mode' => 'Punch mode', +// 'form.group_edit.one_uncompleted' => 'One uncompleted', // 'form.group_edit.allow_overlap' => 'Allow overlap', // 'form.group_edit.future_entries' => 'Future entries', // 'form.group_edit.uncompleted_indicators' => 'Uncompleted indicators', // 'form.group_edit.confirm_save' => 'Confirm saving', -// 'form.group_edit.allow_ip' => 'Allow IP', // 'form.group_edit.advanced_settings' => 'Advanced settings', +// Advanced Group Settings form. See example at https://timetracker.anuko.com/group_advanced_edit.php. +// TODO: Translate the following. +// 'form.group_advanced_edit.allow_ip' => 'Allow IP', +// 'form.group_advanced_edit.password_complexity' => 'Password complexity', +// 'form.group_advanced_edit.2fa' => 'Two factor authentication', + // Deleting Group form. See example at https://timetracker.anuko.com/delete_group.php // TODO: translate the following. // 'form.group_delete.hint' => 'Are you sure you want to delete the entire group?', @@ -688,21 +685,8 @@ // TODO: translate the following. // 'form.display_options.note_on_separate_row' => 'Note on separate row', // 'form.display_options.not_complete_days' => 'Not complete days', +// 'form.display_options.inactive_projects' => 'Inactive projects', +// 'form.display_options.cost_per_hour' => 'Cost per hour', // 'form.display_options.custom_css' => 'Custom CSS', - -// Work plugin strings. See example at https://timetracker.anuko.com/work.php -// TODO: translate the following. -// 'work.error.work_not_available' => 'Work item is not available.', -// 'work.error.offer_not_available' => 'Offer is not available.', -// 'work.type.one_time' => 'one time', // Work type is "one time job" for well defined work ("do exactly this"). -// 'work.type.ongoing' => 'ongoing', // Work type is "ongoing" for complex jobs (billed by the hour, multiple contractors, etc.) -// 'work.label.own_work' => 'Own work', -// 'work.label.own_offers' => 'Own offers', -// 'work.label.offers' => 'Offers', -// 'work.button.send_message' => 'Send message', -// 'work.button.make_offer' => 'Make offer', -// 'work.button.accept' => 'Accept', -// 'work.button.decline' => 'Decline', -// 'work.title.send_message' => 'Sending Message', -// 'work.msg.message_sent' => 'Message sent.', +// 'form.display_options.custom_translation' => 'Custom translation', ); diff --git a/WEB-INF/resources/hu.lang.php b/WEB-INF/resources/hu.lang.php index 1909682fb..e2d7500d3 100644 --- a/WEB-INF/resources/hu.lang.php +++ b/WEB-INF/resources/hu.lang.php @@ -2,7 +2,8 @@ /* Copyright (c) Anuko International Ltd. https://www.anuko.com License: See license.txt */ -// Note: escape apostrophes with THREE backslashes, like here: choisir l\\\'option. +// Note: escape apostrophes with THREE backslashes, like here: 'choisir l\\\'option'. +// Alternatively: use one backslash and surround by double-quotes: "choisir l\'option". // Other characters (such as double-quotes in http links, etc.) do not have to be escaped. // Note to translators: Use proper capitalization rules for your language. @@ -28,6 +29,7 @@ // 'menu.plugins' => 'Plugins', 'menu.time' => 'Munkaidő', // TODO: translate the following. +// 'menu.puncher' => 'Punch', // 'menu.week' => 'Week', // 'menu.expenses' => 'Expenses', 'menu.reports' => 'Riportok', @@ -40,8 +42,6 @@ // 'menu.users' => 'Users', 'menu.groups' => 'Csoportok', // TODO: translate the following. -// 'menu.subgroups' => 'Subgroups', -// TODO: translate the following. // 'menu.export' => 'Export', 'menu.clients' => 'Ügyfelek', 'menu.options' => 'Opciók', @@ -75,6 +75,8 @@ // 'error.report' => 'Select report.', // 'error.record' => 'Select record.', // 'error.auth' => 'Incorrect login or password.', +// 'error.2fa_code' => 'Invalid 2FA code.', +// 'error.weak_password' => 'Weak password.', // 'error.user_exists' => 'User with this login already exists.', // 'error.object_exists' => 'Object with this name already exists.', // 'error.invoice_exists' => 'Invoice with this number already exists.', @@ -99,7 +101,6 @@ // 'error.user_count' => 'Limit on user count.', // 'error.expired' => 'Expiration date reached.', // 'error.file_storage' => 'File storage server error.', // See comment in English file. -// 'error.remote_work' => 'Remote work server error.', // See comment in English file. // Warning messages. // TODO: translate the following. @@ -130,9 +131,11 @@ 'button.import' => 'Csoport importálása', // TODO: translate the following. // 'button.close' => 'Close', +// 'button.start' => 'Start', // 'button.stop' => 'Stop', // 'button.approve' => 'Approve', // 'button.disapprove' => 'Disapprove', +// 'button.sync' => 'Sync', // Used in Android app. The meaning is to synchronize offline time records with server. // Labels for controls on forms. Labels in this section are used on multiple forms. // TODO: translate the following. @@ -199,6 +202,7 @@ 'label.select_none' => 'Senkit nem jelöl ki', // 'label.day_view' => 'Day view', // 'label.week_view' => 'Week view', +// 'label.puncher' => 'Puncher', 'label.id' => 'Azonosító', // TODO: translate the following. // 'label.language' => 'Language', @@ -220,9 +224,6 @@ // 'label.ldap_hint' => 'Type your Windows login and password in the fields below.', 'label.required_fields' => '* kötelezően kitöltendő mezők', 'label.on_behalf' => 'helyett', -'label.role_manager' => '(vezető)', -'label.role_comanager' => '(helyettes)', -'label.role_admin' => '(adminisztrátor)', // TODO: translate the following. // 'label.page' => 'Page', // 'label.condition' => 'Condition', @@ -247,9 +248,9 @@ // 'label.paid_status' => 'Paid status', // 'label.paid' => 'Paid', // 'label.mark_paid' => 'Mark paid', -// 'label.week_menu' => 'Week menu', // 'label.week_note' => 'Week note', // 'label.week_list' => 'Week list', +// 'label.weekends' => 'Weekends', // 'label.work_units' => 'Work units', // 'label.work_units_short' => 'Units', // 'label.totals_only' => 'Totals only', @@ -265,20 +266,8 @@ // 'label.attachments' => 'Attachments', // 'label.files' => 'Files', // 'label.file' => 'File', -// 'label.image' => 'Image', -// 'label.download' => 'Download', // 'label.active_users' => 'Active Users', // 'label.inactive_users' => 'Inactive Users', -// 'label.details' => 'Details', -// 'label.budget' => 'Budget', -// 'label.work' => 'Work', // Table column header for work items, see https://www.anuko.com/time_tracker/what_is/work_plugin.htm -// 'label.offer' => 'Offer', // Table column header for offers, see https://www.anuko.com/time_tracker/what_is/work_plugin.htm -// 'label.contractor' => 'Contractor', // Table column header for offers (contractor is someone who offers to do work). - // Technically, it is either an org name or a combination of org and group names - // because both work items and offers are owned by Time Tracker groups of users. -// 'label.how_to_pay' => 'How to pay', // Label for the "How to pay" field on offers, which allows contractors to specify - // how to pay them, for example: paypal email, check by mail to a specific address, etc. -// 'label.moderator_comment' => 'Moderator comment', // Label for "Moderator comment" field that explains something. // Entity names. We use lower case (in English) because they are used in dropdowns, too. // They are used to associate a custom field with an entity type. @@ -292,9 +281,10 @@ // 'title.error' => 'Error', // 'title.success' => 'Success', 'title.login' => 'Bejelentkezés', +// TODO: translate the follolwing. +// 'title.2fa' => 'Two Factor Authentication', 'title.groups' => 'Csoportok', // TODO: translate the following. -// 'title.subgroups' => 'Subgroups', // 'title.add_group' => 'Adding Group', // 'title.edit_group' => 'Editing Group', 'title.delete_group' => 'Csoport törlése', @@ -305,6 +295,7 @@ // 'title.edit_time_record' => 'Editing Time Record', // 'title.delete_time_record' => 'Deleting Time Record', // 'title.time_files' => 'Time Record Files', +// 'title.puncher' => 'Puncher', // 'title.expenses' => 'Expenses', // 'title.edit_expense' => 'Editing Expense Item', // 'title.delete_expense' => 'Deleting Expense Item', @@ -389,22 +380,6 @@ // 'title.edit_file' => 'Editing File', // 'title.delete_file' => 'Deleting File', // 'title.download_file' => 'Downloading File', -// 'title.work' => 'Work', -// 'title.add_work' => 'Adding Work', -// 'title.edit_work' => 'Editing Work', -// 'title.delete_work' => 'Deleting Work', -// 'title.active_work' => 'Active Work', // Active work items this group outsources to other groups. -// 'title.available_work' => 'Available Work', // Available work items from other organizations. -// 'title.inactive_work' => 'Inactive Work', // Inactive work items this group was outsourcing to other groups. -// 'title.pending_work' => 'Pending Work', // Work items pending moderator approval. -// 'title.offer' => 'Offer', -// 'title.add_offer' => 'Adding Offer', -// 'title.edit_offer' => 'Editing Offer', -// 'title.delete_offer' => 'Deleting Offer', -// 'title.active_offers' => 'Active Offers', // Active offers this group makes available to other groups. -// 'title.available_offers' => 'Available Offers', // Available offers from other organizations. -// 'title.inactive_offers' => 'Inactive Offers', // Inactive offers for group. -// 'title.pending_offers' => 'Pending Offers', // Offers pending moderator approval. // Section for common strings inside combo boxes on forms. Strings shared between forms shall be placed here. // Strings that are used in a single form must go to the specific form section. @@ -439,7 +414,6 @@ // 'dropdown.status_inactive' => 'inactive', // 'dropdown.delete' => 'delete', // 'dropdown.do_not_delete' => 'do not delete', -// 'dropdown.pending_approval' => 'pending approval', // 'dropdown.approved' => 'approved', // 'dropdown.not_approved' => 'not approved', // 'dropdown.paid' => 'paid', @@ -452,6 +426,16 @@ // TODO: translate the following. // 'form.login.about' => 'Anuko Time Tracker is an open source time tracking system.', +// Email subject and body for two-factor authentication. +// TODO: translate the following. +// 'email.2fa_code.subject' => 'Anuko Time Tracker two-factor authentication code', +// 'email.2fa_code.body' => "Dear User,\n\nYour two-factor authentication code is:\n\n%s\n\n", + +// Two-factor authentication form. See example at https://timetracker.anuko.com/2fa.php. +// TODO: translate the following. +// 'form.2fa.hint' => 'Check your email for 2FA code and enter it here.', +// 'form.2fa.2fa_code' => '2FA code', + // Resetting Password form. See example at https://timetracker.anuko.com/password_reset.php. // TODO: translate the following. // 'form.reset_password.message' => 'Password reset request sent by email.', @@ -495,9 +479,11 @@ 'form.reports.select_period' => 'Jelölj meg egy időszakot', 'form.reports.set_period' => 'vagy állíts be konkrét dátumot', // TODO: translate the following. +// 'form.reports.note_containing' => 'Note containing', // 'form.reports.show_fields' => 'Show fields', // 'form.reports.time_fields' => 'Time fields', // 'form.reports.user_fields' => 'User fields', +// 'form.reports.project_fields' => 'Project fields', 'form.reports.group_by' => 'Csoportosítva', 'form.reports.group_by_no' => '--- csoportosítás nélkül ---', 'form.reports.group_by_date' => 'dátum', @@ -512,6 +498,7 @@ // (after generating a report at https://timetracker.anuko.com/reports.php). 'form.report.export' => 'Exportálása', // TODO: is this correct? // TODO: translate the following. +// 'form.report.per_hour' => 'Per hour', // 'form.report.assign_to_invoice' => 'Assign to invoice', // 'form.report.assign_to_timesheet' => 'Assign to timesheet', @@ -525,8 +512,8 @@ // 'form.templates.active_templates' => 'Active Templates', // 'form.templates.inactive_templates' => 'Inactive Templates', -// Invoice form. See example at https://timetracker.anuko.com/invoice.php -// (you can get to this form after generating a report). +// Invoice form. See example at https://timetracker.anuko.com/invoice_view.php +// (you can get to this form after generating an invoice). 'form.invoice.number' => 'Számla azonosító száma', 'form.invoice.person' => 'Személy', @@ -552,6 +539,7 @@ // Users form. See example at https://timetracker.anuko.com/users.php // TODO: translate the following. +// 'form.users.uncompleted_entry_today' => 'User has an uncompleted time entry today', // 'form.users.uncompleted_entry' => 'User has an uncompleted time entry', 'form.users.role' => 'Szerepkör', 'form.users.manager' => 'Vezető', @@ -615,13 +603,19 @@ // 'form.group_edit.type_start_finish' => 'start and finish', // 'form.group_edit.type_duration' => 'duration', // 'form.group_edit.punch_mode' => 'Punch mode', +// 'form.group_edit.one_uncompleted' => 'One uncompleted', // 'form.group_edit.allow_overlap' => 'Allow overlap', // 'form.group_edit.future_entries' => 'Future entries', // 'form.group_edit.uncompleted_indicators' => 'Uncompleted indicators', // 'form.group_edit.confirm_save' => 'Confirm saving', -// 'form.group_edit.allow_ip' => 'Allow IP', // 'form.group_edit.advanced_settings' => 'Advanced settings', +// Advanced Group Settings form. See example at https://timetracker.anuko.com/group_advanced_edit.php. +// TODO: Translate the following. +// 'form.group_advanced_edit.allow_ip' => 'Allow IP', +// 'form.group_advanced_edit.password_complexity' => 'Password complexity', +// 'form.group_advanced_edit.2fa' => 'Two factor authentication', + // Deleting Group form. See example at https://timetracker.anuko.com/delete_group.php // TODO: translate the following. // 'form.group_delete.hint' => 'Are you sure you want to delete the entire group?', @@ -688,21 +682,8 @@ // TODO: translate the following. // 'form.display_options.note_on_separate_row' => 'Note on separate row', // 'form.display_options.not_complete_days' => 'Not complete days', +// 'form.display_options.inactive_projects' => 'Inactive projects', +// 'form.display_options.cost_per_hour' => 'Cost per hour', // 'form.display_options.custom_css' => 'Custom CSS', - -// Work plugin strings. See example at https://timetracker.anuko.com/work.php -// TODO: translate the following. -// 'work.error.work_not_available' => 'Work item is not available.', -// 'work.error.offer_not_available' => 'Offer is not available.', -// 'work.type.one_time' => 'one time', // Work type is "one time job" for well defined work ("do exactly this"). -// 'work.type.ongoing' => 'ongoing', // Work type is "ongoing" for complex jobs (billed by the hour, multiple contractors, etc.) -// 'work.label.own_work' => 'Own work', -// 'work.label.own_offers' => 'Own offers', -// 'work.label.offers' => 'Offers', -// 'work.button.send_message' => 'Send message', -// 'work.button.make_offer' => 'Make offer', -// 'work.button.accept' => 'Accept', -// 'work.button.decline' => 'Decline', -// 'work.title.send_message' => 'Sending Message', -// 'work.msg.message_sent' => 'Message sent.', +// 'form.display_options.custom_translation' => 'Custom translation', ); diff --git a/WEB-INF/resources/it.lang.php b/WEB-INF/resources/it.lang.php index 35035d3e9..73ca8a022 100644 --- a/WEB-INF/resources/it.lang.php +++ b/WEB-INF/resources/it.lang.php @@ -2,7 +2,8 @@ /* Copyright (c) Anuko International Ltd. https://www.anuko.com License: See license.txt */ -// Note: escape apostrophes with THREE backslashes, like here: choisir l\\\'option. +// Note: escape apostrophes with THREE backslashes, like here: 'choisir l\\\'option'. +// Alternatively: use one backslash and surround by double-quotes: "choisir l\'option". // Other characters (such as double-quotes in http links, etc.) do not have to be escaped. // Note to translators: Use proper capitalization rules for your language. @@ -27,6 +28,7 @@ 'menu.plugins' => 'Plugin', 'menu.time' => 'Tempo', // TODO: translate the following. +// 'menu.puncher' => 'Punch', // 'menu.week' => 'Week', 'menu.expenses' => 'Spese', 'menu.reports' => 'Rapporti', @@ -37,8 +39,6 @@ 'menu.tasks' => 'Compiti', 'menu.users' => 'Utenti', 'menu.groups' => 'Gruppi', -// TODO: translate the following. -// 'menu.subgroups' => 'Subgroups', 'menu.export' => 'Esportazione', 'menu.clients' => 'Clienti', 'menu.options' => 'Opzioni', @@ -66,6 +66,9 @@ 'error.report' => 'Seleziona rapporto.', 'error.record' => 'Seleziona record.', 'error.auth' => 'Login o password errati.', +// TODO: translate the following. +// 'error.2fa_code' => 'Invalid 2FA code.', +// 'error.weak_password' => 'Weak password.', 'error.user_exists' => 'Esiste già un utente con questo username.', // TODO: translate the following. // 'error.object_exists' => 'Object with this name already exists.', @@ -94,7 +97,6 @@ // 'error.user_count' => 'Limit on user count.', // 'error.expired' => 'Expiration date reached.', // 'error.file_storage' => 'File storage server error.', // See comment in English file. -// 'error.remote_work' => 'Remote work server error.', // See comment in English file. // Warning messages. // TODO: translate the following. @@ -122,10 +124,13 @@ 'button.export' => 'Esporta gruppo', 'button.import' => 'Importa gruppo', 'button.close' => 'Chiudi', +// TODO: translate the following. +// 'button.start' => 'Start', 'button.stop' => 'Stop', // TODO: translate the following. // 'button.approve' => 'Approve', // 'button.disapprove' => 'Disapprove', +// 'button.sync' => 'Sync', // Used in Android app. The meaning is to synchronize offline time records with server. // Labels for controls on forms. Labels in this section are used on multiple forms. // TODO: translate the following. @@ -183,6 +188,8 @@ 'label.select_none' => 'Deseleziona tutti', 'label.day_view' => 'Vista giornaliera', 'label.week_view' => 'Vista settimanale', +// TODO: translate the following: +// 'label.puncher' => 'Puncher', 'label.id' => 'ID', 'label.language' => 'Lingua', 'label.decimal_mark' => 'Separatore decimale', @@ -201,9 +208,6 @@ 'label.ldap_hint' => 'Digita il tuo Login Windows e la tua password nei campi qui sotto.', 'label.required_fields' => '* campi obbligatori', 'label.on_behalf' => 'a favore di', -'label.role_manager' => '(manager)', -'label.role_comanager' => '(co-manager)', -'label.role_admin' => '(amministratore)', 'label.page' => 'Pagina', 'label.condition' => 'Condizione', 'label.yes' => 'si', @@ -227,11 +231,10 @@ 'label.paid_status' => 'Stato pagamento', 'label.paid' => 'Pagato', 'label.mark_paid' => 'Segna come pagato', -// TODO: translate the following. -// 'label.week_menu' => 'Week menu', 'label.week_note' => 'Nota settimanale', 'label.week_list' => 'Lista settimanale', // TODO: translate the following. +// 'label.weekends' => 'Weekends', // 'label.work_units' => 'Work units', // 'label.work_units_short' => 'Units', 'label.totals_only' => 'Solo i totali', @@ -248,21 +251,8 @@ // 'label.attachments' => 'Attachments', // 'label.files' => 'Files', // 'label.file' => 'File', -// 'label.image' => 'Image', -// 'label.download' => 'Download', 'label.active_users' => 'Utenti attivi', 'label.inactive_users' => 'Utenti inattivi', -// TODO: translate the following. -// 'label.details' => 'Details', -// 'label.budget' => 'Budget', -// 'label.work' => 'Work', // Table column header for work items, see https://www.anuko.com/time_tracker/what_is/work_plugin.htm -// 'label.offer' => 'Offer', // Table column header for offers, see https://www.anuko.com/time_tracker/what_is/work_plugin.htm -// 'label.contractor' => 'Contractor', // Table column header for offers (contractor is someone who offers to do work). - // Technically, it is either an org name or a combination of org and group names - // because both work items and offers are owned by Time Tracker groups of users. -// 'label.how_to_pay' => 'How to pay', // Label for the "How to pay" field on offers, which allows contractors to specify - // how to pay them, for example: paypal email, check by mail to a specific address, etc. -// 'label.moderator_comment' => 'Moderator comment', // Label for "Moderator comment" field that explains something. // Entity names. We use lower case (in English) because they are used in dropdowns, too. // They are used to associate a custom field with an entity type. @@ -280,9 +270,10 @@ // TODO: Translate the following. // 'title.success' => 'Success', 'title.login' => 'Login', +// TODO: translate the follolwing. +// 'title.2fa' => 'Two Factor Authentication', 'title.groups' => 'Gruppi', // TODO: translate the following. -// 'title.subgroups' => 'Subgroups', // 'title.add_group' => 'Adding Group', 'title.edit_group' => 'Modifica gruppo', 'title.delete_group' => 'Elimina gruppo', @@ -293,6 +284,7 @@ 'title.delete_time_record' => 'Eliminazione record temporale', // TODO: Translate the following. // 'title.time_files' => 'Time Record Files', +// 'title.puncher' => 'Puncher', 'title.expenses' => 'Spese', 'title.edit_expense' => 'Modifica voce di spesa', 'title.delete_expense' => 'Eliminezione voce di spesa', @@ -376,22 +368,6 @@ // 'title.edit_file' => 'Editing File', // 'title.delete_file' => 'Deleting File', // 'title.download_file' => 'Downloading File', -// 'title.work' => 'Work', -// 'title.add_work' => 'Adding Work', -// 'title.edit_work' => 'Editing Work', -// 'title.delete_work' => 'Deleting Work', -// 'title.active_work' => 'Active Work', // Active work items this group outsources to other groups. -// 'title.available_work' => 'Available Work', // Available work items from other organizations. -// 'title.inactive_work' => 'Inactive Work', // Inactive work items this group was outsourcing to other groups. -// 'title.pending_work' => 'Pending Work', // Work items pending moderator approval. -// 'title.offer' => 'Offer', -// 'title.add_offer' => 'Adding Offer', -// 'title.edit_offer' => 'Editing Offer', -// 'title.delete_offer' => 'Deleting Offer', -// 'title.active_offers' => 'Active Offers', // Active offers this group makes available to other groups. -// 'title.available_offers' => 'Available Offers', // Available offers from other organizations. -// 'title.inactive_offers' => 'Inactive Offers', // Inactive offers for group. -// 'title.pending_offers' => 'Pending Offers', // Offers pending moderator approval. // Section for common strings inside combo boxes on forms. Strings shared between forms shall be placed here. // Strings that are used in a single form must go to the specific form section. @@ -422,7 +398,6 @@ 'dropdown.delete' => 'elimina', 'dropdown.do_not_delete' => 'non eliminare', // TODO: translate the following. -// 'dropdown.pending_approval' => 'pending approval', // 'dropdown.approved' => 'approved', // 'dropdown.not_approved' => 'not approved', 'dropdown.paid' => 'pagato', @@ -440,6 +415,16 @@ 'form.login.forgot_password' => 'Password dimenticata?', 'form.login.about' => 'Anuko Time Tracker è un sistema open source per registrare i tempi di lavoro.', +// Email subject and body for two-factor authentication. +// TODO: translate the following. +// 'email.2fa_code.subject' => 'Anuko Time Tracker two-factor authentication code', +// 'email.2fa_code.body' => "Dear User,\n\nYour two-factor authentication code is:\n\n%s\n\n", + +// Two-factor authentication form. See example at https://timetracker.anuko.com/2fa.php. +// TODO: translate the following. +// 'form.2fa.hint' => 'Check your email for 2FA code and enter it here.', +// 'form.2fa.2fa_code' => '2FA code', + // Resetting Password form. See example at https://timetracker.anuko.com/password_reset.php. 'form.reset_password.message' => 'Richiesta di reset password inviata via mail.', 'form.reset_password.email_subject' => 'Richiesta reset password per Anuko Time Tracker', @@ -480,10 +465,13 @@ // 'form.reports.include_pending' => 'pending', 'form.reports.select_period' => 'Seleziona il periodo di tempo', 'form.reports.set_period' => 'oppure setta le date', +// TODO: translate the following. +// 'form.reports.note_containing' => 'Note containing', 'form.reports.show_fields' => 'Mostra i campi', // TODO: translate the following. // 'form.reports.time_fields' => 'Time fields', // 'form.reports.user_fields' => 'User fields', +// 'form.reports.project_fields' => 'Project fields', 'form.reports.group_by' => 'Raggruppa per', 'form.reports.group_by_no' => '--- non raggruppare ---', 'form.reports.group_by_date' => 'data', @@ -495,6 +483,8 @@ // Report form. See example at https://timetracker.anuko.com/report.php // (after generating a report at https://timetracker.anuko.com/reports.php). 'form.report.export' => 'Esporta', +// TODO: translate the following. +// 'form.report.per_hour' => 'Per hour', 'form.report.assign_to_invoice' => 'Assegna alla fattura', // TODO: translate the following. // 'form.report.assign_to_timesheet' => 'Assign to timesheet', @@ -509,8 +499,8 @@ // 'form.templates.active_templates' => 'Active Templates', // 'form.templates.inactive_templates' => 'Inactive Templates', -// Invoice form. See example at https://timetracker.anuko.com/invoice.php -// (you can get to this form after generating a report). +// Invoice form. See example at https://timetracker.anuko.com/invoice_view.php +// (you can get to this form after generating an invoice). 'form.invoice.number' => 'Numero fattura', 'form.invoice.person' => 'Persona', @@ -532,6 +522,8 @@ 'form.tasks.inactive_tasks' => 'Compiti inattivi', // Users form. See example at https://timetracker.anuko.com/users.php +// TODO: translate the following. +// 'form.users.uncompleted_entry_today' => 'User has an uncompleted time entry today', 'form.users.uncompleted_entry' => 'Questo utente ha un record temporale incompleto', 'form.users.role' => 'Ruolo', 'form.users.manager' => 'Manager', @@ -590,14 +582,20 @@ 'form.group_edit.type_duration' => 'durata', // TODO: translate the following. // 'form.group_edit.punch_mode' => 'Punch mode', +// 'form.group_edit.one_uncompleted' => 'One uncompleted', // 'form.group_edit.allow_overlap' => 'Allow overlap', // 'form.group_edit.future_entries' => 'Future entries', 'form.group_edit.uncompleted_indicators' => 'Indicatori incompleti', // TODO: translate the following. // 'form.group_edit.confirm_save' => 'Confirm saving', -// 'form.group_edit.allow_ip' => 'Allow IP', // 'form.group_edit.advanced_settings' => 'Advanced settings', +// Advanced Group Settings form. See example at https://timetracker.anuko.com/group_advanced_edit.php. +// TODO: Translate the following. +// 'form.group_advanced_edit.allow_ip' => 'Allow IP', +// 'form.group_advanced_edit.password_complexity' => 'Password complexity', +// 'form.group_advanced_edit.2fa' => 'Two factor authentication', + // Deleting Group form. See example at https://timetracker.anuko.com/delete_group.php // TODO: translate the following. // 'form.group_delete.hint' => 'Are you sure you want to delete the entire group?', @@ -662,21 +660,8 @@ // TODO: translate the following. // 'form.display_options.note_on_separate_row' => 'Note on separate row', // 'form.display_options.not_complete_days' => 'Not complete days', +// 'form.display_options.inactive_projects' => 'Inactive projects', +// 'form.display_options.cost_per_hour' => 'Cost per hour', // 'form.display_options.custom_css' => 'Custom CSS', - -// Work plugin strings. See example at https://timetracker.anuko.com/work.php -// TODO: translate the following. -// 'work.error.work_not_available' => 'Work item is not available.', -// 'work.error.offer_not_available' => 'Offer is not available.', -// 'work.type.one_time' => 'one time', // Work type is "one time job" for well defined work ("do exactly this"). -// 'work.type.ongoing' => 'ongoing', // Work type is "ongoing" for complex jobs (billed by the hour, multiple contractors, etc.) -// 'work.label.own_work' => 'Own work', -// 'work.label.own_offers' => 'Own offers', -// 'work.label.offers' => 'Offers', -// 'work.button.send_message' => 'Send message', -// 'work.button.make_offer' => 'Make offer', -// 'work.button.accept' => 'Accept', -// 'work.button.decline' => 'Decline', -// 'work.title.send_message' => 'Sending Message', -// 'work.msg.message_sent' => 'Message sent.', +// 'form.display_options.custom_translation' => 'Custom translation', ); diff --git a/WEB-INF/resources/ja.lang.php b/WEB-INF/resources/ja.lang.php index 0ba848ba0..0de6e88ea 100644 --- a/WEB-INF/resources/ja.lang.php +++ b/WEB-INF/resources/ja.lang.php @@ -2,7 +2,8 @@ /* Copyright (c) Anuko International Ltd. https://www.anuko.com License: See license.txt */ -// Note: escape apostrophes with THREE backslashes, like here: choisir l\\\'option. +// Note: escape apostrophes with THREE backslashes, like here: 'choisir l\\\'option'. +// Alternatively: use one backslash and surround by double-quotes: "choisir l\'option". // Other characters (such as double-quotes in http links, etc.) do not have to be escaped. $i18n_language = 'Japanese (日本語)'; @@ -27,6 +28,7 @@ // 'menu.plugins' => 'Plugins', 'menu.time' => '時間', // TODO: translate the following. +// 'menu.puncher' => 'Punch', // 'menu.week' => 'Week', // 'menu.expenses' => 'Expenses', 'menu.reports' => 'レポート', @@ -39,7 +41,6 @@ 'menu.users' => 'ユーザー', // TODO: translate the following. // 'menu.groups' => 'Groups', -// 'menu.subgroups' => 'Subgroups', // 'menu.export' => 'Export', 'menu.clients' => 'クライアント', 'menu.options' => 'オプション', @@ -73,6 +74,9 @@ // 'error.report' => 'Select report.', // 'error.record' => 'Select record.', 'error.auth' => '不正確なログインあるいはパスワードが不正確です。', +// TODO: translate the following. +// 'error.2fa_code' => 'Invalid 2FA code.', +// 'error.weak_password' => 'Weak password.', 'error.user_exists' => 'このログインと関連されたユーザーは既に存在します。', // TODO: translate the following. // 'error.object_exists' => 'Object with this name already exists.', @@ -101,7 +105,6 @@ // 'error.user_count' => 'Limit on user count.', // 'error.expired' => 'Expiration date reached.', // 'error.file_storage' => 'File storage server error.', // See comment in English file. -// 'error.remote_work' => 'Remote work server error.', // See comment in English file. // Warning messages. // TODO: translate the following. @@ -133,9 +136,11 @@ 'button.import' => 'チームのインポート', // TODO: replace "team" with "group". // TODO: translate the following. // 'button.close' => 'Close', +// 'button.start' => 'Start', // 'button.stop' => 'Stop', // 'button.approve' => 'Approve', // 'button.disapprove' => 'Disapprove', +// 'button.sync' => 'Sync', // Used in Android app. The meaning is to synchronize offline time records with server. // Labels for controls on forms. Labels in this section are used on multiple forms. // TODO: translate the following. @@ -200,6 +205,7 @@ // TODO: translate the following. // 'label.day_view' => 'Day view', // 'label.week_view' => 'Week view', +// 'label.puncher' => 'Puncher', 'label.id' => '識別子', 'label.language' => '言語', // TODO: translate the following. @@ -221,11 +227,6 @@ 'label.ldap_hint' => '下記のフィールドにあなたのWindowsのログインIDパスワードを入力してください。', 'label.required_fields' => '* 必須のフィールド', 'label.on_behalf' => 'を代表して', -// TODO: translate all 3 roles properly, see https://www.anuko.com/time_tracker/user_guide/user_accounts.htm -// This may require different terms for role_manager and role_comanager. -'label.role_manager' => '(管理者)', -'label.role_comanager' => '(共同管理者)', -// 'label.role_admin' => '(administrator)', // TODO: translate the following. // 'label.page' => 'Page', // 'label.condition' => 'Condition', @@ -250,9 +251,9 @@ // 'label.paid_status' => 'Paid status', // 'label.paid' => 'Paid', // 'label.mark_paid' => 'Mark paid', -// 'label.week_menu' => 'Week menu', // 'label.week_note' => 'Week note', // 'label.week_list' => 'Week list', +// 'label.weekends' => 'Weekends', // 'label.work_units' => 'Work units', // 'label.work_units_short' => 'Units', 'label.totals_only' => '全体だけ', @@ -269,20 +270,8 @@ // 'label.attachments' => 'Attachments', // 'label.files' => 'Files', // 'label.file' => 'File', -// 'label.image' => 'Image', -// 'label.download' => 'Download', // 'label.active_users' => 'Active Users', // 'label.inactive_users' => 'Inactive Users', -// 'label.details' => 'Details', -// 'label.budget' => 'Budget', -// 'label.work' => 'Work', // Table column header for work items, see https://www.anuko.com/time_tracker/what_is/work_plugin.htm -// 'label.offer' => 'Offer', // Table column header for offers, see https://www.anuko.com/time_tracker/what_is/work_plugin.htm -// 'label.contractor' => 'Contractor', // Table column header for offers (contractor is someone who offers to do work). - // Technically, it is either an org name or a combination of org and group names - // because both work items and offers are owned by Time Tracker groups of users. -// 'label.how_to_pay' => 'How to pay', // Label for the "How to pay" field on offers, which allows contractors to specify - // how to pay them, for example: paypal email, check by mail to a specific address, etc. -// 'label.moderator_comment' => 'Moderator comment', // Label for "Moderator comment" field that explains something. // Entity names. We use lower case (in English) because they are used in dropdowns, too. // They are used to associate a custom field with an entity type. @@ -296,9 +285,10 @@ // 'title.error' => 'Error', // 'title.success' => 'Success', 'title.login' => 'ログイン', +// TODO: translate the follolwing. +// 'title.2fa' => 'Two Factor Authentication', 'title.groups' => 'チーム', // TODO: change "teams" to "groups". // TODO: translate the following. -// 'title.subgroups' => 'Subgroups', // 'title.add_group' => 'Adding Group', // 'title.edit_group' => 'Editing Group', 'title.delete_group' => 'チームの削除', // TODO: change "team" to "group". @@ -310,6 +300,7 @@ 'title.delete_time_record' => '時間レコードの削除', // TODO: Translate the following. // 'title.time_files' => 'Time Record Files', +// 'title.puncher' => 'Puncher', // 'title.expenses' => 'Expenses', // 'title.edit_expense' => 'Editing Expense Item', // 'title.delete_expense' => 'Deleting Expense Item', @@ -395,22 +386,6 @@ // 'title.edit_file' => 'Editing File', // 'title.delete_file' => 'Deleting File', // 'title.download_file' => 'Downloading File', -// 'title.work' => 'Work', -// 'title.add_work' => 'Adding Work', -// 'title.edit_work' => 'Editing Work', -// 'title.delete_work' => 'Deleting Work', -// 'title.active_work' => 'Active Work', // Active work items this group outsources to other groups. -// 'title.available_work' => 'Available Work', // Available work items from other organizations. -// 'title.inactive_work' => 'Inactive Work', // Inactive work items this group was outsourcing to other groups. -// 'title.pending_work' => 'Pending Work', // Work items pending moderator approval. -// 'title.offer' => 'Offer', -// 'title.add_offer' => 'Adding Offer', -// 'title.edit_offer' => 'Editing Offer', -// 'title.delete_offer' => 'Deleting Offer', -// 'title.active_offers' => 'Active Offers', // Active offers this group makes available to other groups. -// 'title.available_offers' => 'Available Offers', // Available offers from other organizations. -// 'title.inactive_offers' => 'Inactive Offers', // Inactive offers for group. -// 'title.pending_offers' => 'Pending Offers', // Offers pending moderator approval. // Section for common strings inside combo boxes on forms. Strings shared between forms shall be placed here. // Strings that are used in a single form must go to the specific form section. @@ -440,7 +415,6 @@ // 'dropdown.status_inactive' => 'inactive', // 'dropdown.delete' => 'delete', // 'dropdown.do_not_delete' => 'do not delete', -// 'dropdown.pending_approval' => 'pending approval', // 'dropdown.approved' => 'approved', // 'dropdown.not_approved' => 'not approved', // 'dropdown.paid' => 'paid', @@ -453,6 +427,16 @@ // TODO: translate the following. // 'form.login.about' => 'Anuko Time Tracker is an open source time tracking system.', +// Email subject and body for two-factor authentication. +// TODO: translate the following. +// 'email.2fa_code.subject' => 'Anuko Time Tracker two-factor authentication code', +// 'email.2fa_code.body' => "Dear User,\n\nYour two-factor authentication code is:\n\n%s\n\n", + +// Two-factor authentication form. See example at https://timetracker.anuko.com/2fa.php. +// TODO: translate the following. +// 'form.2fa.hint' => 'Check your email for 2FA code and enter it here.', +// 'form.2fa.2fa_code' => '2FA code', + // Resetting Password form. See example at https://timetracker.anuko.com/password_reset.php. 'form.reset_password.message' => '送信したパスワードの初期化の要求。', // TODO: add "by email" to match the English string. 'form.reset_password.email_subject' => 'Anuko Time Trackerのパスワードの初期化の要求', @@ -499,10 +483,13 @@ // 'form.reports.include_pending' => 'pending', 'form.reports.select_period' => '時間期間の選択', 'form.reports.set_period' => 'あるいは日付を設定', +// TODO: translate the following. +// 'form.reports.note_containing' => 'Note containing', 'form.reports.show_fields' => 'フィールドの表示', // TODO: translate the following. // 'form.reports.time_fields' => 'Time fields', // 'form.reports.user_fields' => 'User fields', +// 'form.reports.project_fields' => 'Project fields', 'form.reports.group_by' => '次のようにグループ化', 'form.reports.group_by_no' => '--- グループの機能がありません ---', 'form.reports.group_by_date' => '日付', @@ -517,6 +504,7 @@ // (after generating a report at https://timetracker.anuko.com/reports.php). 'form.report.export' => '輸出する', // TODO: is this correct? // TODO: translate the following. +// 'form.report.per_hour' => 'Per hour', // 'form.report.assign_to_invoice' => 'Assign to invoice', // 'form.report.assign_to_timesheet' => 'Assign to timesheet', @@ -530,8 +518,8 @@ // 'form.templates.active_templates' => 'Active Templates', // 'form.templates.inactive_templates' => 'Inactive Templates', -// Invoice form. See example at https://timetracker.anuko.com/invoice.php -// (you can get to this form after generating a report). +// Invoice form. See example at https://timetracker.anuko.com/invoice_view.php +// (you can get to this form after generating an invoice). 'form.invoice.number' => '送り状の番号', 'form.invoice.person' => '個人', @@ -557,6 +545,7 @@ // Users form. See example at https://timetracker.anuko.com/users.php // TODO: translate the following. +// 'form.users.uncompleted_entry_today' => 'User has an uncompleted time entry today', // 'form.users.uncompleted_entry' => 'User has an uncompleted time entry', 'form.users.role' => '役割', // TODO: is this correct? 'form.users.manager' => '管理者', @@ -618,13 +607,19 @@ // 'form.group_edit.type_start_finish' => 'start and finish', // 'form.group_edit.type_duration' => 'duration', // 'form.group_edit.punch_mode' => 'Punch mode', +// 'form.group_edit.one_uncompleted' => 'One uncompleted', // 'form.group_edit.allow_overlap' => 'Allow overlap', // 'form.group_edit.future_entries' => 'Future entries', // 'form.group_edit.uncompleted_indicators' => 'Uncompleted indicators', // 'form.group_edit.confirm_save' => 'Confirm saving', -// 'form.group_edit.allow_ip' => 'Allow IP', // 'form.group_edit.advanced_settings' => 'Advanced settings', +// Advanced Group Settings form. See example at https://timetracker.anuko.com/group_advanced_edit.php. +// TODO: Translate the following. +// 'form.group_advanced_edit.allow_ip' => 'Allow IP', +// 'form.group_advanced_edit.password_complexity' => 'Password complexity', +// 'form.group_advanced_edit.2fa' => 'Two factor authentication', + // Deleting Group form. See example at https://timetracker.anuko.com/delete_group.php // TODO: translate the following. // 'form.group_delete.hint' => 'Are you sure you want to delete the entire group?', @@ -691,21 +686,8 @@ // TODO: translate the following. // 'form.display_options.note_on_separate_row' => 'Note on separate row', // 'form.display_options.not_complete_days' => 'Not complete days', +// 'form.display_options.inactive_projects' => 'Inactive projects', +// 'form.display_options.cost_per_hour' => 'Cost per hour', // 'form.display_options.custom_css' => 'Custom CSS', - -// Work plugin strings. See example at https://timetracker.anuko.com/work.php -// TODO: translate the following. -// 'work.error.work_not_available' => 'Work item is not available.', -// 'work.error.offer_not_available' => 'Offer is not available.', -// 'work.type.one_time' => 'one time', // Work type is "one time job" for well defined work ("do exactly this"). -// 'work.type.ongoing' => 'ongoing', // Work type is "ongoing" for complex jobs (billed by the hour, multiple contractors, etc.) -// 'work.label.own_work' => 'Own work', -// 'work.label.own_offers' => 'Own offers', -// 'work.label.offers' => 'Offers', -// 'work.button.send_message' => 'Send message', -// 'work.button.make_offer' => 'Make offer', -// 'work.button.accept' => 'Accept', -// 'work.button.decline' => 'Decline', -// 'work.title.send_message' => 'Sending Message', -// 'work.msg.message_sent' => 'Message sent.', +// 'form.display_options.custom_translation' => 'Custom translation', ); diff --git a/WEB-INF/resources/ko.lang.php b/WEB-INF/resources/ko.lang.php index 975e42169..fcbe14d24 100644 --- a/WEB-INF/resources/ko.lang.php +++ b/WEB-INF/resources/ko.lang.php @@ -2,7 +2,8 @@ /* Copyright (c) Anuko International Ltd. https://www.anuko.com License: See license.txt */ -// Note: escape apostrophes with THREE backslashes, like here: choisir l\\\'option. +// Note: escape apostrophes with THREE backslashes, like here: 'choisir l\\\'option'. +// Alternatively: use one backslash and surround by double-quotes: "choisir l\'option". // Other characters (such as double-quotes in http links, etc.) do not have to be escaped. $i18n_language = 'Korean (한국어)'; @@ -27,6 +28,7 @@ // 'menu.plugins' => 'Plugins', 'menu.time' => '시간', // TODO: translate the following. +// 'menu.puncher' => 'Punch', // 'menu.week' => 'Week', // 'menu.expenses' => 'Expenses', 'menu.reports' => '보고서', @@ -39,7 +41,6 @@ 'menu.users' => '사용자', // TODO: translate the following. // 'menu.groups' => 'Groups', -// 'menu.subgroups' => 'Subgroups', // 'menu.export' => 'Export', 'menu.clients' => '클라이언트', 'menu.options' => '옵션', @@ -73,6 +74,9 @@ // 'error.report' => 'Select report.', // 'error.record' => 'Select record.', 'error.auth' => '부정확한 로그인 혹은 암호가 틀립니다.', +// TODO: translate the following. +// 'error.2fa_code' => 'Invalid 2FA code.', +// 'error.weak_password' => 'Weak password.', 'error.user_exists' => '본 로그인과 연계된 사용자가 이미 있습니다.', // TODO: translate the following. // 'error.object_exists' => 'Object with this name already exists.', @@ -100,7 +104,6 @@ // 'error.user_count' => 'Limit on user count.', // 'error.expired' => 'Expiration date reached.', // 'error.file_storage' => 'File storage server error.', // See comment in English file. -// 'error.remote_work' => 'Remote work server error.', // See comment in English file. // Warning messages. // TODO: translate the following. @@ -132,9 +135,11 @@ 'button.import' => '팀 임포트', // TODO: replace "team" with "group". // TODO: translate the following. // 'button.close' => 'Close', +// 'button.start' => 'Start', // 'button.stop' => 'Stop', // 'button.approve' => 'Approve', // 'button.disapprove' => 'Disapprove', +// 'button.sync' => 'Sync', // Used in Android app. The meaning is to synchronize offline time records with server. // Labels for controls on forms. Labels in this section are used on multiple forms. // TODO: translate the following. @@ -199,6 +204,7 @@ // TODO: translate the following. // 'label.day_view' => 'Day view', // 'label.week_view' => 'Week view', +// 'label.puncher' => 'Puncher', 'label.id' => '식별자', 'label.language' => '언어', // TODO: translate the following. @@ -220,11 +226,6 @@ 'label.ldap_hint' => '아래의 필드들에서 Windows 로그인암호 를 입력하십시오.', 'label.required_fields' => '* 필수 필드', 'label.on_behalf' => '을 대표하여', -// TODO: translate all 3 roles properly, see https://www.anuko.com/time_tracker/user_guide/user_accounts.htm -// This may require different terms for role_manager and role_comanager. -'label.role_manager' => '(관리자)', -'label.role_comanager' => '(공동관리자)', -// 'label.role_admin' => '(administrator)', // TODO: translate the following. // 'label.page' => 'Page', // 'label.condition' => 'Condition', @@ -250,9 +251,9 @@ // 'label.paid_status' => 'Paid status', // 'label.paid' => 'Paid', // 'label.mark_paid' => 'Mark paid', -// 'label.week_menu' => 'Week menu', // 'label.week_note' => 'Week note', // 'label.week_list' => 'Week list', +// 'label.weekends' => 'Weekends', // 'label.work_units' => 'Work units', // 'label.work_units_short' => 'Units', 'label.totals_only' => '오직 전체만', @@ -269,20 +270,8 @@ // 'label.attachments' => 'Attachments', // 'label.files' => 'Files', // 'label.file' => 'File', -// 'label.image' => 'Image', -// 'label.download' => 'Download', // 'label.active_users' => 'Active Users', // 'label.inactive_users' => 'Inactive Users', -// 'label.details' => 'Details', -// 'label.budget' => 'Budget', -// 'label.work' => 'Work', // Table column header for work items, see https://www.anuko.com/time_tracker/what_is/work_plugin.htm -// 'label.offer' => 'Offer', // Table column header for offers, see https://www.anuko.com/time_tracker/what_is/work_plugin.htm -// 'label.contractor' => 'Contractor', // Table column header for offers (contractor is someone who offers to do work). - // Technically, it is either an org name or a combination of org and group names - // because both work items and offers are owned by Time Tracker groups of users. -// 'label.how_to_pay' => 'How to pay', // Label for the "How to pay" field on offers, which allows contractors to specify - // how to pay them, for example: paypal email, check by mail to a specific address, etc. -// 'label.moderator_comment' => 'Moderator comment', // Label for "Moderator comment" field that explains something. // Entity names. We use lower case (in English) because they are used in dropdowns, too. // They are used to associate a custom field with an entity type. @@ -296,9 +285,10 @@ // 'title.error' => 'Error', // 'title.success' => 'Success', 'title.login' => '로그인', +// TODO: translate the follolwing. +// 'title.2fa' => 'Two Factor Authentication', 'title.groups' => '팀', // TODO: change "teams" to "groups". // TODO: translate the following. -// 'title.subgroups' => 'Subgroups', // 'title.add_group' => 'Adding Group', // 'title.edit_group' => 'Editing Group', 'title.delete_group' => '팀 삭제', // TODO: change "team" to "group". @@ -310,6 +300,7 @@ 'title.delete_time_record' => '시간기록을 삭제하기', // TODO: Translate the following. // 'title.time_files' => 'Time Record Files', +// 'title.puncher' => 'Puncher', // 'title.expenses' => 'Expenses', // 'title.edit_expense' => 'Editing Expense Item', // 'title.delete_expense' => 'Deleting Expense Item', @@ -396,22 +387,6 @@ // 'title.edit_file' => 'Editing File', // 'title.delete_file' => 'Deleting File', // 'title.download_file' => 'Downloading File', -// 'title.work' => 'Work', -// 'title.add_work' => 'Adding Work', -// 'title.edit_work' => 'Editing Work', -// 'title.delete_work' => 'Deleting Work', -// 'title.active_work' => 'Active Work', // Active work items this group outsources to other groups. -// 'title.available_work' => 'Available Work', // Available work items from other organizations. -// 'title.inactive_work' => 'Inactive Work', // Inactive work items this group was outsourcing to other groups. -// 'title.pending_work' => 'Pending Work', // Work items pending moderator approval. -// 'title.offer' => 'Offer', -// 'title.add_offer' => 'Adding Offer', -// 'title.edit_offer' => 'Editing Offer', -// 'title.delete_offer' => 'Deleting Offer', -// 'title.active_offers' => 'Active Offers', // Active offers this group makes available to other groups. -// 'title.available_offers' => 'Available Offers', // Available offers from other organizations. -// 'title.inactive_offers' => 'Inactive Offers', // Inactive offers for group. -// 'title.pending_offers' => 'Pending Offers', // Offers pending moderator approval. // Section for common strings inside combo boxes on forms. Strings shared between forms shall be placed here. // Strings that are used in a single form must go to the specific form section. @@ -444,7 +419,6 @@ // 'dropdown.status_inactive' => 'inactive', // 'dropdown.delete' => 'delete', // 'dropdown.do_not_delete' => 'do not delete', -// 'dropdown.pending_approval' => 'pending approval', // 'dropdown.approved' => 'approved', // 'dropdown.not_approved' => 'not approved', // 'dropdown.paid' => 'paid', @@ -457,6 +431,16 @@ // TODO: translate the following. // 'form.login.about' => 'Anuko Time Tracker is an open source time tracking system.', +// Email subject and body for two-factor authentication. +// TODO: translate the following. +// 'email.2fa_code.subject' => 'Anuko Time Tracker two-factor authentication code', +// 'email.2fa_code.body' => "Dear User,\n\nYour two-factor authentication code is:\n\n%s\n\n", + +// Two-factor authentication form. See example at https://timetracker.anuko.com/2fa.php. +// TODO: translate the following. +// 'form.2fa.hint' => 'Check your email for 2FA code and enter it here.', +// 'form.2fa.2fa_code' => '2FA code', + // Resetting Password form. See example at https://timetracker.anuko.com/password_reset.php. 'form.reset_password.message' => '송신한 암호 재설정 요청.', // TODO: add "by email" to match the English string. 'form.reset_password.email_subject' => 'Anuko Time Tracker 암호 재설정 요청', @@ -497,10 +481,13 @@ // 'form.reports.include_pending' => 'pending', 'form.reports.select_period' => '시간 기간을 선택', 'form.reports.set_period' => '혹은 날짜를 설정', +// TODO: translate the following. +// 'form.reports.note_containing' => 'Note containing', 'form.reports.show_fields' => '필드들을 보기', // TODO: translate the following. // 'form.reports.time_fields' => 'Time fields', // 'form.reports.user_fields' => 'User fields', +// 'form.reports.project_fields' => 'Project fields', 'form.reports.group_by' => '다음것에 의한 그룹화', 'form.reports.group_by_no' => '--- 그룹화되지 않음 ---', 'form.reports.group_by_date' => '날짜', @@ -515,6 +502,7 @@ // (after generating a report at https://timetracker.anuko.com/reports.php). 'form.report.export' => '익스포트', // TODO: is this correct? // TODO: translate the following. +// 'form.report.per_hour' => 'Per hour', // 'form.report.assign_to_invoice' => 'Assign to invoice', // 'form.report.assign_to_timesheet' => 'Assign to timesheet', @@ -528,8 +516,8 @@ // 'form.templates.active_templates' => 'Active Templates', // 'form.templates.inactive_templates' => 'Inactive Templates', -// Invoice form. See example at https://timetracker.anuko.com/invoice.php -// (you can get to this form after generating a report). +// Invoice form. See example at https://timetracker.anuko.com/invoice_view.php +// (you can get to this form after generating an invoice). 'form.invoice.number' => '송장 번호', 'form.invoice.person' => '개인', @@ -555,6 +543,7 @@ // Users form. See example at https://timetracker.anuko.com/users.php // TODO: translate the following. +// 'form.users.uncompleted_entry_today' => 'User has an uncompleted time entry today', // 'form.users.uncompleted_entry' => 'User has an uncompleted time entry', 'form.users.role' => '직위', // TODO: is this correct? The term "role" describes user function, as in "team manager role". 'form.users.manager' => '관리자', @@ -616,13 +605,19 @@ // 'form.group_edit.type_start_finish' => 'start and finish', // 'form.group_edit.type_duration' => 'duration', // 'form.group_edit.punch_mode' => 'Punch mode', +// 'form.group_edit.one_uncompleted' => 'One uncompleted', // 'form.group_edit.allow_overlap' => 'Allow overlap', // 'form.group_edit.future_entries' => 'Future entries', // 'form.group_edit.uncompleted_indicators' => 'Uncompleted indicators', // 'form.group_edit.confirm_save' => 'Confirm saving', -// 'form.group_edit.allow_ip' => 'Allow IP', // 'form.group_edit.advanced_settings' => 'Advanced settings', +// Advanced Group Settings form. See example at https://timetracker.anuko.com/group_advanced_edit.php. +// TODO: Translate the following. +// 'form.group_advanced_edit.allow_ip' => 'Allow IP', +// 'form.group_advanced_edit.password_complexity' => 'Password complexity', +// 'form.group_advanced_edit.2fa' => 'Two factor authentication', + // Deleting Group form. See example at https://timetracker.anuko.com/delete_group.php // TODO: translate the following. // 'form.group_delete.hint' => 'Are you sure you want to delete the entire group?', @@ -689,21 +684,8 @@ // TODO: translate the following. // 'form.display_options.note_on_separate_row' => 'Note on separate row', // 'form.display_options.not_complete_days' => 'Not complete days', +// 'form.display_options.inactive_projects' => 'Inactive projects', +// 'form.display_options.cost_per_hour' => 'Cost per hour', // 'form.display_options.custom_css' => 'Custom CSS', - -// Work plugin strings. See example at https://timetracker.anuko.com/work.php -// TODO: translate the following. -// 'work.error.work_not_available' => 'Work item is not available.', -// 'work.error.offer_not_available' => 'Offer is not available.', -// 'work.type.one_time' => 'one time', // Work type is "one time job" for well defined work ("do exactly this"). -// 'work.type.ongoing' => 'ongoing', // Work type is "ongoing" for complex jobs (billed by the hour, multiple contractors, etc.) -// 'work.label.own_work' => 'Own work', -// 'work.label.own_offers' => 'Own offers', -// 'work.label.offers' => 'Offers', -// 'work.button.send_message' => 'Send message', -// 'work.button.make_offer' => 'Make offer', -// 'work.button.accept' => 'Accept', -// 'work.button.decline' => 'Decline', -// 'work.title.send_message' => 'Sending Message', -// 'work.msg.message_sent' => 'Message sent.', +// 'form.display_options.custom_translation' => 'Custom translation', ); diff --git a/WEB-INF/resources/nl.lang.php b/WEB-INF/resources/nl.lang.php index 70750349d..2ca444c0a 100644 --- a/WEB-INF/resources/nl.lang.php +++ b/WEB-INF/resources/nl.lang.php @@ -2,7 +2,8 @@ /* Copyright (c) Anuko International Ltd. https://www.anuko.com License: See license.txt */ -// Note: escape apostrophes with THREE backslashes, like here: choisir l\\\'option. +// Note: escape apostrophes with THREE backslashes, like here: 'choisir l\\\'option'. +// Alternatively: use one backslash and surround by double-quotes: "choisir l\'option". // Other characters (such as double-quotes in http links, etc.) do not have to be escaped. $i18n_language = 'Dutch (Nederlands)'; @@ -23,6 +24,7 @@ 'menu.group' => 'Groep', 'menu.plugins' => 'Plugins', 'menu.time' => 'Tijden', +'menu.puncher' => 'Prikklok', 'menu.week' => 'Week', 'menu.expenses' => 'Kosten', 'menu.reports' => 'Rapporten', @@ -32,7 +34,6 @@ 'menu.tasks' => 'Taken', 'menu.users' => 'Medewerkers', 'menu.groups' => 'Groepen', -'menu.subgroups' => 'Subgroepen', 'menu.export' => 'Exporteren', 'menu.clients' => 'Klanten', 'menu.options' => 'Opties', @@ -59,6 +60,8 @@ 'error.report' => 'Kies rapport.', 'error.record' => 'Kies record.', 'error.auth' => 'Onjuiste inlognaam of wachtwoord.', +'error.2fa_code' => 'Niet geldige 2FA code.', +'error.weak_password' => 'Zwak wachtwoord.', 'error.user_exists' => 'Een gebruiker met deze inlognaam bestaat al.', 'error.object_exists' => 'Een object met deze naam bestaat al.', 'error.invoice_exists' => 'Dit nummer is al eens toegekend aan een factuur.', @@ -81,7 +84,6 @@ 'error.user_count' => 'Limiet op aantal gebruikers.', 'error.expired' => 'Verloop datum is bereikt.', 'error.file_storage' => 'Bestand server probleem.', -'error.remote_work' => 'Werk op afstand server probleem.', // Warning messages. 'warn.sure' => 'Ben je er zeker van?', @@ -107,9 +109,11 @@ 'button.export' => 'Groep exporteren', 'button.import' => 'Groep importeren', 'button.close' => 'Sluiten', +'button.start' => 'Start', 'button.stop' => 'Stop', 'button.approve' => 'Goedkeuren', 'button.disapprove' => 'Afkeuren', +'button.sync' => 'Sychroniseer', // Labels for controls on forms. Labels in this section are used on multiple forms. 'label.menu' => 'Menu', @@ -164,6 +168,7 @@ 'label.select_none' => 'Selecteer niets', 'label.day_view' => 'Dag overzicht', 'label.week_view' => 'Week overzicht', +'label.puncher' => 'Prikklok', 'label.id' => 'ID', 'label.language' => 'Taal', 'label.decimal_mark' => 'Decimaal teken', @@ -182,9 +187,6 @@ 'label.ldap_hint' => 'Type uw Windows login en wachtwoord in de onderstaande velden.', 'label.required_fields' => '* - verplichte velden', 'label.on_behalf' => 'namens', -'label.role_manager' => '(manager)', -'label.role_comanager' => '(co-manager)', -'label.role_admin' => '(beheerder)', 'label.page' => 'Pagina', 'label.condition' => 'Voorwaarde', 'label.yes' => 'ja', @@ -206,9 +208,9 @@ 'label.paid_status' => 'Status van betaling', 'label.paid' => 'Betaald', 'label.mark_paid' => 'Markeer als betaald', -'label.week_menu' => 'Week menu', 'label.week_note' => 'Week aantekening', 'label.week_list' => 'Week overzicht', +'label.weekends' => 'Weekeinden', 'label.work_units' => 'Werk eenheid', 'label.work_units_short' => 'Eenheid', 'label.totals_only' => 'Alleen totalen', @@ -224,17 +226,8 @@ 'label.attachments' => 'Bijlagen', 'label.files' => 'Bestanden', 'label.file' => 'Bestand', -'label.image' => 'Afbeelding', -'label.download' => 'Download', 'label.active_users' => 'Actieve medewerkers', 'label.inactive_users' => 'Inactieve medewerkers', -'label.details' => 'Details', -'label.budget' => 'Budget', -'label.work' => 'Werk', -'label.offer' => 'Aanbod', -'label.contractor' => 'Aanbieder', -'label.how_to_pay' => 'Betaalwijze', -'label.moderator_comment' => 'Commentaar', // Entity names. 'entity.time' => 'tijd', @@ -245,8 +238,8 @@ 'title.error' => 'Fout', 'title.success' => 'Succes', 'title.login' => 'Aanmelden', +'title.2fa' => 'Two Factor Authentication', 'title.groups' => 'Groepen', -'title.subgroups' => 'Subgroepen', 'title.add_group' => 'Groep toevoegen', 'title.edit_group' => 'Groep bewerken', 'title.delete_group' => 'Groep aan het verwijderen', @@ -256,6 +249,7 @@ 'title.edit_time_record' => 'Wijzigen tijdrecord', 'title.delete_time_record' => 'Verwijder tijdrecord', 'title.time_files' => 'Tijden bestanden', +'title.puncher' => 'Prikklok', 'title.expenses' => 'Kosten', 'title.edit_expense' => 'Bewerk kosten artikel', 'title.delete_expense' => 'Verwijder kosten artikel', @@ -331,22 +325,6 @@ 'title.edit_file' => 'Bestand bewerken', 'title.delete_file' => 'Bestand verwijderen', 'title.download_file' => ' Bestand downloaden', -'title.work' => 'Werk', -'title.add_work' => 'Werk toevoegen', -'title.edit_work' => 'Werk bewerken', -'title.delete_work' => 'Werk verwijderen', -'title.active_work' => 'Actief werk', -'title.available_work' => 'Beschikbaar werk', -'title.inactive_work' => 'Inactief werk', -'title.pending_work' => 'Werk in afwachting van goedkeuring', -'title.offer' => 'Aanbod', -'title.add_offer' => 'Aanbieding toevoegen', -'title.edit_offer' => 'Aanbieding bewerken', -'title.delete_offer' => 'Aanbieding verwijderen', -'title.active_offers' => 'Actieve aanbiedingen', -'title.available_offers' => 'Beschikbare aanbiedingen', -'title.inactive_offers' => 'Inactieve aanbiedingen', -'title.pending_offers' => 'Aangeboden werk in afwachting van goedkeuring', // Section for common strings inside combo boxes on forms. Strings shared between forms shall be placed here. // Strings that are used in a single form must go to the specific form section. @@ -375,7 +353,6 @@ 'dropdown.status_inactive' => 'inactief', 'dropdown.delete' => 'verwijderen', 'dropdown.do_not_delete' => 'niet verwijderen', -'dropdown.pending_approval' => 'in afwachting van goedkeuring', 'dropdown.approved' => 'goedgekeurd', 'dropdown.not_approved' => 'afgekeurd', 'dropdown.paid' => 'betaald', @@ -392,6 +369,14 @@ 'form.login.forgot_password' => 'Wachtwoord vergeten?', 'form.login.about' => 'Anuko Time Tracker is een open source tijdregistratiesysteem.', +// Email subject and body for two-factor authentication. +'email.2fa_code.subject' => 'Anuko Time Tracker two-factor authentication code', +'email.2fa_code.body' => "Beste gebruiker,\n\nJouw two-factor authentication code is:\n\n%s\n\n", + +// Two-factor authentication form. See example at https://timetracker.anuko.com/2fa.php. +'form.2fa.hint' => 'Je kunt je 2FA code vinden in een email die aan je is verstuurd en hier invullen.', +'form.2fa.2fa_code' => '2FA code', + // Resetting Password form. See example at https://timetracker.anuko.com/password_reset.php. 'form.reset_password.message' => 'Het verzoek om het wachtwoord te herstellen is verzonden per email.', 'form.reset_password.email_subject' => 'Anuko Time Tracker wachtwoord herstel verzoek', @@ -427,9 +412,11 @@ 'form.reports.include_pending' => 'in afwachting', 'form.reports.select_period' => 'Kies periode', 'form.reports.set_period' => 'of stel datums in', +'form.reports.note_containing' => 'Notitie bevat', 'form.reports.show_fields' => 'Toon velden', 'form.reports.time_fields' => 'Tijd velden', 'form.reports.user_fields' => 'Medewerker velden', +'form.reports.project_fields' => 'Project velden', 'form.reports.group_by' => 'Groeperen op', 'form.reports.group_by_no' => '--- niet groeperen ---', 'form.reports.group_by_date' => 'datum', @@ -441,6 +428,7 @@ // Report form. See example at https://timetracker.anuko.com/report.php // (after generating a report at https://timetracker.anuko.com/reports.php). 'form.report.export' => 'Exporteer', +'form.report.per_hour' => 'Per uur', 'form.report.assign_to_invoice' => 'Voeg toe aan factuur', 'form.report.assign_to_timesheet' => 'Wijs toe aan tijdenoverzicht', @@ -452,8 +440,8 @@ 'form.templates.active_templates' => 'Actieve sjablonen', 'form.templates.inactive_templates' => 'Inactieve sjablonen', -// Invoice form. See example at https://timetracker.anuko.com/invoice.php -// (you can get to this form after generating a report). +// Invoice form. See example at https://timetracker.anuko.com/invoice_view.php +// (you can get to this form after generating an invoice). 'form.invoice.number' => 'Factuur nummer', 'form.invoice.person' => 'Medewerker', @@ -475,6 +463,7 @@ 'form.tasks.inactive_tasks' => 'Inactieve taken', // Users form. See example at https://timetracker.anuko.com/users.php +'form.users.uncompleted_entry_today' => 'Gebruiker heeft vandaag een tijd ingevoerd die niet compleet is', 'form.users.uncompleted_entry' => 'Gebruiker heeft tijd ingevoerd die niet compleet is', 'form.users.role' => 'Rol', 'form.users.manager' => 'Manager', @@ -529,13 +518,18 @@ 'form.group_edit.type_start_finish' => 'begin en einde', 'form.group_edit.type_duration' => 'duur', 'form.group_edit.punch_mode' => 'Start/stop modus', +'form.group_edit.one_uncompleted' => 'Één niet-volledige registratie toestaan', 'form.group_edit.allow_overlap' => 'Sta overlapping van tijden toe', 'form.group_edit.future_entries' => 'Toevoegingen toestaan in de toekomst', 'form.group_edit.uncompleted_indicators' => 'Onvolledige indicatoren', 'form.group_edit.confirm_save' => 'Bevestigen dat je wilt opslaan', -'form.group_edit.allow_ip' => 'Toegestane IP adressen', 'form.group_edit.advanced_settings' => 'Geavanceerde instellingen', +// Advanced Group Settings form. See example at https://timetracker.anuko.com/group_advanced_edit.php. +'form.group_advanced_edit.allow_ip' => 'Toegestane IP adressen', +'form.group_advanced_edit.password_complexity' => 'Wachtwoord complexiteit', +'form.group_advanced_edit.2fa' => 'Two Factor Authentication (2FA)', + // Deleting Group form. See example at https://timetracker.anuko.com/delete_group.php 'form.group_delete.hint' => 'Bent u er zeker van dat u de hele groep wilt verwijderen?', @@ -594,20 +588,8 @@ // Display Options form. See example at https://timetracker.anuko.com/display_options.php. 'form.display_options.note_on_separate_row' => 'Notitie in aparte kolom', // Translator (Henk) comment: "kolom is the right word in Dutch." 'form.display_options.not_complete_days' => 'Niet complete dagen', +'form.display_options.inactive_projects' => 'Niet actieve projecten', +'form.display_options.cost_per_hour' => 'Kosten per uur', 'form.display_options.custom_css' => 'Aangepaste CSS', - -// Work plugin strings. See example at https://timetracker.anuko.com/work.php -'work.error.work_not_available' => 'Werk onderdeel is niet beschikbaar.', -'work.error.offer_not_available' => 'Aanbod is niet beschikbaar.', -'work.type.one_time' => 'eenmalig', -'work.type.ongoing' => 'doorlopend', -'work.label.own_work' => 'Eigen werk', -'work.label.own_offers' => 'Eigen aanbod', -'work.label.offers' => 'Aanbiedingen', -'work.button.send_message' => 'Verstuur bericht', -'work.button.make_offer' => 'maak aanbieding', -'work.button.accept' => 'Accepteer', -'work.button.decline' => 'Weiger', -'work.title.send_message' => 'Bericht wordt verstuurd', -'work.msg.message_sent' => 'Bericht is verstuurd.', +'form.display_options.custom_translation' => 'Maatwerk vertaling', ); diff --git a/WEB-INF/resources/no.lang.php b/WEB-INF/resources/no.lang.php index cfaf305b5..720f71f34 100644 --- a/WEB-INF/resources/no.lang.php +++ b/WEB-INF/resources/no.lang.php @@ -2,7 +2,8 @@ /* Copyright (c) Anuko International Ltd. https://www.anuko.com License: See license.txt */ -// Note: escape apostrophes with THREE backslashes, like here: choisir l\\\'option. +// Note: escape apostrophes with THREE backslashes, like here: 'choisir l\\\'option'. +// Alternatively: use one backslash and surround by double-quotes: "choisir l\'option". // Other characters (such as double-quotes in http links, etc.) do not have to be escaped. $i18n_language = 'Norwegian (Norsk)'; @@ -28,6 +29,7 @@ // 'menu.plugins' => 'Plugins', 'menu.time' => 'Tid', // TODO: translate the following. +// 'menu.puncher' => 'Punch', // 'menu.week' => 'Week', // 'menu.expenses' => 'Expenses', 'menu.reports' => 'Rapporter', @@ -40,7 +42,6 @@ 'menu.users' => 'Brukere', // TODO: translate the following. // 'menu.groups' => 'Groups', -// 'menu.subgroups' => 'Subgroups', 'menu.export' => 'Eksport', 'menu.clients' => 'Klienter', 'menu.options' => 'Opsjoner', @@ -74,6 +75,9 @@ // 'error.report' => 'Select report.', // 'error.record' => 'Select record.', 'error.auth' => 'Feil brukernavn eller passord.', +// TODO: translate the following. +// 'error.2fa_code' => 'Invalid 2FA code.', +// 'error.weak_password' => 'Weak password.', 'error.user_exists' => 'Bruker med et slikt brukernavn eksisterer allerede.', // TODO: translate the following. // 'error.object_exists' => 'Object with this name already exists.', @@ -101,7 +105,6 @@ // 'error.user_count' => 'Limit on user count.', // 'error.expired' => 'Expiration date reached.', // 'error.file_storage' => 'File storage server error.', // See comment in English file. -// 'error.remote_work' => 'Remote work server error.', // See comment in English file. // Warning messages. // TODO: translate the following. @@ -132,9 +135,11 @@ 'button.import' => 'Importer team', // TODO: replace "team" with "group". 'button.close' => 'Lukk', // TODO: translate the following. +// 'button.start' => 'Start', // 'button.stop' => 'Stop', // 'button.approve' => 'Approve', // 'button.disapprove' => 'Disapprove', +// 'button.sync' => 'Sync', // Used in Android app. The meaning is to synchronize offline time records with server. // Labels for controls on forms. Labels in this section are used on multiple forms. // TODO: translate the following. @@ -200,6 +205,7 @@ // TODO: translate the following. // 'label.day_view' => 'Day view', // 'label.week_view' => 'Week view', +// 'label.puncher' => 'Puncher', 'label.id' => 'ID', 'label.language' => 'Språk', // TODO: translate the following. @@ -222,9 +228,6 @@ 'label.required_fields' => '* obligatoriske felt', 'label.on_behalf' => 'på vegne av', // TODO: translate the following. -// 'label.role_manager' => '(manager)', -// 'label.role_comanager' => '(co-manager)', -// 'label.role_admin' => '(administrator)', // 'label.page' => 'Page', // 'label.condition' => 'Condition', // 'label.yes' => 'yes', @@ -248,9 +251,9 @@ // 'label.paid_status' => 'Paid status', // 'label.paid' => 'Paid', // 'label.mark_paid' => 'Mark paid', -// 'label.week_menu' => 'Week menu', // 'label.week_note' => 'Week note', // 'label.week_list' => 'Week list', +// 'label.weekends' => 'Weekends', // 'label.work_units' => 'Work units', // 'label.work_units_short' => 'Units', // 'label.totals_only' => 'Totals only', @@ -266,20 +269,8 @@ // 'label.attachments' => 'Attachments', // 'label.files' => 'Files', // 'label.file' => 'File', -// 'label.image' => 'Image', -// 'label.download' => 'Download', // 'label.active_users' => 'Active Users', // 'label.inactive_users' => 'Inactive Users', -// 'label.details' => 'Details', -// 'label.budget' => 'Budget', -// 'label.work' => 'Work', // Table column header for work items, see https://www.anuko.com/time_tracker/what_is/work_plugin.htm -// 'label.offer' => 'Offer', // Table column header for offers, see https://www.anuko.com/time_tracker/what_is/work_plugin.htm -// 'label.contractor' => 'Contractor', // Table column header for offers (contractor is someone who offers to do work). - // Technically, it is either an org name or a combination of org and group names - // because both work items and offers are owned by Time Tracker groups of users. -// 'label.how_to_pay' => 'How to pay', // Label for the "How to pay" field on offers, which allows contractors to specify - // how to pay them, for example: paypal email, check by mail to a specific address, etc. -// 'label.moderator_comment' => 'Moderator comment', // Label for "Moderator comment" field that explains something. // Entity names. We use lower case (in English) because they are used in dropdowns, too. // They are used to associate a custom field with an entity type. @@ -293,9 +284,9 @@ // 'title.error' => 'Error', // 'title.success' => 'Success', 'title.login' => 'Innlogging', -// TODO: translate the following. +// TODO: translate the follolwing. +// 'title.2fa' => 'Two Factor Authentication', // 'title.groups' => 'Groups', -// 'title.subgroups' => 'Subgroups', // 'title.add_group' => 'Adding Group', // 'title.edit_group' => 'Editing Group', 'title.delete_group' => 'Slett team', // TODO: change "team" to "group". @@ -307,6 +298,7 @@ 'title.delete_time_record' => 'Slett tids oppføringen', // TODO: Translate the following. // 'title.time_files' => 'Time Record Files', +// 'title.puncher' => 'Puncher', // 'title.expenses' => 'Expenses', // 'title.edit_expense' => 'Editing Expense Item', // 'title.delete_expense' => 'Deleting Expense Item', @@ -392,22 +384,6 @@ // 'title.edit_file' => 'Editing File', // 'title.delete_file' => 'Deleting File', // 'title.download_file' => 'Downloading File', -// 'title.work' => 'Work', -// 'title.add_work' => 'Adding Work', -// 'title.edit_work' => 'Editing Work', -// 'title.delete_work' => 'Deleting Work', -// 'title.active_work' => 'Active Work', // Active work items this group outsources to other groups. -// 'title.available_work' => 'Available Work', // Available work items from other organizations. -// 'title.inactive_work' => 'Inactive Work', // Inactive work items this group was outsourcing to other groups. -// 'title.pending_work' => 'Pending Work', // Work items pending moderator approval. -// 'title.offer' => 'Offer', -// 'title.add_offer' => 'Adding Offer', -// 'title.edit_offer' => 'Editing Offer', -// 'title.delete_offer' => 'Deleting Offer', -// 'title.active_offers' => 'Active Offers', // Active offers this group makes available to other groups. -// 'title.available_offers' => 'Available Offers', // Available offers from other organizations. -// 'title.inactive_offers' => 'Inactive Offers', // Inactive offers for group. -// 'title.pending_offers' => 'Pending Offers', // Offers pending moderator approval. // Section for common strings inside combo boxes on forms. Strings shared between forms shall be placed here. // Strings that are used in a single form must go to the specific form section. @@ -440,7 +416,6 @@ // TODO: translate the following. // 'dropdown.delete' => 'delete', // 'dropdown.do_not_delete' => 'do not delete', -// 'dropdown.pending_approval' => 'pending approval', // 'dropdown.approved' => 'approved', // 'dropdown.not_approved' => 'not approved', // 'dropdown.paid' => 'paid', @@ -454,6 +429,16 @@ // 'form.login.about' => 'Anuko Time Tracker is an open source time tracking system.', 'form.login.about' => 'Anuko Time Tracker er et enkelt, brukervennlig tidsregistreringssystem basert på åpen kildekode.', +// Email subject and body for two-factor authentication. +// TODO: translate the following. +// 'email.2fa_code.subject' => 'Anuko Time Tracker two-factor authentication code', +// 'email.2fa_code.body' => "Dear User,\n\nYour two-factor authentication code is:\n\n%s\n\n", + +// Two-factor authentication form. See example at https://timetracker.anuko.com/2fa.php. +// TODO: translate the following. +// 'form.2fa.hint' => 'Check your email for 2FA code and enter it here.', +// 'form.2fa.2fa_code' => '2FA code', + // Resetting Password form. See example at https://timetracker.anuko.com/password_reset.php. // TODO: translate the following. // 'form.reset_password.message' => 'Password reset request sent by email.', @@ -498,10 +483,13 @@ // 'form.reports.include_pending' => 'pending', 'form.reports.select_period' => 'Velg tidsperiode', 'form.reports.set_period' => 'eller sett dato', +// TODO: translate the following. +// 'form.reports.note_containing' => 'Note containing', 'form.reports.show_fields' => 'Vis feltene', // TODO: translate the following. // 'form.reports.time_fields' => 'Time fields', // 'form.reports.user_fields' => 'User fields', +// 'form.reports.project_fields' => 'Project fields', // 'form.reports.group_by' => 'Group by', // 'form.reports.group_by_no' => '--- no grouping ---', 'form.reports.group_by_date' => 'dato', @@ -515,6 +503,7 @@ // (after generating a report at https://timetracker.anuko.com/reports.php). 'form.report.export' => 'Eksporter', // TODO: translate the following. +// 'form.report.per_hour' => 'Per hour', // 'form.report.assign_to_invoice' => 'Assign to invoice', // 'form.report.assign_to_timesheet' => 'Assign to timesheet', @@ -528,8 +517,8 @@ // 'form.templates.active_templates' => 'Active Templates', // 'form.templates.inactive_templates' => 'Inactive Templates', -// Invoice form. See example at https://timetracker.anuko.com/invoice.php -// (you can get to this form after generating a report). +// Invoice form. See example at https://timetracker.anuko.com/invoice_view.php +// (you can get to this form after generating an invoice). 'form.invoice.number' => 'Fakturanummer', 'form.invoice.person' => 'Person', @@ -555,6 +544,7 @@ // Users form. See example at https://timetracker.anuko.com/users.php // TODO: translate the following. +// 'form.users.uncompleted_entry_today' => 'User has an uncompleted time entry today', // 'form.users.uncompleted_entry' => 'User has an uncompleted time entry', 'form.users.role' => 'Rolle', // TODO: translate the following. @@ -617,13 +607,19 @@ // 'form.group_edit.type_start_finish' => 'start and finish', // 'form.group_edit.type_duration' => 'duration', // 'form.group_edit.punch_mode' => 'Punch mode', +// 'form.group_edit.one_uncompleted' => 'One uncompleted', // 'form.group_edit.allow_overlap' => 'Allow overlap', // 'form.group_edit.future_entries' => 'Future entries', // 'form.group_edit.uncompleted_indicators' => 'Uncompleted indicators', // 'form.group_edit.confirm_save' => 'Confirm saving', -// 'form.group_edit.allow_ip' => 'Allow IP', // 'form.group_edit.advanced_settings' => 'Advanced settings', +// Advanced Group Settings form. See example at https://timetracker.anuko.com/group_advanced_edit.php. +// TODO: Translate the following. +// 'form.group_advanced_edit.allow_ip' => 'Allow IP', +// 'form.group_advanced_edit.password_complexity' => 'Password complexity', +// 'form.group_advanced_edit.2fa' => 'Two factor authentication', + // Deleting Group form. See example at https://timetracker.anuko.com/delete_group.php // TODO: translate the following. // 'form.group_delete.hint' => 'Are you sure you want to delete the entire group?', @@ -690,21 +686,8 @@ // TODO: translate the following. // 'form.display_options.note_on_separate_row' => 'Note on separate row', // 'form.display_options.not_complete_days' => 'Not complete days', +// 'form.display_options.inactive_projects' => 'Inactive projects', +// 'form.display_options.cost_per_hour' => 'Cost per hour', // 'form.display_options.custom_css' => 'Custom CSS', - -// Work plugin strings. See example at https://timetracker.anuko.com/work.php -// TODO: translate the following. -// 'work.error.work_not_available' => 'Work item is not available.', -// 'work.error.offer_not_available' => 'Offer is not available.', -// 'work.type.one_time' => 'one time', // Work type is "one time job" for well defined work ("do exactly this"). -// 'work.type.ongoing' => 'ongoing', // Work type is "ongoing" for complex jobs (billed by the hour, multiple contractors, etc.) -// 'work.label.own_work' => 'Own work', -// 'work.label.own_offers' => 'Own offers', -// 'work.label.offers' => 'Offers', -// 'work.button.send_message' => 'Send message', -// 'work.button.make_offer' => 'Make offer', -// 'work.button.accept' => 'Accept', -// 'work.button.decline' => 'Decline', -// 'work.title.send_message' => 'Sending Message', -// 'work.msg.message_sent' => 'Message sent.', +// 'form.display_options.custom_translation' => 'Custom translation', ); diff --git a/WEB-INF/resources/pl.lang.php b/WEB-INF/resources/pl.lang.php index 24664d693..69dddf311 100644 --- a/WEB-INF/resources/pl.lang.php +++ b/WEB-INF/resources/pl.lang.php @@ -2,7 +2,8 @@ /* Copyright (c) Anuko International Ltd. https://www.anuko.com License: See license.txt */ -// Note: escape apostrophes with THREE backslashes, like here: choisir l\\\'option. +// Note: escape apostrophes with THREE backslashes, like here: 'choisir l\\\'option'. +// Alternatively: use one backslash and surround by double-quotes: "choisir l\'option". // Other characters (such as double-quotes in http links, etc.) do not have to be escaped. $i18n_language = 'Polish (Polski)'; @@ -26,6 +27,7 @@ 'menu.plugins' => 'Moduły', 'menu.time' => 'Czas', // TODO: translate the following. +// 'menu.puncher' => 'Punch', // 'menu.week' => 'Week', 'menu.expenses' => 'Wydatki', 'menu.reports' => 'Raporty', @@ -37,7 +39,6 @@ 'menu.users' => 'Użytkownicy', // TODO: translate the following. // 'menu.groups' => 'Groups', -// 'menu.subgroups' => 'Subgroups', 'menu.export' => 'Eksport', 'menu.clients' => 'Klienci', 'menu.options' => 'Opcje', @@ -69,6 +70,9 @@ // TODO: translate the following. // 'error.record' => 'Select record.', 'error.auth' => 'Błędny login lub hasło.', +// TODO: translate the following. +// 'error.2fa_code' => 'Invalid 2FA code.', +// 'error.weak_password' => 'Weak password.', 'error.user_exists' => 'Użytkownik o takiej nazwie już istnieje.', // TODO: translate the following. // 'error.object_exists' => 'Object with this name already exists.', @@ -98,7 +102,6 @@ // 'error.user_count' => 'Limit on user count.', // 'error.expired' => 'Expiration date reached.', // 'error.file_storage' => 'File storage server error.', // See comment in English file. -// 'error.remote_work' => 'Remote work server error.', // See comment in English file. // Warning messages. // TODO: translate the following. @@ -126,10 +129,13 @@ 'button.export' => 'Eksportuj zespół', // TODO: replace "team" with "group". 'button.import' => 'Importuj zespół', // TODO: replace "team" with "group". 'button.close' => 'Zamknij', +// TODO: translate the following. +// 'button.start' => 'Start', 'button.stop' => 'Zatrzymaj', // TODO: translate the following. // 'button.approve' => 'Approve', // 'button.disapprove' => 'Disapprove', +// 'button.sync' => 'Sync', // Used in Android app. The meaning is to synchronize offline time records with server. // Labels for controls on forms. Labels in this section are used on multiple forms. // TODO: translate the following. @@ -191,6 +197,7 @@ // TODO: translate the following. // 'label.day_view' => 'Day view', // 'label.week_view' => 'Week view', +// 'label.puncher' => 'Puncher', 'label.id' => 'ID', 'label.language' => 'Język', 'label.decimal_mark' => 'Znak dziesiętny', @@ -209,9 +216,6 @@ 'label.ldap_hint' => 'Wpisz swoją nazwę użytkownika Windows i hasło w polach poniżej.', 'label.required_fields' => '* - pola wymagane', 'label.on_behalf' => 'w imieniu', -'label.role_manager' => '(Manager)', -'label.role_comanager' => '(Co-manager)', -'label.role_admin' => '(Administrator)', // TODO: translate the following. // 'label.page' => 'Page', // 'label.condition' => 'Condition', @@ -236,9 +240,9 @@ // 'label.paid_status' => 'Paid status', // 'label.paid' => 'Paid', // 'label.mark_paid' => 'Mark paid', -// 'label.week_menu' => 'Week menu', // 'label.week_note' => 'Week note', // 'label.week_list' => 'Week list', +// 'label.weekends' => 'Weekends', // 'label.work_units' => 'Work units', // 'label.work_units_short' => 'Units', 'label.totals_only' => 'Tylko sumy', @@ -255,21 +259,8 @@ // 'label.attachments' => 'Attachments', // 'label.files' => 'Files', // 'label.file' => 'File', -// 'label.image' => 'Image', -// 'label.download' => 'Download', 'label.active_users' => 'Aktywni użytkownicy', 'label.inactive_users' => 'Nieaktywni użytkownicy', -// TODO: translate the following. -// 'label.details' => 'Details', -// 'label.budget' => 'Budget', -// 'label.work' => 'Work', // Table column header for work items, see https://www.anuko.com/time_tracker/what_is/work_plugin.htm -// 'label.offer' => 'Offer', // Table column header for offers, see https://www.anuko.com/time_tracker/what_is/work_plugin.htm -// 'label.contractor' => 'Contractor', // Table column header for offers (contractor is someone who offers to do work). - // Technically, it is either an org name or a combination of org and group names - // because both work items and offers are owned by Time Tracker groups of users. -// 'label.how_to_pay' => 'How to pay', // Label for the "How to pay" field on offers, which allows contractors to specify - // how to pay them, for example: paypal email, check by mail to a specific address, etc. -// 'label.moderator_comment' => 'Moderator comment', // Label for "Moderator comment" field that explains something. // Entity names. We use lower case (in English) because they are used in dropdowns, too. // They are used to associate a custom field with an entity type. @@ -283,9 +274,10 @@ // TODO: Translate the following. // 'title.success' => 'Success', 'title.login' => 'Logowanie', +// TODO: translate the follolwing. +// 'title.2fa' => 'Two Factor Authentication', 'title.groups' => 'Zespoły', // TODO: change "teams" to "groups". // TODO: translate the following. -// 'title.subgroups' => 'Subgroups', // 'title.add_group' => 'Adding Group', 'title.edit_group' => 'Edytowanie zespołu', // TODO: change "team" to "group". 'title.delete_group' => 'Usuwanie zespołu', // TODO: change "team" to "group". @@ -296,6 +288,7 @@ 'title.delete_time_record' => 'Usuwanie wpisu', // TODO: Translate the following. // 'title.time_files' => 'Time Record Files', +// 'title.puncher' => 'Puncher', 'title.expenses' => 'Wydatki', 'title.edit_expense' => 'Edytowanie wpisu', 'title.delete_expense' => 'Usuwanie wpisu', @@ -376,22 +369,6 @@ // 'title.edit_file' => 'Editing File', // 'title.delete_file' => 'Deleting File', // 'title.download_file' => 'Downloading File', -// 'title.work' => 'Work', -// 'title.add_work' => 'Adding Work', -// 'title.edit_work' => 'Editing Work', -// 'title.delete_work' => 'Deleting Work', -// 'title.active_work' => 'Active Work', // Active work items this group outsources to other groups. -// 'title.available_work' => 'Available Work', // Available work items from other organizations. -// 'title.inactive_work' => 'Inactive Work', // Inactive work items this group was outsourcing to other groups. -// 'title.pending_work' => 'Pending Work', // Work items pending moderator approval. -// 'title.offer' => 'Offer', -// 'title.add_offer' => 'Adding Offer', -// 'title.edit_offer' => 'Editing Offer', -// 'title.delete_offer' => 'Deleting Offer', -// 'title.active_offers' => 'Active Offers', // Active offers this group makes available to other groups. -// 'title.available_offers' => 'Available Offers', // Available offers from other organizations. -// 'title.inactive_offers' => 'Inactive Offers', // Inactive offers for group. -// 'title.pending_offers' => 'Pending Offers', // Offers pending moderator approval. // Section for common strings inside combo boxes on forms. Strings shared between forms shall be placed here. // Strings that are used in a single form must go to the specific form section. @@ -424,7 +401,6 @@ 'dropdown.delete' => 'usuń', 'dropdown.do_not_delete' => 'nie usuwaj', // TODO: translate the following. -// 'dropdown.pending_approval' => 'pending approval', // 'dropdown.approved' => 'approved', // 'dropdown.not_approved' => 'not approved', // 'dropdown.paid' => 'paid', @@ -441,6 +417,16 @@ 'form.login.forgot_password' => 'Nie pamiętasz hasła?', 'form.login.about' => 'Anuko Time Tracker jest otwartoźródłowym systemem śledzenia czasu.', +// Email subject and body for two-factor authentication. +// TODO: translate the following. +// 'email.2fa_code.subject' => 'Anuko Time Tracker two-factor authentication code', +// 'email.2fa_code.body' => "Dear User,\n\nYour two-factor authentication code is:\n\n%s\n\n", + +// Two-factor authentication form. See example at https://timetracker.anuko.com/2fa.php. +// TODO: translate the following. +// 'form.2fa.hint' => 'Check your email for 2FA code and enter it here.', +// 'form.2fa.2fa_code' => '2FA code', + // Resetting Password form. See example at https://timetracker.anuko.com/password_reset.php. 'form.reset_password.message' => 'Instrukcje zmiany hasła zostały wysłane na adres e-mail połączony z kontem.', 'form.reset_password.email_subject' => 'Anuko Time Tracker - żądanie zmiany hasła', @@ -482,10 +468,13 @@ // 'form.reports.include_pending' => 'pending', 'form.reports.select_period' => 'Wybierz okres', 'form.reports.set_period' => 'lub ustaw daty', +// TODO: translate the following. +// 'form.reports.note_containing' => 'Note containing', 'form.reports.show_fields' => 'Pokaż pola', // TODO: translate the following. // 'form.reports.time_fields' => 'Time fields', // 'form.reports.user_fields' => 'User fields', +// 'form.reports.project_fields' => 'Project fields', 'form.reports.group_by' => 'Grupowanie wg', 'form.reports.group_by_no' => '--- bez grupowania ---', 'form.reports.group_by_date' => 'daty', @@ -498,6 +487,7 @@ // (after generating a report at https://timetracker.anuko.com/reports.php). 'form.report.export' => 'Eksport', // TODO: translate the following. +// 'form.report.per_hour' => 'Per hour', // 'form.report.assign_to_invoice' => 'Assign to invoice', // 'form.report.assign_to_timesheet' => 'Assign to timesheet', @@ -511,8 +501,8 @@ // 'form.templates.active_templates' => 'Active Templates', // 'form.templates.inactive_templates' => 'Inactive Templates', -// Invoice form. See example at https://timetracker.anuko.com/invoice.php -// (you can get to this form after generating a report). +// Invoice form. See example at https://timetracker.anuko.com/invoice_view.php +// (you can get to this form after generating an invoice). 'form.invoice.number' => 'Numer faktury', 'form.invoice.person' => 'Osoba', @@ -536,6 +526,7 @@ // Users form. See example at https://timetracker.anuko.com/users.php // TODO: translate the following. + // 'form.users.uncompleted_entry_today' => 'User has an uncompleted time entry today', // 'form.users.uncompleted_entry' => 'User has an uncompleted time entry', 'form.users.role' => 'Rola', 'form.users.manager' => 'Manager', @@ -596,13 +587,19 @@ 'form.group_edit.type_duration' => 'czas trwania', // TODO: translate the following. // 'form.group_edit.punch_mode' => 'Punch mode', +// 'form.group_edit.one_uncompleted' => 'One uncompleted', // 'form.group_edit.allow_overlap' => 'Allow overlap', // 'form.group_edit.future_entries' => 'Future entries', // 'form.group_edit.uncompleted_indicators' => 'Uncompleted indicators', // 'form.group_edit.confirm_save' => 'Confirm saving', -// 'form.group_edit.allow_ip' => 'Allow IP', // 'form.group_edit.advanced_settings' => 'Advanced settings', +// Advanced Group Settings form. See example at https://timetracker.anuko.com/group_advanced_edit.php. +// TODO: Translate the following. +// 'form.group_advanced_edit.allow_ip' => 'Allow IP', +// 'form.group_advanced_edit.password_complexity' => 'Password complexity', +// 'form.group_advanced_edit.2fa' => 'Two factor authentication', + // Deleting Group form. See example at https://timetracker.anuko.com/delete_group.php // TODO: translate the following. // 'form.group_delete.hint' => 'Are you sure you want to delete the entire group?', @@ -668,21 +665,8 @@ // TODO: translate the following. // 'form.display_options.note_on_separate_row' => 'Note on separate row', // 'form.display_options.not_complete_days' => 'Not complete days', +// 'form.display_options.inactive_projects' => 'Inactive projects', +// 'form.display_options.cost_per_hour' => 'Cost per hour', // 'form.display_options.custom_css' => 'Custom CSS', - -// Work plugin strings. See example at https://timetracker.anuko.com/work.php -// TODO: translate the following. -// 'work.error.work_not_available' => 'Work item is not available.', -// 'work.error.offer_not_available' => 'Offer is not available.', -// 'work.type.one_time' => 'one time', // Work type is "one time job" for well defined work ("do exactly this"). -// 'work.type.ongoing' => 'ongoing', // Work type is "ongoing" for complex jobs (billed by the hour, multiple contractors, etc.) -// 'work.label.own_work' => 'Own work', -// 'work.label.own_offers' => 'Own offers', -// 'work.label.offers' => 'Offers', -// 'work.button.send_message' => 'Send message', -// 'work.button.make_offer' => 'Make offer', -// 'work.button.accept' => 'Accept', -// 'work.button.decline' => 'Decline', -// 'work.title.send_message' => 'Sending Message', -// 'work.msg.message_sent' => 'Message sent.', +// 'form.display_options.custom_translation' => 'Custom translation', ); diff --git a/WEB-INF/resources/pt-br.lang.php b/WEB-INF/resources/pt-br.lang.php index e41cf26c3..c9a4ad998 100644 --- a/WEB-INF/resources/pt-br.lang.php +++ b/WEB-INF/resources/pt-br.lang.php @@ -2,10 +2,11 @@ /* Copyright (c) Anuko International Ltd. https://www.anuko.com License: See license.txt */ -// Note: escape apostrophes with THREE backslashes, like here: choisir l\\\'option. +// Note: escape apostrophes with THREE backslashes, like here: 'choisir l\\\'option'. +// Alternatively: use one backslash and surround by double-quotes: "choisir l\'option". // Other characters (such as double-quotes in http links, etc.) do not have to be escaped. -$i18n_language = 'Portuguese (Português brasileiro)'; +$i18n_language = 'Portuguese (Português Brasileiro)'; $i18n_months = array('Janeiro', 'Fevereiro', 'Março', 'Abril', 'Maio', 'Junho', 'Julho', 'Agosto', 'Setembro', 'Outubro', 'Novembro', 'Dezembro'); $i18n_weekdays = array('Domingo', 'Segunda-feira', 'Terça-feira', 'Quarta-feira', 'Quinta-feira', 'Sexta-feira', 'Sábado'); $i18n_weekdays_short = array('Dom', 'Seg', 'Ter', 'Qua', 'Qui', 'Sex', 'Sab'); @@ -18,26 +19,21 @@ 'menu.logout' => 'Logout', 'menu.forum' => 'Fórum', 'menu.help' => 'Ajuda', -// TODO: translate the following. -// 'menu.register' => 'Register', +'menu.register' => 'Registro', 'menu.profile' => 'Perfil', -// TODO: translate the following. -// 'menu.group' => 'Group', +'menu.group' => 'Grupo', 'menu.plugins' => 'Plugins', 'menu.time' => 'Tempo', -// TODO: translate the following. -// 'menu.week' => 'Week', -'menu.expenses' => 'Gastos', +'menu.puncher' => 'Punch', +'menu.week' => 'Semana', +'menu.expenses' => 'Despesas', 'menu.reports' => 'Relatórios', -// TODO: translate the following. -// 'menu.timesheets' => 'Timesheets', +'menu.timesheets' => 'Planilha de horas', 'menu.charts' => 'Gráficos', 'menu.projects' => 'Projetos', 'menu.tasks' => 'Tarefas', 'menu.users' => 'Usuários', -// TODO: translate the following. -// 'menu.groups' => 'Groups', -// 'menu.subgroups' => 'Subgroups', +'menu.groups' => 'Grupos', 'menu.export' => 'Exportar', 'menu.clients' => 'Clientes', 'menu.options' => 'Opções', @@ -52,59 +48,50 @@ 'error.access_denied' => 'Acesso negado.', 'error.sys' => 'Erro no sistema.', 'error.db' => 'Erro no banco de dados.', -// TODO: translate the following. -// 'error.registered_recently' => 'Registered recently.', -// 'error.feature_disabled' => 'Feature is disabled.', +'error.registered_recently' => 'Registrado recentemente.', +'error.feature_disabled' => 'Recurso desabilitado.', 'error.field' => 'Dados incorretos "{0}".', 'error.empty' => 'Campo "{0}" está vazio.', 'error.not_equal' => 'Campo "{0}" é diferente do campo "{1}".', -// TODO: translate the following. -// 'error.interval' => 'Field "{0}" must be greater than "{1}".', +'error.interval' => 'Campo "{0}" precisa ser maior que "{1}".', 'error.project' => 'Selecione projeto.', 'error.task' => 'Selecione tarefa.', 'error.client' => 'Selecione cliente.', 'error.report' => 'Selecione relatório.', -// TODO: translate the following. -// 'error.record' => 'Select record.', +'error.record' => 'Selecione o registro.', 'error.auth' => 'Usuário ou senha incorretos.', -'error.user_exists' => 'Já existe usuário com este login.', // TODO: translate the following. -// 'error.object_exists' => 'Object with this name already exists.', +// 'error.2fa_code' => 'Invalid 2FA code.', +// 'error.weak_password' => 'Weak password.', +'error.user_exists' => 'Já existe usuário com este login.', +'error.object_exists' => 'Já existe um objeto com este nome.', 'error.invoice_exists' => 'Já existe fatura com este número.', -// TODO: translate the following. -// 'error.role_exists' => 'Role with this rank already exists.', +'error.role_exists' => 'Já existe uma função com este rank.', 'error.no_invoiceable_items' => 'Não há items faturáveis.', -// TODO: translate the following. -// 'error.no_records' => 'There are no records.', +'error.no_records' => 'Não há registros.', 'error.no_login' => 'Não há usuário com este login.', -'error.no_groups' => 'Sua base de dados está vazia. Entre como admin e crie uma equipe nova.', // TODO: replace "team" with "group". +'error.no_groups' => 'Sua base de dados está vazia. Entre como admin e crie um grupo novo.', 'error.upload' => 'Erro no envio do arquivo.', -'error.range_locked' => 'Período está bloqueado.', -'error.mail_send' => 'Erro enviando o e-mail.', -// TODO: improve the translation above by adding MAIL_SMTP_DEBUG part. -// 'error.mail_send' => 'Error sending mail. Use MAIL_SMTP_DEBUG for diagnostics.', +'error.range_locked' => 'Período bloqueado.', +'error.mail_send' => 'Erro ao enviar e-mail. Use MAIL_SMTP_DEBUG para diagnósticos.', // "Use" means the same in pt-br as in en. 'error.no_email' => 'Não há e-mail associado a este login.', 'error.uncompleted_exists' => 'Entrada incompleta existente. Feche ou remova-a.', 'error.goto_uncompleted' => 'Ir até a entrada incompleta.', 'error.overlap' => 'O intervalo se sobrepõe com entradas já existentes.', 'error.future_date' => 'Data é no futuro.', -// TODO: translate the following. -// 'error.xml' => 'Error in XML file at line %d: %s.', -// 'error.cannot_import' => 'Cannot import: %s.', -// 'error.format' => 'Invalid file format.', -// 'error.user_count' => 'Limit on user count.', -// 'error.expired' => 'Expiration date reached.', -// 'error.file_storage' => 'File storage server error.', // See comment in English file. -// 'error.remote_work' => 'Remote work server error.', // See comment in English file. +'error.xml' => 'Erro no arquivo XML, linha line %d: %s.', +'error.cannot_import' => 'Não foi possível importar: %s.', +'error.format' => 'Formato de arquivo inválido.', +'error.user_count' => 'Limite na contagem de usuários.', +'error.expired' => 'Data de expiração atingida.', +'error.file_storage' => 'Erro relacionado ao servidor de armazenamento de arquivos.', // Warning messages. -// TODO: translate the following. -// 'warn.sure' => 'Are you sure?', -// 'warn.confirm_save' => 'Date has changed. Confirm saving, not copying this item.', +'warn.sure' => 'Tem certeza?', +'warn.confirm_save' => 'A data mudou. Confirme salvando, não copiando este item.', // Success messages. -// TODO: translate the following. -// 'msg.success' => 'Operation completed successfully.', +'msg.success' => 'Operação concluída com sucesso.', // Labels for buttons. 'button.login' => 'Login', @@ -119,19 +106,20 @@ 'button.reset_password' => 'Resetar senha', 'button.send' => 'Enviar', 'button.send_by_email' => 'Enviar por e-mail', -'button.create_group' => 'Criar equipe', // TODO: replace "team" with "group". -'button.export' => 'Exportar equipe', // TODO: replace "team" with "group". -'button.import' => 'Importar equipe', // TODO: replace "team" with "group". +'button.create_group' => 'Criar grupo', +'button.export' => 'Exportar grupo', +'button.import' => 'Importar grupo', 'button.close' => 'Fechar', +'button.start' => 'Iniciar', 'button.stop' => 'Parar', +'button.approve' => 'Aprovar', +'button.disapprove' => 'Desaprovar', // TODO: translate the following. -// 'button.approve' => 'Approve', -// 'button.disapprove' => 'Disapprove', +// 'button.sync' => 'Sync', // Used in Android app. The meaning is to synchronize offline time records with server. // Labels for controls on forms. Labels in this section are used on multiple forms. -// TODO: translate the following. -// 'label.menu' => 'Menu', -'label.group_name' => 'Nome da equipe', // TODO: replace "team" with "group". +'label.menu' => 'Menu', +'label.group_name' => 'Nome do grupo', 'label.address' => 'Endereço', 'label.currency' => 'Moeda', 'label.manager_name' => 'Nome do gerente', @@ -143,18 +131,16 @@ 'label.confirm_password' => 'Confirme a senha', 'label.email' => 'E-mail', 'label.cc' => 'Cc', -// TODO: translate the following. -// 'label.bcc' => 'Bcc', +'label.bcc' => 'Bcc', 'label.subject' => 'Assunto', 'label.date' => 'Data', 'label.start_date' => 'Data inicial', 'label.end_date' => 'Data final', 'label.user' => 'Usuário', 'label.users' => 'Usuários', -// TODO: translate the following. -// 'label.group' => 'Group', -// 'label.subgroups' => 'Subgroups', -// 'label.roles' => 'Roles', +'label.group' => 'Grupo', +'label.subgroups' => 'Subgrupos', +'label.roles' => 'Funções', 'label.client' => 'Cliente', 'label.clients' => 'Clientes', 'label.option' => 'Opção', @@ -168,16 +154,13 @@ 'label.finish' => 'Fim', 'label.duration' => 'Duração', 'label.note' => 'Anotação', -// TODO: translate the following. -// 'label.notes' => 'Notes', +'label.notes' => 'Anotações', 'label.item' => 'Item', 'label.cost' => 'Custo', -// TODO: translate the following. -// 'label.ip' => 'IP', +'label.ip' => 'IP', 'label.day_total' => 'Total diário', 'label.week_total' => 'Total semanal', -// TODO: translate the following. -// 'label.month_total' => 'Month total', +'label.month_total' => 'Total mensal', 'label.today' => 'Hoje', 'label.view' => 'Ver', 'label.edit' => 'Editar', @@ -185,9 +168,9 @@ 'label.configure' => 'Configurar', 'label.select_all' => 'Selecionar todos', 'label.select_none' => 'Desmarcar todos', -// TODO: translate the following. -// 'label.day_view' => 'Day view', -// 'label.week_view' => 'Week view', +'label.day_view' => 'Visão diária', +'label.week_view' => 'Visão semanal', +'label.puncher' => 'Puncher', 'label.id' => 'ID', 'label.language' => 'Idioma', 'label.decimal_mark' => 'Ponto decimal', @@ -206,21 +189,15 @@ 'label.ldap_hint' => 'Entre com o seu login do Windows e senha nos campos abaixo.', 'label.required_fields' => '* - campos obrigatórios', 'label.on_behalf' => 'em nome de', -'label.role_manager' => '(gerente)', -'label.role_comanager' => '(coordenador)', -'label.role_admin' => '(administrador)', 'label.page' => 'Página', -// TODO: translate the following. -// 'label.condition' => 'Condition', -// 'label.yes' => 'yes', -// 'label.no' => 'no', -// 'label.sort' => 'Sort', +'label.condition' => 'Condição', +'label.yes' => 'sim', +'label.no' => 'não', +'label.sort' => 'Ordenar', // Labels for plugins (extensions to Time Tracker that provide additional features). 'label.custom_fields' => 'Campos personalizados', -// Translate the following. -// 'label.monthly_quotas' => 'Monthly quotas', -// TODO: translate the following. -// 'label.entity' => 'Entity', +'label.monthly_quotas' => 'Cotas mensais', +'label.entity' => 'Entidade', 'label.type' => 'Tipo', 'label.type_dropdown' => 'lista suspensa', 'label.type_text' => 'texto', @@ -228,89 +205,71 @@ 'label.fav_report' => 'Relatório favorito', 'label.schedule' => 'Agenda', 'label.what_is_it' => 'O que é?', -// 'label.expense' => 'Expense', -// 'label.quantity' => 'Quantity', -// 'label.paid_status' => 'Paid status', -// 'label.paid' => 'Paid', -// 'label.mark_paid' => 'Mark paid', -// 'label.week_menu' => 'Week menu', -// 'label.week_note' => 'Week note', -// 'label.week_list' => 'Week list', -// 'label.work_units' => 'Work units', -// 'label.work_units_short' => 'Units', +'label.expense' => 'Despesa', +'label.quantity' => 'Quantidade', +'label.paid_status' => 'Status pago', +'label.paid' => 'Pago', +'label.mark_paid' => 'Marcar como pago', +'label.week_note' => 'Anotação da semana', +'label.week_list' => 'Lista da semana', +// TODO: translate the following. +// 'label.weekends' => 'Weekends', +'label.work_units' => 'Unidades de trabalho', +'label.work_units_short' => 'Unidades', 'label.totals_only' => 'Somente totais', -// TODO: translate the following. -// 'label.quota' => 'Quota', -// 'label.timesheet' => 'Timesheet', -// 'label.submitted' => 'Submitted', -// 'label.approved' => 'Approved', -// 'label.approval' => 'Report approval', -// 'label.mark_approved' => 'Mark approved', -// 'label.template' => 'Template', -// 'label.bind_templates_with_projects' => 'Bind templates with projects', -// 'label.prepopulate_note' => 'Prepopulate Note field', -// 'label.attachments' => 'Attachments', -// 'label.files' => 'Files', -// 'label.file' => 'File', -// 'label.image' => 'Image', -// 'label.download' => 'Download', +'label.quota' => 'Cota', +'label.timesheet' => 'Planilha de horas', +'label.submitted' => 'Enviado', +'label.approved' => 'Aprovado', +'label.approval' => 'Aprovação de relatório', +'label.mark_approved' => 'Marcar como aprovado', +'label.template' => 'Modelo', +'label.bind_templates_with_projects' => 'Vincular modelos com projetos', +'label.prepopulate_note' => 'Pré-preencher campo de anotação', +'label.attachments' => 'Anexos', +'label.files' => 'Arquivos', +'label.file' => 'Arquivo', 'label.active_users' => 'Usuários ativos', 'label.inactive_users' => 'Usuários inativos', -// TODO: translate the following. -// 'label.details' => 'Details', -// 'label.budget' => 'Budget', -// 'label.work' => 'Work', // Table column header for work items, see https://www.anuko.com/time_tracker/what_is/work_plugin.htm -// 'label.offer' => 'Offer', // Table column header for offers, see https://www.anuko.com/time_tracker/what_is/work_plugin.htm -// 'label.contractor' => 'Contractor', // Table column header for offers (contractor is someone who offers to do work). - // Technically, it is either an org name or a combination of org and group names - // because both work items and offers are owned by Time Tracker groups of users. -// 'label.how_to_pay' => 'How to pay', // Label for the "How to pay" field on offers, which allows contractors to specify - // how to pay them, for example: paypal email, check by mail to a specific address, etc. -// 'label.moderator_comment' => 'Moderator comment', // Label for "Moderator comment" field that explains something. // Entity names. We use lower case (in English) because they are used in dropdowns, too. // They are used to associate a custom field with an entity type. -// TODO: translate the following. -// 'entity.time' => 'time', -// 'entity.user' => 'user', -// 'entity.project' => 'project', +'entity.time' => 'tempo', +'entity.user' => 'usuário', +'entity.project' => 'projeto', // Form titles. 'title.error' => 'Erro', -// TODO: Translate the following. -// 'title.success' => 'Success', +'title.success' => 'Sucesso', 'title.login' => 'Login', -'title.groups' => 'Equipes', // TODO: change "teams" to "groups". -// TODO: translate the following. -// 'title.subgroups' => 'Subgroups', -// 'title.add_group' => 'Adding Group', -'title.edit_group' => 'Editando equipe', // TODO: change "team" to "group". -'title.delete_group' => 'Apagando equipe', // TODO: change "team" to "group". +// TODO: translate the follolwing. +// 'title.2fa' => 'Two Factor Authentication', +'title.groups' => 'Grupos', +'title.add_group' => 'Adicionando grupo', +'title.edit_group' => 'Editando grupo', +'title.delete_group' => 'Apagando grupo', 'title.reset_password' => 'Resetando a senha', 'title.change_password' => 'Alterando a senha', 'title.time' => 'Tempo', 'title.edit_time_record' => 'Editando entrada de hora', 'title.delete_time_record' => 'Apagando entrada de hora', -// TODO: Translate the following. -// 'title.time_files' => 'Time Record Files', +'title.time_files' => 'Arquivos de registro de tempo', +'title.puncher' => 'Puncher', 'title.expenses' => 'Gastos', 'title.edit_expense' => 'Editando item de gasto', 'title.delete_expense' => 'Apagando item de gasto', -// TODO: translate the following. -// 'title.expense_files' => 'Expense Item Files', +'title.expense_files' => 'Arquivos de itens de despesas', 'title.reports' => 'Relatórios', 'title.report' => 'Report', 'title.send_report' => 'Enviando relatório', -// TODO: Translate the following. -// 'title.timesheets' => 'Timesheets', -// 'title.timesheet' => 'Timesheet', -// 'title.timesheet_files' => 'Timesheet Files', +'title.timesheets' => 'Planilhas de horas', +'title.timesheet' => 'Planilha de horas', +'title.timesheet_files' => 'Arquivos de planilha de horas', 'title.invoice' => 'Fatura', 'title.send_invoice' => 'Enviando fatura', 'title.charts' => 'Gráficos', 'title.projects' => 'Projetos', -// TODO: translate the following. -// 'title.project_files' => 'Project Files', +'title.project_files' => 'Arquivos do projeto', 'title.add_project' => 'Adicionando projeto', 'title.edit_project' => 'Editando projeto', 'title.delete_project' => 'Apagando projeto', @@ -322,11 +281,10 @@ 'title.add_user' => 'Adicionando usuário', 'title.edit_user' => 'Editando usuário', 'title.delete_user' => 'Apagando usuário', -// TODO: translate the following. -// 'title.roles' => 'Roles', -// 'title.add_role' => 'Adding Role', -// 'title.edit_role' => 'Editing Role', -// 'title.delete_role' => 'Deleting Role', +'title.roles' => 'Funções', +'title.add_role' => 'Adicionando função', +'title.edit_role' => 'Editando função', +'title.delete_role' => 'Apagando função', 'title.clients' => 'Clientes', 'title.add_client' => 'Adicionando cliente', 'title.edit_client' => 'Editando cliente', @@ -339,16 +297,14 @@ 'title.add_notification' => 'Adicionando notificação', 'title.edit_notification' => 'Editando notificação', 'title.delete_notification' => 'Apagando notificação', -// TODO: translate the following. -// 'title.add_timesheet' => 'Adding Timesheet', -// 'title.edit_timesheet' => 'Editing Timesheet', -// 'title.delete_timesheet' => 'Deleting Timesheet', -// 'title.monthly_quotas' => 'Monthly Quotas', -'title.export' => 'Exportando dados de equipe', // TODO: replace "team" with "group". -'title.import' => 'Importando dados de equipe', // TODO: replace "team" with "group". +'title.add_timesheet' => 'Adicionando planilha de horas', +'title.edit_timesheet' => 'Editando planilha de horas', +'title.delete_timesheet' => 'Apagando planilha de horas', +'title.monthly_quotas' => 'Cotas mensais', +'title.export' => 'Exportando dados do grupo', +'title.import' => 'Importando dados do grupo', 'title.options' => 'Opções', -// TODO: translate the following. -// 'title.display_options' => 'Display Options', +'title.display_options' => 'Opções de exibição', 'title.profile' => 'Perfil', 'title.plugins' => 'Plugins', 'title.cf_custom_fields' => 'Campos personalizados', @@ -360,41 +316,23 @@ 'title.cf_edit_dropdown_option' => 'Editando opção', 'title.cf_delete_dropdown_option' => 'Apagando opção', 'title.locking' => 'Bloquear', -// TODO: translate the following. -// 'title.week_view' => 'Week View', -// 'title.swap_roles' => 'Swapping Roles', -// 'title.work_units' => 'Work Units', -// 'title.templates' => 'Templates', -// 'title.add_template' => 'Adding Template', -// 'title.edit_template' => 'Editing Template', -// 'title.delete_template' => 'Deleting Template', -// 'title.edit_file' => 'Editing File', -// 'title.delete_file' => 'Deleting File', -// 'title.download_file' => 'Downloading File', -// 'title.work' => 'Work', -// 'title.add_work' => 'Adding Work', -// 'title.edit_work' => 'Editing Work', -// 'title.delete_work' => 'Deleting Work', -// 'title.active_work' => 'Active Work', // Active work items this group outsources to other groups. -// 'title.available_work' => 'Available Work', // Available work items from other organizations. -// 'title.inactive_work' => 'Inactive Work', // Inactive work items this group was outsourcing to other groups. -// 'title.pending_work' => 'Pending Work', // Work items pending moderator approval. -// 'title.offer' => 'Offer', -// 'title.add_offer' => 'Adding Offer', -// 'title.edit_offer' => 'Editing Offer', -// 'title.delete_offer' => 'Deleting Offer', -// 'title.active_offers' => 'Active Offers', // Active offers this group makes available to other groups. -// 'title.available_offers' => 'Available Offers', // Available offers from other organizations. -// 'title.inactive_offers' => 'Inactive Offers', // Inactive offers for group. -// 'title.pending_offers' => 'Pending Offers', // Offers pending moderator approval. +'title.week_view' => 'Visão semanal', +'title.swap_roles' => 'Alteração de funções', +'title.work_units' => 'Unidades de trabalho', +'title.templates' => 'Modelos', +'title.add_template' => 'Adicionando modelo', +'title.edit_template' => 'Editando modelo', +'title.delete_template' => 'Apagando modelo', +'title.edit_file' => 'Editando arquivo', +'title.delete_file' => 'Apagando arquivo', +'title.download_file' => 'Baixando arquivo', // Section for common strings inside combo boxes on forms. Strings shared between forms shall be placed here. // Strings that are used in a single form must go to the specific form section. 'dropdown.all' => '--- todos ---', 'dropdown.no' => '--- não ---', -// TODO: translate the following. -// 'dropdown.current_day' => 'today', -// 'dropdown.previous_day' => 'yesterday', +'dropdown.current_day' => 'hoje', +'dropdown.previous_day' => 'ontem', 'dropdown.selected_day' => 'dia', 'dropdown.current_week' => 'esta semana', 'dropdown.previous_week' => 'última semana', @@ -403,8 +341,7 @@ 'dropdown.previous_month' => 'último mês', 'dropdown.selected_month' => 'mês', 'dropdown.current_year' => 'este ano', -// TODO: translate the following. -// 'dropdown.previous_year' => 'previous year', +'dropdown.previous_year' => 'último ano', 'dropdown.selected_year' => 'ano', 'dropdown.all_time' => 'todas as datas', 'dropdown.projects' => 'projetos', @@ -412,20 +349,17 @@ 'dropdown.clients' => 'clientes', 'dropdown.select' => '--- selecione ---', 'dropdown.select_invoice' => '--- selecione fatura ---', -// TODO: translate the following. -// 'dropdown.select_timesheet' => '--- select timesheet ---', +'dropdown.select_timesheet' => '--- selecione planilha de horas ---', 'dropdown.status_active' => 'ativo', 'dropdown.status_inactive' => 'inativo', 'dropdown.delete' => 'apagar', 'dropdown.do_not_delete' => 'não apagar', -// TODO: translate the following. -// 'dropdown.pending_approval' => 'pending approval', -// 'dropdown.approved' => 'approved', -// 'dropdown.not_approved' => 'not approved', -// 'dropdown.paid' => 'paid', -// 'dropdown.not_paid' => 'not paid', -// 'dropdown.ascending' => 'ascending', -// 'dropdown.descending' => 'descending', +'dropdown.approved' => 'aprovado', +'dropdown.not_approved' => 'não aprovado', +'dropdown.paid' => 'pago', +'dropdown.not_paid' => 'não pago', +'dropdown.ascending' => 'ascendente', +'dropdown.descending' => 'descendente', // Below is a section for strings that are used on individual forms. When a string is used only on one form it should be placed here. // One exception is for closely related forms such as "Time" and "Editing Time Record" with similar controls. In such cases @@ -436,13 +370,20 @@ 'form.login.forgot_password' => 'Esqueceu a senha?', 'form.login.about' => 'Anuko Time Tracker é um sistema de código aberto de rastreamento do tempo.', +// Email subject and body for two-factor authentication. +// TODO: translate the following. +// 'email.2fa_code.subject' => 'Anuko Time Tracker two-factor authentication code', +// 'email.2fa_code.body' => "Dear User,\n\nYour two-factor authentication code is:\n\n%s\n\n", + +// Two-factor authentication form. See example at https://timetracker.anuko.com/2fa.php. +// TODO: translate the following. +// 'form.2fa.hint' => 'Check your email for 2FA code and enter it here.', +// 'form.2fa.2fa_code' => '2FA code', + // Resetting Password form. See example at https://timetracker.anuko.com/password_reset.php. 'form.reset_password.message' => 'Pedido para resetar a senha enviado por e-mail.', 'form.reset_password.email_subject' => 'Pedido de alteração de senha no Anuko Time Tracker', -// TODO: English string has changed. "from IP" added. Re-translate the beginning. -// 'form.reset_password.email_body' => "Dear User,\n\nSomeone from IP %s requested your Anuko Time Tracker password reset. Please visit this link if you want to reset your password.\n\n%s\n\nAnuko Time Tracker is an open source time tracking system. Visit https://www.anuko.com for more information.\n\n", -// "IP %s" probably sounds awkward. -'form.reset_password.email_body' => "Prezado usuário,\n\nAlguém, IP %s, solicitou o reset da sua senha do Anuko Time Tracker. Entre nete link para resetar a sua senha.\n\n%s\n\nAnuko Time Tracker é um sistema de código abertois, de rastreamento do tempo. Visite https://www.anuko.com para mais informações.\n\n", +'form.reset_password.email_body' => "Caro usuário,\n\nAlguém do IP %s solicitou a redefinição da senha do Anuko Time Tracker. Visite este link se quiser redefinir sua senha.\n\n%s\n\nAnuko Time Tracker é um sistema de rastreamento de apontamentos de código aberto. Visite https://www.anuko.com para obter mais informações.\n\n", // Changing Password form. See example at https://timetracker.anuko.com/password_change.php?ref=1. 'form.change_password.tip' => 'Entre com a nova senha e clique em Salvar.', @@ -451,36 +392,36 @@ 'form.time.duration_format' => '(hh:mm ou 0.0h)', 'form.time.billable' => 'Faturável', 'form.time.uncompleted' => 'Incompleta', -// TODO: translate the following. -// 'form.time.remaining_quota' => 'Remaining quota', -// 'form.time.over_quota' => 'Over quota', -// 'form.time.remaining_balance' => 'Remaining balance', -// 'form.time.over_balance' => 'Over balance', +'form.time.remaining_quota' => 'Cota restante', +'form.time.over_quota' => 'Acima da cota', +'form.time.remaining_balance' => 'Saldo restante', +'form.time.over_balance' => 'Saldo excedente', // Editing Time Record form. See example at https://timetracker.anuko.com/time_edit.php (get there by editing an uncompleted time record). -'form.time_edit.uncompleted' => 'Eesta entrada foi salva somente com hora de início. Não é um erro.', +'form.time_edit.uncompleted' => 'Esta entrada foi salva somente com hora de início. Não é um erro.', // Week view form. See example at https://timetracker.anuko.com/week.php. -// TODO: translate the following. -// 'form.week.new_entry' => 'New entry', +'form.week.new_entry' => 'Nova entrada', -// Reports form. See example at https://timetracker.anuko.com/reports.php +// Reports form. See example at https://timetracker.anuko.com/reports.php. 'form.reports.save_as_favorite' => 'Guardar como favorito', 'form.reports.confirm_delete' => 'Tem certeza que deseja remover este relatório dos favoritos?', 'form.reports.include_billable' => 'faturável', 'form.reports.include_not_billable' => 'não faturável', 'form.reports.include_invoiced' => 'faturado', 'form.reports.include_not_invoiced' => 'não faturado', -// TODO: translate the following. -// 'form.reports.include_assigned' => 'assigned', -// 'form.reports.include_not_assigned' => 'not assigned', -// 'form.reports.include_pending' => 'pending', +'form.reports.include_assigned' => 'atribuído', +'form.reports.include_not_assigned' => 'não atribuído', +'form.reports.include_pending' => 'pendente', 'form.reports.select_period' => 'Selecione o período de tempo', 'form.reports.set_period' => 'ou selecionar datas', +// TODO: translate the following. +// 'form.reports.note_containing' => 'Note containing', 'form.reports.show_fields' => 'Exibir campos', +'form.reports.time_fields' => 'Campos de tempo', +'form.reports.user_fields' => 'Campos de usuário', // TODO: translate the following. -// 'form.reports.time_fields' => 'Time fields', -// 'form.reports.user_fields' => 'User fields', +// 'form.reports.project_fields' => 'Project fields', 'form.reports.group_by' => 'Agrupar por', 'form.reports.group_by_no' => '--- sem agrupar ---', 'form.reports.group_by_date' => 'data', @@ -493,94 +434,88 @@ // (after generating a report at https://timetracker.anuko.com/reports.php). 'form.report.export' => 'Exportar', // TODO: translate the following. -// 'form.report.assign_to_invoice' => 'Assign to invoice', -// 'form.report.assign_to_timesheet' => 'Assign to timesheet', +// 'form.report.per_hour' => 'Per hour', +'form.report.assign_to_invoice' => 'Atribuir a fatura', +'form.report.assign_to_timesheet' => 'Atribuir a planilha de horas', -// Timesheets form. See example at https://timetracker.anuko.com/timesheets.php -// TODO: translate the following. -// 'form.timesheets.active_timesheets' => 'Active Timesheets', -// 'form.timesheets.inactive_timesheets' => 'Inactive Timesheets', +// Timesheets form. See example at https://timetracker.anuko.com/timesheets.php. +'form.timesheets.active_timesheets' => 'Planilhas de horas ativas', +'form.timesheets.inactive_timesheets' => 'Planilhas de horas inativas', -// Templates form. See example at https://timetracker.anuko.com/templates.php -// TODO: translate the following. -// 'form.templates.active_templates' => 'Active Templates', -// 'form.templates.inactive_templates' => 'Inactive Templates', +// Templates form. See example at https://timetracker.anuko.com/templates.php. +'form.templates.active_templates' => 'Modelos ativos', +'form.templates.inactive_templates' => 'Modelos inativos', -// Invoice form. See example at https://timetracker.anuko.com/invoice.php -// (you can get to this form after generating a report). +// Invoice form. See example at https://timetracker.anuko.com/invoice_view.php +// (you can get to this form after generating an invoice). 'form.invoice.number' => 'Número da fatura', 'form.invoice.person' => 'Pessoa', -// Deleting Invoice form. See example at https://timetracker.anuko.com/invoice_delete.php +// Deleting Invoice form. See example at https://timetracker.anuko.com/invoice_delete.php. 'form.invoice.invoice_to_delete' => 'Fatura a ser apagada', 'form.invoice.invoice_entries' => 'Entradas de fatura', -// TODO: translate the following. -// 'form.invoice.confirm_deleting_entries' => 'Please confirm deleting invoice entries from Time Tracker.', +'form.invoice.confirm_deleting_entries' => 'Confirme a exclusão das entradas de fatura do Time Tracker.', -// Charts form. See example at https://timetracker.anuko.com/charts.php +// Charts form. See example at https://timetracker.anuko.com/charts.php. 'form.charts.interval' => 'Intervalo', 'form.charts.chart' => 'Gráfico', -// Projects form. See example at https://timetracker.anuko.com/projects.php +// Projects form. See example at https://timetracker.anuko.com/projects.php. 'form.projects.active_projects' => 'Projetos ativos', 'form.projects.inactive_projects' => 'Projetos inativos', -// Tasks form. See example at https://timetracker.anuko.com/tasks.php +// Tasks form. See example at https://timetracker.anuko.com/tasks.php. 'form.tasks.active_tasks' => 'Tarefas ativas', 'form.tasks.inactive_tasks' => 'Tarefas inativas', -// Users form. See example at https://timetracker.anuko.com/users.php +// Users form. See example at https://timetracker.anuko.com/users.php. // TODO: translate the following. -// 'form.users.uncompleted_entry' => 'User has an uncompleted time entry', -'form.users.role' => 'Papel', +// 'form.users.uncompleted_entry_today' => 'User has an uncompleted time entry today', +'form.users.uncompleted_entry' => 'O usuário tem uma entrada incompleta', +'form.users.role' => 'Função', 'form.users.manager' => 'Gerente', 'form.users.comanager' => 'Coordenador', 'form.users.rate' => 'Honorário', 'form.users.default_rate' => 'Honorário padrão por hora', -// Editing User form. See example at https://timetracker.anuko.com/user_edit.php -// TODO: translate the following. -// 'form.user_edit.swap_roles' => 'Swap roles', +// Editing User form. See example at https://timetracker.anuko.com/user_edit.php. +'form.user_edit.swap_roles' => 'Alternar funções', -// Roles form. See example at https://timetracker.anuko.com/roles.php -// TODO: translate the following. -// 'form.roles.active_roles' => 'Active Roles', -// 'form.roles.inactive_roles' => 'Inactive Roles', -// 'form.roles.rank' => 'Rank', -// 'form.roles.rights' => 'Rights', -// 'form.roles.assigned' => 'Assigned', -// 'form.roles.not_assigned' => 'Not assigned', - -// Clients form. See example at https://timetracker.anuko.com/clients.php +// Roles form. See example at https://timetracker.anuko.com/roles.php. +'form.roles.active_roles' => 'Funções ativas', +'form.roles.inactive_roles' => 'Funções inativas', +'form.roles.rank' => 'Rank', +'form.roles.rights' => 'Direitos', +'form.roles.assigned' => 'Atribuído', +'form.roles.not_assigned' => 'Não atribuído', + +// Clients form. See example at https://timetracker.anuko.com/clients.php. 'form.clients.active_clients' => 'Clientes ativos', 'form.clients.inactive_clients' => 'Clientes inativos', -// Deleting Client form. See example at https://timetracker.anuko.com/client_delete.php +// Deleting Client form. See example at https://timetracker.anuko.com/client_delete.php. 'form.client.client_to_delete' => 'Cliente a ser apagado', 'form.client.client_entries' => 'Entradas de cliente', -// Exporting Group Data form. See example at https://timetracker.anuko.com/export.php -// TODO: replace "team" with "group" in the string below. -'form.export.hint' => 'Você pode exportar todos os dados da equipe para um arquivo xml. Isto pode ser útil se você estiver migrando os dados para um servidor próprio.', +// Exporting Group Data form. See example at https://timetracker.anuko.com/export.php. +'form.export.hint' => 'Você pode exportar todos os dados do grupo para um arquivo xml. Isto pode ser útil se você estiver migrando os dados para um servidor próprio.', 'form.export.compression' => 'Compressão', 'form.export.compression_none' => 'nenhuma', 'form.export.compression_bzip' => 'bzip', // Importing Group Data form. See example at https://timetracker.anuko.com/import.php (login as admin first). -'form.import.hint' => 'Importar dados de equipe de um arquivo xml.', // TODO: replace "team" with "group". +'form.import.hint' => 'Importar dados do grupo de um arquivo xml.', 'form.import.file' => 'Selecionar arquivo', 'form.import.success' => 'Importação realizada com sucesso.', // Groups form. See example at https://timetracker.anuko.com/admin_groups.php (login as admin first). -// TODO: replace "team" with "group" in the string below (3 places). -'form.groups.hint' => 'Crie uma nova equipe fazendo uma nova conta de gerente.
Você também pode importar os dados de um arquivo xml de outro servidor Anuko Time Tracker (não havendo colisão de usuários).', +'form.groups.hint' => 'Crie um novo grupo fazendo uma nova conta de gerente.
Você também pode importar os dados de um arquivo xml de outro servidor Anuko Time Tracker (não havendo colisão de usuários).', // Group Settings form. See example at https://timetracker.anuko.com/group_edit.php. 'form.group_edit.12_hours' => '12 horas', 'form.group_edit.24_hours' => '24 horas', -// TODO: translate the following. -// 'form.group_edit.display_options' => 'Display options', -// 'form.group_edit.holidays' => 'Holidays', +'form.group_edit.display_options' => 'Opções de exibição', +'form.group_edit.holidays' => 'Feriados', 'form.group_edit.tracking_mode' => 'Modo de acompanhamento', 'form.group_edit.mode_time' => 'tempo', 'form.group_edit.mode_projects' => 'projetos', @@ -589,18 +524,23 @@ 'form.group_edit.type_all' => 'todos', 'form.group_edit.type_start_finish' => 'início e fim', 'form.group_edit.type_duration' => 'duração', +'form.group_edit.punch_mode' => 'Modo punch', // TODO: translate the following. -// 'form.group_edit.punch_mode' => 'Punch mode', -// 'form.group_edit.allow_overlap' => 'Allow overlap', -// 'form.group_edit.future_entries' => 'Future entries', -// 'form.group_edit.uncompleted_indicators' => 'Uncompleted indicators', -// 'form.group_edit.confirm_save' => 'Confirm saving', -// 'form.group_edit.allow_ip' => 'Allow IP', -// 'form.group_edit.advanced_settings' => 'Advanced settings', - -// Deleting Group form. See example at https://timetracker.anuko.com/delete_group.php -// TODO: translate the following. -// 'form.group_delete.hint' => 'Are you sure you want to delete the entire group?', +// 'form.group_edit.one_uncompleted' => 'One uncompleted', +'form.group_edit.allow_overlap' => 'Permitir sobreposição', +'form.group_edit.future_entries' => 'Entradas futuros', +'form.group_edit.uncompleted_indicators' => 'Indicadores incompletos', +'form.group_edit.confirm_save' => 'Confirme o salvamento', +'form.group_edit.advanced_settings' => 'Configurações avançadas', + +// Advanced Group Settings form. See example at https://timetracker.anuko.com/group_advanced_edit.php. +'form.group_advanced_edit.allow_ip' => 'Permitir IP', +// TODO: Translate the following. +// 'form.group_advanced_edit.password_complexity' => 'Password complexity', +// 'form.group_advanced_edit.2fa' => 'Two factor authentication', + +// Deleting Group form. See example at https://timetracker.anuko.com/delete_group.php. +'form.group_delete.hint' => 'Tem certeza de que deseja excluir todo o grupo?', // Mail form. See example at https://timetracker.anuko.com/report_send.php when emailing a report. 'form.mail.to' => 'Para', @@ -610,74 +550,58 @@ 'form.mail.invoice_sent' => 'Fatura enviada.', // Quotas configuration form. See example at https://timetracker.anuko.com/quotas.php after enabling Monthly quotas plugin. -// TODO: translate the following. -// 'form.quota.year' => 'Year', -// 'form.quota.month' => 'Month', -// 'form.quota.workday_hours' => 'Hours in a work day', -// 'form.quota.hint' => 'If values are empty, quotas are calculated automatically based on workday hours and holidays.', +'form.quota.year' => 'Ano', +'form.quota.month' => 'Mês', +'form.quota.workday_hours' => 'Horas em um dia útil', +'form.quota.hint' => 'Se os valores estiverem vazios, as cotas serão calculadas automaticamente com base nas horas de trabalho e feriados.', // Swap roles form. See example at https://timetracker.anuko.com/swap_roles.php. -// TODO: translate the following. -// 'form.swap.hint' => 'Demote yourself to a lower role by swapping roles with someone else. This cannot be undone.', -// 'form.swap.swap_with' => 'Swap roles with', +'form.swap.hint' => 'Rebaixe-se a função inferior trocando funções com outra pessoa. Isto não pode ser desfeito.', +'form.swap.swap_with' => 'Trocar função com', // Work Units configuration form. See example at https://timetracker.anuko.com/work_units.php after enabling Work units plugin. -// TODO: translate the following. -// 'form.work_units.minutes_in_unit' => 'Minutes in unit', -// 'form.work_units.1st_unit_threshold' => '1st unit threshold', +'form.work_units.minutes_in_unit' => 'Minutos em unidade', +'form.work_units.1st_unit_threshold' => 'Limiar da 1ª unidade', // Roles and rights. These strings are used in multiple places. Grouped here to provide consistent translations. -// TODO: translate the following. -// 'role.user.label' => 'User', -// 'role.user.low_case_label' => 'user', -// 'role.user.description' => 'A regular member without management rights.', -// 'role.client.label' => 'Client', -// 'role.client.low_case_label' => 'client', -// 'role.client.description' => 'A client can view its own data.', -// 'role.supervisor.label' => 'Supervisor', -// 'role.supervisor.low_case_label' => 'supervisor', -// 'role.supervisor.description' => 'A person with a small set of management rights.', -// 'role.comanager.label' => 'Co-manager', -// 'role.comanager.low_case_label' => 'co-manager', -// 'role.comanager.description' => 'A person with a big set of management functions.', -// 'role.manager.label' => 'Manager', -// 'role.manager.low_case_label' => 'manager', -// 'role.manager.description' => 'Group manager. Can do most of things for a group.', -// 'role.top_manager.label' => 'Top manager', -// 'role.top_manager.low_case_label' => 'top manager', -// 'role.top_manager.description' => 'Top group manager. Can do everything in a tree of groups.', -// 'role.admin.label' => 'Administrator', -// 'role.admin.low_case_label' => 'administrator', -// 'role.admin.description' => 'Site adminsitrator.', +'role.user.label' => 'Usuário', +'role.user.low_case_label' => 'usuário', +'role.user.description' => 'Um membro regular sem direitos de gestão.', +'role.client.label' => 'Cliente', +'role.client.low_case_label' => 'cliente', +'role.client.description' => 'Um cliente pode visualizar seus próprios dados.', +'role.supervisor.label' => 'Supervisor', +'role.supervisor.low_case_label' => 'supervisor', +'role.supervisor.description' => 'Uma pessoa com um pequeno conjunto de direitos de gerenciamento.', +'role.comanager.label' => 'Co-gerente', +'role.comanager.low_case_label' => 'co-gerente', +'role.comanager.description' => 'Uma pessoa com um grande conjunto de funções de gerenciamento.', +'role.manager.label' => 'Gerente', +'role.manager.low_case_label' => 'gerente', +'role.manager.description' => 'Gerente de grupo. Pode fazer a maioria das coisas para um grupo.', +'role.top_manager.label' => 'Gerente geral', +'role.top_manager.low_case_label' => 'gerente geral', +'role.top_manager.description' => 'Principal gerente de grupo. Pode fazer tudo em uma árvore de grupos.', +'role.admin.label' => 'Administrador', +'role.admin.low_case_label' => 'administrador', +'role.admin.description' => 'Adminsitrador do site/sistema.', // Timesheet View form. See example at https://timetracker.anuko.com/timesheet_view.php. -// TODO: translate the following. -// 'form.timesheet_view.submit_subject' => 'Timesheet approval request', -// 'form.timesheet_view.submit_body' => "A new timesheet requires approval.

User: %s.", -// 'form.timesheet_view.approve_subject' => 'Timesheet approved', -// 'form.timesheet_view.approve_body' => "Your timesheet %s was approved.

%s", -// 'form.timesheet_view.disapprove_subject' => 'Timesheet not approved', -// 'form.timesheet_view.disapprove_body' => "Your timesheet %s was not approved.

%s", +'form.timesheet_view.submit_subject' => 'Solicitação de aprovação de planilha de horas', +'form.timesheet_view.submit_body' => "Uma nova planilha de horas requer aprovação.

Usuário: %s.", +'form.timesheet_view.approve_subject' => 'Planilha de horas aprovada', +'form.timesheet_view.approve_body' => "Sua planilha de horas %s foi aprovada.

%s", +'form.timesheet_view.disapprove_subject' => 'Planilha de horas não aprovada', +'form.timesheet_view.disapprove_body' => "Sua planilha de horas %s não foi aprovada.

%s", // Display Options form. See example at https://timetracker.anuko.com/display_options.php. +'form.display_options.note_on_separate_row' => 'Anotação em linha separada', +'form.display_options.not_complete_days' => 'Dias não completos', // TODO: translate the following. -// 'form.display_options.note_on_separate_row' => 'Note on separate row', -// 'form.display_options.not_complete_days' => 'Not complete days', -// 'form.display_options.custom_css' => 'Custom CSS', - -// Work plugin strings. See example at https://timetracker.anuko.com/work.php +// 'form.display_options.inactive_projects' => 'Inactive projects', +// 'form.display_options.cost_per_hour' => 'Cost per hour', +'form.display_options.custom_css' => 'CSS customizado', // TODO: translate the following. -// 'work.error.work_not_available' => 'Work item is not available.', -// 'work.error.offer_not_available' => 'Offer is not available.', -// 'work.type.one_time' => 'one time', // Work type is "one time job" for well defined work ("do exactly this"). -// 'work.type.ongoing' => 'ongoing', // Work type is "ongoing" for complex jobs (billed by the hour, multiple contractors, etc.) -// 'work.label.own_work' => 'Own work', -// 'work.label.own_offers' => 'Own offers', -// 'work.label.offers' => 'Offers', -// 'work.button.send_message' => 'Send message', -// 'work.button.make_offer' => 'Make offer', -// 'work.button.accept' => 'Accept', -// 'work.button.decline' => 'Decline', -// 'work.title.send_message' => 'Sending Message', -// 'work.msg.message_sent' => 'Message sent.', +// 'form.display_options.custom_translation' => 'Custom translation', ); + diff --git a/WEB-INF/resources/pt.lang.php b/WEB-INF/resources/pt.lang.php index 13912a44d..abcd5793f 100644 --- a/WEB-INF/resources/pt.lang.php +++ b/WEB-INF/resources/pt.lang.php @@ -2,7 +2,8 @@ /* Copyright (c) Anuko International Ltd. https://www.anuko.com License: See license.txt */ -// Note: escape apostrophes with THREE backslashes, like here: choisir l\\\'option. +// Note: escape apostrophes with THREE backslashes, like here: 'choisir l\\\'option'. +// Alternatively: use one backslash and surround by double-quotes: "choisir l\'option". // Other characters (such as double-quotes in http links, etc.) do not have to be escaped. // Note to translators: Please use proper capitalization rules for your language. @@ -29,6 +30,7 @@ // 'menu.plugins' => 'Plugins', 'menu.time' => 'Tempo', // TODO: translate the following. +// 'menu.puncher' => 'Punch', // 'menu.week' => 'Week', // 'menu.expenses' => 'Expenses', 'menu.reports' => 'Relatórios', @@ -41,7 +43,6 @@ 'menu.users' => 'Usuários', // TODO: translate the following. // 'menu.groups' => 'Groups', -// 'menu.subgroups' => 'Subgroups', // 'menu.export' => 'Export', // 'menu.clients' => 'Clients', 'menu.options' => 'Opções', @@ -72,6 +73,8 @@ // 'error.report' => 'Select report.', // 'error.record' => 'Select record.', // 'error.auth' => 'Incorrect login or password.', +// 'error.2fa_code' => 'Invalid 2FA code.', +// 'error.weak_password' => 'Weak password.', // 'error.user_exists' => 'User with this login already exists.', // 'error.object_exists' => 'Object with this name already exists.', // 'error.invoice_exists' => 'Invoice with this number already exists.', @@ -94,7 +97,6 @@ // 'error.user_count' => 'Limit on user count.', // 'error.expired' => 'Expiration date reached.', // 'error.file_storage' => 'File storage server error.', // See comment in English file. -// 'error.remote_work' => 'Remote work server error.', // See comment in English file. // Warning messages. // TODO: translate the following. @@ -125,9 +127,11 @@ // 'button.export' => 'Export group', // 'button.import' => 'Import group', // 'button.close' => 'Close', +// 'button.start' => 'Start', // 'button.stop' => 'Stop', // 'button.approve' => 'Approve', // 'button.disapprove' => 'Disapprove', +// 'button.sync' => 'Sync', // Used in Android app. The meaning is to synchronize offline time records with server. // Labels for controls on forms. Labels in this section are used on multiple forms. // TODO: translate the following. @@ -189,6 +193,7 @@ // 'label.select_none' => 'Deselect all', // 'label.day_view' => 'Day view', // 'label.week_view' => 'Week view', +// 'label.puncher' => 'Puncher', // 'label.id' => 'ID', // 'label.language' => 'Language', // 'label.decimal_mark' => 'Decimal mark', @@ -210,10 +215,6 @@ 'label.required_fields' => '* campos obrigatórios', // TODO: translate the following. // 'label.on_behalf' => 'on behalf of', -'label.role_manager' => '(gerente)', -// TODO: translate the following. -// 'label.role_comanager' => '(co-manager)', -// 'label.role_admin' => '(administrator)', // 'label.page' => 'Page', // 'label.condition' => 'Condition', // 'label.yes' => 'yes', @@ -236,9 +237,9 @@ // 'label.paid_status' => 'Paid status', // 'label.paid' => 'Paid', // 'label.mark_paid' => 'Mark paid', -// 'label.week_menu' => 'Week menu', // 'label.week_note' => 'Week note', // 'label.week_list' => 'Week list', +// 'label.weekends' => 'Weekends', // 'label.work_units' => 'Work units', // 'label.work_units_short' => 'Units', // 'label.totals_only' => 'Totals only', @@ -254,20 +255,8 @@ // 'label.attachments' => 'Attachments', // 'label.files' => 'Files', // 'label.file' => 'File', -// 'label.image' => 'Image', -// 'label.download' => 'Download', // 'label.active_users' => 'Active Users', // 'label.inactive_users' => 'Inactive Users', -// 'label.details' => 'Details', -// 'label.budget' => 'Budget', -// 'label.work' => 'Work', // Table column header for work items, see https://www.anuko.com/time_tracker/what_is/work_plugin.htm -// 'label.offer' => 'Offer', // Table column header for offers, see https://www.anuko.com/time_tracker/what_is/work_plugin.htm -// 'label.contractor' => 'Contractor', // Table column header for offers (contractor is someone who offers to do work). - // Technically, it is either an org name or a combination of org and group names - // because both work items and offers are owned by Time Tracker groups of users. -// 'label.how_to_pay' => 'How to pay', // Label for the "How to pay" field on offers, which allows contractors to specify - // how to pay them, for example: paypal email, check by mail to a specific address, etc. -// 'label.moderator_comment' => 'Moderator comment', // Label for "Moderator comment" field that explains something. // Entity names. We use lower case (in English) because they are used in dropdowns, too. // They are used to associate a custom field with an entity type. @@ -281,9 +270,9 @@ // 'title.error' => 'Error', // 'title.success' => 'Success', 'title.login' => 'Login', -// TODO: translate the following. +// TODO: translate the follolwing. +// 'title.2fa' => 'Two Factor Authentication', // 'title.groups' => 'Groups', -// 'title.subgroups' => 'Subgroups', // 'title.add_group' => 'Adding Group', // 'title.edit_group' => 'Editing Group', // 'title.delete_group' => 'Deleting Group', @@ -293,6 +282,7 @@ // 'title.edit_time_record' => 'Editing Time Record', // 'title.delete_time_record' => 'Deleting Time Record', // 'title.time_files' => 'Time Record Files', +// 'title.puncher' => 'Puncher', // 'title.expenses' => 'Expenses', // 'title.edit_expense' => 'Editing Expense Item', // 'title.delete_expense' => 'Deleting Expense Item', @@ -376,22 +366,6 @@ // 'title.edit_file' => 'Editing File', // 'title.delete_file' => 'Deleting File', // 'title.download_file' => 'Downloading File', -// 'title.work' => 'Work', -// 'title.add_work' => 'Adding Work', -// 'title.edit_work' => 'Editing Work', -// 'title.delete_work' => 'Deleting Work', -// 'title.active_work' => 'Active Work', // Active work items this group outsources to other groups. -// 'title.available_work' => 'Available Work', // Available work items from other organizations. -// 'title.inactive_work' => 'Inactive Work', // Inactive work items this group was outsourcing to other groups. -// 'title.pending_work' => 'Pending Work', // Work items pending moderator approval. -// 'title.offer' => 'Offer', -// 'title.add_offer' => 'Adding Offer', -// 'title.edit_offer' => 'Editing Offer', -// 'title.delete_offer' => 'Deleting Offer', -// 'title.active_offers' => 'Active Offers', // Active offers this group makes available to other groups. -// 'title.available_offers' => 'Available Offers', // Available offers from other organizations. -// 'title.inactive_offers' => 'Inactive Offers', // Inactive offers for group. -// 'title.pending_offers' => 'Pending Offers', // Offers pending moderator approval. // Section for common strings inside combo boxes on forms. Strings shared between forms shall be placed here. // Strings that are used in a single form must go to the specific form section. @@ -423,7 +397,6 @@ // 'dropdown.status_inactive' => 'inactive', // 'dropdown.delete' => 'delete', // 'dropdown.do_not_delete' => 'do not delete', -// 'dropdown.pending_approval' => 'pending approval', // 'dropdown.approved' => 'approved', // 'dropdown.not_approved' => 'not approved', // 'dropdown.paid' => 'paid', @@ -436,6 +409,16 @@ // TODO: translate the following. // 'form.login.about' => 'Anuko Time Tracker is an open source time tracking system.', +// Email subject and body for two-factor authentication. +// TODO: translate the following. +// 'email.2fa_code.subject' => 'Anuko Time Tracker two-factor authentication code', +// 'email.2fa_code.body' => "Dear User,\n\nYour two-factor authentication code is:\n\n%s\n\n", + +// Two-factor authentication form. See example at https://timetracker.anuko.com/2fa.php. +// TODO: translate the following. +// 'form.2fa.hint' => 'Check your email for 2FA code and enter it here.', +// 'form.2fa.2fa_code' => '2FA code', + // Resetting Password form. See example at https://timetracker.anuko.com/password_reset.php. // TODO: translate the following. // 'form.reset_password.message' => 'Password reset request sent by email.', @@ -477,10 +460,13 @@ // 'form.reports.include_pending' => 'pending', 'form.reports.select_period' => 'Selecione o período de tempo', 'form.reports.set_period' => 'ou selecionar datas', +// TODO: translate the following. +// 'form.reports.note_containing' => 'Note containing', 'form.reports.show_fields' => 'Exibir campos', // TODO: translate the following. // 'form.reports.time_fields' => 'Time fields', // 'form.reports.user_fields' => 'User fields', +// 'form.reports.project_fields' => 'Project fields', // 'form.reports.group_by' => 'Group by', // 'form.reports.group_by_no' => '--- no grouping ---', // 'form.reports.group_by_date' => 'date', @@ -493,6 +479,7 @@ // (after generating a report at https://timetracker.anuko.com/reports.php). // TODO: translate the following. // 'form.report.export' => 'Export', +// 'form.report.per_hour' => 'Per hour', // 'form.report.assign_to_invoice' => 'Assign to invoice', // 'form.report.assign_to_timesheet' => 'Assign to timesheet', @@ -506,8 +493,8 @@ // 'form.templates.active_templates' => 'Active Templates', // 'form.templates.inactive_templates' => 'Inactive Templates', -// Invoice form. See example at https://timetracker.anuko.com/invoice.php -// (you can get to this form after generating a report). +// Invoice form. See example at https://timetracker.anuko.com/invoice_view.php +// (you can get to this form after generating an invoice). // TODO: translate the following. // 'form.invoice.number' => 'Invoice number', // 'form.invoice.person' => 'Person', @@ -534,6 +521,7 @@ // Users form. See example at https://timetracker.anuko.com/users.php // TODO: translate the following. +// 'form.users.uncompleted_entry_today' => 'User has an uncompleted time entry today', // 'form.users.uncompleted_entry' => 'User has an uncompleted time entry', // 'form.users.role' => 'Role', 'form.users.manager' => 'Gerente', @@ -597,13 +585,19 @@ // 'form.group_edit.type_start_finish' => 'start and finish', // 'form.group_edit.type_duration' => 'duration', // 'form.group_edit.punch_mode' => 'Punch mode', +// 'form.group_edit.one_uncompleted' => 'One uncompleted', // 'form.group_edit.allow_overlap' => 'Allow overlap', // 'form.group_edit.future_entries' => 'Future entries', // 'form.group_edit.uncompleted_indicators' => 'Uncompleted indicators', // 'form.group_edit.confirm_save' => 'Confirm saving', -// 'form.group_edit.allow_ip' => 'Allow IP', // 'form.group_edit.advanced_settings' => 'Advanced settings', +// Advanced Group Settings form. See example at https://timetracker.anuko.com/group_advanced_edit.php. +// TODO: Translate the following. +// 'form.group_advanced_edit.allow_ip' => 'Allow IP', +// 'form.group_advanced_edit.password_complexity' => 'Password complexity', +// 'form.group_advanced_edit.2fa' => 'Two factor authentication', + // Deleting Group form. See example at https://timetracker.anuko.com/delete_group.php // TODO: translate the following. // 'form.group_delete.hint' => 'Are you sure you want to delete the entire group?', @@ -670,21 +664,8 @@ // TODO: translate the following. // 'form.display_options.note_on_separate_row' => 'Note on separate row', // 'form.display_options.not_complete_days' => 'Not complete days', +// 'form.display_options.inactive_projects' => 'Inactive projects', +// 'form.display_options.cost_per_hour' => 'Cost per hour', // 'form.display_options.custom_css' => 'Custom CSS', - -// Work plugin strings. See example at https://timetracker.anuko.com/work.php -// TODO: translate the following. -// 'work.error.work_not_available' => 'Work item is not available.', -// 'work.error.offer_not_available' => 'Offer is not available.', -// 'work.type.one_time' => 'one time', // Work type is "one time job" for well defined work ("do exactly this"). -// 'work.type.ongoing' => 'ongoing', // Work type is "ongoing" for complex jobs (billed by the hour, multiple contractors, etc.) -// 'work.label.own_work' => 'Own work', -// 'work.label.own_offers' => 'Own offers', -// 'work.label.offers' => 'Offers', -// 'work.button.send_message' => 'Send message', -// 'work.button.make_offer' => 'Make offer', -// 'work.button.accept' => 'Accept', -// 'work.button.decline' => 'Decline', -// 'work.title.send_message' => 'Sending Message', -// 'work.msg.message_sent' => 'Message sent.', +// 'form.display_options.custom_translation' => 'Custom translation', ); diff --git a/WEB-INF/resources/ro.lang.php b/WEB-INF/resources/ro.lang.php index 0c531ad11..03db7da44 100644 --- a/WEB-INF/resources/ro.lang.php +++ b/WEB-INF/resources/ro.lang.php @@ -2,7 +2,8 @@ /* Copyright (c) Anuko International Ltd. https://www.anuko.com License: See license.txt */ -// Note: escape apostrophes with THREE backslashes, like here: choisir l\\\'option. +// Note: escape apostrophes with THREE backslashes, like here: 'choisir l\\\'option'. +// Alternatively: use one backslash and surround by double-quotes: "choisir l\'option". // Other characters (such as double-quotes in http links, etc.) do not have to be escaped. // Note to translators: Please use proper capitalization rules for your language. @@ -29,6 +30,7 @@ // 'menu.plugins' => 'Plugins', 'menu.time' => 'Timpul', // TODO: translate the following. +// 'menu.puncher' => 'Punch', // 'menu.week' => 'Week', // 'menu.expenses' => 'Expenses', 'menu.reports' => 'Rapoarte', @@ -41,7 +43,6 @@ 'menu.users' => 'Utilizatori', // TODO: translate the following. // 'menu.groups' => 'Groups', -// 'menu.subgroups' => 'Subgroups', // 'menu.export' => 'Export', 'menu.clients' => 'Clienti', // TODO: translate the following. @@ -78,6 +79,8 @@ // 'error.record' => 'Select record.', 'error.auth' => 'Nume de utilizator sau parola incorecta.', // TODO: translate the following. +// 'error.2fa_code' => 'Invalid 2FA code.', +// 'error.weak_password' => 'Weak password.', // 'error.user_exists' => 'User with this login already exists.', // 'error.object_exists' => 'Object with this name already exists.', // 'error.invoice_exists' => 'Invoice with this number already exists.', @@ -101,7 +104,6 @@ // 'error.user_count' => 'Limit on user count.', // 'error.expired' => 'Expiration date reached.', // 'error.file_storage' => 'File storage server error.', // See comment in English file. -// 'error.remote_work' => 'Remote work server error.', // See comment in English file. // Warning messages. // TODO: translate the following. @@ -132,9 +134,11 @@ 'button.import' => 'Importa echipa', // TODO: replace "team" with "group". // TODO: translate the following. // 'button.close' => 'Close', +// 'button.start' => 'Start', // 'button.stop' => 'Stop', // 'button.approve' => 'Approve', // 'button.disapprove' => 'Disapprove', +// 'button.sync' => 'Sync', // Used in Android app. The meaning is to synchronize offline time records with server. // Labels for controls on forms. Labels in this section are used on multiple forms. // TODO: translate the following. @@ -199,6 +203,7 @@ // TODO: translate the following. // 'label.day_view' => 'Day view', // 'label.week_view' => 'Week view', +// 'label.puncher' => 'Puncher', 'label.id' => 'ID', // TODO: translate the following. // 'label.language' => 'Language', @@ -220,9 +225,6 @@ // 'label.ldap_hint' => 'Type your Windows login and password in the fields below.', 'label.required_fields' => '* date obligatorii', 'label.on_behalf' => 'in numele', -'label.role_manager' => '(manager)', -'label.role_comanager' => '(co-manager)', -'label.role_admin' => '(administrator)', // TODO: translate the following. // 'label.page' => 'Page', // 'label.condition' => 'Condition', @@ -247,9 +249,9 @@ // 'label.paid_status' => 'Paid status', // 'label.paid' => 'Paid', // 'label.mark_paid' => 'Mark paid', -// 'label.week_menu' => 'Week menu', // 'label.week_note' => 'Week note', // 'label.week_list' => 'Week list', +// 'label.weekends' => 'Weekends', // 'label.work_units' => 'Work units', // 'label.work_units_short' => 'Units', 'label.totals_only' => 'Numai totaluri', @@ -266,20 +268,8 @@ // 'label.attachments' => 'Attachments', // 'label.files' => 'Files', // 'label.file' => 'File', -// 'label.image' => 'Image', -// 'label.download' => 'Download', // 'label.active_users' => 'Active Users', // 'label.inactive_users' => 'Inactive Users', -// 'label.details' => 'Details', -// 'label.budget' => 'Budget', -// 'label.work' => 'Work', // Table column header for work items, see https://www.anuko.com/time_tracker/what_is/work_plugin.htm -// 'label.offer' => 'Offer', // Table column header for offers, see https://www.anuko.com/time_tracker/what_is/work_plugin.htm -// 'label.contractor' => 'Contractor', // Table column header for offers (contractor is someone who offers to do work). - // Technically, it is either an org name or a combination of org and group names - // because both work items and offers are owned by Time Tracker groups of users. -// 'label.how_to_pay' => 'How to pay', // Label for the "How to pay" field on offers, which allows contractors to specify - // how to pay them, for example: paypal email, check by mail to a specific address, etc. -// 'label.moderator_comment' => 'Moderator comment', // Label for "Moderator comment" field that explains something. // Entity names. We use lower case (in English) because they are used in dropdowns, too. // They are used to associate a custom field with an entity type. @@ -296,9 +286,10 @@ // 'title.error' => 'Error', // 'title.success' => 'Success', 'title.login' => 'Autentificare', +// TODO: translate the follolwing. +// 'title.2fa' => 'Two Factor Authentication', 'title.groups' => 'Echipe', // TODO: change "teams" to "groups". // TODO: translate the following. -// 'title.subgroups' => 'Subgroups', // 'title.add_group' => 'Adding Group', // 'title.edit_group' => 'Editing Group', 'title.delete_group' => 'Șterge echipa', // TODO: change "team" to "group". @@ -310,6 +301,7 @@ 'title.delete_time_record' => 'Ștergerea inregistrarii timpului', // TODO: Translate the following. // 'title.time_files' => 'Time Record Files', +// 'title.puncher' => 'Puncher', // 'title.expenses' => 'Expenses', // 'title.edit_expense' => 'Editing Expense Item', // 'title.delete_expense' => 'Deleting Expense Item', @@ -394,22 +386,6 @@ // 'title.edit_file' => 'Editing File', // 'title.delete_file' => 'Deleting File', // 'title.download_file' => 'Downloading File', -// 'title.work' => 'Work', -// 'title.add_work' => 'Adding Work', -// 'title.edit_work' => 'Editing Work', -// 'title.delete_work' => 'Deleting Work', -// 'title.active_work' => 'Active Work', // Active work items this group outsources to other groups. -// 'title.available_work' => 'Available Work', // Available work items from other organizations. -// 'title.inactive_work' => 'Inactive Work', // Inactive work items this group was outsourcing to other groups. -// 'title.pending_work' => 'Pending Work', // Work items pending moderator approval. -// 'title.add_offer' => 'Adding Offer', -// 'title.offer' => 'Offer', -// 'title.edit_offer' => 'Editing Offer', -// 'title.delete_offer' => 'Deleting Offer', -// 'title.active_offers' => 'Active Offers', // Active offers this group makes available to other groups. -// 'title.available_offers' => 'Available Offers', // Available offers from other organizations. -// 'title.inactive_offers' => 'Inactive Offers', // Inactive offers for group. -// 'title.pending_offers' => 'Pending Offers', // Offers pending moderator approval. // Section for common strings inside combo boxes on forms. Strings shared between forms shall be placed here. // Strings that are used in a single form must go to the specific form section. @@ -443,7 +419,6 @@ // TODO: translate the following. // 'dropdown.delete' => 'delete', // 'dropdown.do_not_delete' => 'do not delete', -// 'dropdown.pending_approval' => 'pending approval', // 'dropdown.approved' => 'approved', // 'dropdown.not_approved' => 'not approved', // 'dropdown.paid' => 'paid', @@ -456,6 +431,16 @@ // TODO: translate the following. // 'form.login.about' => 'Anuko Time Tracker is an open source time tracking system.', +// Email subject and body for two-factor authentication. +// TODO: translate the following. +// 'email.2fa_code.subject' => 'Anuko Time Tracker two-factor authentication code', +// 'email.2fa_code.body' => "Dear User,\n\nYour two-factor authentication code is:\n\n%s\n\n", + +// Two-factor authentication form. See example at https://timetracker.anuko.com/2fa.php. +// TODO: translate the following. +// 'form.2fa.hint' => 'Check your email for 2FA code and enter it here.', +// 'form.2fa.2fa_code' => '2FA code', + // Resetting Password form. See example at https://timetracker.anuko.com/password_reset.php. 'form.reset_password.message' => 'Cererea de resetare a parolei a fost trimisa.', // TODO: add "by email" to match the English string. 'form.reset_password.email_subject' => 'Anuko Time Tracker - cerere de resetare a parolei', @@ -498,10 +483,13 @@ // 'form.reports.include_pending' => 'pending', 'form.reports.select_period' => 'Alege perioada', 'form.reports.set_period' => 'sau introdu intervalul de date', +// TODO: translate the following. +// 'form.reports.note_containing' => 'Note containing', 'form.reports.show_fields' => 'Arata campuri', // TODO: translate the following. // 'form.reports.time_fields' => 'Time fields', // 'form.reports.user_fields' => 'User fields', +// 'form.reports.project_fields' => 'Project fields', 'form.reports.group_by' => 'Grupat dupa', 'form.reports.group_by_no' => '--- fara grupare ---', 'form.reports.group_by_date' => 'data', @@ -515,6 +503,7 @@ // (after generating a report at https://timetracker.anuko.com/reports.php). 'form.report.export' => 'Exporta', // TODO: translate the following. +// 'form.report.per_hour' => 'Per hour', // 'form.report.assign_to_invoice' => 'Assign to invoice', // 'form.report.assign_to_timesheet' => 'Assign to timesheet', @@ -528,8 +517,8 @@ // 'form.templates.active_templates' => 'Active Templates', // 'form.templates.inactive_templates' => 'Inactive Templates', -// Invoice form. See example at https://timetracker.anuko.com/invoice.php -// (you can get to this form after generating a report). +// Invoice form. See example at https://timetracker.anuko.com/invoice_view.php +// (you can get to this form after generating an invoice). 'form.invoice.number' => 'Numar factura', 'form.invoice.person' => 'Persoana', @@ -555,6 +544,7 @@ // Users form. See example at https://timetracker.anuko.com/users.php // TODO: translate the following. +// 'form.users.uncompleted_entry_today' => 'User has an uncompleted time entry today', // 'form.users.uncompleted_entry' => 'User has an uncompleted time entry', 'form.users.role' => 'Functie', // TODO: is "Rol" a better term here? 'form.users.manager' => 'Manager', @@ -618,13 +608,19 @@ // 'form.group_edit.type_start_finish' => 'start and finish', // 'form.group_edit.type_duration' => 'duration', // 'form.group_edit.punch_mode' => 'Punch mode', +// 'form.group_edit.one_uncompleted' => 'One uncompleted', // 'form.group_edit.allow_overlap' => 'Allow overlap', // 'form.group_edit.future_entries' => 'Future entries', // 'form.group_edit.uncompleted_indicators' => 'Uncompleted indicators', // 'form.group_edit.confirm_save' => 'Confirm saving', -// 'form.group_edit.allow_ip' => 'Allow IP', // 'form.group_edit.advanced_settings' => 'Advanced settings', +// Advanced Group Settings form. See example at https://timetracker.anuko.com/group_advanced_edit.php. +// TODO: Translate the following. +// 'form.group_advanced_edit.allow_ip' => 'Allow IP', +// 'form.group_advanced_edit.password_complexity' => 'Password complexity', +// 'form.group_advanced_edit.2fa' => 'Two factor authentication', + // Deleting Group form. See example at https://timetracker.anuko.com/delete_group.php // TODO: translate the following. // 'form.group_delete.hint' => 'Are you sure you want to delete the entire group?', @@ -691,21 +687,8 @@ // TODO: translate the following. // 'form.display_options.note_on_separate_row' => 'Note on separate row', // 'form.display_options.not_complete_days' => 'Not complete days', +// 'form.display_options.inactive_projects' => 'Inactive projects', +// 'form.display_options.cost_per_hour' => 'Cost per hour', // 'form.display_options.custom_css' => 'Custom CSS', - -// Work plugin strings. See example at https://timetracker.anuko.com/work.php -// TODO: translate the following. -// 'work.error.work_not_available' => 'Work item is not available.', -// 'work.error.offer_not_available' => 'Offer is not available.', -// 'work.type.one_time' => 'one time', // Work type is "one time job" for well defined work ("do exactly this"). -// 'work.type.ongoing' => 'ongoing', // Work type is "ongoing" for complex jobs (billed by the hour, multiple contractors, etc.) -// 'work.label.own_work' => 'Own work', -// 'work.label.own_offers' => 'Own offers', -// 'work.label.offers' => 'Offers', -// 'work.button.send_message' => 'Send message', -// 'work.button.make_offer' => 'Make offer', -// 'work.button.accept' => 'Accept', -// 'work.button.decline' => 'Decline', -// 'work.title.send_message' => 'Sending Message', -// 'work.msg.message_sent' => 'Message sent.', +// 'form.display_options.custom_translation' => 'Custom translation', ); diff --git a/WEB-INF/resources/ru.lang.php b/WEB-INF/resources/ru.lang.php index db94bf685..6c924df2d 100644 --- a/WEB-INF/resources/ru.lang.php +++ b/WEB-INF/resources/ru.lang.php @@ -2,7 +2,8 @@ /* Copyright (c) Anuko International Ltd. https://www.anuko.com License: See license.txt */ -// Note: escape apostrophes with THREE backslashes, like here: choisir l\\\'option. +// Note: escape apostrophes with THREE backslashes, like here: 'choisir l\\\'option'. +// Alternatively: use one backslash and surround by double-quotes: "choisir l\'option". // Other characters (such as double-quotes in http links, etc.) do not have to be escaped. $i18n_language = 'Russian (Русский)'; @@ -23,6 +24,7 @@ 'menu.group' => 'Группа', 'menu.plugins' => 'Плагины', 'menu.time' => 'Время', +'menu.puncher' => 'Пробить', 'menu.week' => 'Неделя', 'menu.expenses' => 'Расходы', 'menu.reports' => 'Отчёты', @@ -32,7 +34,6 @@ 'menu.tasks' => 'Задачи', 'menu.users' => 'Пользователи', 'menu.groups' => 'Группы', -'menu.subgroups' => 'Подгруппы', 'menu.export' => 'Экспорт', 'menu.clients' => 'Клиенты', 'menu.options' => 'Опции', @@ -59,6 +60,8 @@ 'error.report' => 'Выберите отчёт.', 'error.record' => 'Выберите запись.', 'error.auth' => 'Неправильно введен логин или пароль.', +'error.2fa_code' => 'Неверный код 2FA.', +'error.weak_password' => 'Слабый пароль.', 'error.user_exists' => 'Пользователь с таким логином уже существует.', 'error.object_exists' => 'Объект с таким именем уже есть.', 'error.invoice_exists' => 'Счёт с таким номером уже есть.', @@ -81,7 +84,6 @@ 'error.user_count' => 'Ограничение на количество пользователей.', 'error.expired' => 'Достигнута дата экспирации.', 'error.file_storage' => 'Ошибка сервера для хранения файлов.', -'error.remote_work' => 'Ошибка сервера удаленной работы.', // Warning messages. 'warn.sure' => 'Вы уверены?', @@ -107,9 +109,11 @@ 'button.export' => 'Экспортировать группу', 'button.import' => 'Импортировать группу', 'button.close' => 'Закрыть', +'button.start' => 'Начать', 'button.stop' => 'Завершить', 'button.approve' => 'Одобрить', 'button.disapprove' => 'Не одобрить', +'button.sync' => 'Синхронизовать', // Labels for controls on forms. Labels in this section are used on multiple forms. 'label.menu' => 'Меню', @@ -164,6 +168,7 @@ 'label.select_none' => 'Снять все отметки', 'label.day_view' => 'День', 'label.week_view' => 'Неделя', +'label.puncher' => 'Пробиватель', 'label.id' => 'ID', 'label.language' => 'Язык', 'label.decimal_mark' => 'Десятичный знак', @@ -182,9 +187,6 @@ 'label.ldap_hint' => 'Введите свои Windows логин и пароль в поля ниже.', 'label.required_fields' => '* - обязательные для заполнения поля', 'label.on_behalf' => 'от имени', -'label.role_manager' => '(менеджер)', -'label.role_comanager' => '(ассистент менеджера)', -'label.role_admin' => '(администратор)', 'label.page' => 'Стр', 'label.condition' => 'Условие', 'label.yes' => 'да', @@ -206,9 +208,9 @@ 'label.paid_status' => 'Статус оплаты', 'label.paid' => 'Оплачено', 'label.mark_paid' => 'Отметить оплату', -'label.week_menu' => 'Меню недели', 'label.week_note' => 'Комментарий недели', 'label.week_list' => 'Список недели', +'label.weekends' => 'Выходные', 'label.work_units' => 'Единицы работы', 'label.work_units_short' => 'Единицы', 'label.totals_only' => 'Только итоги', @@ -224,17 +226,8 @@ 'label.attachments' => 'Приложения', 'label.files' => 'Файлы', 'label.file' => 'Файл', -'label.image' => 'Изображение', -'label.download' => 'Скачать', 'label.active_users' => 'Активные пользователи', 'label.inactive_users' => 'Неактивные пользователи', -'label.details' => 'Детали', -'label.budget' => 'Бюджет', -'label.work' => 'Работа', -'label.offer' => 'Предложение', -'label.contractor' => 'Подрядчик', -'label.how_to_pay' => 'Как оплатить', -'label.moderator_comment' => 'Комментарий модератора', // Entity names. We use lower case (in English) because they are used in dropdowns, too. // They are used to associate a custom field with an entity type. @@ -246,8 +239,8 @@ 'title.error' => 'Ошибка', 'title.success' => 'Успех', 'title.login' => 'Вход в систему', +'title.2fa' => 'Двухфакторная аутентификация', 'title.groups' => 'Группы', -'title.subgroups' => 'Подгруппы', 'title.add_group' => 'Добавление группы', 'title.edit_group' => 'Редактирование группы', 'title.delete_group' => 'Удаление группы', @@ -257,6 +250,7 @@ 'title.edit_time_record' => 'Редактирование записи о времени', 'title.delete_time_record' => 'Удаление записи о времени', 'title.time_files' => 'Файлы записи о времени', +'title.puncher' => 'Пробиватель', 'title.expenses' => 'Расходы', 'title.edit_expense' => 'Редактирование предмета расхода', 'title.delete_expense' => 'Удаление предмета расхода', @@ -332,22 +326,6 @@ 'title.edit_file' => 'Редактирование файла', 'title.delete_file' => 'Удаление файла', 'title.download_file' => 'Скачивание файла', -'title.work' => 'Работа', -'title.add_work' => 'Добавление работы', -'title.edit_work' => 'Редактирование работы', -'title.delete_work' => 'Удаление работы', -'title.active_work' => 'Активная работа', -'title.available_work' => 'Имеющаяся работа', -'title.inactive_work' => 'Неактивная работа', -'title.pending_work' => 'Работа в ожидании', -'title.offer' => 'Предложение', -'title.add_offer' => 'Добавление предложения', -'title.edit_offer' => 'Редактирование предложения', -'title.delete_offer' => 'Удаление предложения', -'title.active_offers' => 'Активные предложения', -'title.available_offers' => 'Имеющиеся предложения', -'title.inactive_offers' => 'Неактивные предложения', -'title.pending_offers' => 'Предложения в ожидании', // Section for common strings inside combo boxes on forms. Strings shared between forms shall be placed here. // Strings that are used in a single form must go to the specific form section. @@ -376,7 +354,6 @@ 'dropdown.status_inactive' => 'неактивный', 'dropdown.delete' => 'удалить', 'dropdown.do_not_delete' => 'не удалять', -'dropdown.pending_approval' => 'в ожидании одобрения', 'dropdown.approved' => 'одобрено', 'dropdown.not_approved' => 'не одобрено', 'dropdown.paid' => 'оплачено', @@ -393,6 +370,14 @@ 'form.login.forgot_password' => 'Забыли пароль?', 'form.login.about' => 'Anuko Time Tracker - это открытая (open source) система трекинга рабочего времени.', +// Email subject and body for two-factor authentication. +'email.2fa_code.subject' => 'Anuko Time Tracker код для двухфакторной аутентификации', +'email.2fa_code.body' => "Уважаемый пользователь,\n\nВаш код для двухфакторной аутентификации:\n\n%s\n\n", + +// Two-factor authentication form. See example at https://timetracker.anuko.com/2fa.php. +'form.2fa.hint' => 'Проверьте электронную почту и введите высланный вам 2FA код здесь.', +'form.2fa.2fa_code' => '2FA код', + // Resetting Password form. See example at https://timetracker.anuko.com/password_reset.php. 'form.reset_password.message' => 'Запрос на сброс пароля отослан по e-mail.', 'form.reset_password.email_subject' => 'Сброс пароля к Anuko Time Tracker', @@ -428,9 +413,11 @@ 'form.reports.include_pending' => 'в ожидании', 'form.reports.select_period' => 'Выберите интервал времени', 'form.reports.set_period' => 'или укажите даты', +'form.reports.note_containing' => 'Комментарий содержит', 'form.reports.show_fields' => 'Показывать поля', 'form.reports.time_fields' => 'Поля времени', 'form.reports.user_fields' => 'Поля пользователя', +'form.reports.project_fields' => 'Поля проекта', 'form.reports.group_by' => 'Группировать по', 'form.reports.group_by_no' => '--- без группировки ---', 'form.reports.group_by_date' => 'дате', @@ -442,6 +429,7 @@ // Report form. See example at https://timetracker.anuko.com/report.php // (after generating a report at https://timetracker.anuko.com/reports.php). 'form.report.export' => 'Экспортировать', +'form.report.per_hour' => 'За час', 'form.report.assign_to_invoice' => 'Включить в счёт', 'form.report.assign_to_timesheet' => 'Включить в табель', @@ -453,8 +441,8 @@ 'form.templates.active_templates' => 'Активные шаблоны', 'form.templates.inactive_templates' => 'Неактивные шаблоны', -// Invoice form. See example at https://timetracker.anuko.com/invoice.php -// (you can get to this form after generating a report). +// Invoice form. See example at https://timetracker.anuko.com/invoice_view.php +// (you can get to this form after generating an invoice). 'form.invoice.number' => 'Номер счёта', 'form.invoice.person' => 'Работник', @@ -476,6 +464,7 @@ 'form.tasks.inactive_tasks' => 'Неактивные задачи', // Users form. See example at https://timetracker.anuko.com/users.php +'form.users.uncompleted_entry_today' => 'Пользователь имеет неоконченную запись сегодня', 'form.users.uncompleted_entry' => 'Пользователь имеет неоконченную запись', 'form.users.role' => 'Роль', 'form.users.manager' => 'Менеджер', @@ -530,13 +519,18 @@ 'form.group_edit.type_start_finish' => 'начало и конец', 'form.group_edit.type_duration' => 'длительность', 'form.group_edit.punch_mode' => 'Пробивать время', +'form.group_edit.one_uncompleted' => 'Одна незавершённая', 'form.group_edit.allow_overlap' => 'Возможное перекрывание', 'form.group_edit.future_entries' => 'Будущие записи', 'form.group_edit.uncompleted_indicators' => 'Индикаторы незавершения', 'form.group_edit.confirm_save' => 'Предупреждать при сохранении', -'form.group_edit.allow_ip' => 'Разрешить доступ с IP', 'form.group_edit.advanced_settings' => 'Продвинутые настройки', +// Advanced Group Settings form. See example at https://timetracker.anuko.com/group_advanced_edit.php. +'form.group_advanced_edit.allow_ip' => 'Разрешить доступ с IP', +'form.group_advanced_edit.password_complexity' => 'Сложность пароля', +'form.group_advanced_edit.2fa' => 'Двухфакторная аутентификация', + // Deleting Group form. See example at https://timetracker.anuko.com/delete_group.php 'form.group_delete.hint' => 'Вы уверены, что хотите удалить всю группу?', @@ -595,20 +589,8 @@ // Display Options form. See example at https://timetracker.anuko.com/display_options.php. 'form.display_options.note_on_separate_row' => 'Комментарий в отдельном ряду', 'form.display_options.not_complete_days' => 'Незавершенные дни', +'form.display_options.inactive_projects' => 'Неактивные проекты', +'form.display_options.cost_per_hour' => 'Стоимость за час', 'form.display_options.custom_css' => 'Пользовательская CSS', - -// Work plugin strings. See example at https://timetracker.anuko.com/work.php -'work.error.work_not_available' => 'Работа отсутствует.', -'work.error.offer_not_available' => 'Предложение отсутствует.', -'work.type.one_time' => 'одноразовая', -'work.type.ongoing' => 'постоянная', -'work.label.own_work' => 'Своя работа', -'work.label.own_offers' => 'Свои предложения', -'work.label.offers' => 'Предложения', -'work.button.send_message' => 'Отправить сообщение', -'work.button.make_offer' => 'Сделать предложение', -'work.button.accept' => 'Принять', -'work.button.decline' => 'Отклонить', -'work.title.send_message' => 'Отсылка сообщения', -'work.msg.message_sent' => 'Сообщение отослано.', +'form.display_options.custom_translation' => 'Пользовательский перевод', ); diff --git a/WEB-INF/resources/sk.lang.php b/WEB-INF/resources/sk.lang.php index 57c14a7db..0404fb8d0 100644 --- a/WEB-INF/resources/sk.lang.php +++ b/WEB-INF/resources/sk.lang.php @@ -2,7 +2,8 @@ /* Copyright (c) Anuko International Ltd. https://www.anuko.com License: See license.txt */ -// Note: escape apostrophes with THREE backslashes, like here: choisir l\\\'option. +// Note: escape apostrophes with THREE backslashes, like here: 'choisir l\\\'option'. +// Alternatively: use one backslash and surround by double-quotes: "choisir l\'option". // Other characters (such as double-quotes in http links, etc.) do not have to be escaped. $i18n_language = 'Slovak (Slovenčina)'; @@ -26,6 +27,7 @@ 'menu.plugins' => 'Moduly', 'menu.time' => 'Časový záznam', // TODO: translate the following. +// 'menu.puncher' => 'Punch', // 'menu.week' => 'Week', // 'menu.expenses' => 'Expenses', 'menu.reports' => 'Zostavy', @@ -37,7 +39,6 @@ 'menu.users' => 'Používatelia', // TODO: translate the following. // 'menu.groups' => 'Groups', -// 'menu.subgroups' => 'Subgroups', 'menu.export' => 'Export', 'menu.clients' => 'Klienti', 'menu.options' => 'Nastavenia', @@ -70,6 +71,9 @@ // 'error.report' => 'Select report.', // 'error.record' => 'Select record.', 'error.auth' => 'Nesprávne prihlasovacie meno alebo heslo.', +// TODO: translate the following. +// 'error.2fa_code' => 'Invalid 2FA code.', +// 'error.weak_password' => 'Weak password.', 'error.user_exists' => 'Používateľ s týmto prihlasovacím menom už existuje.', // TODO: translate the following. // 'error.object_exists' => 'Object with this name already exists.', @@ -99,7 +103,6 @@ // 'error.user_count' => 'Limit on user count.', // 'error.expired' => 'Expiration date reached.', // 'error.file_storage' => 'File storage server error.', // See comment in English file. -// 'error.remote_work' => 'Remote work server error.', // See comment in English file. // Warning messages. // TODO: translate the following. @@ -129,9 +132,11 @@ 'button.import' => 'Importovať tím', // TODO: replace "team" with "group". 'button.close' => 'Zatvoriť', // TODO: translate the following. +// 'button.start' => 'Start', // 'button.stop' => 'Stop', // 'button.approve' => 'Approve', // 'button.disapprove' => 'Disapprove', +// 'button.sync' => 'Sync', // Used in Android app. The meaning is to synchronize offline time records with server. // Labels for controls on forms. Labels in this section are used on multiple forms. // TODO: translate the following. @@ -193,6 +198,7 @@ // TODO: translate the following. // 'label.day_view' => 'Day view', // 'label.week_view' => 'Week view', +// 'label.puncher' => 'Puncher', 'label.id' => 'ID', 'label.language' => 'Jazyk', // TODO: translate the following. @@ -212,9 +218,6 @@ 'label.ldap_hint' => 'Zadajte prihlasovacie meno do Windowsu a heslo do polí nižšie.', 'label.required_fields' => '* - povinné polia', 'label.on_behalf' => 'v zastúpení', -'label.role_manager' => '(manažér)', -'label.role_comanager' => '(spolu-manažér)', -'label.role_admin' => '(administrátor)', // TODO: translate the following. // 'label.page' => 'Page', // 'label.condition' => 'Condition', @@ -240,9 +243,9 @@ // 'label.paid_status' => 'Paid status', // 'label.paid' => 'Paid', // 'label.mark_paid' => 'Mark paid', -// 'label.week_menu' => 'Week menu', // 'label.week_note' => 'Week note', // 'label.week_list' => 'Week list', +// 'label.weekends' => 'Weekends', // 'label.work_units' => 'Work units', // 'label.work_units_short' => 'Units', 'label.totals_only' => 'Iba celkové', @@ -259,21 +262,8 @@ // 'label.attachments' => 'Attachments', // 'label.files' => 'Files', // 'label.file' => 'File', -// 'label.image' => 'Image', -// 'label.download' => 'Download', 'label.active_users' => 'Aktívny používatelia', 'label.inactive_users' => 'Neaktívny používatelia', -// TODO: translate the following. -// 'label.details' => 'Details', -// 'label.budget' => 'Budget', -// 'label.work' => 'Work', // Table column header for work items, see https://www.anuko.com/time_tracker/what_is/work_plugin.htm -// 'label.offer' => 'Offer', // Table column header for offers, see https://www.anuko.com/time_tracker/what_is/work_plugin.htm -// 'label.contractor' => 'Contractor', // Table column header for offers (contractor is someone who offers to do work). - // Technically, it is either an org name or a combination of org and group names - // because both work items and offers are owned by Time Tracker groups of users. -// 'label.how_to_pay' => 'How to pay', // Label for the "How to pay" field on offers, which allows contractors to specify - // how to pay them, for example: paypal email, check by mail to a specific address, etc. -// 'label.moderator_comment' => 'Moderator comment', // Label for "Moderator comment" field that explains something. // Entity names. We use lower case (in English) because they are used in dropdowns, too. // They are used to associate a custom field with an entity type. @@ -287,9 +277,10 @@ // TODO: Translate the following. // 'title.success' => 'Success', 'title.login' => 'Prihlásenie', +// TODO: translate the follolwing. +// 'title.2fa' => 'Two Factor Authentication', 'title.groups' => 'Tímy', // TODO: change "teams" to "groups". // TODO: translate the following. -// 'title.subgroups' => 'Subgroups', // 'title.add_group' => 'Adding Group', // 'title.edit_group' => 'Editing Group', 'title.delete_group' => 'Vymazávanie tímu', // TODO: change "team" to "group". @@ -300,6 +291,7 @@ 'title.delete_time_record' => 'Vymazávanie časového záznamu', // TODO: Translate the following. // 'title.time_files' => 'Time Record Files', +// 'title.puncher' => 'Puncher', // 'title.expenses' => 'Expenses', // 'title.edit_expense' => 'Editing Expense Item', // 'title.delete_expense' => 'Deleting Expense Item', @@ -379,22 +371,6 @@ // 'title.edit_file' => 'Editing File', // 'title.delete_file' => 'Deleting File', // 'title.download_file' => 'Downloading File', -// 'title.work' => 'Work', -// 'title.add_work' => 'Adding Work', -// 'title.edit_work' => 'Editing Work', -// 'title.delete_work' => 'Deleting Work', -// 'title.active_work' => 'Active Work', // Active work items this group outsources to other groups. -// 'title.available_work' => 'Available Work', // Available work items from other organizations. -// 'title.inactive_work' => 'Inactive Work', // Inactive work items this group was outsourcing to other groups. -// 'title.pending_work' => 'Pending Work', // Work items pending moderator approval. -// 'title.offer' => 'Offer', -// 'title.add_offer' => 'Adding Offer', -// 'title.edit_offer' => 'Editing Offer', -// 'title.delete_offer' => 'Deleting Offer', -// 'title.active_offers' => 'Active Offers', // Active offers this group makes available to other groups. -// 'title.available_offers' => 'Available Offers', // Available offers from other organizations. -// 'title.inactive_offers' => 'Inactive Offers', // Inactive offers for group. -// 'title.pending_offers' => 'Pending Offers', // Offers pending moderator approval. // Section for common strings inside combo boxes on forms. Strings shared between forms shall be placed here. // Strings that are used in a single form must go to the specific form section. @@ -428,7 +404,6 @@ // TODO: translate the following. // 'dropdown.delete' => 'delete', // 'dropdown.do_not_delete' => 'do not delete', -// 'dropdown.pending_approval' => 'pending approval', // 'dropdown.approved' => 'approved', // 'dropdown.not_approved' => 'not approved', // 'dropdown.paid' => 'paid', @@ -445,6 +420,16 @@ 'form.login.forgot_password' => 'Zabudnuté heslo?', 'form.login.about' => 'Anuko Time Tracker je systém na sledovanie času s otvoreným zdrojovým kódom.', +// Email subject and body for two-factor authentication. +// TODO: translate the following. +// 'email.2fa_code.subject' => 'Anuko Time Tracker two-factor authentication code', +// 'email.2fa_code.body' => "Dear User,\n\nYour two-factor authentication code is:\n\n%s\n\n", + +// Two-factor authentication form. See example at https://timetracker.anuko.com/2fa.php. +// TODO: translate the following. +// 'form.2fa.hint' => 'Check your email for 2FA code and enter it here.', +// 'form.2fa.2fa_code' => '2FA code', + // Resetting Password form. See example at https://timetracker.anuko.com/password_reset.php. 'form.reset_password.message' => 'Žiadosť o obnovenie hesla bola odoslaná e-mailom.', 'form.reset_password.email_subject' => 'Žiadosť o obnovenie hesla do Anuko Time Tracker', @@ -486,10 +471,13 @@ // 'form.reports.include_pending' => 'pending', 'form.reports.select_period' => 'Vyberte časový rozsah', 'form.reports.set_period' => 'alebo nastavte dátumy', +// TODO: translate the following. +// 'form.reports.note_containing' => 'Note containing', 'form.reports.show_fields' => 'Zobraziť polia', // TODO: translate the following. // 'form.reports.time_fields' => 'Time fields', // 'form.reports.user_fields' => 'User fields', +// 'form.reports.project_fields' => 'Project fields', // 'form.reports.group_by' => 'Group by', // 'form.reports.group_by_no' => '--- no grouping ---', 'form.reports.group_by_date' => 'dátum', @@ -502,6 +490,7 @@ // (after generating a report at https://timetracker.anuko.com/reports.php). 'form.report.export' => 'Exportovať', // TODO: translate the following. +// 'form.report.per_hour' => 'Per hour', // 'form.report.assign_to_invoice' => 'Assign to invoice', // 'form.report.assign_to_timesheet' => 'Assign to timesheet', @@ -515,8 +504,8 @@ // 'form.templates.active_templates' => 'Active Templates', // 'form.templates.inactive_templates' => 'Inactive Templates', -// Invoice form. See example at https://timetracker.anuko.com/invoice.php -// (you can get to this form after generating a report). +// Invoice form. See example at https://timetracker.anuko.com/invoice_view.php +// (you can get to this form after generating an invoice). 'form.invoice.number' => 'Číslo faktúry', 'form.invoice.person' => 'Osoba', @@ -540,6 +529,7 @@ // Users form. See example at https://timetracker.anuko.com/users.php // TODO: translate the following. +// 'form.users.uncompleted_entry_today' => 'User has an uncompleted time entry today', // 'form.users.uncompleted_entry' => 'User has an uncompleted time entry', 'form.users.role' => 'Rola', 'form.users.manager' => 'Manažér', @@ -601,13 +591,19 @@ 'form.group_edit.type_duration' => 'trvanie', // TODO: translate the following. // 'form.group_edit.punch_mode' => 'Punch mode', +// 'form.group_edit.one_uncompleted' => 'One uncompleted', // 'form.group_edit.allow_overlap' => 'Allow overlap', // 'form.group_edit.future_entries' => 'Future entries', // 'form.group_edit.uncompleted_indicators' => 'Uncompleted indicators', // 'form.group_edit.confirm_save' => 'Confirm saving', -// 'form.group_edit.allow_ip' => 'Allow IP', // 'form.group_edit.advanced_settings' => 'Advanced settings', +// Advanced Group Settings form. See example at https://timetracker.anuko.com/group_advanced_edit.php. +// TODO: Translate the following. +// 'form.group_advanced_edit.allow_ip' => 'Allow IP', +// 'form.group_advanced_edit.password_complexity' => 'Password complexity', +// 'form.group_advanced_edit.2fa' => 'Two factor authentication', + // Deleting Group form. See example at https://timetracker.anuko.com/delete_group.php // TODO: translate the following. // 'form.group_delete.hint' => 'Are you sure you want to delete the entire group?', @@ -673,21 +669,8 @@ // TODO: translate the following. // 'form.display_options.note_on_separate_row' => 'Note on separate row', // 'form.display_options.not_complete_days' => 'Not complete days', +// 'form.display_options.inactive_projects' => 'Inactive projects', +// 'form.display_options.cost_per_hour' => 'Cost per hour', // 'form.display_options.custom_css' => 'Custom CSS', - -// Work plugin strings. See example at https://timetracker.anuko.com/work.php -// TODO: translate the following. -// 'work.error.work_not_available' => 'Work item is not available.', -// 'work.error.offer_not_available' => 'Offer is not available.', -// 'work.type.one_time' => 'one time', // Work type is "one time job" for well defined work ("do exactly this"). -// 'work.type.ongoing' => 'ongoing', // Work type is "ongoing" for complex jobs (billed by the hour, multiple contractors, etc.) -// 'work.label.own_work' => 'Own work', -// 'work.label.own_offers' => 'Own offers', -// 'work.label.offers' => 'Offers', -// 'work.button.send_message' => 'Send message', -// 'work.button.make_offer' => 'Make offer', -// 'work.button.accept' => 'Accept', -// 'work.button.decline' => 'Decline', -// 'work.title.send_message' => 'Sending Message', -// 'work.msg.message_sent' => 'Message sent.', +// 'form.display_options.custom_translation' => 'Custom translation', ); diff --git a/WEB-INF/resources/sl.lang.php b/WEB-INF/resources/sl.lang.php index 32e200f81..b3153ad79 100644 --- a/WEB-INF/resources/sl.lang.php +++ b/WEB-INF/resources/sl.lang.php @@ -2,7 +2,8 @@ /* Copyright (c) Anuko International Ltd. https://www.anuko.com License: See license.txt */ -// Note: escape apostrophes with THREE backslashes, like here: choisir l\\\'option. +// Note: escape apostrophes with THREE backslashes, like here: 'choisir l\\\'option'. +// Alternatively: use one backslash and surround by double-quotes: "choisir l\'option". // Other characters (such as double-quotes in http links, etc.) do not have to be escaped. // Note to translators: Use proper capitalization rules for your language. @@ -28,6 +29,7 @@ // 'menu.group' => 'Group', // 'menu.plugins' => 'Plugins', // 'menu.time' => 'Time', +// 'menu.puncher' => 'Punch', // 'menu.week' => 'Week', // 'menu.expenses' => 'Expenses', 'menu.reports' => 'Poročila', @@ -39,7 +41,6 @@ // 'menu.tasks' => 'Tasks', // 'menu.users' => 'Users', // 'menu.groups' => 'Groups', -// 'menu.subgroups' => 'Subgroups', // 'menu.export' => 'Export', 'menu.clients' => 'Stranke', 'menu.options' => 'Možnosti', @@ -69,6 +70,8 @@ // 'error.report' => 'Select report.', // 'error.record' => 'Select record.', // 'error.auth' => 'Incorrect login or password.', +// 'error.2fa_code' => 'Invalid 2FA code.', +// 'error.weak_password' => 'Weak password.', // 'error.user_exists' => 'User with this login already exists.', // 'error.object_exists' => 'Object with this name already exists.', // 'error.invoice_exists' => 'Invoice with this number already exists.', @@ -91,7 +94,6 @@ // 'error.user_count' => 'Limit on user count.', // 'error.expired' => 'Expiration date reached.', // 'error.file_storage' => 'File storage server error.', // See comment in English file. -// 'error.remote_work' => 'Remote work server error.', // See comment in English file. // Warning messages. // TODO: translate the following. @@ -122,9 +124,11 @@ 'button.import' => 'Uvozi tim', // TODO: replace "team" with "group". // TODO: translate the following. // 'button.close' => 'Close', +// 'button.start' => 'Start', // 'button.stop' => 'Stop', // 'button.approve' => 'Approve', // 'button.disapprove' => 'Disapprove', +// 'button.sync' => 'Sync', // Used in Android app. The meaning is to synchronize offline time records with server. // Labels for controls on forms. Labels in this section are used on multiple forms. // TODO: translate the following. @@ -186,6 +190,7 @@ // 'label.select_none' => 'Deselect all', // 'label.day_view' => 'Day view', // 'label.week_view' => 'Week view', +// 'label.puncher' => 'Puncher', // 'label.id' => 'ID', // 'label.language' => 'Language', // 'label.decimal_mark' => 'Decimal mark', @@ -206,9 +211,6 @@ // 'label.ldap_hint' => 'Type your Windows login and password in the fields below.', // 'label.required_fields' => '* - required fields', // 'label.on_behalf' => 'on behalf of', -// 'label.role_manager' => '(manager)', -// 'label.role_comanager' => '(co-manager)', -// 'label.role_admin' => '(administrator)', // 'label.page' => 'Page', // 'label.condition' => 'Condition', // 'label.yes' => 'yes', @@ -231,9 +233,9 @@ // 'label.paid_status' => 'Paid status', // 'label.paid' => 'Paid', // 'label.mark_paid' => 'Mark paid', -// 'label.week_menu' => 'Week menu', // 'label.week_note' => 'Week note', // 'label.week_list' => 'Week list', +// 'label.weekends' => 'Weekends', // 'label.work_units' => 'Work units', // 'label.work_units_short' => 'Units', // 'label.totals_only' => 'Totals only', @@ -249,20 +251,8 @@ // 'label.attachments' => 'Attachments', // 'label.files' => 'Files', // 'label.file' => 'File', -// 'label.image' => 'Image', -// 'label.download' => 'Download', // 'label.active_users' => 'Active Users', // 'label.inactive_users' => 'Inactive Users', -// 'label.details' => 'Details', -// 'label.budget' => 'Budget', -// 'label.work' => 'Work', // Table column header for work items, see https://www.anuko.com/time_tracker/what_is/work_plugin.htm -// 'label.offer' => 'Offer', // Table column header for offers, see https://www.anuko.com/time_tracker/what_is/work_plugin.htm -// 'label.contractor' => 'Contractor', // Table column header for offers (contractor is someone who offers to do work). - // Technically, it is either an org name or a combination of org and group names - // because both work items and offers are owned by Time Tracker groups of users. -// 'label.how_to_pay' => 'How to pay', // Label for the "How to pay" field on offers, which allows contractors to specify - // how to pay them, for example: paypal email, check by mail to a specific address, etc. -// 'label.moderator_comment' => 'Moderator comment', // Label for "Moderator comment" field that explains something. // Entity names. We use lower case (in English) because they are used in dropdowns, too. // They are used to associate a custom field with an entity type. @@ -276,9 +266,10 @@ // 'title.error' => 'Error', // 'title.success' => 'Success', 'title.login' => 'Prijava', +// TODO: translate the follolwing. +// 'title.2fa' => 'Two Factor Authentication', 'title.groups' => 'Timi', // TODO: change "teams" to "groups". // TODO: translate the following. -// 'title.subgroups' => 'Subgroups', // 'title.add_group' => 'Adding Group', // 'title.edit_group' => 'Editing Group', // 'title.delete_group' => 'Deleting Group', @@ -289,6 +280,7 @@ // 'title.edit_time_record' => 'Editing Time Record', // 'title.delete_time_record' => 'Deleting Time Record', // 'title.time_files' => 'Time Record Files', +// 'title.puncher' => 'Puncher', // 'title.expenses' => 'Expenses', // 'title.edit_expense' => 'Editing Expense Item', // 'title.delete_expense' => 'Deleting Expense Item', @@ -369,22 +361,6 @@ // 'title.edit_file' => 'Editing File', // 'title.delete_file' => 'Deleting File', // 'title.download_file' => 'Downloading File', -// 'title.work' => 'Work', -// 'title.add_work' => 'Adding Work', -// 'title.edit_work' => 'Editing Work', -// 'title.delete_work' => 'Deleting Work', -// 'title.active_work' => 'Active Work', // Active work items this group outsources to other groups. -// 'title.available_work' => 'Available Work', // Available work items from other organizations. -// 'title.inactive_work' => 'Inactive Work', // Inactive work items this group was outsourcing to other groups. -// 'title.pending_work' => 'Pending Work', // Work items pending moderator approval. -// 'title.offer' => 'Offer', -// 'title.add_offer' => 'Adding Offer', -// 'title.edit_offer' => 'Editing Offer', -// 'title.delete_offer' => 'Deleting Offer', -// 'title.active_offers' => 'Active Offers', // Active offers this group makes available to other groups. -// 'title.available_offers' => 'Available Offers', // Available offers from other organizations. -// 'title.inactive_offers' => 'Inactive Offers', // Inactive offers for group. -// 'title.pending_offers' => 'Pending Offers', // Offers pending moderator approval. // Section for common strings inside combo boxes on forms. Strings shared between forms shall be placed here. // Strings that are used in a single form must go to the specific form section. @@ -415,7 +391,6 @@ // 'dropdown.status_inactive' => 'inactive', // 'dropdown.delete' => 'delete', // 'dropdown.do_not_delete' => 'do not delete', -// 'dropdown.pending_approval' => 'pending approval', // 'dropdown.approved' => 'approved', // 'dropdown.not_approved' => 'not approved', // 'dropdown.paid' => 'paid', @@ -428,6 +403,16 @@ // 'form.login.forgot_password' => 'Forgot password?', // 'form.login.about' => 'Anuko Time Tracker is an open source time tracking system.', +// Email subject and body for two-factor authentication. +// TODO: translate the following. +// 'email.2fa_code.subject' => 'Anuko Time Tracker two-factor authentication code', +// 'email.2fa_code.body' => "Dear User,\n\nYour two-factor authentication code is:\n\n%s\n\n", + +// Two-factor authentication form. See example at https://timetracker.anuko.com/2fa.php. +// TODO: translate the following. +// 'form.2fa.hint' => 'Check your email for 2FA code and enter it here.', +// 'form.2fa.2fa_code' => '2FA code', + // Resetting Password form. See example at https://timetracker.anuko.com/password_reset.php. 'form.reset_password.message' => 'Zahteva za razveljavitev gesla je bila poslana.', // TODO: add "by email" to match the English string. 'form.reset_password.email_subject' => 'Anuko Time Tracker zahteva za razveljavitev gesla', @@ -472,9 +457,11 @@ // 'form.reports.include_pending' => 'pending', // 'form.reports.select_period' => 'Select time period', // 'form.reports.set_period' => 'or set dates', +// 'form.reports.note_containing' => 'Note containing', // 'form.reports.show_fields' => 'Show fields', // 'form.reports.time_fields' => 'Time fields', // 'form.reports.user_fields' => 'User fields', +// 'form.reports.project_fields' => 'Project fields', // 'form.reports.group_by' => 'Group by', // 'form.reports.group_by_no' => '--- no grouping ---', // 'form.reports.group_by_date' => 'date', @@ -488,6 +475,7 @@ // (after generating a report at https://timetracker.anuko.com/reports.php). // TODO: translate the following. // 'form.report.export' => 'Export', +// 'form.report.per_hour' => 'Per hour', // 'form.report.assign_to_invoice' => 'Assign to invoice', // 'form.report.assign_to_timesheet' => 'Assign to timesheet', @@ -501,8 +489,8 @@ // 'form.templates.active_templates' => 'Active Templates', // 'form.templates.inactive_templates' => 'Inactive Templates', -// Invoice form. See example at https://timetracker.anuko.com/invoice.php -// (you can get to this form after generating a report). +// Invoice form. See example at https://timetracker.anuko.com/invoice_view.php +// (you can get to this form after generating an invoice). // TODO: translate the following. // 'form.invoice.number' => 'Invoice number', // 'form.invoice.person' => 'Person', @@ -529,6 +517,7 @@ // Users form. See example at https://timetracker.anuko.com/users.php // TODO: translate the following. +// 'form.users.uncompleted_entry_today' => 'User has an uncompleted time entry today', // 'form.users.uncompleted_entry' => 'User has an uncompleted time entry', // 'form.users.role' => 'Role', // 'form.users.manager' => 'Manager', @@ -591,13 +580,19 @@ // 'form.group_edit.type_start_finish' => 'start and finish', // 'form.group_edit.type_duration' => 'duration', // 'form.group_edit.punch_mode' => 'Punch mode', +// 'form.group_edit.one_uncompleted' => 'One uncompleted', // 'form.group_edit.allow_overlap' => 'Allow overlap', // 'form.group_edit.future_entries' => 'Future entries', // 'form.group_edit.uncompleted_indicators' => 'Uncompleted indicators', // 'form.group_edit.confirm_save' => 'Confirm saving', -// 'form.group_edit.allow_ip' => 'Allow IP', // 'form.group_edit.advanced_settings' => 'Advanced settings', +// Advanced Group Settings form. See example at https://timetracker.anuko.com/group_advanced_edit.php. +// TODO: Translate the following. +// 'form.group_advanced_edit.allow_ip' => 'Allow IP', +// 'form.group_advanced_edit.password_complexity' => 'Password complexity', +// 'form.group_advanced_edit.2fa' => 'Two factor authentication', + // Deleting Group form. See example at https://timetracker.anuko.com/delete_group.php // TODO: translate the following. // 'form.group_delete.hint' => 'Are you sure you want to delete the entire group?', @@ -664,21 +659,8 @@ // TODO: translate the following. // 'form.display_options.note_on_separate_row' => 'Note on separate row', // 'form.display_options.not_complete_days' => 'Not complete days', +// 'form.display_options.inactive_projects' => 'Inactive projects', +// 'form.display_options.cost_per_hour' => 'Cost per hour', // 'form.display_options.custom_css' => 'Custom CSS', - -// Work plugin strings. See example at https://timetracker.anuko.com/work.php -// TODO: translate the following. -// 'work.error.work_not_available' => 'Work item is not available.', -// 'work.error.offer_not_available' => 'Offer is not available.', -// 'work.type.one_time' => 'one time', // Work type is "one time job" for well defined work ("do exactly this"). -// 'work.type.ongoing' => 'ongoing', // Work type is "ongoing" for complex jobs (billed by the hour, multiple contractors, etc.) -// 'work.label.own_work' => 'Own work', -// 'work.label.own_offers' => 'Own offers', -// 'work.label.offers' => 'Offers', -// 'work.button.send_message' => 'Send message', -// 'work.button.make_offer' => 'Make offer', -// 'work.button.accept' => 'Accept', -// 'work.button.decline' => 'Decline', -// 'work.title.send_message' => 'Sending Message', -// 'work.msg.message_sent' => 'Message sent.', +// 'form.display_options.custom_translation' => 'Custom translation', ); diff --git a/WEB-INF/resources/sr.lang.php b/WEB-INF/resources/sr.lang.php index 38cf8565b..6902d9223 100644 --- a/WEB-INF/resources/sr.lang.php +++ b/WEB-INF/resources/sr.lang.php @@ -2,7 +2,8 @@ /* Copyright (c) Anuko International Ltd. https://www.anuko.com License: See license.txt */ -// Note: escape apostrophes with THREE backslashes, like here: choisir l\\\'option. +// Note: escape apostrophes with THREE backslashes, like here: 'choisir l\\\'option'. +// Alternatively: use one backslash and surround by double-quotes: "choisir l\'option". // Other characters (such as double-quotes in http links, etc.) do not have to be escaped. $i18n_language = 'Serbian (Srpski)'; @@ -26,6 +27,7 @@ 'menu.plugins' => 'Dodaci', 'menu.time' => 'Vreme', // TODO: translate the following. +// 'menu.puncher' => 'Punch', // 'menu.week' => 'Week', 'menu.expenses' => 'Troškovi', 'menu.reports' => 'Izveštaji', @@ -37,7 +39,6 @@ 'menu.users' => 'Korisnici', // TODO: translate the following. // 'menu.groups' => 'Groups', -// 'menu.subgroups' => 'Subgroups', 'menu.export' => 'Izvoz', 'menu.clients' => 'Klijenti', 'menu.options' => 'Opcije', @@ -66,6 +67,9 @@ // TODO: translate the following. // 'error.record' => 'Select record.', 'error.auth' => 'Pogrešno korisničko ime ili lozinka.', +// TODO: translate the following. +// 'error.2fa_code' => 'Invalid 2FA code.', +// 'error.weak_password' => 'Weak password.', 'error.user_exists' => 'Korisnik pod ovim imenom već postoji.', // TODO: translate the following. // 'error.object_exists' => 'Object with this name already exists.', @@ -95,7 +99,6 @@ // 'error.user_count' => 'Limit on user count.', // 'error.expired' => 'Expiration date reached.', // 'error.file_storage' => 'File storage server error.', // See comment in English file. -// 'error.remote_work' => 'Remote work server error.', // See comment in English file. // Warning messages. // TODO: translate the following. @@ -123,10 +126,13 @@ 'button.export' => 'Izvezi tim', // TODO: replace "team" with "group". 'button.import' => 'Uvezi tim', // TODO: replace "team" with "group". 'button.close' => 'Zatvori', +// TODO: translate the following. +// 'button.start' => 'Start', 'button.stop' => 'Stani', // TODO: translate the following. // 'button.approve' => 'Approve', // 'button.disapprove' => 'Disapprove', +// 'button.sync' => 'Sync', // Used in Android app. The meaning is to synchronize offline time records with server. // Labels for controls on forms. Labels in this section are used on multiple forms. // TODO: translate the following. @@ -188,6 +194,7 @@ // TODO: translate the following. // 'label.day_view' => 'Day view', // 'label.week_view' => 'Week view', +// 'label.puncher' => 'Puncher', 'label.id' => 'ID', 'label.language' => 'Jezik', 'label.decimal_mark' => 'Decimala', @@ -206,9 +213,6 @@ 'label.ldap_hint' => 'Unesi tvoju Windows prijavu i lozinku u polje ispod.', 'label.required_fields' => '* - obavezna polja', 'label.on_behalf' => 'ispred', -'label.role_manager' => '(menadžer)', -'label.role_comanager' => '(saradnik)', -'label.role_admin' => '(administrator)', 'label.page' => 'Strana', // TODO: translate the following. // 'label.condition' => 'Condition', @@ -235,9 +239,9 @@ // 'label.paid_status' => 'Paid status', // 'label.paid' => 'Paid', // 'label.mark_paid' => 'Mark paid', -// 'label.week_menu' => 'Week menu', // 'label.week_note' => 'Week note', // 'label.week_list' => 'Week list', +// 'label.weekends' => 'Weekends', // 'label.work_units' => 'Work units', 'label.totals_only' => 'Samo zbirno', // TODO: translate the following. @@ -253,21 +257,8 @@ // 'label.attachments' => 'Attachments', // 'label.files' => 'Files', // 'label.file' => 'File', -// 'label.image' => 'Image', -// 'label.download' => 'Download', 'label.active_users' => 'Aktivni korisnik', 'label.inactive_users' => 'Neaktivni korisnik', -// TODO: translate the following. -// 'label.details' => 'Details', -// 'label.budget' => 'Budget', -// 'label.work' => 'Work', // Table column header for work items, see https://www.anuko.com/time_tracker/what_is/work_plugin.htm -// 'label.offer' => 'Offer', // Table column header for offers, see https://www.anuko.com/time_tracker/what_is/work_plugin.htm -// 'label.contractor' => 'Contractor', // Table column header for offers (contractor is someone who offers to do work). - // Technically, it is either an org name or a combination of org and group names - // because both work items and offers are owned by Time Tracker groups of users. -// 'label.how_to_pay' => 'How to pay', // Label for the "How to pay" field on offers, which allows contractors to specify - // how to pay them, for example: paypal email, check by mail to a specific address, etc. -// 'label.moderator_comment' => 'Moderator comment', // Label for "Moderator comment" field that explains something. // Entity names. We use lower case (in English) because they are used in dropdowns, too. // They are used to associate a custom field with an entity type. @@ -281,9 +272,10 @@ // TODO: Translate the following. // 'title.success' => 'Success', 'title.login' => 'Prijava', +// TODO: translate the follolwing. +// 'title.2fa' => 'Two Factor Authentication', 'title.groups' => 'Timovi', // TODO: change "teams" to "groups". // TODO: translate the following. -// 'title.subgroups' => 'Subgroups', // 'title.add_group' => 'Adding Group', 'title.edit_group' => 'Izmeni tim', // TODO: change "team" to "group". 'title.delete_group' => 'Obriši tim', // TODO: change "team" to "group". @@ -294,6 +286,7 @@ 'title.delete_time_record' => 'Obriši unos vremena', // TODO: Translate the following. // 'title.time_files' => 'Time Record Files', +// 'title.puncher' => 'Puncher', 'title.expenses' => 'Troškovi', 'title.edit_expense' => 'Izmeni stavke troškova', 'title.delete_expense' => 'Obriši stavke troškova', @@ -374,22 +367,6 @@ // 'title.edit_file' => 'Editing File', // 'title.delete_file' => 'Deleting File', // 'title.download_file' => 'Downloading File', -// 'title.work' => 'Work', -// 'title.add_work' => 'Adding Work', -// 'title.edit_work' => 'Editing Work', -// 'title.delete_work' => 'Deleting Work', -// 'title.active_work' => 'Active Work', // Active work items this group outsources to other groups. -// 'title.available_work' => 'Available Work', // Available work items from other organizations. -// 'title.inactive_work' => 'Inactive Work', // Inactive work items this group was outsourcing to other groups. -// 'title.pending_work' => 'Pending Work', // Work items pending moderator approval. -// 'title.offer' => 'Offer', -// 'title.add_offer' => 'Adding Offer', -// 'title.edit_offer' => 'Editing Offer', -// 'title.delete_offer' => 'Deleting Offer', -// 'title.active_offers' => 'Active Offers', // Active offers this group makes available to other groups. -// 'title.available_offers' => 'Available Offers', // Available offers from other organizations. -// 'title.inactive_offers' => 'Inactive Offers', // Inactive offers for group. -// 'title.pending_offers' => 'Pending Offers', // Offers pending moderator approval. // Section for common strings inside combo boxes on forms. Strings shared between forms shall be placed here. // Strings that are used in a single form must go to the specific form section. @@ -422,7 +399,6 @@ 'dropdown.delete' => 'obriši', 'dropdown.do_not_delete' => 'nemoj obrisati', // TODO: translate the following. -// 'dropdown.pending_approval' => 'pending approval', // 'dropdown.approved' => 'approved', // 'dropdown.not_approved' => 'not approved', // 'dropdown.paid' => 'paid', @@ -441,6 +417,16 @@ // 'form.login.about' => 'Anuko Time Tracker is an open source time tracking system.', 'form.login.about' => 'Anuko Time Tracker je jednostavan i lak za korišćenje za praćenje radnog vremena.', +// Email subject and body for two-factor authentication. +// TODO: translate the following. +// 'email.2fa_code.subject' => 'Anuko Time Tracker two-factor authentication code', +// 'email.2fa_code.body' => "Dear User,\n\nYour two-factor authentication code is:\n\n%s\n\n", + +// Two-factor authentication form. See example at https://timetracker.anuko.com/2fa.php. +// TODO: translate the following. +// 'form.2fa.hint' => 'Check your email for 2FA code and enter it here.', +// 'form.2fa.2fa_code' => '2FA code', + // Izmena forme za lozinku. Pogledajte primer na https://timetracker.anuko.com/password_reset.php. 'form.reset_password.message' => 'Zahtev za izmenu lozinke je poslat mejlom.', 'form.reset_password.email_subject' => 'Anuko Time Tracker zahtev za izmenu lozinke', @@ -482,10 +468,13 @@ // 'form.reports.include_pending' => 'pending', 'form.reports.select_period' => 'Odaberi vremenski raspon', 'form.reports.set_period' => 'ili podesi datum', +// TODO: translate the following. +// 'form.reports.note_containing' => 'Note containing', 'form.reports.show_fields' => 'Prikaži polja u izveštaju', // TODO: translate the following. // 'form.reports.time_fields' => 'Time fields', // 'form.reports.user_fields' => 'User fields', +// 'form.reports.project_fields' => 'Project fields', 'form.reports.group_by' => 'Grupiši po', 'form.reports.group_by_no' => '--- nemoj grupisati ---', 'form.reports.group_by_date' => 'datum', @@ -498,6 +487,7 @@ // (after generating a report at https://timetracker.anuko.com/reports.php). 'form.report.export' => 'Izvoz', // TODO: translate the following. +// 'form.report.per_hour' => 'Per hour', // 'form.report.assign_to_invoice' => 'Assign to invoice', // 'form.report.assign_to_timesheet' => 'Assign to timesheet', @@ -511,8 +501,8 @@ // 'form.templates.active_templates' => 'Active Templates', // 'form.templates.inactive_templates' => 'Inactive Templates', -// Forma izveštaja. Pogledajte primer na https://timetracker.anuko.com/invoice.php -// (you can get to this form after generating a report). +// Invoice form. See example at https://timetracker.anuko.com/invoice_view.php +// (you can get to this form after generating an invoice). 'form.invoice.number' => 'Broj računa', 'form.invoice.person' => 'Osoba', @@ -536,6 +526,7 @@ // Korisnička forma. Pogledajte primer na https://timetracker.anuko.com/users.php // TODO: translate the following. +// 'form.users.uncompleted_entry_today' => 'User has an uncompleted time entry today', // 'form.users.uncompleted_entry' => 'User has an uncompleted time entry', 'form.users.role' => 'Funkcija', 'form.users.manager' => 'Menadžer', @@ -596,13 +587,19 @@ 'form.group_edit.type_duration' => 'trajanje', // TODO: translate the following. // 'form.group_edit.punch_mode' => 'Punch mode', +// 'form.group_edit.one_uncompleted' => 'One uncompleted', // 'form.group_edit.allow_overlap' => 'Allow overlap', // 'form.group_edit.future_entries' => 'Future entries', // 'form.group_edit.uncompleted_indicators' => 'Uncompleted indicators', // 'form.group_edit.confirm_save' => 'Confirm saving', -// 'form.group_edit.allow_ip' => 'Allow IP', // 'form.group_edit.advanced_settings' => 'Advanced settings', +// Advanced Group Settings form. See example at https://timetracker.anuko.com/group_advanced_edit.php. +// TODO: Translate the following. +// 'form.group_advanced_edit.allow_ip' => 'Allow IP', +// 'form.group_advanced_edit.password_complexity' => 'Password complexity', +// 'form.group_advanced_edit.2fa' => 'Two factor authentication', + // Deleting Group form. See example at https://timetracker.anuko.com/delete_group.php // TODO: translate the following. // 'form.group_delete.hint' => 'Are you sure you want to delete the entire group?', @@ -670,21 +667,8 @@ // TODO: translate the following. // 'form.display_options.note_on_separate_row' => 'Note on separate row', // 'form.display_options.not_complete_days' => 'Not complete days', +// 'form.display_options.inactive_projects' => 'Inactive projects', +// 'form.display_options.cost_per_hour' => 'Cost per hour', // 'form.display_options.custom_css' => 'Custom CSS', - -// Work plugin strings. See example at https://timetracker.anuko.com/work.php -// TODO: translate the following. -// 'work.error.work_not_available' => 'Work item is not available.', -// 'work.error.offer_not_available' => 'Offer is not available.', -// 'work.type.one_time' => 'one time', // Work type is "one time job" for well defined work ("do exactly this"). -// 'work.type.ongoing' => 'ongoing', // Work type is "ongoing" for complex jobs (billed by the hour, multiple contractors, etc.) -// 'work.label.own_work' => 'Own work', -// 'work.label.own_offers' => 'Own offers', -// 'work.label.offers' => 'Offers', -// 'work.button.send_message' => 'Send message', -// 'work.button.make_offer' => 'Make offer', -// 'work.button.accept' => 'Accept', -// 'work.button.decline' => 'Decline', -// 'work.title.send_message' => 'Sending Message', -// 'work.msg.message_sent' => 'Message sent.', +// 'form.display_options.custom_translation' => 'Custom translation', ); diff --git a/WEB-INF/resources/sv.lang.php b/WEB-INF/resources/sv.lang.php index fe05e89a7..dba0fd6f2 100644 --- a/WEB-INF/resources/sv.lang.php +++ b/WEB-INF/resources/sv.lang.php @@ -2,7 +2,8 @@ /* Copyright (c) Anuko International Ltd. https://www.anuko.com License: See license.txt */ -// Note: escape apostrophes with THREE backslashes, like here: choisir l\\\'option. +// Note: escape apostrophes with THREE backslashes, like here: 'choisir l\\\'option'. +// Alternatively: use one backslash and surround by double-quotes: "choisir l\'option". // Other characters (such as double-quotes in http links, etc.) do not have to be escaped. $i18n_language = 'Swedish (Svenska)'; @@ -25,6 +26,7 @@ 'menu.plugins' => 'Tillägg', 'menu.time' => 'Tider', // TODO: translate the following. +// 'menu.puncher' => 'Punch', // 'menu.week' => 'Week', 'menu.expenses' => 'Kostnader', 'menu.reports' => 'Rapporter', @@ -35,8 +37,6 @@ 'menu.tasks' => 'Arbetsuppgifter', 'menu.users' => 'Användare', 'menu.groups' => 'Grupper', -// TODO: translate the following. -// 'menu.subgroups' => 'Subgroups', 'menu.export' => 'Exportera', 'menu.clients' => 'Kunder', 'menu.options' => 'Alternativ', @@ -66,6 +66,9 @@ // TODO: translate the following. // 'error.record' => 'Select record.', 'error.auth' => 'Ogiltigt användarnamn eller lösenord.', +// TODO: translate the following. +// 'error.2fa_code' => 'Invalid 2FA code.', +// 'error.weak_password' => 'Weak password.', 'error.user_exists' => 'Det finns redan en användare med det här användarnamnet.', // TODO: translate the following. // 'error.object_exists' => 'Object with this name already exists.', @@ -94,7 +97,6 @@ // 'error.user_count' => 'Limit on user count.', // 'error.expired' => 'Expiration date reached.', // 'error.file_storage' => 'File storage server error.', // See comment in English file. -// 'error.remote_work' => 'Remote work server error.', // See comment in English file. // Warning messages. // TODO: translate the following. @@ -122,10 +124,13 @@ 'button.export' => 'Exportera grupp', 'button.import' => 'Importera grupp', 'button.close' => 'Stäng', +// TODO: translate the following. +// 'button.start' => 'Start', 'button.stop' => 'Avsluta', // TODO: translate the following. // 'button.approve' => 'Approve', // 'button.disapprove' => 'Disapprove', +// 'button.sync' => 'Sync', // Used in Android app. The meaning is to synchronize offline time records with server. // Labels for controls on forms. Labels in this section are used on multiple forms. // TODO: translate the following. @@ -185,6 +190,7 @@ // TODO: translate the following. // 'label.day_view' => 'Day view', // 'label.week_view' => 'Week view', +// 'label.puncher' => 'Puncher', 'label.id' => 'ID', 'label.language' => 'Språk', 'label.decimal_mark' => 'Decimaltecken', @@ -203,9 +209,6 @@ 'label.ldap_hint' => 'Fyll i ditt användarnamn och lösenord för Windows i fälten nedan.', 'label.required_fields' => '* - Obligatoriska fält', 'label.on_behalf' => 'agerar som', -'label.role_manager' => '(Ansvarig)', -'label.role_comanager' => '(Delansvarig)', -'label.role_admin' => '(Administratör)', 'label.page' => 'Sida', 'label.condition' => 'Villkor', // TODO: translate the following. @@ -231,9 +234,9 @@ // 'label.paid_status' => 'Paid status', // 'label.paid' => 'Paid', // 'label.mark_paid' => 'Mark paid', -// 'label.week_menu' => 'Week menu', // 'label.week_note' => 'Week note', // 'label.week_list' => 'Week list', +// 'label.weekends' => 'Weekends', // 'label.work_units' => 'Work units', // 'label.work_units_short' => 'Units', 'label.totals_only' => 'Visa endast summeringar', @@ -250,21 +253,8 @@ // 'label.attachments' => 'Attachments', // 'label.files' => 'Files', // 'label.file' => 'File', -// 'label.image' => 'Image', -// 'label.download' => 'Download', 'label.active_users' => 'Aktiva användare', 'label.inactive_users' => 'Inaktiva användare', -// TODO: translate the following. -// 'label.details' => 'Details', -// 'label.budget' => 'Budget', -// 'label.work' => 'Work', // Table column header for work items, see https://www.anuko.com/time_tracker/what_is/work_plugin.htm -// 'label.offer' => 'Offer', // Table column header for offers, see https://www.anuko.com/time_tracker/what_is/work_plugin.htm -// 'label.contractor' => 'Contractor', // Table column header for offers (contractor is someone who offers to do work). - // Technically, it is either an org name or a combination of org and group names - // because both work items and offers are owned by Time Tracker groups of users. -// 'label.how_to_pay' => 'How to pay', // Label for the "How to pay" field on offers, which allows contractors to specify - // how to pay them, for example: paypal email, check by mail to a specific address, etc. -// 'label.moderator_comment' => 'Moderator comment', // Label for "Moderator comment" field that explains something. // Entity names. We use lower case (in English) because they are used in dropdowns, too. // They are used to associate a custom field with an entity type. @@ -278,9 +268,10 @@ // TODO: Translate the following. // 'title.success' => 'Success', 'title.login' => 'Logga in', +// TODO: translate the follolwing. +// 'title.2fa' => 'Two Factor Authentication', 'title.groups' => 'Grupper', // TODO: translate the following. -// 'title.subgroups' => 'Subgroups', // 'title.add_group' => 'Adding Group', 'title.edit_group' => 'Redigera grupp', 'title.delete_group' => 'Ta bort grupp', @@ -291,6 +282,7 @@ 'title.delete_time_record' => 'Ta bort tidsregistrering', // TODO: Translate the following. // 'title.time_files' => 'Time Record Files', +// 'title.puncher' => 'Puncher', 'title.expenses' => 'Kostnader', 'title.edit_expense' => 'Redigera kostnad', 'title.delete_expense' => 'Ta bort kostnad', @@ -375,22 +367,6 @@ // 'title.edit_file' => 'Editing File', // 'title.delete_file' => 'Deleting File', // 'title.download_file' => 'Downloading File', -// 'title.work' => 'Work', -// 'title.add_work' => 'Adding Work', -// 'title.edit_work' => 'Editing Work', -// 'title.delete_work' => 'Deleting Work', -// 'title.active_work' => 'Active Work', // Active work items this group outsources to other groups. -// 'title.available_work' => 'Available Work', // Available work items from other organizations. -// 'title.inactive_work' => 'Inactive Work', // Inactive work items this group was outsourcing to other groups. -// 'title.pending_work' => 'Pending Work', // Work items pending moderator approval. -// 'title.offer' => 'Offer', -// 'title.add_offer' => 'Adding Offer', -// 'title.edit_offer' => 'Editing Offer', -// 'title.delete_offer' => 'Deleting Offer', -// 'title.active_offers' => 'Active Offers', // Active offers this group makes available to other groups. -// 'title.available_offers' => 'Available Offers', // Available offers from other organizations. -// 'title.inactive_offers' => 'Inactive Offers', // Inactive offers for group. -// 'title.pending_offers' => 'Pending Offers', // Offers pending moderator approval. // Section for common strings inside combo boxes on forms. Strings shared between forms shall be placed here. // Strings that are used in a single form must go to the specific form section. @@ -421,7 +397,6 @@ 'dropdown.delete' => 'Ta bort', 'dropdown.do_not_delete' => 'Ta inte bort', // TODO: translate the following. -// 'dropdown.pending_approval' => 'pending approval', // 'dropdown.approved' => 'approved', // 'dropdown.not_approved' => 'not approved', // 'dropdown.paid' => 'paid', @@ -440,6 +415,16 @@ // 'form.login.about' => 'Anuko Time Tracker is an open source time tracking system.', 'form.login.about' => 'Anuko Time Tracker är en lättanvänd applikation byggd med öppen källkod som enkelt låter dig spåra och hålla koll på arbetstider.', +// Email subject and body for two-factor authentication. +// TODO: translate the following. +// 'email.2fa_code.subject' => 'Anuko Time Tracker two-factor authentication code', +// 'email.2fa_code.body' => "Dear User,\n\nYour two-factor authentication code is:\n\n%s\n\n", + +// Two-factor authentication form. See example at https://timetracker.anuko.com/2fa.php. +// TODO: translate the following. +// 'form.2fa.hint' => 'Check your email for 2FA code and enter it here.', +// 'form.2fa.2fa_code' => '2FA code', + // Resetting Password form. See example at https://timetracker.anuko.com/password_reset.php. 'form.reset_password.message' => 'Begäran om att återställa lösenordet skickades via e-post.', 'form.reset_password.email_subject' => 'Återställning av lösenord för Anuko Time Tracker begärd', @@ -481,10 +466,13 @@ // 'form.reports.include_pending' => 'pending', 'form.reports.select_period' => 'Välj intervall', 'form.reports.set_period' => 'eller ställ in datum', +// TODO: translate the following. +// 'form.reports.note_containing' => 'Note containing', 'form.reports.show_fields' => 'Visa fält', // TODO: translate the following. // 'form.reports.time_fields' => 'Time fields', // 'form.reports.user_fields' => 'User fields', +// 'form.reports.project_fields' => 'Project fields', 'form.reports.group_by' => 'Gruppera efter', 'form.reports.group_by_no' => '--- Ingen gruppering ---', 'form.reports.group_by_date' => 'Datum', @@ -497,6 +485,7 @@ // (after generating a report at https://timetracker.anuko.com/reports.php). 'form.report.export' => 'Exportera som', // TODO: translate the following. +// 'form.report.per_hour' => 'Per hour', // 'form.report.assign_to_invoice' => 'Assign to invoice', // 'form.report.assign_to_timesheet' => 'Assign to timesheet', @@ -510,8 +499,8 @@ // 'form.templates.active_templates' => 'Active Templates', // 'form.templates.inactive_templates' => 'Inactive Templates', -// Invoice form. See example at https://timetracker.anuko.com/invoice.php -// (you can get to this form after generating a report). +// Invoice form. See example at https://timetracker.anuko.com/invoice_view.php +// (you can get to this form after generating an invoice). 'form.invoice.number' => 'Fakturanummer', 'form.invoice.person' => 'Person', @@ -534,6 +523,8 @@ 'form.tasks.inactive_tasks' => 'Inaktiva arbetsuppgifter', // Users form. See example at https://timetracker.anuko.com/users.php +// TODO: translate the following. +// 'form.users.uncompleted_entry_today' => 'User has an uncompleted time entry today', 'form.users.uncompleted_entry' => 'Användaren har en oavslutad tidsregistrering', 'form.users.role' => 'Roll', 'form.users.manager' => 'Ansvarig', @@ -594,14 +585,20 @@ 'form.group_edit.type_duration' => 'Varaktighet', // TODO: translate the following. // 'form.group_edit.punch_mode' => 'Punch mode', +// 'form.group_edit.one_uncompleted' => 'One uncompleted', // 'form.group_edit.allow_overlap' => 'Allow overlap', // 'form.group_edit.future_entries' => 'Future entries', 'form.group_edit.uncompleted_indicators' => 'Indikatorer för oavslutad registrering', // TODO: translate the following. // 'form.group_edit.confirm_save' => 'Confirm saving', -// 'form.group_edit.allow_ip' => 'Allow IP', // 'form.group_edit.advanced_settings' => 'Advanced settings', +// Advanced Group Settings form. See example at https://timetracker.anuko.com/group_advanced_edit.php. +// TODO: Translate the following. +// 'form.group_advanced_edit.allow_ip' => 'Allow IP', +// 'form.group_advanced_edit.password_complexity' => 'Password complexity', +// 'form.group_advanced_edit.2fa' => 'Two factor authentication', + // Deleting Group form. See example at https://timetracker.anuko.com/delete_group.php // TODO: translate the following. // 'form.group_delete.hint' => 'Are you sure you want to delete the entire group?', @@ -668,21 +665,8 @@ // TODO: translate the following. // 'form.display_options.note_on_separate_row' => 'Note on separate row', // 'form.display_options.not_complete_days' => 'Not complete days', +// 'form.display_options.inactive_projects' => 'Inactive projects', +// 'form.display_options.cost_per_hour' => 'Cost per hour', // 'form.display_options.custom_css' => 'Custom CSS', - -// Work plugin strings. See example at https://timetracker.anuko.com/work.php -// TODO: translate the following. -// 'work.error.work_not_available' => 'Work item is not available.', -// 'work.error.offer_not_available' => 'Offer is not available.', -// 'work.type.one_time' => 'one time', // Work type is "one time job" for well defined work ("do exactly this"). -// 'work.type.ongoing' => 'ongoing', // Work type is "ongoing" for complex jobs (billed by the hour, multiple contractors, etc.) -// 'work.label.own_work' => 'Own work', -// 'work.label.own_offers' => 'Own offers', -// 'work.label.offers' => 'Offers', -// 'work.button.send_message' => 'Send message', -// 'work.button.make_offer' => 'Make offer', -// 'work.button.accept' => 'Accept', -// 'work.button.decline' => 'Decline', -// 'work.title.send_message' => 'Sending Message', -// 'work.msg.message_sent' => 'Message sent.', +// 'form.display_options.custom_translation' => 'Custom translation', ); diff --git a/WEB-INF/resources/tr.lang.php b/WEB-INF/resources/tr.lang.php index 777440e88..f2531ee76 100644 --- a/WEB-INF/resources/tr.lang.php +++ b/WEB-INF/resources/tr.lang.php @@ -2,7 +2,8 @@ /* Copyright (c) Anuko International Ltd. https://www.anuko.com License: See license.txt */ -// Note: escape apostrophes with THREE backslashes, like here: choisir l\\\'option. +// Note: escape apostrophes with THREE backslashes, like here: 'choisir l\\\'option'. +// Alternatively: use one backslash and surround by double-quotes: "choisir l\'option". // Other characters (such as double-quotes in http links, etc.) do not have to be escaped. // Note to translators: Use proper capitalization rules for your language. @@ -29,6 +30,7 @@ // 'menu.plugins' => 'Plugins', 'menu.time' => 'Zaman', // TODO: translate the following. +// 'menu.puncher' => 'Punch', // 'menu.week' => 'Week', // 'menu.expenses' => 'Expenses', 'menu.reports' => 'Raporlar', @@ -41,7 +43,6 @@ 'menu.users' => 'Kullanıcılar', // TODO: translate the following. // 'menu.groups' => 'Groups', -// 'menu.subgroups' => 'Subgroups', // 'menu.export' => 'Export', 'menu.clients' => 'Müşteriler', // TODO: translate the following. @@ -81,6 +82,8 @@ // 'error.record' => 'Select record.', 'error.auth' => 'Hatalı kullanıcı adı veya parola.', // TODO: translate the following. +// 'error.2fa_code' => 'Invalid 2FA code.', +// 'error.weak_password' => 'Weak password.', // 'error.user_exists' => 'User with this login already exists.', // 'error.object_exists' => 'Object with this name already exists.', // 'error.invoice_exists' => 'Invoice with this number already exists.', @@ -104,7 +107,6 @@ // 'error.user_count' => 'Limit on user count.', // 'error.expired' => 'Expiration date reached.', // 'error.file_storage' => 'File storage server error.', // See comment in English file. -// 'error.remote_work' => 'Remote work server error.', // See comment in English file. // Warning messages. // TODO: translate the following. @@ -136,9 +138,11 @@ 'button.import' => 'Ekibi içeri aktar', // TODO: replace "team" with "group". // TODO: translate the following. // 'button.close' => 'Close', +// 'button.start' => 'Start', // 'button.stop' => 'Stop', // 'button.approve' => 'Approve', // 'button.disapprove' => 'Disapprove', +// 'button.sync' => 'Sync', // Used in Android app. The meaning is to synchronize offline time records with server. // Labels for controls on forms. Labels in this section are used on multiple forms. // TODO: translate the following. @@ -206,6 +210,7 @@ // TODO: translate the following. // 'label.day_view' => 'Day view', // 'label.week_view' => 'Week view', +// 'label.puncher' => 'Puncher', 'label.id' => 'ID', // TODO: translate the following. // 'label.language' => 'Language', @@ -227,9 +232,6 @@ // 'label.ldap_hint' => 'Type your Windows login and password in the fields below.', 'label.required_fields' => '* zorunlu bilgi', 'label.on_behalf' => 'adına', -'label.role_manager' => '(yönetici)', -'label.role_comanager' => '(yardımcı yönetici)', -'label.role_admin' => '(sistem yönetici)', // TODO: translate the following. // 'label.page' => 'Page', // 'label.condition' => 'Condition', @@ -254,9 +256,9 @@ // 'label.paid_status' => 'Paid status', // 'label.paid' => 'Paid', // 'label.mark_paid' => 'Mark paid', -// 'label.week_menu' => 'Week menu', // 'label.week_note' => 'Week note', // 'label.week_list' => 'Week list', +// 'label.weekends' => 'Weekends', // 'label.work_units' => 'Work units', // 'label.work_units_short' => 'Units', 'label.totals_only' => 'Sadece toplamlar', @@ -273,20 +275,8 @@ // 'label.attachments' => 'Attachments', // 'label.files' => 'Files', // 'label.file' => 'File', -// 'label.image' => 'Image', -// 'label.download' => 'Download', // 'label.active_users' => 'Active Users', // 'label.inactive_users' => 'Inactive Users', -// 'label.details' => 'Details', -// 'label.budget' => 'Budget', -// 'label.work' => 'Work', // Table column header for work items, see https://www.anuko.com/time_tracker/what_is/work_plugin.htm -// 'label.offer' => 'Offer', // Table column header for offers, see https://www.anuko.com/time_tracker/what_is/work_plugin.htm -// 'label.contractor' => 'Contractor', // Table column header for offers (contractor is someone who offers to do work). - // Technically, it is either an org name or a combination of org and group names - // because both work items and offers are owned by Time Tracker groups of users. -// 'label.how_to_pay' => 'How to pay', // Label for the "How to pay" field on offers, which allows contractors to specify - // how to pay them, for example: paypal email, check by mail to a specific address, etc. -// 'label.moderator_comment' => 'Moderator comment', // Label for "Moderator comment" field that explains something. // Entity names. We use lower case (in English) because they are used in dropdowns, too. // They are used to associate a custom field with an entity type. @@ -305,9 +295,10 @@ // 'title.error' => 'Error', // 'title.success' => 'Success', 'title.login' => 'Giriş', -'title.groups' => 'Ekipler', // TODO: change "teams" to "groups". +// TODO: translate the follolwing. +// 'title.2fa' => 'Two Factor Authentication', + 'title.groups' => 'Ekipler', // TODO: change "teams" to "groups". // TODO: translate the following. -// 'title.subgroups' => 'Subgroups', // 'title.add_group' => 'Adding Group', // 'title.edit_group' => 'Editing Group', 'title.delete_group' => 'Ekibi sil', // TODO: change "team" to "group". @@ -319,6 +310,7 @@ 'title.delete_time_record' => 'Zaman kaydını siliyor', // TODO: Translate the following. // 'title.time_files' => 'Time Record Files', +// 'title.puncher' => 'Puncher', // 'title.expenses' => 'Expenses', // 'title.edit_expense' => 'Editing Expense Item', // 'title.delete_expense' => 'Deleting Expense Item', @@ -403,22 +395,6 @@ // 'title.edit_file' => 'Editing File', // 'title.delete_file' => 'Deleting File', // 'title.download_file' => 'Downloading File', -// 'title.work' => 'Work', -// 'title.add_work' => 'Adding Work', -// 'title.edit_work' => 'Editing Work', -// 'title.delete_work' => 'Deleting Work', -// 'title.active_work' => 'Active Work', // Active work items this group outsources to other groups. -// 'title.available_work' => 'Available Work', // Available work items from other organizations. -// 'title.inactive_work' => 'Inactive Work', // Inactive work items this group was outsourcing to other groups. -// 'title.pending_work' => 'Pending Work', // Work items pending moderator approval. -// 'title.offer' => 'Offer', -// 'title.add_offer' => 'Adding Offer', -// 'title.edit_offer' => 'Editing Offer', -// 'title.delete_offer' => 'Deleting Offer', -// 'title.active_offers' => 'Active Offers', // Active offers this group makes available to other groups. -// 'title.available_offers' => 'Available Offers', // Available offers from other organizations. -// 'title.inactive_offers' => 'Inactive Offers', // Inactive offers for group. -// 'title.pending_offers' => 'Pending Offers', // Offers pending moderator approval. // Section for common strings inside combo boxes on forms. Strings shared between forms shall be placed here. // Strings that are used in a single form must go to the specific form section. @@ -451,7 +427,6 @@ // 'dropdown.status_inactive' => 'inactive', // 'dropdown.delete' => 'delete', // 'dropdown.do_not_delete' => 'do not delete', -// 'dropdown.pending_approval' => 'pending approval', // 'dropdown.approved' => 'approved', // 'dropdown.not_approved' => 'not approved', // 'dropdown.paid' => 'paid', @@ -464,6 +439,16 @@ // TODO: translate the following. // 'form.login.about' => 'Anuko Time Tracker is an open source time tracking system.', +// Email subject and body for two-factor authentication. +// TODO: translate the following. +// 'email.2fa_code.subject' => 'Anuko Time Tracker two-factor authentication code', +// 'email.2fa_code.body' => "Dear User,\n\nYour two-factor authentication code is:\n\n%s\n\n", + +// Two-factor authentication form. See example at https://timetracker.anuko.com/2fa.php. +// TODO: translate the following. +// 'form.2fa.hint' => 'Check your email for 2FA code and enter it here.', +// 'form.2fa.2fa_code' => '2FA code', + // Resetting Password form. See example at https://timetracker.anuko.com/password_reset.php. 'form.reset_password.message' => 'Parola sıfırlama talebi yollandı.', // TODO: add "by email" to match the English string. 'form.reset_password.email_subject' => 'Anuko Time Tracker parola sıfırlama talebi', @@ -509,10 +494,13 @@ // 'form.reports.include_pending' => 'pending', 'form.reports.select_period' => 'Zaman aralığını seç', 'form.reports.set_period' => 'ya da tarihleri belirle', +// TODO: translate the following. +// 'form.reports.note_containing' => 'Note containing', 'form.reports.show_fields' => 'Alanları göster', // TODO: translate the following. // 'form.reports.time_fields' => 'Time fields', // 'form.reports.user_fields' => 'User fields', +// 'form.reports.project_fields' => 'Project fields', 'form.reports.group_by' => 'Gruplandırma kıstas', 'form.reports.group_by_no' => '--- gruplama yok ---', 'form.reports.group_by_date' => 'tarih', @@ -527,6 +515,7 @@ // (after generating a report at https://timetracker.anuko.com/reports.php). // TODO: translate the following. // 'form.report.export' => 'Export', +// 'form.report.per_hour' => 'Per hour', // 'form.report.assign_to_invoice' => 'Assign to invoice', // 'form.report.assign_to_timesheet' => 'Assign to timesheet', @@ -540,8 +529,8 @@ // 'form.templates.active_templates' => 'Active Templates', // 'form.templates.inactive_templates' => 'Inactive Templates', -// Invoice form. See example at https://timetracker.anuko.com/invoice.php -// (you can get to this form after generating a report). +// Invoice form. See example at https://timetracker.anuko.com/invoice_view.php +// (you can get to this form after generating an invoice). 'form.invoice.number' => 'Fatura numarası', 'form.invoice.person' => 'Kişi', @@ -567,6 +556,7 @@ // Users form. See example at https://timetracker.anuko.com/users.php // TODO: translate the following. +// 'form.users.uncompleted_entry_today' => 'User has an uncompleted time entry today', // 'form.users.uncompleted_entry' => 'User has an uncompleted time entry', 'form.users.role' => 'Rol', // TODO: translate the following. @@ -631,13 +621,19 @@ // 'form.group_edit.type_start_finish' => 'start and finish', // 'form.group_edit.type_duration' => 'duration', // 'form.group_edit.punch_mode' => 'Punch mode', +// 'form.group_edit.one_uncompleted' => 'One uncompleted', // 'form.group_edit.allow_overlap' => 'Allow overlap', // 'form.group_edit.future_entries' => 'Future entries', // 'form.group_edit.uncompleted_indicators' => 'Uncompleted indicators', // 'form.group_edit.confirm_save' => 'Confirm saving', -// 'form.group_edit.allow_ip' => 'Allow IP', // 'form.group_edit.advanced_settings' => 'Advanced settings', +// Advanced Group Settings form. See example at https://timetracker.anuko.com/group_advanced_edit.php. +// TODO: Translate the following. +// 'form.group_advanced_edit.allow_ip' => 'Allow IP', +// 'form.group_advanced_edit.password_complexity' => 'Password complexity', +// 'form.group_advanced_edit.2fa' => 'Two factor authentication', + // Deleting Group form. See example at https://timetracker.anuko.com/delete_group.php // TODO: translate the following. // 'form.group_delete.hint' => 'Are you sure you want to delete the entire group?', @@ -704,21 +700,8 @@ // TODO: translate the following. // 'form.display_options.note_on_separate_row' => 'Note on separate row', // 'form.display_options.not_complete_days' => 'Not complete days', +// 'form.display_options.inactive_projects' => 'Inactive projects', +// 'form.display_options.cost_per_hour' => 'Cost per hour', // 'form.display_options.custom_css' => 'Custom CSS', - -// Work plugin strings. See example at https://timetracker.anuko.com/work.php -// TODO: translate the following. -// 'work.error.work_not_available' => 'Work item is not available.', -// 'work.error.offer_not_available' => 'Offer is not available.', -// 'work.type.one_time' => 'one time', // Work type is "one time job" for well defined work ("do exactly this"). -// 'work.type.ongoing' => 'ongoing', // Work type is "ongoing" for complex jobs (billed by the hour, multiple contractors, etc.) -// 'work.label.own_work' => 'Own work', -// 'work.label.own_offers' => 'Own offers', -// 'work.label.offers' => 'Offers', -// 'work.button.send_message' => 'Send message', -// 'work.button.make_offer' => 'Make offer', -// 'work.button.accept' => 'Accept', -// 'work.button.decline' => 'Decline', -// 'work.title.send_message' => 'Sending Message', -// 'work.msg.message_sent' => 'Message sent.', +// 'form.display_options.custom_translation' => 'Custom translation', ); diff --git a/WEB-INF/resources/zh-cn.lang.php b/WEB-INF/resources/zh-cn.lang.php index 01e639bdd..d30515863 100644 --- a/WEB-INF/resources/zh-cn.lang.php +++ b/WEB-INF/resources/zh-cn.lang.php @@ -2,7 +2,8 @@ /* Copyright (c) Anuko International Ltd. https://www.anuko.com License: See license.txt */ -// Note: escape apostrophes with THREE backslashes, like here: choisir l\\\'option. +// Note: escape apostrophes with THREE backslashes, like here: 'choisir l\\\'option'. +// Alternatively: use one backslash and surround by double-quotes: "choisir l\'option". // Other characters (such as double-quotes in http links, etc.) do not have to be escaped. $i18n_language = 'Chinese (简体中文)'; @@ -26,6 +27,7 @@ // 'menu.plugins' => 'Plugins', 'menu.time' => '时间记录', // TODO: translate the following. +// 'menu.puncher' => 'Punch', // 'menu.week' => 'Week', 'menu.expenses' => '费用', 'menu.reports' => '报告', @@ -37,7 +39,6 @@ 'menu.users' => '用户', // TODO: translate the following. // 'menu.groups' => 'Groups', -// 'menu.subgroups' => 'Subgroups', 'menu.export' => '导出数据', 'menu.clients' => '客户', 'menu.options' => '选项', @@ -68,6 +69,9 @@ // 'error.report' => 'Select report.', // 'error.record' => 'Select record.', 'error.auth' => '不正确的用户名或密码。', +// TODO: translate the following. +// 'error.2fa_code' => 'Invalid 2FA code.', +// 'error.weak_password' => 'Weak password.', 'error.user_exists' => '该用户登录信息已经存在。', // TODO: translate the following. // 'error.object_exists' => 'Object with this name already exists.', @@ -94,7 +98,6 @@ // 'error.user_count' => 'Limit on user count.', // 'error.expired' => 'Expiration date reached.', // 'error.file_storage' => 'File storage server error.', // See comment in English file. -// 'error.remote_work' => 'Remote work server error.', // See comment in English file. // Warning messages. // TODO: translate the following. @@ -125,9 +128,11 @@ 'button.import' => '导入团队信息', // TODO: replace "team" with "group". 'button.close' => '关闭', // TODO: translate the following. +// 'button.start' => 'Start', // 'button.stop' => 'Stop', // 'button.approve' => 'Approve', // 'button.disapprove' => 'Disapprove', +// 'button.sync' => 'Sync', // Used in Android app. The meaning is to synchronize offline time records with server. // Labels for controls on forms. Labels in this section are used on multiple forms. // TODO: translate the following. @@ -192,6 +197,7 @@ // TODO: translate the following. // 'label.day_view' => 'Day view', // 'label.week_view' => 'Week view', +// 'label.puncher' => 'Puncher', 'label.id' => 'ID号', 'label.language' => '语言', // TODO: translate the following. @@ -214,9 +220,6 @@ 'label.ldap_hint' => '在下面的栏目输入您的Windows用户名密码。', 'label.required_fields' => '* 必填栏目', 'label.on_behalf' => '代表', -'label.role_manager' => '(经理)', -'label.role_comanager' => '(合作经理人)', -'label.role_admin' => '(管理员)', 'label.page' => '页码', // TODO: translate the following. // 'label.condition' => 'Condition', @@ -241,9 +244,9 @@ // 'label.paid_status' => 'Paid status', // 'label.paid' => 'Paid', // 'label.mark_paid' => 'Mark paid', -// 'label.week_menu' => 'Week menu', // 'label.week_note' => 'Week note', // 'label.week_list' => 'Week list', +// 'label.weekends' => 'Weekends', // 'label.work_units' => 'Work units', // 'label.work_units_short' => 'Units', // 'label.totals_only' => 'Totals only', @@ -259,20 +262,8 @@ // 'label.attachments' => 'Attachments', // 'label.files' => 'Files', // 'label.file' => 'File', -// 'label.image' => 'Image', -// 'label.download' => 'Download', // 'label.active_users' => 'Active Users', // 'label.inactive_users' => 'Inactive Users', -// 'label.details' => 'Details', -// 'label.budget' => 'Budget', -// 'label.work' => 'Work', // Table column header for work items, see https://www.anuko.com/time_tracker/what_is/work_plugin.htm -// 'label.offer' => 'Offer', // Table column header for offers, see https://www.anuko.com/time_tracker/what_is/work_plugin.htm -// 'label.contractor' => 'Contractor', // Table column header for offers (contractor is someone who offers to do work). - // Technically, it is either an org name or a combination of org and group names - // because both work items and offers are owned by Time Tracker groups of users. -// 'label.how_to_pay' => 'How to pay', // Label for the "How to pay" field on offers, which allows contractors to specify - // how to pay them, for example: paypal email, check by mail to a specific address, etc. -// 'label.moderator_comment' => 'Moderator comment', // Label for "Moderator comment" field that explains something. // Entity names. We use lower case (in English) because they are used in dropdowns, too. // They are used to associate a custom field with an entity type. @@ -286,9 +277,10 @@ // 'title.error' => 'Error', // 'title.success' => 'Success', 'title.login' => '登录', +// TODO: translate the follolwing. +// 'title.2fa' => 'Two Factor Authentication', 'title.groups' => '团队', // TODO: change "teams" to "groups". // TODO: translate the following. -// 'title.subgroups' => 'Subgroups', // 'title.add_group' => 'Adding Group', // 'title.edit_group' => 'Editing Group', 'title.delete_group' => '删除团队', // TODO: change "team" to "group". @@ -300,6 +292,7 @@ 'title.delete_time_record' => '删除时间记录', // TODO: Translate the following. // 'title.time_files' => 'Time Record Files', +// 'title.puncher' => 'Puncher', // 'title.expenses' => 'Expenses', // 'title.edit_expense' => 'Editing Expense Item', // 'title.delete_expense' => 'Deleting Expense Item', @@ -385,22 +378,6 @@ // 'title.edit_file' => 'Editing File', // 'title.delete_file' => 'Deleting File', // 'title.download_file' => 'Downloading File', -// 'title.work' => 'Work', -// 'title.add_work' => 'Adding Work', -// 'title.edit_work' => 'Editing Work', -// 'title.delete_work' => 'Deleting Work', -// 'title.active_work' => 'Active Work', // Active work items this group outsources to other groups. -// 'title.available_work' => 'Available Work', // Available work items from other organizations. -// 'title.inactive_work' => 'Inactive Work', // Inactive work items this group was outsourcing to other groups. -// 'title.pending_work' => 'Pending Work', // Work items pending moderator approval. -// 'title.offer' => 'Offer', -// 'title.add_offer' => 'Adding Offer', -// 'title.edit_offer' => 'Editing Offer', -// 'title.delete_offer' => 'Deleting Offer', -// 'title.active_offers' => 'Active Offers', // Active offers this group makes available to other groups. -// 'title.available_offers' => 'Available Offers', // Available offers from other organizations. -// 'title.inactive_offers' => 'Inactive Offers', // Inactive offers for group. -// 'title.pending_offers' => 'Pending Offers', // Offers pending moderator approval. // Section for common strings inside combo boxes on forms. Strings shared between forms shall be placed here. // Strings that are used in a single form must go to the specific form section. @@ -432,7 +409,6 @@ // 'dropdown.status_inactive' => 'inactive', // 'dropdown.delete' => 'delete', // 'dropdown.do_not_delete' => 'do not delete', -// 'dropdown.pending_approval' => 'pending approval', // 'dropdown.approved' => 'approved', // 'dropdown.not_approved' => 'not approved', // 'dropdown.paid' => 'paid', @@ -446,6 +422,16 @@ // 'form.login.about' => 'Anuko Time Tracker is an open source time tracking system.', 'form.login.about' => 'Anuko Time Tracker 是一种简单、易用、开放源代码的实时跟踪系统。', +// Email subject and body for two-factor authentication. +// TODO: translate the following. +// 'email.2fa_code.subject' => 'Anuko Time Tracker two-factor authentication code', +// 'email.2fa_code.body' => "Dear User,\n\nYour two-factor authentication code is:\n\n%s\n\n", + +// Two-factor authentication form. See example at https://timetracker.anuko.com/2fa.php. +// TODO: translate the following. +// 'form.2fa.hint' => 'Check your email for 2FA code and enter it here.', +// 'form.2fa.2fa_code' => '2FA code', + // Resetting Password form. See example at https://timetracker.anuko.com/password_reset.php. 'form.reset_password.message' => '密码重设请求已经发送。', // TODO: Add "by email" to match the English string. 'form.reset_password.email_subject' => 'Anuko时间追踪器密码重设请求', @@ -486,10 +472,13 @@ // 'form.reports.include_pending' => 'pending', 'form.reports.select_period' => '选择时间段', 'form.reports.set_period' => '或设定日期', +// TODO: translate the following. +// 'form.reports.note_containing' => 'Note containing', 'form.reports.show_fields' => '显示栏目', // TODO: translate the following. // 'form.reports.time_fields' => 'Time fields', // 'form.reports.user_fields' => 'User fields', +// 'form.reports.project_fields' => 'Project fields', 'form.reports.group_by' => '分组方式', 'form.reports.group_by_no' => '--- 没有分组 ---', 'form.reports.group_by_date' => '日期', @@ -504,6 +493,7 @@ // (after generating a report at https://timetracker.anuko.com/reports.php). // TODO: translate the following. // 'form.report.export' => 'Export', +// 'form.report.per_hour' => 'Per hour', // 'form.report.assign_to_invoice' => 'Assign to invoice', // 'form.report.assign_to_timesheet' => 'Assign to timesheet', @@ -517,8 +507,8 @@ // 'form.templates.active_templates' => 'Active Templates', // 'form.templates.inactive_templates' => 'Inactive Templates', -// Invoice form. See example at https://timetracker.anuko.com/invoice.php -// (you can get to this form after generating a report). +// Invoice form. See example at https://timetracker.anuko.com/invoice_view.php +// (you can get to this form after generating an invoice). 'form.invoice.number' => '发票号码', 'form.invoice.person' => '人', // TODO: is this correct? @@ -544,6 +534,7 @@ // Users form. See example at https://timetracker.anuko.com/users.php // TODO: translate the following. +// 'form.users.uncompleted_entry_today' => 'User has an uncompleted time entry today', // 'form.users.uncompleted_entry' => 'User has an uncompleted time entry', 'form.users.role' => '角色', 'form.users.manager' => '经理', @@ -605,13 +596,19 @@ // 'form.group_edit.type_start_finish' => 'start and finish', // 'form.group_edit.type_duration' => 'duration', // 'form.group_edit.punch_mode' => 'Punch mode', +// 'form.group_edit.one_uncompleted' => 'One uncompleted', // 'form.group_edit.allow_overlap' => 'Allow overlap', // 'form.group_edit.future_entries' => 'Future entries', // 'form.group_edit.uncompleted_indicators' => 'Uncompleted indicators', // 'form.group_edit.confirm_save' => 'Confirm saving', -// 'form.group_edit.allow_ip' => 'Allow IP', // 'form.group_edit.advanced_settings' => 'Advanced settings', +// Advanced Group Settings form. See example at https://timetracker.anuko.com/group_advanced_edit.php. +// TODO: Translate the following. +// 'form.group_advanced_edit.allow_ip' => 'Allow IP', +// 'form.group_advanced_edit.password_complexity' => 'Password complexity', +// 'form.group_advanced_edit.2fa' => 'Two factor authentication', + // Deleting Group form. See example at https://timetracker.anuko.com/delete_group.php // TODO: translate the following. // 'form.group_delete.hint' => 'Are you sure you want to delete the entire group?', @@ -678,21 +675,8 @@ // TODO: translate the following. // 'form.display_options.note_on_separate_row' => 'Note on separate row', // 'form.display_options.not_complete_days' => 'Not complete days', +// 'form.display_options.inactive_projects' => 'Inactive projects', +// 'form.display_options.cost_per_hour' => 'Cost per hour', // 'form.display_options.custom_css' => 'Custom CSS', - -// Work plugin strings. See example at https://timetracker.anuko.com/work.php -// TODO: translate the following. -// 'work.error.work_not_available' => 'Work item is not available.', -// 'work.error.offer_not_available' => 'Offer is not available.', -// 'work.type.one_time' => 'one time', // Work type is "one time job" for well defined work ("do exactly this"). -// 'work.type.ongoing' => 'ongoing', // Work type is "ongoing" for complex jobs (billed by the hour, multiple contractors, etc.) -// 'work.label.own_work' => 'Own work', -// 'work.label.own_offers' => 'Own offers', -// 'work.label.offers' => 'Offers', -// 'work.button.send_message' => 'Send message', -// 'work.button.make_offer' => 'Make offer', -// 'work.button.accept' => 'Accept', -// 'work.button.decline' => 'Decline', -// 'work.title.send_message' => 'Sending Message', -// 'work.msg.message_sent' => 'Message sent.', +// 'form.display_options.custom_translation' => 'Custom translation', ); diff --git a/WEB-INF/resources/zh-tw.lang.php b/WEB-INF/resources/zh-tw.lang.php index 66dc7099f..890244f66 100644 --- a/WEB-INF/resources/zh-tw.lang.php +++ b/WEB-INF/resources/zh-tw.lang.php @@ -2,7 +2,8 @@ /* Copyright (c) Anuko International Ltd. https://www.anuko.com License: See license.txt */ -// Note: escape apostrophes with THREE backslashes, like here: choisir l\\\'option. +// Note: escape apostrophes with THREE backslashes, like here: 'choisir l\\\'option'. +// Alternatively: use one backslash and surround by double-quotes: "choisir l\'option". // Other characters (such as double-quotes in http links, etc.) do not have to be escaped. $i18n_language = 'Chinese (簡體中文)'; @@ -25,6 +26,7 @@ // 'menu.group' => 'Group', // 'menu.plugins' => 'Plugins', // 'menu.time' => 'Time', +// 'menu.puncher' => 'Punch', // 'menu.week' => 'Week', // 'menu.expenses' => 'Expenses', 'menu.reports' => '報告', @@ -37,7 +39,6 @@ 'menu.users' => '用戶', // TODO: translate the following. // 'menu.groups' => 'Groups', -// 'menu.subgroups' => 'Subgroups', 'menu.export' => '輸出資料', 'menu.clients' => '客戶', 'menu.options' => '選項', @@ -71,6 +72,9 @@ // 'error.report' => 'Select report.', // 'error.record' => 'Select record.', 'error.auth' => '不正確的用戶名或密碼。', +// TODO: translate the following. +// 'error.2fa_code' => 'Invalid 2FA code.', +// 'error.weak_password' => 'Weak password.', 'error.user_exists' => '該使用者登錄資訊已經存在。', // TODO: translate the following. // 'error.object_exists' => 'Object with this name already exists.', @@ -98,7 +102,6 @@ // 'error.user_count' => 'Limit on user count.', // 'error.expired' => 'Expiration date reached.', // 'error.file_storage' => 'File storage server error.', // See comment in English file. -// 'error.remote_work' => 'Remote work server error.', // See comment in English file. // Warning messages. // TODO: translate the following. @@ -129,9 +132,11 @@ 'button.import' => '輸入團隊資訊', // TODO: replace "team" with "group". // TODO: translate the following. // 'button.close' => 'Close', +// 'button.start' => 'Start', // 'button.stop' => 'Stop', // 'button.approve' => 'Approve', // 'button.disapprove' => 'Disapprove', +// 'button.sync' => 'Sync', // Used in Android app. The meaning is to synchronize offline time records with server. // Labels for controls on forms. Labels in this section are used on multiple forms. // TODO: translate the following. @@ -197,6 +202,7 @@ // TODO: translate the following. // 'label.day_view' => 'Day view', // 'label.week_view' => 'Week view', +// 'label.puncher' => 'Puncher', 'label.id' => 'ID號', 'label.language' => '語言', // TODO: translate the following. @@ -219,9 +225,6 @@ 'label.ldap_hint' => '在下麵的欄目輸入您的Windows用戶名密碼。', 'label.required_fields' => '* 必填欄目', 'label.on_behalf' => '代表', -'label.role_manager' => '(經理)', -'label.role_comanager' => '(合作經理人)', -'label.role_admin' => '(管理員)', // TODO: translate the following. // 'label.page' => 'Page', // 'label.condition' => 'Condition', @@ -246,9 +249,9 @@ // 'label.paid_status' => 'Paid status', // 'label.paid' => 'Paid', // 'label.mark_paid' => 'Mark paid', -// 'label.week_menu' => 'Week menu', // 'label.week_note' => 'Week note', // 'label.week_list' => 'Week list', +// 'label.weekends' => 'Weekends', // 'label.work_units' => 'Work units', // 'label.work_units_short' => 'Units', // 'label.totals_only' => 'Totals only', @@ -264,20 +267,8 @@ // 'label.attachments' => 'Attachments', // 'label.files' => 'Files', // 'label.file' => 'File', -// 'label.image' => 'Image', -// 'label.download' => 'Download', // 'label.active_users' => 'Active Users', // 'label.inactive_users' => 'Inactive Users', -// 'label.details' => 'Details', -// 'label.budget' => 'Budget', -// 'label.work' => 'Work', // Table column header for work items, see https://www.anuko.com/time_tracker/what_is/work_plugin.htm -// 'label.offer' => 'Offer', // Table column header for offers, see https://www.anuko.com/time_tracker/what_is/work_plugin.htm -// 'label.contractor' => 'Contractor', // Table column header for offers (contractor is someone who offers to do work). - // Technically, it is either an org name or a combination of org and group names - // because both work items and offers are owned by Time Tracker groups of users. -// 'label.how_to_pay' => 'How to pay', // Label for the "How to pay" field on offers, which allows contractors to specify - // how to pay them, for example: paypal email, check by mail to a specific address, etc. -// 'label.moderator_comment' => 'Moderator comment', // Label for "Moderator comment" field that explains something. // Entity names. We use lower case (in English) because they are used in dropdowns, too. // They are used to associate a custom field with an entity type. @@ -291,9 +282,10 @@ // 'title.error' => 'Error', // 'title.success' => 'Success', 'title.login' => '登錄', +// TODO: translate the follolwing. +// 'title.2fa' => 'Two Factor Authentication', 'title.groups' => '團隊', // TODO: change "teams" to "groups". // TODO: translate the following. -// 'title.subgroups' => 'Subgroups', // 'title.add_group' => 'Adding Group', // 'title.edit_group' => 'Editing Group', 'title.delete_group' => '刪除團隊', // TODO: change "team" to "group". @@ -305,6 +297,7 @@ 'title.delete_time_record' => '刪除時間記錄', // TODO: Translate the following. // 'title.time_files' => 'Time Record Files', +// 'title.puncher' => 'Puncher', // 'title.expenses' => 'Expenses', // 'title.edit_expense' => 'Editing Expense Item', // 'title.delete_expense' => 'Deleting Expense Item', @@ -389,22 +382,6 @@ // 'title.edit_file' => 'Editing File', // 'title.delete_file' => 'Deleting File', // 'title.download_file' => 'Downloading File', -// 'title.work' => 'Work', -// 'title.add_work' => 'Adding Work', -// 'title.edit_work' => 'Editing Work', -// 'title.delete_work' => 'Deleting Work', -// 'title.active_work' => 'Active Work', // Active work items this group outsources to other groups. -// 'title.available_work' => 'Available Work', // Available work items from other organizations. -// 'title.inactive_work' => 'Inactive Work', // Inactive work items this group was outsourcing to other groups. -// 'title.pending_work' => 'Pending Work', // Work items pending moderator approval. -// 'title.offer' => 'Offer', -// 'title.add_offer' => 'Adding Offer', -// 'title.edit_offer' => 'Editing Offer', -// 'title.delete_offer' => 'Deleting Offer', -// 'title.active_offers' => 'Active Offers', // Active offers this group makes available to other groups. -// 'title.available_offers' => 'Available Offers', // Available offers from other organizations. -// 'title.inactive_offers' => 'Inactive Offers', // Inactive offers for group. -// 'title.pending_offers' => 'Pending Offers', // Offers pending moderator approval. // Section for common strings inside combo boxes on forms. Strings shared between forms shall be placed here. // Strings that are used in a single form must go to the specific form section. @@ -436,7 +413,6 @@ // 'dropdown.status_inactive' => 'inactive', // 'dropdown.delete' => 'delete', // 'dropdown.do_not_delete' => 'do not delete', -// 'dropdown.pending_approval' => 'pending approval', // 'dropdown.approved' => 'approved', // 'dropdown.not_approved' => 'not approved', // 'dropdown.paid' => 'paid', @@ -449,6 +425,16 @@ // TODO: translate the following. // 'form.login.about' => 'Anuko Time Tracker is an open source time tracking system.', +// Email subject and body for two-factor authentication. +// TODO: translate the following. +// 'email.2fa_code.subject' => 'Anuko Time Tracker two-factor authentication code', +// 'email.2fa_code.body' => "Dear User,\n\nYour two-factor authentication code is:\n\n%s\n\n", + +// Two-factor authentication form. See example at https://timetracker.anuko.com/2fa.php. +// TODO: translate the following. +// 'form.2fa.hint' => 'Check your email for 2FA code and enter it here.', +// 'form.2fa.2fa_code' => '2FA code', + // Resetting Password form. See example at https://timetracker.anuko.com/password_reset.php. 'form.reset_password.message' => '密碼重設請求已經發送。', // TODO: Add "by email" to match the English string. 'form.reset_password.email_subject' => 'Anuko時間追蹤器密碼重設請求', @@ -489,10 +475,13 @@ // 'form.reports.include_pending' => 'pending', 'form.reports.select_period' => '選擇時間段', 'form.reports.set_period' => '或設定日期', +// TODO: translate the following. +// 'form.reports.note_containing' => 'Note containing', 'form.reports.show_fields' => '顯示欄目', // TODO: translate the following. // 'form.reports.time_fields' => 'Time fields', // 'form.reports.user_fields' => 'User fields', +// 'form.reports.project_fields' => 'Project fields', 'form.reports.group_by' => '分組方式', 'form.reports.group_by_no' => '--- 沒有分組 ---', 'form.reports.group_by_date' => '日期', @@ -507,6 +496,7 @@ // (after generating a report at https://timetracker.anuko.com/reports.php). // TODO: translate the following. // 'form.report.export' => 'Export', +// 'form.report.per_hour' => 'Per hour', // 'form.report.assign_to_invoice' => 'Assign to invoice', // 'form.report.assign_to_timesheet' => 'Assign to timesheet', @@ -520,8 +510,8 @@ // 'form.templates.active_templates' => 'Active Templates', // 'form.templates.inactive_templates' => 'Inactive Templates', -// Invoice form. See example at https://timetracker.anuko.com/invoice.php -// (you can get to this form after generating a report). +// Invoice form. See example at https://timetracker.anuko.com/invoice_view.php +// (you can get to this form after generating an invoice). 'form.invoice.number' => '發票號碼', 'form.invoice.person' => '人', // TODO: is this correct? @@ -547,6 +537,7 @@ // Users form. See example at https://timetracker.anuko.com/users.php // TODO: translate the following. +// 'form.users.uncompleted_entry_today' => 'User has an uncompleted time entry today', // 'form.users.uncompleted_entry' => 'User has an uncompleted time entry', 'form.users.role' => '角色', 'form.users.manager' => '經理', @@ -608,13 +599,19 @@ // 'form.group_edit.type_start_finish' => 'start and finish', // 'form.group_edit.type_duration' => 'duration', // 'form.group_edit.punch_mode' => 'Punch mode', +// 'form.group_edit.one_uncompleted' => 'One uncompleted', // 'form.group_edit.allow_overlap' => 'Allow overlap', // 'form.group_edit.future_entries' => 'Future entries', // 'form.group_edit.uncompleted_indicators' => 'Uncompleted indicators', // 'form.group_edit.confirm_save' => 'Confirm saving', -// 'form.group_edit.allow_ip' => 'Allow IP', // 'form.group_edit.advanced_settings' => 'Advanced settings', +// Advanced Group Settings form. See example at https://timetracker.anuko.com/group_advanced_edit.php. +// TODO: Translate the following. +// 'form.group_advanced_edit.allow_ip' => 'Allow IP', +// 'form.group_advanced_edit.password_complexity' => 'Password complexity', +// 'form.group_advanced_edit.2fa' => 'Two factor authentication', + // Deleting Group form. See example at https://timetracker.anuko.com/delete_group.php // TODO: translate the following. // 'form.group_delete.hint' => 'Are you sure you want to delete the entire group?', @@ -681,21 +678,8 @@ // TODO: translate the following. // 'form.display_options.note_on_separate_row' => 'Note on separate row', // 'form.display_options.not_complete_days' => 'Not complete days', +// 'form.display_options.inactive_projects' => 'Inactive projects', +// 'form.display_options.cost_per_hour' => 'Cost per hour', // 'form.display_options.custom_css' => 'Custom CSS', - -// Work plugin strings. See example at https://timetracker.anuko.com/work.php -// TODO: translate the following. -// 'work.error.work_not_available' => 'Work item is not available.', -// 'work.error.offer_not_available' => 'Offer is not available.', -// 'work.type.one_time' => 'one time', // Work type is "one time job" for well defined work ("do exactly this"). -// 'work.type.ongoing' => 'ongoing', // Work type is "ongoing" for complex jobs (billed by the hour, multiple contractors, etc.) -// 'work.label.own_work' => 'Own work', -// 'work.label.own_offers' => 'Own offers', -// 'work.label.offers' => 'Offers', -// 'work.button.send_message' => 'Send message', -// 'work.button.make_offer' => 'Make offer', -// 'work.button.accept' => 'Accept', -// 'work.button.decline' => 'Decline', -// 'work.title.send_message' => 'Sending Message', -// 'work.msg.message_sent' => 'Message sent.', +// 'form.display_options.custom_translation' => 'Custom translation', ); diff --git a/WEB-INF/templates/2fa.tpl b/WEB-INF/templates/2fa.tpl new file mode 100644 index 000000000..d425cbc11 --- /dev/null +++ b/WEB-INF/templates/2fa.tpl @@ -0,0 +1,30 @@ +{* Copyright (c) Anuko International Ltd. https://www.anuko.com +License: See license.txt *} + +

{$i18n.form.2fa.hint}
+{$forms.twoFactorAuthForm.open} + + + + + + + + + + + + + + + + + + + + + + +
{$forms.twoFactorAuthForm.login.control}
{$forms.twoFactorAuthForm.password.control}
{$forms.twoFactorAuthForm.auth_code.control}
{$forms.twoFactorAuthForm.btn_login.control}
+{$forms.twoFactorAuthForm.close} + diff --git a/WEB-INF/templates/access_denied2.tpl b/WEB-INF/templates/access_denied.tpl similarity index 100% rename from WEB-INF/templates/access_denied2.tpl rename to WEB-INF/templates/access_denied.tpl diff --git a/WEB-INF/templates/admin_group_add2.tpl b/WEB-INF/templates/admin_group_add.tpl similarity index 100% rename from WEB-INF/templates/admin_group_add2.tpl rename to WEB-INF/templates/admin_group_add.tpl diff --git a/WEB-INF/templates/admin_group_delete2.tpl b/WEB-INF/templates/admin_group_delete.tpl similarity index 100% rename from WEB-INF/templates/admin_group_delete2.tpl rename to WEB-INF/templates/admin_group_delete.tpl diff --git a/WEB-INF/templates/admin_group_edit2.tpl b/WEB-INF/templates/admin_group_edit.tpl similarity index 100% rename from WEB-INF/templates/admin_group_edit2.tpl rename to WEB-INF/templates/admin_group_edit.tpl diff --git a/WEB-INF/templates/admin_groups2.tpl b/WEB-INF/templates/admin_groups.tpl similarity index 100% rename from WEB-INF/templates/admin_groups2.tpl rename to WEB-INF/templates/admin_groups.tpl diff --git a/WEB-INF/templates/admin_options2.tpl b/WEB-INF/templates/admin_options.tpl similarity index 100% rename from WEB-INF/templates/admin_options2.tpl rename to WEB-INF/templates/admin_options.tpl diff --git a/WEB-INF/templates/cf_custom_field_add2.tpl b/WEB-INF/templates/cf_custom_field_add.tpl similarity index 100% rename from WEB-INF/templates/cf_custom_field_add2.tpl rename to WEB-INF/templates/cf_custom_field_add.tpl diff --git a/WEB-INF/templates/cf_custom_field_delete2.tpl b/WEB-INF/templates/cf_custom_field_delete.tpl similarity index 100% rename from WEB-INF/templates/cf_custom_field_delete2.tpl rename to WEB-INF/templates/cf_custom_field_delete.tpl diff --git a/WEB-INF/templates/cf_custom_field_edit2.tpl b/WEB-INF/templates/cf_custom_field_edit.tpl similarity index 100% rename from WEB-INF/templates/cf_custom_field_edit2.tpl rename to WEB-INF/templates/cf_custom_field_edit.tpl diff --git a/WEB-INF/templates/cf_custom_fields2.tpl b/WEB-INF/templates/cf_custom_fields.tpl similarity index 93% rename from WEB-INF/templates/cf_custom_fields2.tpl rename to WEB-INF/templates/cf_custom_fields.tpl index 70253cb3f..812943db8 100644 --- a/WEB-INF/templates/cf_custom_fields2.tpl +++ b/WEB-INF/templates/cf_custom_fields.tpl @@ -19,6 +19,8 @@ License: See license.txt *} {$i18n.entity.time} {elseif CustomFields::ENTITY_USER == $field['entity_type']} {$i18n.entity.user} + {elseif CustomFields::ENTITY_PROJECT == $field['entity_type']} + {$i18n.entity.project} {else} {/if} diff --git a/WEB-INF/templates/cf_dropdown_option_add2.tpl b/WEB-INF/templates/cf_dropdown_option_add.tpl similarity index 100% rename from WEB-INF/templates/cf_dropdown_option_add2.tpl rename to WEB-INF/templates/cf_dropdown_option_add.tpl diff --git a/WEB-INF/templates/cf_dropdown_option_delete2.tpl b/WEB-INF/templates/cf_dropdown_option_delete.tpl similarity index 100% rename from WEB-INF/templates/cf_dropdown_option_delete2.tpl rename to WEB-INF/templates/cf_dropdown_option_delete.tpl diff --git a/WEB-INF/templates/cf_dropdown_option_edit2.tpl b/WEB-INF/templates/cf_dropdown_option_edit.tpl similarity index 100% rename from WEB-INF/templates/cf_dropdown_option_edit2.tpl rename to WEB-INF/templates/cf_dropdown_option_edit.tpl diff --git a/WEB-INF/templates/cf_dropdown_options2.tpl b/WEB-INF/templates/cf_dropdown_options.tpl similarity index 100% rename from WEB-INF/templates/cf_dropdown_options2.tpl rename to WEB-INF/templates/cf_dropdown_options.tpl diff --git a/WEB-INF/templates/charts2.tpl b/WEB-INF/templates/charts.tpl similarity index 51% rename from WEB-INF/templates/charts2.tpl rename to WEB-INF/templates/charts.tpl index ff15ea8f2..27d335ffb 100644 --- a/WEB-INF/templates/charts2.tpl +++ b/WEB-INF/templates/charts.tpl @@ -19,24 +19,69 @@ function adjustTodayLinks() { } } } + +// handleFavReportSelection - manages visibility of controls that are not relevant +// when a fav report is selected. +function handleFavReportSelection() { + var selectedFavReportId = document.getElementById("favorite_report").value; + + var userLabel = document.getElementById("user_label"); + var userControl = document.getElementById("user_control"); + var intervalLabel = document.getElementById("interval_label"); + var intervalControl = document.getElementById("interval_control"); + var smallScreenCalendarDiv = document.getElementById("small_screen_calendar"); + var largeScreenCalendarDiv = document.getElementById("large_screen_calendar"); + + if (selectedFavReportId == -1) { + if (userLabel != null) { + userLabel.style.display = ""; + } + if (userControl != null) { + userControl.style.display = ""; + } + if (intervalLabel != null) { + intervalLabel.style.display = ""; + } + if (intervalControl != null) { + intervalControl.style.display = ""; + } + if (smallScreenCalendarDiv != null) { + smallScreenCalendarDiv.style.display = ""; + } + if (largeScreenCalendarDiv != null) { + largeScreenCalendarDiv.style.display = ""; + } + } else { + if (userLabel != null) { + userLabel.style.display = "none"; + } + if (userControl != null) { + userControl.style.display = "none"; + } + if (intervalLabel != null) { + intervalLabel.style.display = "none"; + } + if (intervalControl != null) { + intervalControl.style.display = "none"; + } + if (smallScreenCalendarDiv != null) { + smallScreenCalendarDiv.style.display = "none"; + } + if (largeScreenCalendarDiv != null) { + largeScreenCalendarDiv.style.display = "none"; + } + } +} {$forms.chartForm.open} -
{$forms.chartForm.date.control}
+
{$forms.chartForm.date.control}
- -{if $user_dropdown} - + + - - - - -{/if} - - - - + + {if $chart_selector} @@ -47,6 +92,20 @@ function adjustTodayLinks() { {/if} +{if isset($user_dropdown)} + + + + + + +{/if} + + + + + +
{$forms.chartForm.date.control}
{$forms.chartForm.date.control}
{$forms.chartForm.user.control}
{$forms.chartForm.interval.control}{$forms.chartForm.favorite_report.control}
{$forms.chartForm.user.control}
{$forms.chartForm.interval.control}
diff --git a/WEB-INF/templates/client_add2.tpl b/WEB-INF/templates/client_add.tpl similarity index 100% rename from WEB-INF/templates/client_add2.tpl rename to WEB-INF/templates/client_add.tpl diff --git a/WEB-INF/templates/client_delete2.tpl b/WEB-INF/templates/client_delete.tpl similarity index 100% rename from WEB-INF/templates/client_delete2.tpl rename to WEB-INF/templates/client_delete.tpl diff --git a/WEB-INF/templates/client_edit2.tpl b/WEB-INF/templates/client_edit.tpl similarity index 100% rename from WEB-INF/templates/client_edit2.tpl rename to WEB-INF/templates/client_edit.tpl diff --git a/WEB-INF/templates/clients2.tpl b/WEB-INF/templates/clients.tpl similarity index 100% rename from WEB-INF/templates/clients2.tpl rename to WEB-INF/templates/clients.tpl diff --git a/WEB-INF/templates/display_options2.tpl b/WEB-INF/templates/display_options.tpl similarity index 66% rename from WEB-INF/templates/display_options2.tpl rename to WEB-INF/templates/display_options.tpl index e4da2cfac..0d0418af9 100644 --- a/WEB-INF/templates/display_options2.tpl +++ b/WEB-INF/templates/display_options.tpl @@ -41,10 +41,32 @@ License: See license.txt *} {$i18n.label.what_is_it} + + + + {$forms.displayOptionsForm.report_inactive_projects.control} + {$i18n.label.what_is_it} + {$i18n.label.what_is_it} + + + + + + {$forms.displayOptionsForm.report_cost_per_hour.control} + {$i18n.label.what_is_it} + {$i18n.label.what_is_it} + +
{$i18n.form.display_options.custom_css} {$forms.displayOptionsForm.custom_css.control} + {$i18n.label.what_is_it} +
+
+ {$i18n.form.display_options.custom_translation} + {$forms.displayOptionsForm.custom_translation.control} + {$i18n.label.what_is_it}
{$forms.displayOptionsForm.btn_save.control}
diff --git a/WEB-INF/templates/entity_files2.tpl b/WEB-INF/templates/entity_files.tpl similarity index 100% rename from WEB-INF/templates/entity_files2.tpl rename to WEB-INF/templates/entity_files.tpl diff --git a/WEB-INF/templates/expense_delete2.tpl b/WEB-INF/templates/expense_delete.tpl similarity index 100% rename from WEB-INF/templates/expense_delete2.tpl rename to WEB-INF/templates/expense_delete.tpl diff --git a/WEB-INF/templates/expense_edit2.tpl b/WEB-INF/templates/expense_edit.tpl similarity index 100% rename from WEB-INF/templates/expense_edit2.tpl rename to WEB-INF/templates/expense_edit.tpl diff --git a/WEB-INF/templates/expenses2.tpl b/WEB-INF/templates/expenses.tpl similarity index 99% rename from WEB-INF/templates/expenses2.tpl rename to WEB-INF/templates/expenses.tpl index 5b1da05fc..859ae3506 100644 --- a/WEB-INF/templates/expenses2.tpl +++ b/WEB-INF/templates/expenses.tpl @@ -119,7 +119,7 @@ function recalculateCost() {
{$forms.expensesForm.date.control}
-{if $user_dropdown} +{if isset($user_dropdown)} diff --git a/WEB-INF/templates/export2.tpl b/WEB-INF/templates/export.tpl similarity index 100% rename from WEB-INF/templates/export2.tpl rename to WEB-INF/templates/export.tpl diff --git a/WEB-INF/templates/file_delete2.tpl b/WEB-INF/templates/file_delete.tpl similarity index 100% rename from WEB-INF/templates/file_delete2.tpl rename to WEB-INF/templates/file_delete.tpl diff --git a/WEB-INF/templates/file_download2.tpl b/WEB-INF/templates/file_download.tpl similarity index 100% rename from WEB-INF/templates/file_download2.tpl rename to WEB-INF/templates/file_download.tpl diff --git a/WEB-INF/templates/file_edit2.tpl b/WEB-INF/templates/file_edit.tpl similarity index 100% rename from WEB-INF/templates/file_edit2.tpl rename to WEB-INF/templates/file_edit.tpl diff --git a/WEB-INF/templates/footer.tpl b/WEB-INF/templates/footer.tpl index a5ae56f90..8844f56e8 100644 --- a/WEB-INF/templates/footer.tpl +++ b/WEB-INF/templates/footer.tpl @@ -1,28 +1,7 @@ - - - - - -
{$forms.expensesForm.date.control}
-

 

+ {* page-content *} - - - - -
{$i18n.footer.contribute_msg}
-
- - - - -
Anuko Time Tracker {constant('APP_VERSION')} | Copyright © Anuko | - {$i18n.footer.credits} | - {$i18n.footer.license} | - {$i18n.footer.improve} -
-
+ + -
diff --git a/WEB-INF/templates/footer2.tpl b/WEB-INF/templates/footer2.tpl deleted file mode 100644 index 8844f56e8..000000000 --- a/WEB-INF/templates/footer2.tpl +++ /dev/null @@ -1,7 +0,0 @@ -
{* page-content *} - - - - - - diff --git a/WEB-INF/templates/group_add2.tpl b/WEB-INF/templates/group_add.tpl similarity index 100% rename from WEB-INF/templates/group_add2.tpl rename to WEB-INF/templates/group_add.tpl diff --git a/WEB-INF/templates/group_advanced_edit2.tpl b/WEB-INF/templates/group_advanced_edit.tpl similarity index 60% rename from WEB-INF/templates/group_advanced_edit2.tpl rename to WEB-INF/templates/group_advanced_edit.tpl index 1f55adf77..82eac702c 100644 --- a/WEB-INF/templates/group_advanced_edit2.tpl +++ b/WEB-INF/templates/group_advanced_edit.tpl @@ -24,15 +24,33 @@ License: See license.txt *}
- + - + {$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}
diff --git a/WEB-INF/templates/group_delete2.tpl b/WEB-INF/templates/group_delete.tpl similarity index 100% rename from WEB-INF/templates/group_delete2.tpl rename to WEB-INF/templates/group_delete.tpl diff --git a/WEB-INF/templates/group_edit2.tpl b/WEB-INF/templates/group_edit.tpl similarity index 93% rename from WEB-INF/templates/group_edit2.tpl rename to WEB-INF/templates/group_edit.tpl index 05a134dd9..909b485e6 100644 --- a/WEB-INF/templates/group_edit2.tpl +++ b/WEB-INF/templates/group_edit.tpl @@ -111,6 +111,15 @@ function chLocation(newLocation) { document.location = newLocation; }
+ + + + {$forms.groupForm.one_uncompleted.control} + {$i18n.label.what_is_it} + {$i18n.label.what_is_it} + + +
diff --git a/WEB-INF/templates/groups2.tpl b/WEB-INF/templates/groups.tpl similarity index 100% rename from WEB-INF/templates/groups2.tpl rename to WEB-INF/templates/groups.tpl diff --git a/WEB-INF/templates/header.tpl b/WEB-INF/templates/header.tpl index 29ec892d3..af6151acb 100644 --- a/WEB-INF/templates/header.tpl +++ b/WEB-INF/templates/header.tpl @@ -2,6 +2,7 @@ + @@ -11,7 +12,7 @@ {if $user->getCustomCss()} {/if} - Time Tracker{if $title} - {$title}{/if} + Time Tracker{if isset($title) && $title} - {$title}{/if} - + -{assign var="tab_width" value="700"} - - - - - +
- - - - -{if $user->custom_logo} - - -
-{else} - -{/if} - - - - -
- - - + + Time Tracker {else} - + Anuko Time Tracker {/if} - - -
-
- + + +{* top menu for small screens *} + +{* end of top menu for small screens *} -{if $authenticated} +{* top menu for large screens *} +{if (isset($authenticated) && $authenticated)} {if $user->can('administer_site')} - - - - - -
  - {$i18n.menu.logout} - {$i18n.menu.forum} - {$i18n.menu.help} -
- + {* top menu for admin *} + + {* end of top menu for admin *} - - - - - - - + {* sub menu for admin *} + + {* end of sub menu for admin *} {else} - - - - + {/if} + + + +
  - {$i18n.menu.logout} + {* top menu for authorized user *} +
+ + + {if $user->exists() && $user->can('manage_own_settings')} - {$i18n.menu.profile} + {/if} {if $user->can('manage_basic_settings')} - {$i18n.menu.group} + {/if} {if $user->can('manage_features')} - {$i18n.menu.plugins} - {/if} - {$i18n.menu.forum} - {$i18n.menu.help} - - -
{$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 *} - - - - + + + {* end of sub menu for authorized user *} {/if} {else} - - - - + + +
  - {$i18n.menu.login} + {* top menu for non authorized user *} +
+ + + {if isTrue('MULTIORG_MODE') && constant('AUTH_MODULE') == 'db'} - {$i18n.menu.register} + {/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} - - - {* No need to escape as it is done in the class. *} -
{$title}{if (isset($timestring) && $timestring)}: {$timestring}{/if}
{$user->getUserPartForHeader()}
+{* page title and user details *} +{if isset($title) && $title} +
{$title}{if isset($timestring) && $timestring}: {$timestring}{/if}
+ {if (isset($authenticated) && $authenticated)} +
{$user->getUserPartForHeader()}
{* No need to escape as it is done in the class. *} + {/if} {/if} - +{* end of page title and user details *} - +{* output errors *} {if $err->yes()} - - - - -
+
{foreach $err->getErrors() as $error} - {$error.message}
{* No need to escape as they are not coming from user and may contain a link. *} +{$error.message}
{* No need to escape as they are not coming from user and may contain a link. *} {/foreach} -
+ {/if} - +{* end of output errors *} - +{* output messages *} {if $msg->yes()} - - - - -
+
{foreach $msg->getErrors() as $message} - {$message.message}
{* No need to escape. *} +{$message.message}
{* No need to escape. *} {/foreach} -
+ {/if} - +{* end of output messages *} + +
diff --git a/WEB-INF/templates/header2.tpl b/WEB-INF/templates/header2.tpl deleted file mode 100644 index 1d8627a66..000000000 --- a/WEB-INF/templates/header2.tpl +++ /dev/null @@ -1,191 +0,0 @@ - - - - - - - - -{if (isset($i18n.language.rtl) && $i18n.language.rtl)} - -{/if} -{if $user->getCustomCss()} - -{/if} - Time Tracker{if isset($title) && $title} - {$title}{/if} - - - - - - - - - -{* 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 *} - - {* sub menu for admin *} -
- - - - - {if isTrue('WORK_SERVER_ADMINISTRATION')} - - {/if} - -
{$i18n.menu.groups}{$i18n.menu.options}{$i18n.label.work}
-
- {* end of sub menu for admin *} - {else} - {* 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 *} - - {* sub menu for authorized user *} -
- - - {if $user->exists() && ($user->can('track_own_time') || $user->can('track_time'))} - - {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'))) && - (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->isPluginEnabled('wk') && ($user->can('update_work') || $user->can('bid_on_work') || $user->can('manage_work')) && $user->exists()} - - {/if} - {if $user->can('export_data')} - - {/if} - -
{$i18n.menu.time}{$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}{$i18n.title.work}{$i18n.menu.export}
-
- {* end of sub menu for authorized user *} - {/if} -{else} - {* top menu for non authorized user *} -
- - - - {if isTrue('MULTIORG_MODE') && constant('AUTH_MODULE') == 'db'} - - {/if} - - - -
{$i18n.menu.login}{$i18n.menu.register}{$i18n.menu.forum}{$i18n.menu.help}
-
-{/if} -{* end of top menu for large screens *} - -{* page title and user details *} -{if isset($title) && $title} -
{$title}{if isset($timestring) && $timestring}: {$timestring}{/if}
- {if (isset($authenticated) && $authenticated)} -
{$user->getUserPartForHeader()}
{* No need to escape as it is done in the class. *} - {/if} -{/if} -{* end of page title and user details *} - -{* output errors *} -{if $err->yes()} -
- {foreach $err->getErrors() as $error} -{$error.message}
{* No need to escape as they are not coming from user and may contain a link. *} - {/foreach} -
-{/if} -{* end of output errors *} - -{* output messages *} -{if $msg->yes()} -
- {foreach $msg->getErrors() as $message} -{$message.message}
{* No need to escape. *} - {/foreach} -
-{/if} -{* end of output messages *} - -
diff --git a/WEB-INF/templates/import2.tpl b/WEB-INF/templates/import.tpl similarity index 100% rename from WEB-INF/templates/import2.tpl rename to WEB-INF/templates/import.tpl diff --git a/WEB-INF/templates/index2.tpl b/WEB-INF/templates/index2.tpl deleted file mode 100644 index 410a7616a..000000000 --- a/WEB-INF/templates/index2.tpl +++ /dev/null @@ -1,5 +0,0 @@ -{include file="header2.tpl"} - -{if $content_page_name}{include file="$content_page_name"}{/if} - -{include file="footer2.tpl"} diff --git a/WEB-INF/templates/invoice_add2.tpl b/WEB-INF/templates/invoice_add.tpl similarity index 100% rename from WEB-INF/templates/invoice_add2.tpl rename to WEB-INF/templates/invoice_add.tpl diff --git a/WEB-INF/templates/invoice_delete2.tpl b/WEB-INF/templates/invoice_delete.tpl similarity index 78% rename from WEB-INF/templates/invoice_delete2.tpl rename to WEB-INF/templates/invoice_delete.tpl index 6fb7cb6c4..951e5e816 100644 --- a/WEB-INF/templates/invoice_delete2.tpl +++ b/WEB-INF/templates/invoice_delete.tpl @@ -23,7 +23,10 @@ License: See license.txt *}
{$forms.invoiceDeleteForm.delete_invoice_entries.control}{$forms.invoiceDeleteForm.delete_invoice_entries.control} + {$i18n.label.what_is_it} + {$i18n.label.what_is_it} +
diff --git a/WEB-INF/templates/invoice_view2.tpl b/WEB-INF/templates/invoice_view.tpl similarity index 100% rename from WEB-INF/templates/invoice_view2.tpl rename to WEB-INF/templates/invoice_view.tpl diff --git a/WEB-INF/templates/invoices2.tpl b/WEB-INF/templates/invoices.tpl similarity index 100% rename from WEB-INF/templates/invoices2.tpl rename to WEB-INF/templates/invoices.tpl diff --git a/WEB-INF/templates/locking2.tpl b/WEB-INF/templates/locking.tpl similarity index 100% rename from WEB-INF/templates/locking2.tpl rename to WEB-INF/templates/locking.tpl diff --git a/WEB-INF/templates/login.db2.tpl b/WEB-INF/templates/login.db.tpl similarity index 100% rename from WEB-INF/templates/login.db2.tpl rename to WEB-INF/templates/login.db.tpl diff --git a/WEB-INF/templates/login.ldap2.tpl b/WEB-INF/templates/login.ldap.tpl similarity index 100% rename from WEB-INF/templates/login.ldap2.tpl rename to WEB-INF/templates/login.ldap.tpl diff --git a/WEB-INF/templates/login2.tpl b/WEB-INF/templates/login.tpl similarity index 85% rename from WEB-INF/templates/login2.tpl rename to WEB-INF/templates/login.tpl index 755763c35..e6a8a4ed3 100644 --- a/WEB-INF/templates/login2.tpl +++ b/WEB-INF/templates/login.tpl @@ -9,7 +9,7 @@ function get_date() { {$forms.loginForm.open} -{include file="login.`$smarty.const.AUTH_MODULE`2.tpl"} +{include file="login.`$smarty.const.AUTH_MODULE`.tpl"} {$forms.loginForm.close} {if !empty($about_text)} diff --git a/WEB-INF/templates/mail2.tpl b/WEB-INF/templates/mail.tpl similarity index 100% rename from WEB-INF/templates/mail2.tpl rename to WEB-INF/templates/mail.tpl diff --git a/WEB-INF/templates/notification_add2.tpl b/WEB-INF/templates/notification_add.tpl similarity index 100% rename from WEB-INF/templates/notification_add2.tpl rename to WEB-INF/templates/notification_add.tpl diff --git a/WEB-INF/templates/notification_delete2.tpl b/WEB-INF/templates/notification_delete.tpl similarity index 100% rename from WEB-INF/templates/notification_delete2.tpl rename to WEB-INF/templates/notification_delete.tpl diff --git a/WEB-INF/templates/notification_edit2.tpl b/WEB-INF/templates/notification_edit.tpl similarity index 100% rename from WEB-INF/templates/notification_edit2.tpl rename to WEB-INF/templates/notification_edit.tpl diff --git a/WEB-INF/templates/notifications2.tpl b/WEB-INF/templates/notifications.tpl similarity index 100% rename from WEB-INF/templates/notifications2.tpl rename to WEB-INF/templates/notifications.tpl diff --git a/WEB-INF/templates/password_change2.tpl b/WEB-INF/templates/password_change.tpl similarity index 100% rename from WEB-INF/templates/password_change2.tpl rename to WEB-INF/templates/password_change.tpl diff --git a/WEB-INF/templates/password_reset2.tpl b/WEB-INF/templates/password_reset.tpl similarity index 100% rename from WEB-INF/templates/password_reset2.tpl rename to WEB-INF/templates/password_reset.tpl diff --git a/WEB-INF/templates/plugins2.tpl b/WEB-INF/templates/plugins.tpl similarity index 92% rename from WEB-INF/templates/plugins2.tpl rename to WEB-INF/templates/plugins.tpl index 370c93c59..e4402f62d 100644 --- a/WEB-INF/templates/plugins2.tpl +++ b/WEB-INF/templates/plugins.tpl @@ -4,6 +4,17 @@ License: See license.txt *} -{if $uncompleted} +{if $uncompleted_today} -{* Copyright (c) Anuko International Ltd. https://www.anuko.com -License: See license.txt *} - {$forms.reportForm.open} @@ -358,6 +424,15 @@ License: See license.txt *} {/if} + + + + + +
{$i18n.label.fav_report}:
{$forms.reportForm.note_containing.control} + {$i18n.label.what_is_it} + {$i18n.label.what_is_it} +
@@ -507,6 +582,19 @@ License: See license.txt *} {/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.project_fields}
{$forms.reportForm.$control_name.control} {$forms.reportForm.$checkbox_control_name.control}
diff --git a/WEB-INF/templates/role_add2.tpl b/WEB-INF/templates/role_add.tpl similarity index 100% rename from WEB-INF/templates/role_add2.tpl rename to WEB-INF/templates/role_add.tpl diff --git a/WEB-INF/templates/role_delete2.tpl b/WEB-INF/templates/role_delete.tpl similarity index 100% rename from WEB-INF/templates/role_delete2.tpl rename to WEB-INF/templates/role_delete.tpl diff --git a/WEB-INF/templates/role_edit2.tpl b/WEB-INF/templates/role_edit.tpl similarity index 100% rename from WEB-INF/templates/role_edit2.tpl rename to WEB-INF/templates/role_edit.tpl diff --git a/WEB-INF/templates/roles2.tpl b/WEB-INF/templates/roles.tpl similarity index 100% rename from WEB-INF/templates/roles2.tpl rename to WEB-INF/templates/roles.tpl diff --git a/WEB-INF/templates/site_map.tpl b/WEB-INF/templates/site_map.tpl index c951f2b99..8688f7677 100644 --- a/WEB-INF/templates/site_map.tpl +++ b/WEB-INF/templates/site_map.tpl @@ -4,9 +4,6 @@
- {if isTrue('WORK_SERVER_ADMINISTRATION')} - - {/if}
{* end of sub menu for admin *} {* main menu for admin *} @@ -21,6 +18,9 @@
{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} @@ -37,7 +37,7 @@ {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'))) && + {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'))} diff --git a/WEB-INF/templates/success2.tpl b/WEB-INF/templates/success.tpl similarity index 100% rename from WEB-INF/templates/success2.tpl rename to WEB-INF/templates/success.tpl diff --git a/WEB-INF/templates/swap_roles2.tpl b/WEB-INF/templates/swap_roles.tpl similarity index 100% rename from WEB-INF/templates/swap_roles2.tpl rename to WEB-INF/templates/swap_roles.tpl diff --git a/WEB-INF/templates/task_add2.tpl b/WEB-INF/templates/task_add.tpl similarity index 100% rename from WEB-INF/templates/task_add2.tpl rename to WEB-INF/templates/task_add.tpl diff --git a/WEB-INF/templates/task_delete2.tpl b/WEB-INF/templates/task_delete.tpl similarity index 100% rename from WEB-INF/templates/task_delete2.tpl rename to WEB-INF/templates/task_delete.tpl diff --git a/WEB-INF/templates/task_edit2.tpl b/WEB-INF/templates/task_edit.tpl similarity index 100% rename from WEB-INF/templates/task_edit2.tpl rename to WEB-INF/templates/task_edit.tpl diff --git a/WEB-INF/templates/tasks2.tpl b/WEB-INF/templates/tasks.tpl similarity index 100% rename from WEB-INF/templates/tasks2.tpl rename to WEB-INF/templates/tasks.tpl diff --git a/WEB-INF/templates/template_add2.tpl b/WEB-INF/templates/template_add.tpl similarity index 100% rename from WEB-INF/templates/template_add2.tpl rename to WEB-INF/templates/template_add.tpl diff --git a/WEB-INF/templates/template_delete2.tpl b/WEB-INF/templates/template_delete.tpl similarity index 100% rename from WEB-INF/templates/template_delete2.tpl rename to WEB-INF/templates/template_delete.tpl diff --git a/WEB-INF/templates/template_edit2.tpl b/WEB-INF/templates/template_edit.tpl similarity index 100% rename from WEB-INF/templates/template_edit2.tpl rename to WEB-INF/templates/template_edit.tpl diff --git a/WEB-INF/templates/templates2.tpl b/WEB-INF/templates/templates.tpl similarity index 100% rename from WEB-INF/templates/templates2.tpl rename to WEB-INF/templates/templates.tpl diff --git a/WEB-INF/templates/time2.tpl b/WEB-INF/templates/time.tpl similarity index 91% rename from WEB-INF/templates/time2.tpl rename to WEB-INF/templates/time.tpl index f1ba587dc..fa24e8062 100644 --- a/WEB-INF/templates/time2.tpl +++ b/WEB-INF/templates/time.tpl @@ -3,9 +3,19 @@ License: See license.txt *} {include file="time_script.tpl"} + + {if $show_navigation}
- {$i18n.label.day_view} / {$i18n.label.week_view} + {$i18n.label.day_view} + {if $user->isPluginEnabled('pu')} / {$i18n.label.puncher}{/if} + {if $user->isPluginEnabled('wv')} / {$i18n.label.week_view}{/if}
{/if} @@ -13,7 +23,7 @@ License: See license.txt *}
{$forms.timeRecordForm.date.control}
-{if $user_dropdown} +{if isset($user_dropdown)} @@ -111,6 +121,9 @@ License: See license.txt *}
{$forms.timeRecordForm.date.control}
{if $time_records} + + +
@@ -147,7 +160,7 @@ License: See license.txt *} {if $show_client} {/if} - {* record custom fileds *} + {* 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']} @@ -175,17 +188,14 @@ License: See license.txt *} {/if} {/if} - {/if} - + {if $show_files} {/if} @@ -68,8 +68,8 @@ License: See license.txt *} {if $show_client} {/if} - + {if $show_files} {/if} diff --git a/WEB-INF/templates/user_add2.tpl b/WEB-INF/templates/user_add.tpl similarity index 100% rename from WEB-INF/templates/user_add2.tpl rename to WEB-INF/templates/user_add.tpl diff --git a/WEB-INF/templates/user_delete2.tpl b/WEB-INF/templates/user_delete.tpl similarity index 100% rename from WEB-INF/templates/user_delete2.tpl rename to WEB-INF/templates/user_delete.tpl diff --git a/WEB-INF/templates/user_edit2.tpl b/WEB-INF/templates/user_edit.tpl similarity index 98% rename from WEB-INF/templates/user_edit2.tpl rename to WEB-INF/templates/user_edit.tpl index 744e8c40e..e4d916a5c 100644 --- a/WEB-INF/templates/user_edit2.tpl +++ b/WEB-INF/templates/user_edit.tpl @@ -57,7 +57,11 @@ function setRate(element) { // handleClientRole - manages visibility and content of controls related to client role, // also hides and unselects projects when client role is selected. function handleClientRole() { - var selectedRoleId = document.getElementById("role").value; + var roleControl = document.getElementById("role"); + if (roleControl == null) { + return; // Role control not available, nothing to do... + } + var selectedRoleId = roleControl.value; var clientControl = document.getElementById("client"); var clientBlock = document.getElementById("client_block"); var nonClientBlock = document.getElementById("non_client_block"); diff --git a/WEB-INF/templates/users2.tpl b/WEB-INF/templates/users.tpl similarity index 90% rename from WEB-INF/templates/users2.tpl rename to WEB-INF/templates/users.tpl index c8c2d0046..2a226238d 100644 --- a/WEB-INF/templates/users2.tpl +++ b/WEB-INF/templates/users.tpl @@ -23,7 +23,13 @@ License: See license.txt *} diff --git a/WEB-INF/templates/week.tpl b/WEB-INF/templates/week.tpl index 92ce31d92..6032d4eb5 100644 --- a/WEB-INF/templates/week.tpl +++ b/WEB-INF/templates/week.tpl @@ -1,3 +1,6 @@ +{* 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} -
{$record.client|escape}{$i18n.label.files} + {if $record.approved || $record.timesheet_id || $record.invoice_id}   {else} - {$i18n.label.edit} {if ($record.duration == '0:00' && $record.start <> '')} - - - - + {/if} + {$i18n.label.edit} {/if} diff --git a/WEB-INF/templates/time_delete2.tpl b/WEB-INF/templates/time_delete.tpl similarity index 100% rename from WEB-INF/templates/time_delete2.tpl rename to WEB-INF/templates/time_delete.tpl diff --git a/WEB-INF/templates/time_edit2.tpl b/WEB-INF/templates/time_edit.tpl similarity index 100% rename from WEB-INF/templates/time_edit2.tpl rename to WEB-INF/templates/time_edit.tpl diff --git a/WEB-INF/templates/time_script.tpl b/WEB-INF/templates/time_script.tpl index 67c5062cc..0dafae8cd 100644 --- a/WEB-INF/templates/time_script.tpl +++ b/WEB-INF/templates/time_script.tpl @@ -12,7 +12,6 @@ // project_ids[143] = "325,370,390,400"; // Comma-separated list of project ids for client. // project_names[325] = "Time Tracker"; // Project name. // task_ids[325] = "100,101,302,303,304"; // Comma-separated list ot task ids for project. -// task_names[100] = "Coding"; // Task name. // template_ids[325] = "17,21"; // Comma-separated list ot template ids for project. NOT YET IMPLEMENTED. // Prepare an array of project ids for clients. @@ -38,11 +37,6 @@ var task_ids = new Array(); {foreach $project_list as $project} task_ids[{$project.id}] = "{$project.tasks}"; {/foreach} -// Prepare an array of task names. -var task_names = new Array(); -{foreach $task_list as $task} - task_names[{$task.id}] = "{$task.name|escape:'javascript'}"; -{/foreach} // Prepare an array of template ids for projects. var template_ids = new Array(); @@ -143,7 +137,6 @@ function fillProjectDropdown(id) { // tasks associated with a selected project (project id is passed here as id). function fillTaskDropdown(id) { var str_ids = task_ids[id]; - var dropdown = document.getElementById("task"); if (dropdown == null) return; // Nothing to do. @@ -155,20 +148,20 @@ function fillTaskDropdown(id) { // Add mandatory top option. dropdown.options[0] = new Option(empty_label_task, '', true); - // Populate the dropdown from the task_names array. + // Populate the Task dropdown. if (str_ids) { var ids = new Array(); ids = str_ids.split(","); var len = ids.length; + // Iterate through $task_list because it is sorted by upper(name). var idx = 1; - for (var i = 0; i < len; i++) { - var t_id = ids[i]; - if (task_names[t_id]) { - dropdown.options[idx] = new Option(task_names[t_id], t_id); + {foreach $task_list as $task} + if (ids.includes("{$task.id}")) { + dropdown.options[idx] = new Option("{$task.name|escape:'javascript'}", {$task.id}); idx++; } - } + {/foreach} // If a previously selected item is still in dropdown - select it. if (dropdown.options.length > 0) { @@ -256,20 +249,34 @@ function formDisable(formField) { var x; if (((formFieldValue != "") && (formFieldName == "start")) || ((formFieldValue != "") && (formFieldName == "finish"))) { + // Either start or finish field not empty. x = eval("document.timeRecordForm.duration"); x.value = ""; x.disabled = true; x.style.background = "#e9e9e9"; + return; } if (((formFieldValue == "") && (formFieldName == "start") && (document.timeRecordForm.finish.value == "")) || ((formFieldValue == "") && (formFieldName == "finish") && (document.timeRecordForm.start.value == ""))) { + // Both start and finish fields are emtpy. x = eval("document.timeRecordForm.duration"); x.value = ""; x.disabled = false; x.style.background = "white"; + return; + } + + if (((formFieldValue == "") && (formFieldName == "start")) || ((formFieldValue == "") && (formFieldName == "finish"))) { + // Either start or finish field is empty. + x = eval("document.timeRecordForm.duration"); + x.value = ""; + x.disabled = true; + x.style.background = "#e9e9e9"; + return; } if ((formFieldValue != "") && (formFieldName == "duration")) { + // Duration field is not empty. x = eval("document.timeRecordForm.start"); x.value = ""; x.disabled = true; @@ -278,9 +285,11 @@ function formDisable(formField) { x.value = ""; x.disabled = true; x.style.background = "#e9e9e9"; + return; } if ((formFieldValue == "") && (formFieldName == "duration")) { + // Duration field is empty. x = eval("document.timeRecordForm.start"); x.disabled = false; x.style.background = "white"; diff --git a/WEB-INF/templates/timesheet_add2.tpl b/WEB-INF/templates/timesheet_add.tpl similarity index 100% rename from WEB-INF/templates/timesheet_add2.tpl rename to WEB-INF/templates/timesheet_add.tpl diff --git a/WEB-INF/templates/timesheet_delete2.tpl b/WEB-INF/templates/timesheet_delete.tpl similarity index 100% rename from WEB-INF/templates/timesheet_delete2.tpl rename to WEB-INF/templates/timesheet_delete.tpl diff --git a/WEB-INF/templates/timesheet_edit2.tpl b/WEB-INF/templates/timesheet_edit.tpl similarity index 100% rename from WEB-INF/templates/timesheet_edit2.tpl rename to WEB-INF/templates/timesheet_edit.tpl diff --git a/WEB-INF/templates/timesheet_view2.tpl b/WEB-INF/templates/timesheet_view.tpl similarity index 100% rename from WEB-INF/templates/timesheet_view2.tpl rename to WEB-INF/templates/timesheet_view.tpl diff --git a/WEB-INF/templates/timesheets2.tpl b/WEB-INF/templates/timesheets.tpl similarity index 94% rename from WEB-INF/templates/timesheets2.tpl rename to WEB-INF/templates/timesheets.tpl index fe6a9603f..2bd927369 100644 --- a/WEB-INF/templates/timesheets2.tpl +++ b/WEB-INF/templates/timesheets.tpl @@ -5,18 +5,18 @@ License: See license.txt *} function chLocation(newLocation) { document.location = newLocation; } +{$forms.timesheetsForm.open} -{if $user_dropdown} - {$forms.timesheetsForm.open} +{if isset($user_dropdown)} - {$forms.timesheetsForm.close} {/if}
{$forms.timesheetsForm.user.control}
+{$forms.timesheetsForm.close}
{if $inactive_timesheets}
{$i18n.form.timesheets.active_timesheets}
@@ -27,8 +27,8 @@ License: See license.txt *} {if $show_client}
{$i18n.label.client}{$i18n.label.submitted} - {$i18n.label.approved} + {$i18n.label.submitted}{$i18n.label.approved}{$i18n.label.client}{$i18n.label.submitted} - {$i18n.label.approved} + {$i18n.label.submitted}{$i18n.label.approved}
{if isset($uncompleted_indicators) && $uncompleted_indicators} - + {if $u.has_uncompleted_entry_today} + + {elseif $u.has_uncompleted_entry} + + {else} + + {/if} {/if} {$u.name|escape}
-{if $show_navigation} +
{$forms.weekTimeForm.date.control}
+
+ +{if isset($user_dropdown)} + - + + -{/if} - - - - -
{$forms.weekTimeForm.date.control}
- {$i18n.label.day_view} / {$i18n.label.week_view} - {$forms.weekTimeForm.user.control}
- -{if $user_dropdown} - - - - + {/if} {if $show_client} - - - - + + + + + + {/if} {if $show_billable} - - - - + + + + + {/if} -{if $custom_fields && $custom_fields->timeFields} +{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="control_name" value='time_field_'|cat:$timeField['id']} + + + + + + {/foreach} {/if} {if $show_project} - - - - + + + + + + {/if} {if $show_task} - - - - + + + + + + {/if} {if $show_week_note} - - - - -{/if} -
{$i18n.label.user}:{$forms.weekTimeForm.user.control}
{$i18n.label.client}{if $user->isOptionEnabled('client_required')} (*){/if}:{$forms.weekTimeForm.client.control}
{$forms.weekTimeForm.client.control}
 
 
{$timeField['label']|escape}{if $timeField['required']} (*){/if}:{$forms.weekTimeForm.$control_name.control}
{$forms.weekTimeForm.$control_name.control}
{$i18n.label.project} (*):{$forms.weekTimeForm.project.control}
{$forms.weekTimeForm.project.control}
{$i18n.label.task}{if $task_required} (*){/if}:{$forms.weekTimeForm.task.control}
{$forms.weekTimeForm.task.control}
{$i18n.label.week_note}:{$forms.weekTimeForm.note.control}
-
- - -
{$forms.weekTimeForm.date.control}
-
- - - + + + + + +{/if}
{$forms.weekTimeForm.week_durations.control}
{$forms.weekTimeForm.comment.control}
- - +
+
- + -
{$forms.weekTimeForm.btn_submit.control}{$forms.weekTimeForm.week_durations.control}
 
+
{$forms.weekTimeForm.btn_submit.control}
+{$forms.weekTimeForm.close} {if $show_week_list} - - - -
-{if $time_records} - - - +
+
{$i18n.label.date}
+ + + {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.client}{$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.note}{$i18n.label.duration}{$i18n.label.note}
{$record.date}
{$record.date}{$record.$control_name|escape}{$record.client|escape}{$record.client|escape}{$record.project|escape}{$record.project|escape}{$record.task|escape}{$record.task|escape}{if $record.start}{$record.start}{else} {/if}{if $record.finish}{$record.finish}{else} {/if}{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}{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}{$i18n.label.files}{$i18n.label.files} - {if $record.approved || $record.timesheet_id || $record.invoice_id} -   + + {if (isset($record.approved) && $record.approved) || (isset($record.timesheet_id) && $record.timesheet_id) || (isset($record.invoice_id) && $record.invoice_id)} +   {else} - {$i18n.label.edit} + {$i18n.label.edit} {/if} - - {if $record.approved || $record.timesheet_id || $record.invoice_id} -   + + {if (isset($record.approved) && $record.approved) || (isset($record.timesheet_id) && $record.timesheet_id) || (isset($record.invoice_id) && $record.invoice_id)} +   {else} - {$i18n.label.delete} + {$i18n.label.delete} {/if} -
-{/if} -
{/if} - {if $time_records} - - {if $show_week_list} +
+
- - + + - {/if} {if $user->isPluginEnabled('mq')} - + {if $over_quota} - + {else} - + {/if} {/if}
{$i18n.label.week_total}: {$week_total}{$i18n.label.week_total}: {$week_total}
{$i18n.label.month_total}: {$month_total}{$i18n.label.month_total}: {$month_total}{$i18n.form.time.over_quota}: {$quota_remaining}{$i18n.form.time.over_quota}: {$quota_remaining}{$i18n.form.time.remaining_quota}: {$quota_remaining}{$i18n.form.time.remaining_quota}: {$quota_remaining}
+
{/if} -{$forms.weekTimeForm.close} diff --git a/WEB-INF/templates/week_view2.tpl b/WEB-INF/templates/week_view.tpl similarity index 79% rename from WEB-INF/templates/week_view2.tpl rename to WEB-INF/templates/week_view.tpl index df74b402b..9cd7b90a2 100644 --- a/WEB-INF/templates/week_view2.tpl +++ b/WEB-INF/templates/week_view.tpl @@ -3,9 +3,9 @@ License: See license.txt *} {$forms.weekViewForm.open} - + - + + + + + + +
{$forms.weekViewForm.week_menu.control} {$i18n.label.what_is_it} {$i18n.label.what_is_it} @@ -39,6 +39,15 @@ License: See license.txt *}
{$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/admin_offer_edit.tpl b/WEB-INF/templates/work/admin_offer_edit.tpl deleted file mode 100644 index bd9a6542b..000000000 --- a/WEB-INF/templates/work/admin_offer_edit.tpl +++ /dev/null @@ -1,70 +0,0 @@ -{$forms.offerForm.open} - - - - -
- -{if $work_id} - - - - - - - - - -{/if} - - - - - - - - - - - - -{if $show_files} - - - - -{/if} - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
{$i18n.label.work}:{$work_name}
{$forms.offerForm.work_description.control}
 
{$i18n.label.offer} (*):{$forms.offerForm.offer_name.control}
{$i18n.label.description}:{$forms.offerForm.description.control}
{$i18n.label.details}:{$forms.offerForm.details.control}
{$i18n.label.file}:{$forms.offerForm.newfile.control}
{$i18n.label.currency}:{$forms.offerForm.currency.control}
{$i18n.label.budget} (*):{$forms.offerForm.budget.control}
{$i18n.label.how_to_pay} (*):{$forms.offerForm.payment_info.control}
{$i18n.label.status}:{$forms.offerForm.status.control}
{$i18n.label.moderator_comment}:{$forms.offerForm.moderator_comment.control}
{$i18n.label.required_fields}
 
{$forms.offerForm.btn_approve.control} {$forms.offerForm.btn_save.control} {$forms.offerForm.btn_disapprove.control}
-
-{$forms.offerForm.close} diff --git a/WEB-INF/templates/work/admin_work.tpl b/WEB-INF/templates/work/admin_work.tpl deleted file mode 100644 index 13f37e911..000000000 --- a/WEB-INF/templates/work/admin_work.tpl +++ /dev/null @@ -1,104 +0,0 @@ - - - - - - -
-{if $pending_work} - - - - - - - - - - - {foreach $pending_work as $work_item} - - - - - - - - - {/foreach} -
{$i18n.title.pending_work}
{$i18n.label.work}{$i18n.label.description}{$i18n.label.client}{$i18n.label.budget}
{$work_item.subject|escape}{$work_item.description|escape}{$work_item.group_name|escape} ({$work_item.site_id}.{$work_item.group_id}){$work_item.amount_with_currency}{$i18n.label.edit}{$i18n.label.delete}
-{/if} - -
- -{if $pending_offers} - - - - - - - - - - - {foreach $pending_offers as $offer} - - - - - - - - - {/foreach} -
{$i18n.title.pending_offers}
{$i18n.label.offer}{$i18n.label.description}{$i18n.label.contractor}{$i18n.label.budget}
{$offer.subject|escape}{$offer.description|escape}{$offer.group_name|escape} ({$offer.site_id}.{$offer.group_id}){$offer.amount_with_currency}{$i18n.label.edit}{$i18n.label.delete}
-{/if} - -
- - - -{if $available_work} - - - - - - - {foreach $available_work as $work_item} - - - - - - - {/foreach} -{/if} -
{$i18n.title.available_work}
{$i18n.label.work}{$i18n.label.description}{$i18n.label.client}{$i18n.label.budget}
{$work_item.subject|escape}{$work_item.description|escape}{$work_item.group_name|escape}{$work_item.amount_with_currency}
- -
- - - -{if $available_offers} - - - - - - - {foreach $available_offers as $offer} - - - - - - - {/foreach} -{/if} -
{$i18n.title.available_offers}
{$i18n.label.offer}{$i18n.label.description}{$i18n.label.contractor}{$i18n.label.budget}
{$offer.subject|escape}{$offer.description|escape}{$offer.group_name|escape}{$offer.amount_with_currency}
- -
diff --git a/WEB-INF/templates/work/admin_work_edit.tpl b/WEB-INF/templates/work/admin_work_edit.tpl deleted file mode 100644 index 1ae59a6fe..000000000 --- a/WEB-INF/templates/work/admin_work_edit.tpl +++ /dev/null @@ -1,67 +0,0 @@ -{$forms.workForm.open} - -{if $offer} - - - -{/if} - - - -
- - - - - -
{$i18n.label.offer}: {$offer.subject}
{$i18n.label.contractor}: {$offer.group_name}
{$i18n.label.description}: {$offer.descr_short|escape}
{$i18n.label.budget}: {$offer.amount_with_currency}
-
- - - - - - - - - - - - - -{if $show_files} - - - - -{/if} - - - - - - - - - - - - - - - - - - - - - - - - - - - -
{$i18n.label.work} (*):{$forms.workForm.work_name.control}
{$i18n.label.description}:{$forms.workForm.description.control}
{$i18n.label.details}:{$forms.workForm.details.control}
{$i18n.label.file}:{$forms.workForm.newfile.control}
{$i18n.label.currency}:{$forms.workForm.currency.control}
{$i18n.label.budget} (*):{$forms.workForm.budget.control}
{$i18n.label.status}:{$forms.workForm.status.control}
{$i18n.label.moderator_comment}:{$forms.workForm.moderator_comment.control}
{$i18n.label.required_fields}
 
{$forms.workForm.btn_save.control}
-
-{$forms.workForm.close} diff --git a/WEB-INF/templates/work/header.tpl b/WEB-INF/templates/work/header.tpl deleted file mode 100644 index 75a0d586f..000000000 --- a/WEB-INF/templates/work/header.tpl +++ /dev/null @@ -1,207 +0,0 @@ - - - - - - -{if $i18n.language.rtl} - -{/if} - Time Tracker{if $title} - {$title}{/if} - - - - - - - -{assign var="tab_width" value="700"} - - - - - - + +
- - - - -{if $user->custom_logo} - - -
-{else} - -{/if} - - - - -
- - - -{if $user->custom_logo} - -{else} - -{/if} - - -
-
- - -{if $authenticated} - {if $user->can('administer_site')} - - - - - -
  - {$i18n.menu.logout} - {$i18n.menu.forum} - {$i18n.menu.help} -
- - - - - - - - - - {else} - - - - - -
  - {$i18n.menu.logout} - {if $user->exists() && $user->can('manage_own_settings')} - {$i18n.menu.profile} - {/if} - {if $user->can('manage_basic_settings')} - {$i18n.menu.group} - {/if} - {if $user->can('manage_features')} - {$i18n.menu.plugins} - {/if} - {$i18n.menu.forum} - {$i18n.menu.help} -
- - - - - - - - - - {/if} -{else} - - - - - -
  - {$i18n.menu.login} - {if isTrue('MULTIORG_MODE') && constant('AUTH_MODULE') == 'db'} - {$i18n.menu.register} - {/if} - {$i18n.menu.forum} - {$i18n.menu.help} -
-{/if} -
- - -{if $title} - - - {* No need to escape as it is done in the class. *} -
{$title}{if $timestring}: {$timestring}{/if}
{$user->getUserPartForHeader()}
-{/if} - - - -{if $err->yes()} - - - - -
- {foreach $err->getErrors() as $error} - {$error.message}
{* No need to escape as they are not coming from user and may contain a link. *} - {/foreach} -
-{/if} - - - -{if $msg->yes()} - - - - -
- {foreach $msg->getErrors() as $message} - {$message.message}
{* No need to escape. *} - {/foreach} -
-{/if} - diff --git a/WEB-INF/templates/work/index.tpl b/WEB-INF/templates/work/index.tpl deleted file mode 100644 index 6817f3963..000000000 --- a/WEB-INF/templates/work/index.tpl +++ /dev/null @@ -1,5 +0,0 @@ -{include file="work/header.tpl"} - -{if $content_page_name}{include file="$content_page_name"}{/if} - -{include file="footer.tpl"} diff --git a/WEB-INF/templates/work/offer_add.tpl b/WEB-INF/templates/work/offer_add.tpl deleted file mode 100644 index 80e07d803..000000000 --- a/WEB-INF/templates/work/offer_add.tpl +++ /dev/null @@ -1,74 +0,0 @@ -{$forms.offerForm.open} - -{if $work_item} - - - -{/if} - - - - -
- - - - -
{$i18n.label.work}: {$work_item.subject}
{$i18n.label.description}: {$work_item.descr_short|escape}
{$i18n.label.budget}: {$work_item.amount_with_currency}
-
- -{if $work_id} - - - - - - - - - -{/if} - - - - - - - - - - - - -{if $show_files} - - - - -{/if} - - - - - - - - - - - - - - - - - - - - - - - -
{$i18n.label.work}:{$work_name}
{$forms.offerForm.work_description.control}
 
{$i18n.label.offer} (*):{$forms.offerForm.offer_name.control}
{$i18n.label.description}:{$forms.offerForm.description.control}
{$i18n.label.details}:{$forms.offerForm.details.control}
{$i18n.label.file}:{$forms.offerForm.newfile.control}
{$i18n.label.currency}:{$forms.offerForm.currency.control}
{$i18n.label.budget} (*):{$forms.offerForm.budget.control} {$i18n.label.what_is_it}
{$i18n.label.how_to_pay} (*):{$forms.offerForm.payment_info.control} {$i18n.label.what_is_it}
{$i18n.label.required_fields}
 
{$forms.offerForm.btn_add.control}
-
-{$forms.offerForm.close} diff --git a/WEB-INF/templates/work/offer_delete.tpl b/WEB-INF/templates/work/offer_delete.tpl deleted file mode 100644 index 7d8954cfb..000000000 --- a/WEB-INF/templates/work/offer_delete.tpl +++ /dev/null @@ -1,20 +0,0 @@ -{$forms.offerDeleteForm.open} - - - - -
- - - - - - - - - - - -
{$offer_to_delete|escape}
 
{$forms.offerDeleteForm.btn_delete.control}  {$forms.offerDeleteForm.btn_cancel.control}
-
-{$forms.offerDeleteForm.close} diff --git a/WEB-INF/templates/work/offer_edit.tpl b/WEB-INF/templates/work/offer_edit.tpl deleted file mode 100644 index 5c1e89a33..000000000 --- a/WEB-INF/templates/work/offer_edit.tpl +++ /dev/null @@ -1,67 +0,0 @@ -{$forms.offerForm.open} - -{if $work_item} - - - -{/if} - - - - -
- - - - -
{$i18n.label.work}: {$work_item.subject}
{$i18n.label.description}: {$work_item.descr_short|escape}
{$i18n.label.budget}: {$work_item.amount_with_currency}
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - -{if $show_moderator_comment} - - - - -{/if} - - - - - - - - - - - -
{$i18n.label.offer} (*):{$forms.offerForm.offer_name.control}
{$i18n.label.description}:{$forms.offerForm.description.control}
{$i18n.label.details}:{$forms.offerForm.details.control}
{$i18n.label.currency}:{$forms.offerForm.currency.control}
{$i18n.label.budget} (*):{$forms.offerForm.budget.control} {$i18n.label.what_is_it}
{$i18n.label.how_to_pay} (*):{$forms.offerForm.payment_info.control} {$i18n.label.what_is_it}
{$i18n.label.status}:{$forms.offerForm.status.control}
{$i18n.label.moderator_comment}:{$forms.offerForm.moderator_comment.control}
{$i18n.label.required_fields}
 
{$forms.offerForm.btn_save.control}
-
-{$forms.offerForm.close} diff --git a/WEB-INF/templates/work/offer_message.tpl b/WEB-INF/templates/work/offer_message.tpl deleted file mode 100644 index c97a3db91..000000000 --- a/WEB-INF/templates/work/offer_message.tpl +++ /dev/null @@ -1,32 +0,0 @@ - - -{$forms.messageForm.open} - -{if $offer} - - - -{/if} - - - -
- - - - -
{$i18n.label.offer}: {$offer.subject}
{$i18n.label.description}: {$offer.descr_short|escape}
{$i18n.label.budget}: {$offer.amount_with_currency}
-
- - - - - - - - -
{$i18n.work.label.message}:{$forms.messageForm.message_body.control}
{$forms.messageForm.btn_send.control}
-
-{$forms.messageForm.close} diff --git a/WEB-INF/templates/work/offer_view.tpl b/WEB-INF/templates/work/offer_view.tpl deleted file mode 100644 index 3d9d7d0c5..000000000 --- a/WEB-INF/templates/work/offer_view.tpl +++ /dev/null @@ -1,47 +0,0 @@ - - -{$forms.offerForm.open} - - - - -
- - - - - - - - - - - - - - - - - - - - - -
{$i18n.label.contractor}:{$forms.offerForm.contractor.control}
{$i18n.label.offer}:{$forms.offerForm.offer_name.control}
{$i18n.label.description}:{$forms.offerForm.description.control}
{$i18n.label.details}:{$forms.offerForm.details.control}
{$i18n.label.budget}:{$forms.offerForm.budget.control}
-
-{$forms.offerForm.close} - - - - - -
- - - - - -
-
diff --git a/WEB-INF/templates/work/offer_view_own.tpl b/WEB-INF/templates/work/offer_view_own.tpl deleted file mode 100644 index d00ae7ea1..000000000 --- a/WEB-INF/templates/work/offer_view_own.tpl +++ /dev/null @@ -1,42 +0,0 @@ - - -{$forms.offerForm.open} - -{if $work_item} - - - -{/if} - - - - -
- - - - -
{$i18n.label.work}: {$work_item.subject}
{$i18n.label.description}: {$work_item.descr_short|escape}
{$i18n.label.budget}: {$work_item.amount_with_currency}
-
- - - - - - - - - - - - - - - - - -
{$i18n.label.offer}:{$forms.offerForm.offer_name.control}
{$i18n.label.description}:{$forms.offerForm.description.control}
{$i18n.label.details}:{$forms.offerForm.details.control}
{$i18n.label.budget}:{$forms.offerForm.budget.control}
-
-{$forms.offerForm.close} diff --git a/WEB-INF/templates/work/work.tpl b/WEB-INF/templates/work/work.tpl deleted file mode 100644 index 67235b64f..000000000 --- a/WEB-INF/templates/work/work.tpl +++ /dev/null @@ -1,140 +0,0 @@ - - - - - - -
-{if $own_work_items} - - - - - - - - - - - - {foreach $own_work_items as $work_item} - - - - - - - {if $work_item.can_edit} - - {else} - - {/if} - {if $work_item.can_delete} - - {else} - - {/if} - - {/foreach} -
{$i18n.work.label.own_work}
{$i18n.label.work}{$i18n.label.description}{$i18n.label.status}{$i18n.work.label.offers}{$i18n.label.budget}
{$work_item.subject|escape}{$work_item.description|escape}{$work_item.status_label}{if $work_item.num_offers}{$work_item.num_offers}{/if}{$work_item.amount_with_currency}{$i18n.label.edit}{$i18n.label.delete}
- - - - - -

-
-
-{/if} - - - -{if $available_work_items} - - - - - - - {foreach $available_work_items as $work_item} - - - - - - - {/foreach} -{/if} -
{$i18n.title.available_work}
{$i18n.label.work}{$i18n.label.description}{$i18n.label.client}{$i18n.label.budget}
{$work_item.subject|escape}{$work_item.description|escape}{$work_item.group_name|escape}{$work_item.amount_with_currency}
- - - - - -

-
-
- -{if $own_offers} - - - - - - - - - - - {foreach $own_offers as $offer} - - - - - - - - - {/foreach} -
{$i18n.work.label.own_offers}
{$i18n.label.offer}{$i18n.label.description}{$i18n.label.status}{$i18n.label.budget}
{$offer.subject|escape}{$offer.description|escape}{$offer.status_label}{$offer.amount_with_currency}{$i18n.label.edit}{$i18n.label.delete}
- - - - - -

-
-
-{/if} - - - -{if $available_offers} - - - - - - - {foreach $available_offers as $offer} - - - - - - - {/foreach} -{/if} -
{$i18n.title.available_offers}
{$i18n.label.offer}{$i18n.label.description}{$i18n.label.contractor}{$i18n.label.budget}
{$offer.subject|escape}{$offer.description|escape}{$offer.group_name|escape}{$offer.amount_with_currency}
- - - - - -

-
-
- -
diff --git a/WEB-INF/templates/work/work_add.tpl b/WEB-INF/templates/work/work_add.tpl deleted file mode 100644 index eca0ba799..000000000 --- a/WEB-INF/templates/work/work_add.tpl +++ /dev/null @@ -1,62 +0,0 @@ -{$forms.workForm.open} - -{if $offer} - - - -{/if} - - - -
- - - - -
{$i18n.label.offer}: {$offer.subject}
{$i18n.label.description}: {$offer.descr_short|escape}
{$i18n.label.budget}: {$offer.amount_with_currency}
-
- - - - - - - - - - - - - - - - - -{if $show_files} - - - - -{/if} - - - - - - - - - - - - - - - - - - - -
{$i18n.label.work} (*):{$forms.workForm.work_name.control}
{$i18n.label.type}:{$forms.workForm.work_type.control}
{$i18n.label.description}:{$forms.workForm.description.control}
{$i18n.label.details}:{$forms.workForm.details.control}
{$i18n.label.file}:{$forms.workForm.newfile.control}
{$i18n.label.currency}:{$forms.workForm.currency.control}
{$i18n.label.budget} (*):{$forms.workForm.budget.control} {$i18n.label.what_is_it}
{$i18n.label.required_fields}
 
{$forms.workForm.btn_add.control}
-
-{$forms.workForm.close} diff --git a/WEB-INF/templates/work/work_delete.tpl b/WEB-INF/templates/work/work_delete.tpl deleted file mode 100644 index 2831b0f15..000000000 --- a/WEB-INF/templates/work/work_delete.tpl +++ /dev/null @@ -1,20 +0,0 @@ -{$forms.workDeleteForm.open} - - - - -
- - - - - - - - - - - -
{$work_to_delete|escape}
 
{$forms.workDeleteForm.btn_delete.control}  {$forms.workDeleteForm.btn_cancel.control}
-
-{$forms.workDeleteForm.close} diff --git a/WEB-INF/templates/work/work_edit.tpl b/WEB-INF/templates/work/work_edit.tpl deleted file mode 100644 index 7165d5e19..000000000 --- a/WEB-INF/templates/work/work_edit.tpl +++ /dev/null @@ -1,73 +0,0 @@ -{$forms.workForm.open} - -{if $offer} - - - -{/if} - - - -
- - - - - -
{$i18n.label.offer}: {$offer.subject}
{$i18n.label.contractor}: {$offer.group_name}
{$i18n.label.description}: {$offer.descr_short|escape}
{$i18n.label.budget}: {$offer.amount_with_currency}
-
- - - - - - - - - - - - - - - - - -{if $show_files} - - - - -{/if} - - - - - - - - - - - - -{if $show_moderator_comment} - - - - -{/if} - - - - - - - - - - - -
{$i18n.label.work} (*):{$forms.workForm.work_name.control}
{$i18n.label.type}:{$forms.workForm.work_type.control}
{$i18n.label.description}:{$forms.workForm.description.control}
{$i18n.label.details}:{$forms.workForm.details.control}
{$i18n.label.file}:{$forms.workForm.newfile.control}
{$i18n.label.currency}:{$forms.workForm.currency.control}
{$i18n.label.budget} (*):{$forms.workForm.budget.control} {$i18n.label.what_is_it}
{$i18n.label.status}:{$forms.workForm.status.control}
{$i18n.label.moderator_comment}:{$forms.workForm.moderator_comment.control}
{$i18n.label.required_fields}
 
{$forms.workForm.btn_save.control}
-
-{$forms.workForm.close} diff --git a/WEB-INF/templates/work/work_message.tpl b/WEB-INF/templates/work/work_message.tpl deleted file mode 100644 index 3f237659c..000000000 --- a/WEB-INF/templates/work/work_message.tpl +++ /dev/null @@ -1,32 +0,0 @@ - - -{$forms.messageForm.open} - -{if $work_item} - - - -{/if} - - - -
- - - - -
{$i18n.label.work}: {$work_item.subject}
{$i18n.label.description}: {$work_item.descr_short|escape}
{$i18n.label.budget}: {$work_item.amount_with_currency}
-
- - - - - - - - -
{$i18n.work.label.message}:{$forms.messageForm.message_body.control}
{$forms.messageForm.btn_send.control}
-
-{$forms.messageForm.close} diff --git a/WEB-INF/templates/work/work_offer_view.tpl b/WEB-INF/templates/work/work_offer_view.tpl deleted file mode 100644 index e2afe0003..000000000 --- a/WEB-INF/templates/work/work_offer_view.tpl +++ /dev/null @@ -1,57 +0,0 @@ -{$forms.offerForm.open} - -{if $work_item} - - - -{/if} - - - -
- - - - - -
{$i18n.label.work}: {$work_item.subject}
{$i18n.label.client}: {$work_item.group_name}
{$i18n.label.description}: {$work_item.descr_short|escape}
{$i18n.label.budget}: {$work_item.amount_with_currency}
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
{$i18n.label.contractor}:{$forms.offerForm.contractor.control}
{$i18n.label.offer}:{$forms.offerForm.offer_name.control}
{$i18n.label.description}:{$forms.offerForm.description.control}
{$i18n.label.details}:{$forms.offerForm.details.control}
{$i18n.label.budget}:{$forms.offerForm.budget.control}
{$i18n.label.status}:{$forms.offerForm.status.control}
 
{$forms.offerForm.btn_accept.control} {$forms.offerForm.btn_decline.control}
{$i18n.label.comment}:{$forms.offerForm.client_comment.control}
-
-{$forms.offerForm.close} diff --git a/WEB-INF/templates/work/work_offers.tpl b/WEB-INF/templates/work/work_offers.tpl deleted file mode 100644 index ea0d8816f..000000000 --- a/WEB-INF/templates/work/work_offers.tpl +++ /dev/null @@ -1,41 +0,0 @@ - - - - - - - - - - -
- - - - - -
{$i18n.label.work}: {$work_item.subject}
{$i18n.label.client}: {$work_item.group_name}
{$i18n.label.description}: {$work_item.descr_short|escape}
{$i18n.label.budget}: {$work_item.amount_with_currency}
-
- -{if $work_item_offers} - - - - - - - - {foreach $work_item_offers as $offer} - - - - - - - {/foreach} -
{$i18n.label.contractor}{$i18n.label.description}{$i18n.label.status}{$i18n.label.budget}
{$offer.group_name|escape}{$offer.description|escape}{$offer.status_label}{$offer.amount_with_currency}
-{/if} - -
diff --git a/WEB-INF/templates/work/work_view.tpl b/WEB-INF/templates/work/work_view.tpl deleted file mode 100644 index 468cb9ddd..000000000 --- a/WEB-INF/templates/work/work_view.tpl +++ /dev/null @@ -1,47 +0,0 @@ - - -{$forms.workForm.open} - - - - -
- - - - - - - - - - - - - - - - - - - - - -
{$i18n.label.client}:{$forms.workForm.client.control}
{$i18n.label.work}:{$forms.workForm.work_name.control}
{$i18n.label.description}:{$forms.workForm.description.control}
{$i18n.label.details}:{$forms.workForm.details.control}
{$i18n.label.budget}:{$forms.workForm.budget.control}
-
-{$forms.workForm.close} - - - - - -
- - - - - -
-
diff --git a/WEB-INF/templates/work/work_view_own.tpl b/WEB-INF/templates/work/work_view_own.tpl deleted file mode 100644 index 61e270b4b..000000000 --- a/WEB-INF/templates/work/work_view_own.tpl +++ /dev/null @@ -1,38 +0,0 @@ -{$forms.workForm.open} - -{if $offer} - - - -{/if} - - - -
- - - - - -
{$i18n.label.offer}: {$offer.subject}
{$i18n.label.contractor}: {$offer.group_name}
{$i18n.label.description}: {$offer.descr_short|escape}
{$i18n.label.budget}: {$offer.amount_with_currency}
-
- - - - - - - - - - - - - - - - - -
{$i18n.label.work}:{$forms.workForm.work_name.control}
{$i18n.label.description}:{$forms.workForm.description.control}
{$i18n.label.details}:{$forms.workForm.details.control}
{$i18n.label.budget}:{$forms.workForm.budget.control}
-
-{$forms.workForm.close} diff --git a/WEB-INF/templates/work_units2.tpl b/WEB-INF/templates/work_units.tpl similarity index 100% rename from WEB-INF/templates/work_units2.tpl rename to WEB-INF/templates/work_units.tpl diff --git a/access_denied.php b/access_denied.php index 2aa3c5d77..80900d570 100644 --- a/access_denied.php +++ b/access_denied.php @@ -8,5 +8,5 @@ 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_denied2.tpl'); -$smarty->display('index2.tpl'); +$smarty->assign('content_page_name', 'access_denied.tpl'); +$smarty->display('index.tpl'); diff --git a/admin_group_add.php b/admin_group_add.php index 976b5c6db..9aeff1831 100644 --- a/admin_group_add.php +++ b/admin_group_add.php @@ -69,12 +69,14 @@ $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 (!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 (!$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()) @@ -101,6 +103,6 @@ $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_add2.tpl'); +$smarty->assign('content_page_name', 'admin_group_add.tpl'); $smarty->assign('title', $i18n->get('title.add_group')); -$smarty->display('index2.tpl'); +$smarty->display('index.tpl'); diff --git a/admin_group_delete.php b/admin_group_delete.php index 83999da5d..909b2347a 100644 --- a/admin_group_delete.php +++ b/admin_group_delete.php @@ -13,7 +13,7 @@ } $group_id = (int)$request->getParameter('id'); $group_name = ttAdmin::getGroupName($group_id); -if (!($group_id && $group_name)) { +if (!($group_id && $group_name != null)) { header('Location: access_denied.php'); exit(); } @@ -42,5 +42,5 @@ $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_delete2.tpl'); -$smarty->display('index2.tpl'); +$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 index 5474ca2c3..496c7b8fb 100644 --- a/admin_group_edit.php +++ b/admin_group_edit.php @@ -14,7 +14,7 @@ } $group_id = (int)$request->getParameter('id'); $group_name = ttAdmin::getGroupName($group_id); -if (!($group_id && $group_name)) { +if (!($group_id && $group_name != null)) { header('Location: access_denied.php'); exit(); } @@ -110,5 +110,5 @@ $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_edit2.tpl'); -$smarty->display('index2.tpl'); +$smarty->assign('content_page_name', 'admin_group_edit.tpl'); +$smarty->display('index.tpl'); diff --git a/admin_groups.php b/admin_groups.php index 915f7d778..8dffd3931 100644 --- a/admin_groups.php +++ b/admin_groups.php @@ -14,5 +14,5 @@ $smarty->assign('groups', ttOrgHelper::getOrgs()); $smarty->assign('title', $i18n->get('title.groups')); -$smarty->assign('content_page_name', 'admin_groups2.tpl'); -$smarty->display('index2.tpl'); +$smarty->assign('content_page_name', 'admin_groups.tpl'); +$smarty->display('index.tpl'); diff --git a/admin_options.php b/admin_options.php index bc1269336..aed852320 100644 --- a/admin_options.php +++ b/admin_options.php @@ -48,7 +48,7 @@ // 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 || $cl_password2)) { + 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)) @@ -72,5 +72,5 @@ $smarty->assign('auth_external', $auth->isPasswordExternal()); $smarty->assign('forms', array($form->getName()=>$form->toArray())); $smarty->assign('title', $i18n->get('title.options')); -$smarty->assign('content_page_name', 'admin_options2.tpl'); -$smarty->display('index2.tpl'); +$smarty->assign('content_page_name', 'admin_options.tpl'); +$smarty->display('index.tpl'); diff --git a/cf_custom_field_add.php b/cf_custom_field_add.php index 14b233a52..87d46bcd5 100644 --- a/cf_custom_field_add.php +++ b/cf_custom_field_add.php @@ -15,7 +15,6 @@ header('Location: feature_disabled.php'); exit(); } -$fields = CustomFields::getFields(); // End of access checks. if ($request->isPost()) { @@ -29,7 +28,8 @@ $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_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->get('label.type_text'), @@ -41,6 +41,7 @@ if ($request->isPost()) { // Validate user input. if (!ttValidString($cl_field_name)) $err->add($i18n->get('error.field'), $i18n->get('label.thing_name')); + if (CustomFields::getFieldByName($cl_field_name, $cl_entity_type) != null) $err->add($i18n->get('error.object_exists')); if ($err->no()) { $res = CustomFields::insertField($cl_field_name, $cl_entity_type, $cl_field_type, $cl_required); @@ -55,5 +56,5 @@ $smarty->assign('forms', array($form->getName()=>$form->toArray())); $smarty->assign('onload', 'onLoad="document.fieldForm.name.focus()"'); $smarty->assign('title', $i18n->get('title.cf_add_custom_field')); -$smarty->assign('content_page_name', 'cf_custom_field_add2.tpl'); -$smarty->display('index2.tpl'); +$smarty->assign('content_page_name', 'cf_custom_field_add.tpl'); +$smarty->display('index.tpl'); diff --git a/cf_custom_field_delete.php b/cf_custom_field_delete.php index 7cfd72035..ba222c657 100644 --- a/cf_custom_field_delete.php +++ b/cf_custom_field_delete.php @@ -50,5 +50,5 @@ $smarty->assign('forms', array($form->getName()=>$form->toArray())); $smarty->assign('onload', 'onLoad="document.fieldDeleteForm.btn_cancel.focus()"'); $smarty->assign('title', $i18n->get('title.cf_delete_custom_field')); -$smarty->assign('content_page_name', 'cf_custom_field_delete2.tpl'); -$smarty->display('index2.tpl'); +$smarty->assign('content_page_name', 'cf_custom_field_delete.tpl'); +$smarty->display('index.tpl'); diff --git a/cf_custom_field_edit.php b/cf_custom_field_edit.php index 36e76d4bf..3f959ee22 100644 --- a/cf_custom_field_edit.php +++ b/cf_custom_field_edit.php @@ -68,5 +68,5 @@ $smarty->assign('forms', array($form->getName()=>$form->toArray())); $smarty->assign('onload', 'onLoad="document.fieldForm.name.focus()"'); $smarty->assign('title', $i18n->get('title.cf_edit_custom_field')); -$smarty->assign('content_page_name', 'cf_custom_field_edit2.tpl'); -$smarty->display('index2.tpl'); +$smarty->assign('content_page_name', 'cf_custom_field_edit.tpl'); +$smarty->display('index.tpl'); diff --git a/cf_custom_fields.php b/cf_custom_fields.php index 7d670636e..6552bb7f6 100644 --- a/cf_custom_fields.php +++ b/cf_custom_fields.php @@ -34,5 +34,5 @@ $smarty->assign('forms', array($form->getName()=>$form->toArray())); $smarty->assign('custom_fields', $fields); $smarty->assign('title', $i18n->get('title.cf_custom_fields')); -$smarty->assign('content_page_name', 'cf_custom_fields2.tpl'); -$smarty->display('index2.tpl'); +$smarty->assign('content_page_name', 'cf_custom_fields.tpl'); +$smarty->display('index.tpl'); diff --git a/cf_dropdown_option_add.php b/cf_dropdown_option_add.php index f1bf00908..582794ae5 100644 --- a/cf_dropdown_option_add.php +++ b/cf_dropdown_option_add.php @@ -49,5 +49,5 @@ $smarty->assign('forms', array($form->getName()=>$form->toArray())); $smarty->assign('onload', 'onLoad="document.optionAddForm.name.focus()"'); $smarty->assign('title', $i18n->get('title.cf_add_dropdown_option')); -$smarty->assign('content_page_name', 'cf_dropdown_option_add2.tpl'); -$smarty->display('index2.tpl'); +$smarty->assign('content_page_name', 'cf_dropdown_option_add.tpl'); +$smarty->display('index.tpl'); diff --git a/cf_dropdown_option_delete.php b/cf_dropdown_option_delete.php index e23288bd5..50c685014 100644 --- a/cf_dropdown_option_delete.php +++ b/cf_dropdown_option_delete.php @@ -17,7 +17,7 @@ } $cl_id = (int)$request->getParameter('id'); $option = CustomFields::getOptionName($cl_id); -if (!$option) { +if ($option == null) { header('Location: access_denied.php'); exit(); } @@ -53,5 +53,5 @@ $smarty->assign('forms', array($form->getName()=>$form->toArray())); $smarty->assign('onload', 'onLoad="document.optionDeleteForm.btn_cancel.focus()"'); $smarty->assign('title', $i18n->get('title.cf_delete_dropdown_option')); -$smarty->assign('content_page_name', 'cf_dropdown_option_delete2.tpl'); -$smarty->display('index2.tpl'); +$smarty->assign('content_page_name', 'cf_dropdown_option_delete.tpl'); +$smarty->display('index.tpl'); diff --git a/cf_dropdown_option_edit.php b/cf_dropdown_option_edit.php index c73200ffc..2477b057c 100644 --- a/cf_dropdown_option_edit.php +++ b/cf_dropdown_option_edit.php @@ -17,7 +17,7 @@ } $cl_id = (int)$request->getParameter('id'); $cl_name = CustomFields::getOptionName($cl_id); -if (!$cl_name) { +if ($cl_name == null) { header('Location: access_denied.php'); exit(); } @@ -51,5 +51,5 @@ $smarty->assign('forms', array($form->getName()=>$form->toArray())); $smarty->assign('onload', 'onLoad="document.optionEditForm.name.focus()"'); $smarty->assign('title', $i18n->get('title.cf_edit_dropdown_option')); -$smarty->assign('content_page_name', 'cf_dropdown_option_edit2.tpl'); -$smarty->display('index2.tpl'); +$smarty->assign('content_page_name', 'cf_dropdown_option_edit.tpl'); +$smarty->display('index.tpl'); diff --git a/cf_dropdown_options.php b/cf_dropdown_options.php index 2570f0ee0..06f53a7d8 100644 --- a/cf_dropdown_options.php +++ b/cf_dropdown_options.php @@ -28,5 +28,5 @@ $smarty->assign('field_id', $field_id); $smarty->assign('options', $options); $smarty->assign('title', $i18n->get('title.cf_dropdown_options')); -$smarty->assign('content_page_name', 'cf_dropdown_options2.tpl'); -$smarty->display('index2.tpl'); +$smarty->assign('content_page_name', 'cf_dropdown_options.tpl'); +$smarty->display('index.tpl'); diff --git a/charts.php b/charts.php index 25e6695d4..9dbb95c70 100644 --- a/charts.php +++ b/charts.php @@ -6,15 +6,18 @@ require_once('initialize.php'); import('form.Form'); -import('DateAndTime'); +import('ttDate'); import('ttChartHelper'); import('ttUserConfig'); import('PieChartEx'); import('ttUserHelper'); import('ttTeamHelper'); +import('ttFavReportHelper'); + +define('ALL_USERS_OPTION_ID', -1); // An identifier for "all users" seclection in User dropdown. // Access checks. -if (!(ttAccessAllowed('view_own_charts') || ttAccessAllowed('view_charts'))) { +if (!(ttAccessAllowed('view_own_charts') || ttAccessAllowed('view_charts') || ttAccessAllowed('view_all_charts'))) { header('Location: access_denied.php'); exit(); } @@ -26,7 +29,7 @@ header('Location: access_denied.php'); // Nobody to display a chart for. exit(); } -if ($user->behalf_id && (!$user->can('view_charts') || !$user->checkBehalfId())) { +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(); } @@ -34,21 +37,34 @@ 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 (!$user->isUserValid((int)$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 which we display this page. +// Determine user for whom 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(); +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(); @@ -57,52 +73,105 @@ // 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; if ($request->isPost()) { - $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); + $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); -} else { - // Initialize chart interval. - $cl_interval = @$_SESSION['chart_interval']; - if (!$cl_interval) $cl_interval = $uc->getValue(SYSC_CHART_INTERVAL); + + // 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 { + // 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->can('view_charts')) { +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.user_changed.value=1;this.form.submit();', 'name'=>'user', - 'value'=>$user_id, + 'value'=>$userDropdownSelectionId, 'data'=>$user_list, 'datakeys'=>array('id','name'), )); @@ -117,6 +186,7 @@ $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'); @@ -129,32 +199,14 @@ )); $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; -} - // Calendar. $chart_form->addInput(array('type'=>'calendar','name'=>'date','value'=>$cl_date)); // calendar // Get data for our chart. -$totals = ttChartHelper::getTotals($user_id, $cl_type, $cl_date, $cl_interval); +if ($cl_fav_report == -1) + $totals = ttChartHelper::getTotals($userDropdownSelectionId, $cl_type, $cl_date, $cl_interval); +else + $totals = ttChartHelper::getTotalsForFavReport($cl_fav_report, $cl_type); $smarty->assign('totals', $totals); // Prepare chart for drawing. @@ -202,8 +254,8 @@ $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()"'); +$smarty->assign('onload', 'onLoad="adjustTodayLinks();handleFavReportSelection();"'); $smarty->assign('forms', array($chart_form->getName() => $chart_form->toArray())); $smarty->assign('title', $i18n->get('title.charts')); -$smarty->assign('content_page_name', 'charts2.tpl'); -$smarty->display('index2.tpl'); +$smarty->assign('content_page_name', 'charts.tpl'); +$smarty->display('index.tpl'); diff --git a/client_add.php b/client_add.php index 1b91a4080..f1f45333e 100644 --- a/client_add.php +++ b/client_add.php @@ -68,5 +68,5 @@ $smarty->assign('onload', 'onLoad="document.clientForm.name.focus()"'); $smarty->assign('show_projects', $show_projects); $smarty->assign('title', $i18n->get('title.add_client')); -$smarty->assign('content_page_name', 'client_add2.tpl'); -$smarty->display('index2.tpl'); +$smarty->assign('content_page_name', 'client_add.tpl'); +$smarty->display('index.tpl'); diff --git a/client_delete.php b/client_delete.php index 147f2db54..e832c6bcb 100644 --- a/client_delete.php +++ b/client_delete.php @@ -50,5 +50,5 @@ $smarty->assign('client_to_delete', $client_to_delete); $smarty->assign('forms', array($form->getName()=>$form->toArray())); $smarty->assign('title', $i18n->get('title.delete_client')); -$smarty->assign('content_page_name', 'client_delete2.tpl'); -$smarty->display('index2.tpl'); +$smarty->assign('content_page_name', 'client_delete.tpl'); +$smarty->display('index.tpl'); diff --git a/client_edit.php b/client_edit.php index c7e0a92e3..665fda15e 100644 --- a/client_edit.php +++ b/client_edit.php @@ -64,6 +64,7 @@ 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')) { @@ -104,5 +105,5 @@ $smarty->assign('forms', array($form->getName()=>$form->toArray())); $smarty->assign('show_projects', $show_projects); $smarty->assign('title', $i18n->get('title.edit_client')); -$smarty->assign('content_page_name', 'client_edit2.tpl'); -$smarty->display('index2.tpl'); +$smarty->assign('content_page_name', 'client_edit.tpl'); +$smarty->display('index.tpl'); diff --git a/clients.php b/clients.php index 77ade9c7b..309eb155a 100644 --- a/clients.php +++ b/clients.php @@ -27,5 +27,5 @@ $smarty->assign('active_clients', $active_clients); $smarty->assign('inactive_clients', $inactive_clients); $smarty->assign('title', $i18n->get('title.clients')); -$smarty->assign('content_page_name', 'clients2.tpl'); -$smarty->display('index2.tpl'); +$smarty->assign('content_page_name', 'clients.tpl'); +$smarty->display('index.tpl'); diff --git a/dbinstall.php b/dbinstall.php index 322b05663..44fede0cd 100644 --- a/dbinstall.php +++ b/dbinstall.php @@ -91,13 +91,22 @@ function ttGenerateKeys() { } // Check if PHP version is good enough. - // $required_version = '5.2.1'; // Something in TCPDF library does not work below this one. - $required_version = '5.4.0'; // Week view (week.php) requires 5.4 because of []-way of referencing arrays. - // This needs further investigation as we use [] elsewhere without obvious problems. - if (version_compare(phpversion(), $required_version, '>=')) { - echo('PHP version: '.phpversion().', good enough.
'); + $required_version = '7.0'; // smarty 4.3.0 that we embed uses null coalescing operator (introduced in php 7). + + // Print a warning about php <= 7.4 because we are no longer testing there. + if (version_compare(phpversion(), '7.4', '<')) { + echo('Warning: This app is no longer tested with PHP version: '.phpversion().'.
'); + } + + // Print a warning about php >= 8.2 because of insufficient testing there. + if (version_compare(phpversion(), '8.2', '>=')) { + echo('Error: This app was not tested with PHP version: '.phpversion().'.
'); } else { - echo('Error: PHP version is not high enough: '.phpversion().'. Required: '.$required_version.'.
'); + if (version_compare(phpversion(), $required_version, '>=')) { + echo('PHP version: '.phpversion().', good enough.
'); + } else { + echo('Error: PHP version is not high enough: '.phpversion().'. Required: '.$required_version.'.
'); + } } // Depending on DSN, require either mysqli or mysql extensions. @@ -171,13 +180,17 @@ function ttGenerateKeys() { echo('There are no tables in database. Execute step 1 - Create database structure.
'); } - $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'].'.'); + 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(); @@ -187,7 +200,7 @@ function ttGenerateKeys() { if ($_POST) { print "Processing...
\n"; - if ($_POST["crstructure"]) { + if (array_key_exists('crstructure', $_POST)) { $sqlQuery = join("\n", file("mysql.sql")); $sqlQuery = str_replace("TYPE=MyISAM","",$sqlQuery); $queries = explode(";",$sqlQuery); @@ -201,7 +214,7 @@ function ttGenerateKeys() { } } - if ($_POST["convert5to7"]) { + 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"); @@ -215,7 +228,7 @@ function ttGenerateKeys() { ttExecute("alter table users drop `u_aprojects`"); } - if ($_POST["convert7to133"]) { + 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`"); @@ -230,7 +243,7 @@ function ttGenerateKeys() { // 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"; @@ -267,15 +280,15 @@ function ttGenerateKeys() { } } - if ($_POST["convert133to1340"]) { + 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 @@ -302,7 +315,7 @@ function ttGenerateKeys() { } } - if ($_POST["convert1340to1485"]) { + 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"); @@ -407,7 +420,7 @@ function ttGenerateKeys() { } // 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. @@ -504,7 +517,7 @@ function ttGenerateKeys() { print "Updated $clients_updated clients...
\n"; } - if ($_POST["convert1485to1579"]) { + 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"); @@ -611,7 +624,7 @@ function ttGenerateKeys() { ttExecute("create index invoice_idx on tt_log(invoice_id)"); } - if ($_POST["convert1579to1600"]) { + 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"); @@ -629,7 +642,7 @@ function ttGenerateKeys() { } // 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); @@ -668,7 +681,7 @@ function ttGenerateKeys() { } // The update_custom_fields function updates option_id field in tt_custom_field_log table. - if ($_POST['update_custom_fields']) { + if (array_key_exists('update_custom_fields', $_POST)) { $mdb2 = getConnection(); $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); @@ -679,7 +692,7 @@ function ttGenerateKeys() { } // 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); @@ -689,7 +702,7 @@ function ttGenerateKeys() { print "Updated $affected teams...
\n"; } - if ($_POST["convert1600to11400"]) { + 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"); @@ -737,7 +750,7 @@ function ttGenerateKeys() { ttExecute("ALTER TABLE `tt_log` ADD `paid` tinyint(4) NULL default '0' AFTER `billable`"); } - if ($_POST["convert11400to11744"]) { + 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`"); @@ -780,7 +793,7 @@ function ttGenerateKeys() { } // The update_role_id function assigns a role_id to users, who don't have it. - if ($_POST['update_role_id']) { + if (array_key_exists('update_role_id', $_POST)) { import('I18n'); $mdb2 = getConnection(); @@ -819,7 +832,7 @@ function ttGenerateKeys() { print "Updated $users_updated users...
\n"; } - if ($_POST["convert11744to11797"]) { + 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"); @@ -924,7 +937,7 @@ function ttGenerateKeys() { } // The update_group_id function updates group_id field in tt_log and tt_expense_items tables. - if ($_POST["update_group_id"]) { + if (array_key_exists('update_group_id', $_POST)) { $mdb2 = getConnection(); $sql = "(select distinct user_id from tt_log where group_id is null) union distinct (select distinct user_id from tt_expense_items where group_id is null)"; @@ -970,7 +983,7 @@ function ttGenerateKeys() { print "Updated $tt_expense_items_updated tt_expense_items records...
\n"; } - if ($_POST["convert11797to11900"]) { + 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`"); @@ -1137,7 +1150,7 @@ function ttGenerateKeys() { 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 ($_POST["convert11900to11923"]) { + 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')"); @@ -1170,9 +1183,26 @@ function ttGenerateKeys() { 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 ($_POST["cleanup"]) { + if (array_key_exists('cleanup', $_POST)) { $mdb2 = getConnection(); $inactive_orgs = ttOrgHelper::getInactiveOrgs(); @@ -1183,6 +1213,13 @@ function ttGenerateKeys() { $res = ttOrgHelper::deleteOrg($inactive_orgs[$i]); } + // 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"); @@ -1194,7 +1231,8 @@ function ttGenerateKeys() { ttExecute("OPTIMIZE TABLE tt_expense_items"); ttExecute("OPTIMIZE TABLE tt_fav_reports"); ttExecute("OPTIMIZE TABLE tt_invoices"); - ttExecute("OPTIMIZE TABLE tt_log"); + 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"); @@ -1220,7 +1258,7 @@ function ttGenerateKeys() {

DB Install

-
Create database structure (v1.19.23) + Create database structure (v1.22.3)
(applies only to new installations, do not execute when updating)
@@ -1269,8 +1307,8 @@ function ttGenerateKeys() {
Update database structure (v1.19 to v1.19.23)Update database structure (v1.19 to v1.22.3)
diff --git a/default.css b/default.css index e04cab3d5..d1a77ebd8 100644 --- a/default.css +++ b/default.css @@ -145,6 +145,20 @@ div.page-hint { 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; @@ -363,7 +377,7 @@ input[type="text"].short-text-field { } /* textareas for fields */ -#description, #address, #item_name, #comment, #custom_css, #content { +#description, #address, #item_name, #comment, #custom_css, #custom_translation, #content { width: 220px; border-radius: 4px; border: 1px solid #c9c9c9; @@ -371,7 +385,7 @@ input[type="text"].short-text-field { } /* textareas with focus */ -#description:focus, #address:focus, #item_name:focus, #comment:focus, #custom_css:focus, #content: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; @@ -526,6 +540,11 @@ div.sitemap-item { margin: .4rem; } +div.punch-timer { + font-size: 2rem; + color: blue; +} + /* media rules for small screens */ @media screen and (max-width: 600px) { @@ -799,8 +818,6 @@ input[type=checkbox], label { div.table-divider { height: 30px; } -div#LoginAboutText { width: 400px; } - .uncompleted-entry { display: inline-block; height: 8px; @@ -808,8 +825,10 @@ div#LoginAboutText { width: 400px; } border: 1px solid rgba(0, 0, 0, .1); border-radius: 50%; background-color: rgba(0, 0, 0, .1); + margin-right: .25em; } -.uncompleted-entry.active { background-color: red; } +.uncompleted-entry.active-today { background-color: red; } +.uncompleted-entry.active { background-color: purple; } .table_icon { height: 16px; diff --git a/display_options.php b/display_options.php index 53a86ca9e..e0b7bc987 100644 --- a/display_options.php +++ b/display_options.php @@ -20,13 +20,19 @@ $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'); @@ -46,14 +52,18 @@ // 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()) { @@ -62,9 +72,12 @@ $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_css' => $cl_custom_css, + 'custom_translation' => $cl_custom_translation))) { header('Location: success.php'); exit(); } else @@ -74,5 +87,5 @@ $smarty->assign('forms', array($form->getName()=>$form->toArray())); $smarty->assign('title', $i18n->get('title.display_options')); -$smarty->assign('content_page_name', 'display_options2.tpl'); -$smarty->display('index2.tpl'); +$smarty->assign('content_page_name', 'display_options.tpl'); +$smarty->display('index.tpl'); diff --git a/expense_delete.php b/expense_delete.php index 7ecd2c6d3..6dbf421a9 100644 --- a/expense_delete.php +++ b/expense_delete.php @@ -4,7 +4,7 @@ require_once('initialize.php'); import('form.Form'); -import('DateAndTime'); +import('ttDate'); import('ttExpenseHelper'); // Access checks. @@ -30,7 +30,7 @@ if ($request->getParameter('delete_button')) { // Delete button pressed. // Determine if it is okay to delete the record. - $item_date = new DateAndTime(DB_DATEFORMAT, $expense_item['date']); + $item_date = new ttDate($expense_item['date']); if ($user->isDateLocked($item_date)) $err->add($i18n->get('error.range_locked')); @@ -60,5 +60,5 @@ $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_delete2.tpl'); -$smarty->display('index2.tpl'); +$smarty->assign('content_page_name', 'expense_delete.tpl'); +$smarty->display('index.tpl'); diff --git a/expense_edit.php b/expense_edit.php index f4d3c2216..98098a19f 100644 --- a/expense_edit.php +++ b/expense_edit.php @@ -5,9 +5,9 @@ require_once('initialize.php'); import('form.Form'); import('ttGroupHelper'); -import('DateAndTime'); import('ttTimeHelper'); import('ttExpenseHelper'); +import('ttDate'); // Access checks. if (!(ttAccessAllowed('track_own_expenses') || ttAccessAllowed('track_expenses'))) { @@ -27,9 +27,17 @@ header('Location: access_denied.php'); exit(); } +if ($request->isPost()) { + // Validate that browser_today parameter is in correct format. + $browser_today = $request->getParameter('browser_today'); + if ($browser_today && !ttValidDbDateFormatDate($browser_today)) { + header('Location: access_denied.php'); + exit(); + } +} // End of access checks. -$item_date = new DateAndTime(DB_DATEFORMAT, $expense_item['date']); +$item_date = new ttDate($expense_item['date']); $confirm_save = $user->getConfigOption('confirm_save'); $trackingMode = $user->getTrackingMode(); $show_project = MODE_PROJECTS == $trackingMode || MODE_PROJECTS_AND_TASKS == $trackingMode; @@ -142,12 +150,14 @@ 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->getDateFormat(), $cl_date); + $new_date = new ttDate($cl_date, $user->getDateFormat()); // Prohibit creating entries in future. if (!$user->isOptionEnabled('future_entries')) { - $browser_today = new DateAndTime(DB_DATEFORMAT, $request->getParameter('browser_today', null)); - if ($new_date->after($browser_today)) + $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')); @@ -170,9 +180,9 @@ // Now, an update. if ($err->no()) { - if (ttExpenseHelper::update(array('id'=>$cl_id,'date'=>$new_date->toString(DB_DATEFORMAT), + if (ttExpenseHelper::update(array('id'=>$cl_id,'date'=>$new_date->toString(), 'client_id'=>$cl_client,'project_id'=>$cl_project,'name'=>$cl_item_name,'cost'=>$cl_cost,'paid'=>$cl_paid))) { - header('Location: expenses.php?date='.$new_date->toString(DB_DATEFORMAT)); + header('Location: expenses.php?date='.$new_date->toString()); exit(); } } @@ -186,9 +196,9 @@ // Now, a new insert. if ($err->no()) { - if (ttExpenseHelper::insert(array('date'=>$new_date->toString(DB_DATEFORMAT),'client_id'=>$cl_client, + 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(DB_DATEFORMAT)); + header('Location: expenses.php?date='.$new_date->toString()); exit(); } else $err->add($i18n->get('error.db')); @@ -211,5 +221,5 @@ $smarty->assign('client_list', $client_list); $smarty->assign('project_list', $project_list); $smarty->assign('title', $i18n->get('title.edit_expense')); -$smarty->assign('content_page_name', 'expense_edit2.tpl'); -$smarty->display('index2.tpl'); +$smarty->assign('content_page_name', 'expense_edit.tpl'); +$smarty->display('index.tpl'); diff --git a/expense_files.php b/expense_files.php index 5642cb383..7fe2a5d1a 100644 --- a/expense_files.php +++ b/expense_files.php @@ -61,5 +61,5 @@ $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_files2.tpl'); -$smarty->display('index2.tpl'); +$smarty->assign('content_page_name', 'entity_files.tpl'); +$smarty->display('index.tpl'); diff --git a/expenses.php b/expenses.php index d5978228e..3cbbd8bec 100644 --- a/expenses.php +++ b/expenses.php @@ -6,7 +6,7 @@ import('form.Form'); import('ttUserHelper'); import('ttGroupHelper'); -import('DateAndTime'); +import('ttDate'); import('ttTimeHelper'); import('ttExpenseHelper'); import('ttFileHelper'); @@ -33,11 +33,26 @@ exit(); } if ($request->isPost() && $request->getParameter('user')) { - if (!$user->isUserValid((int)$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. @@ -51,13 +66,12 @@ // 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'); @@ -175,6 +189,10 @@ // Submit. if ($request->isPost()) { if ($request->getParameter('btn_submit')) { + // Use the "limit" plugin if we have one. Ignore include errors. + // The "limit" plugin is not required for normal operation of Time Tracker. + @include('plugins/limit/access_check.php'); + // Validate user input. if ($user->isPluginEnabled('cl') && $user->isOptionEnabled('client_required') && !$cl_client) $err->add($i18n->get('error.client')); @@ -185,11 +203,12 @@ // Prohibit creating entries in future. if (!$user->isOptionEnabled('future_entries')) { - $browser_today = new DateAndTime(DB_DATEFORMAT, $request->getParameter('browser_today', null)); - if ($selected_date->after($browser_today)) + $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')); } - if (!ttTimeHelper::canAdd()) $err->add($i18n->get('error.expired')); // Finished validating input data. // Prohibit creating entries in locked range. @@ -230,5 +249,5 @@ $smarty->assign('project_list', $project_list); $smarty->assign('timestring', $selected_date->toString($user->getDateFormat())); $smarty->assign('title', $i18n->get('title.expenses')); -$smarty->assign('content_page_name', 'expenses2.tpl'); -$smarty->display('index2.tpl'); +$smarty->assign('content_page_name', 'expenses.tpl'); +$smarty->display('index.tpl'); diff --git a/export.php b/export.php index b5e1206dd..2bcba2dce 100644 --- a/export.php +++ b/export.php @@ -55,5 +55,5 @@ $smarty->assign('forms', array($form->getName()=>$form->toArray())); $smarty->assign('title', $i18n->get('title.export')); -$smarty->assign('content_page_name', 'export2.tpl'); -$smarty->display('index2.tpl'); +$smarty->assign('content_page_name', 'export.tpl'); +$smarty->display('index.tpl'); diff --git a/feature_disabled.php b/feature_disabled.php index c04afe734..d15413541 100644 --- a/feature_disabled.php +++ b/feature_disabled.php @@ -8,5 +8,5 @@ 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_denied2.tpl'); -$smarty->display('index2.tpl'); +$smarty->assign('content_page_name', 'access_denied.tpl'); +$smarty->display('index.tpl'); diff --git a/file_delete.php b/file_delete.php index 1b415c19b..e6cb53378 100644 --- a/file_delete.php +++ b/file_delete.php @@ -98,5 +98,5 @@ $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_delete2.tpl'); -$smarty->display('index2.tpl'); +$smarty->assign('content_page_name', 'file_delete.tpl'); +$smarty->display('index.tpl'); diff --git a/file_download.php b/file_download.php index ff7e1633a..2fb52f79b 100644 --- a/file_download.php +++ b/file_download.php @@ -59,5 +59,5 @@ } $smarty->assign('title', $i18n->get('title.download_file')); -$smarty->assign('content_page_name', 'file_download2.tpl'); -$smarty->display('index2.tpl'); +$smarty->assign('content_page_name', 'file_download.tpl'); +$smarty->display('index.tpl'); diff --git a/file_edit.php b/file_edit.php index 745293c2e..aeb8f70c7 100644 --- a/file_edit.php +++ b/file_edit.php @@ -96,5 +96,5 @@ $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_edit2.tpl'); -$smarty->display('index2.tpl'); +$smarty->assign('content_page_name', 'file_edit.tpl'); +$smarty->display('index.tpl'); diff --git a/group_add.php b/group_add.php index 8436acd80..6f88af205 100644 --- a/group_add.php +++ b/group_add.php @@ -46,5 +46,5 @@ $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_add2.tpl'); -$smarty->display('index2.tpl'); +$smarty->assign('content_page_name', 'group_add.tpl'); +$smarty->display('index.tpl'); diff --git a/group_advanced_edit.php b/group_advanced_edit.php index efb7a49e7..26134c789 100644 --- a/group_advanced_edit.php +++ b/group_advanced_edit.php @@ -16,17 +16,22 @@ // End of access checks. $group = ttGroupHelper::getGroupAttrs($user->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'); @@ -34,6 +39,8 @@ $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'))); @@ -43,15 +50,22 @@ 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_edit.allow_ip')); + 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))) { + 'allow_ip' => $cl_allow_ip, + 'password_complexity' => $cl_password_complexity, + 'config' => $config->getConfig()))) { header('Location: success.php'); exit(); } else @@ -62,5 +76,5 @@ $smarty->assign('forms', array($form->getName()=>$form->toArray())); $smarty->assign('title', $i18n->get('title.edit_group')); -$smarty->assign('content_page_name', 'group_advanced_edit2.tpl'); -$smarty->display('index2.tpl'); +$smarty->assign('content_page_name', 'group_advanced_edit.tpl'); +$smarty->display('index.tpl'); diff --git a/group_delete.php b/group_delete.php index c1e47d02c..377bb80e9 100644 --- a/group_delete.php +++ b/group_delete.php @@ -64,5 +64,5 @@ $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_delete2.tpl'); -$smarty->display('index2.tpl'); +$smarty->assign('content_page_name', 'group_delete.tpl'); +$smarty->display('index.tpl'); diff --git a/group_edit.php b/group_edit.php index 097c34054..dbfca56bb 100644 --- a/group_edit.php +++ b/group_edit.php @@ -66,6 +66,7 @@ $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'); @@ -82,6 +83,7 @@ $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'); @@ -166,6 +168,9 @@ // 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)); @@ -200,6 +205,7 @@ 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); @@ -231,5 +237,5 @@ $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_edit2.tpl'); -$smarty->display('index2.tpl'); +$smarty->assign('content_page_name', 'group_edit.tpl'); +$smarty->display('index.tpl'); diff --git a/groups.php b/groups.php index 5773220a2..ca01a5f7c 100644 --- a/groups.php +++ b/groups.php @@ -11,9 +11,16 @@ header('Location: access_denied.php'); exit(); } -if ($request->isPost() && !$user->isGroupValid($request->getParameter('group'))) { - header('Location: access_denied.php'); // Wrong group id in post. - exit(); +if ($request->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. @@ -39,5 +46,5 @@ $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', 'groups2.tpl'); -$smarty->display('index2.tpl'); +$smarty->assign('content_page_name', 'groups.tpl'); +$smarty->display('index.tpl'); diff --git a/import.php b/import.php index c4b59c240..b743b3f5a 100644 --- a/import.php +++ b/import.php @@ -25,5 +25,5 @@ $smarty->assign('forms', array($form->getName()=>$form->toArray()) ); $smarty->assign('title', $i18n->get('title.import')); -$smarty->assign('content_page_name', 'import2.tpl'); -$smarty->display('index2.tpl'); +$smarty->assign('content_page_name', 'import.tpl'); +$smarty->display('index.tpl'); diff --git a/initialize.php b/initialize.php index e7773d3d7..18946798c 100644 --- a/initialize.php +++ b/initialize.php @@ -12,7 +12,16 @@ // Disable displaying errors on screen. ini_set('display_errors', 'Off'); -define("APP_VERSION", "1.19.28.5530"); +// Disable mysqli fatal error behaviour when using php8.1 or greater. +// See https://php.watch/versions/8.1/mysqli-error-mode +if (version_compare(phpversion(), '8.1', '>=')) { + 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"); @@ -108,6 +117,11 @@ define('CHARSET', 'utf-8'); +// 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); + date_default_timezone_set(@date_default_timezone_get()); // Initialize global objects that are needed for the application. diff --git a/invoice_add.php b/invoice_add.php index bc337a000..e7018f009 100644 --- a/invoice_add.php +++ b/invoice_add.php @@ -80,5 +80,5 @@ $smarty->assign('onload', 'onLoad="document.invoiceForm.number.focus()"'); $smarty->assign('show_project', $show_project); $smarty->assign('title', $i18n->get('title.add_invoice')); -$smarty->assign('content_page_name', 'invoice_add2.tpl'); -$smarty->display('index2.tpl'); +$smarty->assign('content_page_name', 'invoice_add.tpl'); +$smarty->display('index.tpl'); diff --git a/invoice_delete.php b/invoice_delete.php index 7e80b1265..5b473dfa0 100644 --- a/invoice_delete.php +++ b/invoice_delete.php @@ -51,5 +51,5 @@ $smarty->assign('forms', array($form->getName()=>$form->toArray())); $smarty->assign('onload', 'onLoad="document.invoiceDeleteForm.btn_cancel.focus()"'); $smarty->assign('title', $i18n->get('title.delete_invoice')); -$smarty->assign('content_page_name', 'invoice_delete2.tpl'); -$smarty->display('index2.tpl'); +$smarty->assign('content_page_name', 'invoice_delete.tpl'); +$smarty->display('index.tpl'); diff --git a/invoice_send.php b/invoice_send.php index c3126eb9c..5483a09b1 100644 --- a/invoice_send.php +++ b/invoice_send.php @@ -80,5 +80,5 @@ $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', 'mail2.tpl'); -$smarty->display('index2.tpl'); +$smarty->assign('content_page_name', 'mail.tpl'); +$smarty->display('index.tpl'); diff --git a/invoice_view.php b/invoice_view.php index 621ae4a0c..b1fcf91f9 100644 --- a/invoice_view.php +++ b/invoice_view.php @@ -3,7 +3,7 @@ License: See license.txt */ require_once('initialize.php'); -import('DateAndTime'); +import('ttDate'); import('ttInvoiceHelper'); import('ttClientHelper'); import('form.Form'); @@ -25,7 +25,7 @@ } // End of access checks. -$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']); @@ -107,5 +107,5 @@ $smarty->assign('invoice_items', $invoice_items); $smarty->assign('colspan', $colspan); $smarty->assign('title', $i18n->get('title.view_invoice')); -$smarty->assign('content_page_name', 'invoice_view2.tpl'); -$smarty->display('index2.tpl'); +$smarty->assign('content_page_name', 'invoice_view.tpl'); +$smarty->display('index.tpl'); diff --git a/invoices.php b/invoices.php index bbca99cc7..d34e1220e 100644 --- a/invoices.php +++ b/invoices.php @@ -76,7 +76,7 @@ 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($request->getParameter('sorting_changed')) { + 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, @@ -90,5 +90,5 @@ $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', 'invoices2.tpl'); -$smarty->display('index2.tpl'); +$smarty->assign('content_page_name', 'invoices.tpl'); +$smarty->display('index.tpl'); diff --git a/locking.php b/locking.php index 5277ec90d..04c8d9419 100644 --- a/locking.php +++ b/locking.php @@ -37,5 +37,5 @@ $smarty->assign('forms', array($form->getName()=>$form->toArray())); $smarty->assign('title', $i18n->get('title.locking')); -$smarty->assign('content_page_name', 'locking2.tpl'); -$smarty->display('index2.tpl'); +$smarty->assign('content_page_name', 'locking.tpl'); +$smarty->display('index.tpl'); diff --git a/login.php b/login.php index 31d7f6d19..2d9dde044 100644 --- a/login.php +++ b/login.php @@ -6,6 +6,18 @@ import('form.Form'); import('ttOrgHelper'); import('ttUser'); +import('ttUserHelper'); + +// 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(); + } +} +// End of access checks. $cl_login = $request->getParameter('login'); if ($cl_login == null && $request->isGet()) $cl_login = @$_COOKIE[LOGIN_COOKIE_NAME]; @@ -22,32 +34,103 @@ 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(LOGIN_COOKIE_NAME, $cl_login, time() + COOKIE_EXPIRE, '/'); + } else { + $err->add($i18n->get('error.auth')); + } + } - $user = new ttUser(null, $auth->getUserId()); - // 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(); + // Do we have to use 2FA? + if ($err->no() && $loginSucceeded) { + $user = new ttUser(null, $auth->getUserId()); + + if ($user->initialized) { + // Determine if we have to additionally use two-factor authentication. + $config = $user->getConfigHelper(); + $use2FA = $config->getDefinedValue('2fa'); + } else { + $err->add($i18n->get('error.db')); + } + } + + // If we have to use 2FA, email auth code to user and redirect to 2fa.php. + if ($err->no() && $use2FA && !$user->can('override_2fa')) { + // To keep things simple, we use the same code as for password resets. + $cryptographically_strong = true; + $random_bytes = openssl_random_pseudo_bytes(16, $cryptographically_strong); + if ($random_bytes === false) die ("openssl_random_pseudo_bytes function call failed..."); + $temp_ref = bin2hex($random_bytes); + ttUserHelper::saveTmpRef($temp_ref, $user->id); + + // For user languague in email. + $user_i18n = null; + if ($user->lang != $i18n->lang) { + $user_i18n = new I18n(); + $user_i18n->load($user->lang); } else - $err->add($i18n->get('error.auth')); + $user_i18n = &$i18n; + + // Where do we email to? + $receiver = null; + if ($user->email) + $receiver = $user->email; + else { + if (ttValidEmail($user->login)) + $receiver = $user->login; + } + if (!$receiver) $err->add($user_i18n->get('error.no_email')); + + // Send 2FA_code email to user. + if ($receiver) { + import('mail.Mailer'); + $mailer = new Mailer(); + $mailer->setCharSet(CHARSET); + $mailer->setSender(SENDER); + $mailer->setReceiver("$receiver"); + + $subject = $user_i18n->get('email.2fa_code.subject'); + $body = sprintf($user_i18n->get('email.2fa_code.body'), $temp_ref); + + $mailer->setMailMode(MAIL_MODE); + if (!$mailer->send($subject, $body)) + $err->add($i18n->get('error.mail_send')); + } + + $auth->doLogout(); + + // Redirect to 2fa.php if we have no errors. + if ($err->no()) { + header('Location: 2fa.php'); + exit(); + } + } + + if ($err->no() && $loginSucceeded) { + // Set current user date (as determined by user browser) into session. + $current_user_date = $request->getParameter('browser_today', null); + if ($current_user_date) + $_SESSION['date'] = $current_user_date; + + // Redirect, depending on user role. + if ($user->can('administer_site')) { + header('Location: admin_groups.php'); + } elseif ($user->isClient()) { + header('Location: reports.php'); + } else { + header('Location: time.php'); + } + exit(); } } // isPost @@ -55,12 +138,12 @@ $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->get('title.login')); -$smarty->assign('content_page_name', 'login2.tpl'); $smarty->assign('about_text', $i18n->get('form.login.about')); -$smarty->display('index2.tpl'); +$smarty->assign('title', $i18n->get('title.login')); +$smarty->assign('content_page_name', 'login.tpl'); +$smarty->display('index.tpl'); diff --git a/mysql.sql b/mysql.sql index e9f4cc031..f614d0ac8 100644 --- a/mysql.sql +++ b/mysql.sql @@ -39,12 +39,14 @@ CREATE TABLE `tt_groups` ( `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`) ); @@ -77,7 +79,7 @@ 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,update_work,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_work,bid_on_work,manage_features,manage_advanced_settings,manage_roles,export_data,approve_all_reports,approve_own_timesheets,manage_subgroups,view_client_unapproved,delete_group'); +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'); # @@ -85,9 +87,9 @@ INSERT INTO `tt_roles` (`group_id`, `name`, `rank`, `rights`) VALUES (0, 'Top ma # CREATE TABLE `tt_users` ( `id` int(11) NOT NULL auto_increment, # user id - `login` varchar(50) COLLATE utf8mb4_bin NOT NULL,# user login + `login` varchar(80) COLLATE utf8mb4_bin NOT NULL,# user login `password` varchar(50) default NULL, # password hash - `name` varchar(100) default NULL, # user name + `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 @@ -272,6 +274,7 @@ CREATE TABLE `tt_fav_reports` ( `invoice` tinyint(4) default NULL, # whether to include invoiced, not invoiced, or all records `timesheet` tinyint(4) default NULL, # include records with a specific timesheet status, or all records `paid_status` tinyint(4) default NULL, # whether to include paid, not paid, or all records + `note_containing` varchar(80) default NULL, # include only records with notes containing this text `users` text default NULL, # Comma-separated list of user ids. Nothing here means "all" users. `period` tinyint(4) default NULL, # selected period type for report `period_start` date default NULL, # period start @@ -607,42 +610,6 @@ CREATE TABLE `tt_files` ( ); -# -# Structure for table tt_work_currencies. -# This table keeps currencies supported by remote work plugin. -# -CREATE TABLE `tt_work_currencies` ( - `id` int(10) unsigned NOT NULL, # currency id - `name` varchar(10) NOT NULL, # currency name (USD, CAD, etc.) - PRIMARY KEY (`id`) -); - -# Create an index that guarantees unique work currencies. -create unique index currency_idx on tt_work_currencies(`name`); - -INSERT INTO `tt_work_currencies` (`id`, `name`) VALUES ('1', 'USD'), ('2', 'CAD'), ('3', 'AUD'), ('4', 'EUR'), ('5', 'NZD'); - - -# -# Structure for table tt_work_categories. -# This table keeps work categories supported by remote work plugin. -# -CREATE TABLE `tt_work_categories` ( - `id` int(10) unsigned NOT NULL, # Category id. - `parents` text default NULL, # Comma-separated list of parent ids associated with this category. - `name` varchar(64) NOT NULL, # English category name. - `name_localized` text default NULL, # Comma-separated list of localized category names in other languages. - # Example: es:Codificación,ru:Кодирование. - PRIMARY KEY (`id`) -); - -# Insert some default categories. Table content to be updated at run time, though. -INSERT INTO `tt_work_categories` (`id`, `parents`, `name`, `name_localized`) VALUES ('1', NULL, 'Coding', 'es:Codificación,ru:Кодирование'); -INSERT INTO `tt_work_categories` (`id`, `parents`, `name`, `name_localized`) VALUES ('2', NULL, 'Other', 'es:Otra,ru:Другое'); -INSERT INTO `tt_work_categories` (`id`, `parents`, `name`, `name_localized`) VALUES ('3', '1', 'PHP', NULL); -INSERT INTO `tt_work_categories` (`id`, `parents`, `name`, `name_localized`) VALUES ('4', '1', 'C/C++', NULL); - - # # Structure for table tt_site_config. This table stores configuration data # for Time Tracker site as a whole. @@ -656,4 +623,4 @@ CREATE TABLE `tt_site_config` ( PRIMARY KEY (`param_name`) ); -INSERT INTO `tt_site_config` (`param_name`, `param_value`, `created`) VALUES ('version_db', '1.19.23', now()); # TODO: change when structure changes. +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 1290bfa23..99602fe5d 100644 --- a/notification_add.php +++ b/notification_add.php @@ -95,5 +95,5 @@ $smarty->assign('forms', array($form->getName()=>$form->toArray())); $smarty->assign('title', $i18n->get('title.add_notification')); -$smarty->assign('content_page_name', 'notification_add2.tpl'); -$smarty->display('index2.tpl'); +$smarty->assign('content_page_name', 'notification_add.tpl'); +$smarty->display('index.tpl'); diff --git a/notification_delete.php b/notification_delete.php index 741826c03..93820427c 100644 --- a/notification_delete.php +++ b/notification_delete.php @@ -51,5 +51,5 @@ $smarty->assign('forms', array($form->getName()=>$form->toArray())); $smarty->assign('onload', 'onLoad="document.notificationDeleteForm.btn_cancel.focus()"'); $smarty->assign('title', $i18n->get('title.delete_notification')); -$smarty->assign('content_page_name', 'notification_delete2.tpl'); -$smarty->display('index2.tpl'); +$smarty->assign('content_page_name', 'notification_delete.tpl'); +$smarty->display('index.tpl'); diff --git a/notification_edit.php b/notification_edit.php index 43dbabcff..164127e93 100644 --- a/notification_edit.php +++ b/notification_edit.php @@ -106,5 +106,5 @@ $smarty->assign('forms', array($form->getName()=>$form->toArray())); $smarty->assign('title', $i18n->get('title.edit_notification')); -$smarty->assign('content_page_name', 'notification_edit2.tpl'); -$smarty->display('index2.tpl'); +$smarty->assign('content_page_name', 'notification_edit.tpl'); +$smarty->display('index.tpl'); diff --git a/notifications.php b/notifications.php index 23d1feb16..5945320ef 100644 --- a/notifications.php +++ b/notifications.php @@ -40,5 +40,5 @@ $smarty->assign('forms', array($form->getName()=>$form->toArray())); $smarty->assign('notifications', $notifications); $smarty->assign('title', $i18n->get('title.notifications')); -$smarty->assign('content_page_name', 'notifications2.tpl'); -$smarty->display('index2.tpl'); +$smarty->assign('content_page_name', 'notifications.tpl'); +$smarty->display('index.tpl'); diff --git a/password_change.php b/password_change.php index f501a78e5..d395b6396 100644 --- a/password_change.php +++ b/password_change.php @@ -49,6 +49,9 @@ 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')); + // Check password complexity. + if (!ttCheckPasswordComplexity($cl_password1)) + $err->add($i18n->get('error.weak_password')); if ($err->no()) { // Use the "limit" plugin if we have one. Ignore include errors. @@ -77,5 +80,5 @@ $smarty->assign('forms', array($form->getName() => $form->toArray())); $smarty->assign('title', $i18n->get('title.change_password')); -$smarty->assign('content_page_name', 'password_change2.tpl'); -$smarty->display('index2.tpl'); +$smarty->assign('content_page_name', 'password_change.tpl'); +$smarty->display('index.tpl'); diff --git a/password_reset.php b/password_reset.php index 53099b9af..9645139e4 100644 --- a/password_reset.php +++ b/password_reset.php @@ -76,6 +76,7 @@ $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) @@ -85,7 +86,9 @@ $cl_subject = $user_i18n->get('form.reset_password.email_subject'); - $dir_name = trim(@constant('DIR_NAME'), '/'); + $dir_name = $app_root = ''; + if (defined('DIR_NAME')) + $dir_name = trim(constant('DIR_NAME'), '/'); if (!empty($dir_name)) $app_root = '/'.$dir_name; @@ -103,5 +106,5 @@ $smarty->assign('forms', array($form->getName()=>$form->toArray())); $smarty->assign('onload', 'onLoad="document.resetPasswordForm.login.focus()"'); $smarty->assign('title', $i18n->get('title.reset_password')); -$smarty->assign('content_page_name', 'password_reset2.tpl'); -$smarty->display('index2.tpl'); +$smarty->assign('content_page_name', 'password_reset.tpl'); +$smarty->display('index.tpl'); diff --git a/plugins.php b/plugins.php index 730889bc0..a4372694a 100644 --- a/plugins.php +++ b/plugins.php @@ -15,6 +15,7 @@ if ($request->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'); @@ -31,12 +32,12 @@ $cl_timesheets = (bool)$request->getParameter('timesheets'); $cl_templates = (bool)$request->getParameter('templates'); $cl_attachments = (bool)$request->getParameter('attachments'); - $cl_work = (bool)$request->getParameter('work'); } 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()); + $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); @@ -53,7 +54,6 @@ $cl_timesheets = in_array('ts', $plugins); $cl_templates = in_array('tp', $plugins); $cl_attachments = in_array('at', $plugins); - $cl_work = in_array('wk', $plugins); } $form = new Form('pluginsForm'); @@ -70,13 +70,13 @@ $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()')); -$form->addInput(array('type'=>'checkbox','name'=>'work','value'=>$cl_work,'onchange'=>'handlePluginCheckboxes()')); // Submit button. $form->addInput(array('type'=>'submit','name'=>'btn_save','value'=>$i18n->get('button.save'))); @@ -89,6 +89,8 @@ $plugins = ''; if ($cl_charts) $plugins .= ',ch'; + if ($cl_puncher) + $plugins .= ',pu'; if ($cl_clients) $plugins .= ',cl'; if ($cl_invoices) @@ -119,8 +121,6 @@ $plugins .= ',tp'; if ($cl_attachments) $plugins .= ',at'; - if ($cl_work) - $plugins .= ',wk'; $plugins = trim($plugins, ','); // Prepare a new config string. @@ -141,5 +141,5 @@ $smarty->assign('onload', 'onLoad="handlePluginCheckboxes();"'); $smarty->assign('user_exists', $user->exists()); $smarty->assign('title', $i18n->get('title.plugins')); -$smarty->assign('content_page_name', 'plugins2.tpl'); -$smarty->display('index2.tpl'); +$smarty->assign('content_page_name', 'plugins.tpl'); +$smarty->display('index.tpl'); diff --git a/plugins/CustomFields.class.php b/plugins/CustomFields.class.php index a11e40a32..b561af804 100644 --- a/plugins/CustomFields.class.php +++ b/plugins/CustomFields.class.php @@ -1,31 +1,6 @@ quote($option_name); + $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; @@ -156,6 +132,11 @@ static function insertOption($field_id, $option_name) { if (is_a($affected, 'PEAR_Error')) return false; } + + // Update entities_modified, too. + if (!ttGroupHelper::updateEntitiesModified()) + return false; + return true; } @@ -170,7 +151,14 @@ static function updateOption($id, $option_name) { $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); - return (!is_a($affected, 'PEAR_Error')); + if (is_a($affected, 'PEAR_Error')) + return false; + + // Update entities_modified, too. + if (!ttGroupHelper::updateEntitiesModified()) + return false; + + return true; } // delete Option deletes an option and all custom field log entries that used it. @@ -196,7 +184,14 @@ static function deleteOption($id) { $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); - return (!is_a($affected, 'PEAR_Error')); + if (is_a($affected, 'PEAR_Error')) + return false; + + // Update entities_modified, too. + if (!ttGroupHelper::updateEntitiesModified()) + return false; + + return true; } // getOptions returns an array of options for a custom field. @@ -226,6 +221,8 @@ 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; @@ -234,10 +231,10 @@ static function getOptionName($id) { $res = $mdb2->query($sql); if (!is_a($res, 'PEAR_Error')) { $val = $res->fetchRow(); - $name = $val['value']; - return $name; + if (isset($val['value'])) + return $val['value']; } - return false; + return null; } // getFields returns an array of custom fields for group. @@ -281,6 +278,27 @@ static function getField($id) { return false; } + // 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 null; + } + // getFieldIdForOption returns field id from an associated option id. static function getFieldIdForOption($option_id) { global $user; @@ -314,7 +332,14 @@ static function insertField($field_name, $entity_type, $field_type, $required) { $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); - return (!is_a($affected, 'PEAR_Error')); + if (is_a($affected, 'PEAR_Error')) + return false; + + // Update entities_modified, too. + if (!ttGroupHelper::updateEntitiesModified()) + return false; + + return true; } // The updateField updates custom field for group. @@ -328,7 +353,14 @@ static function updateField($id, $name, $type, $required) { $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); - return (!is_a($affected, 'PEAR_Error')); + if (is_a($affected, 'PEAR_Error')) + return false; + + // Update entities_modified, too. + if (!ttGroupHelper::updateEntitiesModified()) + return false; + + return true; } // The deleteField deletes a custom field, its options and log entries for group. @@ -340,24 +372,40 @@ static function deleteField($field_id) { $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); - return (!is_a($affected, 'PEAR_Error')); + if (is_a($affected, 'PEAR_Error')) + return false; + + // Update entities_modified, too. + if (!ttGroupHelper::updateEntitiesModified()) + return false; + + return true; } // insertTimeFields - inserts time custom fields into tt_custom_field_log. diff --git a/plugins/MonthlyQuota.class.php b/plugins/MonthlyQuota.class.php index 0a8ee4e9b..f37cd7885 100644 --- a/plugins/MonthlyQuota.class.php +++ b/plugins/MonthlyQuota.class.php @@ -1,30 +1,6 @@ group_id, $this->org_id, $year, $month, $minutes)"; $affected = $this->db->exec($insertSql); - return (!is_a($affected, 'PEAR_Error')); + if (is_a($affected, 'PEAR_Error')) + return false; } + + // Update entities_modified, too. + if (!ttGroupHelper::updateEntitiesModified()) + return false; + return true; } @@ -98,13 +80,13 @@ public function getUserQuota($year, $month) { // 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->mYear, $selected_date->mMonth); - $workdaysInMonth = $this->getNumWorkdays($selected_date->mMonth, $selected_date->mYear); + $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->mDate; $i++) { - $date = ttTimeHelper::dateInDatabaseFormat($selected_date->mYear, $selected_date->mMonth, $i); + 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++; } diff --git a/plugins/work_constants.php b/plugins/work_constants.php deleted file mode 100644 index 207ca5d35..000000000 --- a/plugins/work_constants.php +++ /dev/null @@ -1,36 +0,0 @@ -assign('forms', array($form->getName()=>$form->toArray())); $smarty->assign('title', $i18n->get('title.add_predefined_expense')); -$smarty->assign('content_page_name', 'predefined_expense_add2.tpl'); -$smarty->display('index2.tpl'); +$smarty->assign('content_page_name', 'predefined_expense_add.tpl'); +$smarty->display('index.tpl'); diff --git a/predefined_expense_delete.php b/predefined_expense_delete.php index 26b726fc1..e965b0be2 100644 --- a/predefined_expense_delete.php +++ b/predefined_expense_delete.php @@ -48,5 +48,5 @@ $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_delete2.tpl'); -$smarty->display('index2.tpl'); +$smarty->assign('content_page_name', 'predefined_expense_delete.tpl'); +$smarty->display('index.tpl'); diff --git a/predefined_expense_edit.php b/predefined_expense_edit.php index d1b557553..8f4750245 100644 --- a/predefined_expense_edit.php +++ b/predefined_expense_edit.php @@ -55,5 +55,5 @@ $smarty->assign('forms', array($form->getName()=>$form->toArray())); $smarty->assign('title', $i18n->get('title.edit_predefined_expense')); -$smarty->assign('content_page_name', 'predefined_expense_edit2.tpl'); -$smarty->display('index2.tpl'); +$smarty->assign('content_page_name', 'predefined_expense_edit.tpl'); +$smarty->display('index.tpl'); diff --git a/predefined_expenses.php b/predefined_expenses.php index c44aee27b..9f94bd1cc 100644 --- a/predefined_expenses.php +++ b/predefined_expenses.php @@ -33,5 +33,5 @@ $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_expenses2.tpl'); -$smarty->display('index2.tpl'); +$smarty->assign('content_page_name', 'predefined_expenses.tpl'); +$smarty->display('index.tpl'); diff --git a/profile_edit.php b/profile_edit.php index f85473fe4..3958a4018 100644 --- a/profile_edit.php +++ b/profile_edit.php @@ -67,6 +67,9 @@ 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')); + // 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. @@ -87,5 +90,5 @@ $smarty->assign('auth_external', $auth->isPasswordExternal()); $smarty->assign('forms', array($form->getName()=>$form->toArray())); $smarty->assign('title', $i18n->get('title.profile')); -$smarty->assign('content_page_name', 'profile_edit2.tpl'); -$smarty->display('index2.tpl'); +$smarty->assign('content_page_name', 'profile_edit.tpl'); +$smarty->display('index.tpl'); diff --git a/project_add.php b/project_add.php index 16af7a8d1..859fdc259 100644 --- a/project_add.php +++ b/project_add.php @@ -19,6 +19,13 @@ } // End of access checks. +// Use custom fields plugin if it is enabled. +if ($user->isPluginEnabled('cf')) { + require_once('plugins/CustomFields.class.php'); + $custom_fields = new CustomFields(); + $smarty->assign('custom_fields', $custom_fields); +} + $showFiles = $user->isPluginEnabled('at'); $users = ttGroupHelper::getActiveUsers(); foreach ($users as $user_item) @@ -35,6 +42,18 @@ $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']; @@ -45,6 +64,20 @@ $form = new Form('projectForm'); $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)); @@ -56,26 +89,37 @@ // 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')); + // 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)) { - $id = ttProjectHelper::insert(array('name' => $cl_name, + $project_id = ttProjectHelper::insert(array('name' => $cl_name, 'description' => $cl_description, 'users' => $cl_users, 'tasks' => $cl_tasks, '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 ($id && $showFiles && $_FILES['newfile']['name']) { + if ($project_id && $showFiles && $_FILES['newfile']['name']) { $fileHelper = new ttFileHelper($err); $fields = array('entity_type'=>'project', - 'entity_id' => $id, + 'entity_id' => $project_id, 'file_name' => $_FILES['newfile']['name']); $fileHelper->putFile($fields); } - if ($id) { + if ($project_id && $result) { header('Location: projects.php'); exit(); } else @@ -91,5 +135,5 @@ $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_add2.tpl'); -$smarty->display('index2.tpl'); +$smarty->assign('content_page_name', 'project_add.tpl'); +$smarty->display('index.tpl'); diff --git a/project_delete.php b/project_delete.php index 2d6ca2895..4f4353b87 100644 --- a/project_delete.php +++ b/project_delete.php @@ -47,5 +47,5 @@ $smarty->assign('forms', array($form->getName()=>$form->toArray())); $smarty->assign('onload', 'onLoad="document.projectDeleteForm.btn_cancel.focus()"'); $smarty->assign('title', $i18n->get('title.delete_project')); -$smarty->assign('content_page_name', 'project_delete2.tpl'); -$smarty->display('index2.tpl'); +$smarty->assign('content_page_name', 'project_delete.tpl'); +$smarty->display('index.tpl'); diff --git a/project_edit.php b/project_edit.php index cbb2c1f9b..a0190ff83 100644 --- a/project_edit.php +++ b/project_edit.php @@ -24,6 +24,13 @@ } // End of access checks. +// Use custom fields plugin if it is enabled. +if ($user->isPluginEnabled('cf')) { + require_once('plugins/CustomFields.class.php'); + $custom_fields = new CustomFields(); + $smarty->assign('custom_fields', $custom_fields); +} + $users = ttGroupHelper::getActiveUsers(); foreach ($users as $user_item) $all_users[$user_item['id']] = $user_item['name']; @@ -39,9 +46,33 @@ $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 { $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']; $cl_users = ttProjectHelper::getAssignedUsers($cl_project_id); $cl_tasks = explode(',', $project['tasks']); @@ -51,6 +82,20 @@ $form->addInput(array('type'=>'hidden','name'=>'id','value'=>$cl_project_id)); $form->addInput(array('type'=>'text','maxlength'=>'100','name'=>'project_name','value'=>$cl_name)); $form->addInput(array('type'=>'textarea','name'=>'description','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->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)); @@ -63,6 +108,14 @@ // 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')); + // 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')); @@ -71,28 +124,39 @@ $existing_project = ttProjectHelper::getProjectByName($cl_name); if (!$existing_project || ($cl_project_id == $existing_project['id'])) { // Update project information. - if (ttProjectHelper::update(array( + $result = ttProjectHelper::update(array( 'id' => $cl_project_id, 'name' => $cl_name, 'description' => $cl_description, 'status' => $cl_status, 'users' => $cl_users, - 'tasks' => $cl_tasks))) { + '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 + } else $err->add($i18n->get('error.db')); - } else + } else $err->add($i18n->get('error.object_exists')); } if ($request->getParameter('btn_copy')) { if (!ttProjectHelper::getProjectByName($cl_name)) { - if (ttProjectHelper::insert(array('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 @@ -108,5 +172,5 @@ $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_edit2.tpl'); -$smarty->display('index2.tpl'); +$smarty->assign('content_page_name', 'project_edit.tpl'); +$smarty->display('index.tpl'); diff --git a/project_files.php b/project_files.php index 60fc4d9e3..8b38a8ce6 100644 --- a/project_files.php +++ b/project_files.php @@ -62,5 +62,5 @@ $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_files2.tpl'); -$smarty->display('index2.tpl'); +$smarty->assign('content_page_name', 'entity_files.tpl'); +$smarty->display('index.tpl'); diff --git a/projects.php b/projects.php index e9a0b5dcf..ed4d371df 100644 --- a/projects.php +++ b/projects.php @@ -30,5 +30,5 @@ $smarty->assign('inactive_projects', $inactive_projects); $smarty->assign('show_files', $showFiles); $smarty->assign('title', $i18n->get('title.projects')); -$smarty->assign('content_page_name', 'projects2.tpl'); -$smarty->display('index2.tpl'); +$smarty->assign('content_page_name', 'projects.tpl'); +$smarty->display('index.tpl'); diff --git a/timer.php b/puncher.php similarity index 77% rename from timer.php rename to puncher.php index aa51a4dc4..26b371fcd 100644 --- a/timer.php +++ b/puncher.php @@ -8,13 +8,37 @@ import('ttGroupHelper'); import('ttClientHelper'); import('ttTimeHelper'); -import('DateAndTime'); +import('ttDate'); // Access check. if (!ttAccessAllowed('track_own_time')) { header('Location: access_denied.php'); exit(); } +if (!$user->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'); @@ -24,16 +48,11 @@ $showTask = MODE_PROJECTS_AND_TASKS == $trackingMode; $taskRequired = false; if ($showTask) $taskRequired = $user->getConfigOption('task_required'); +$oneUncompleted = $user->getConfigOption('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); -if(!$cl_date) - $cl_date = $selected_date->toString(DB_DATEFORMAT); -$_SESSION['date'] = $cl_date; -// TODO: for timer page we may limit the day to today only. +// 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')) { @@ -42,13 +61,16 @@ $smarty->assign('custom_fields', $custom_fields); } -// Obtain uncompleted record. Assumption is that only 1 uncompleted record is allowed. +// 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()); -$enable_controls = ($uncompleted == null); // Initialize variables. -$cl_start = trim($request->getParameter('browser_time')); -$cl_finish = trim($request->getParameter('browser_time')); +$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. @@ -183,11 +205,11 @@ $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 = $uncompleted ? false : true; -if (!$uncompleted) - $form->addInput(array('type'=>'submit','name'=>'btn_start','onclick'=>'browser_time.value=get_time()','value'=>$i18n->get('label.start'),'enable'=>$enable_start)); +$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_time.value=get_time()','value'=>$i18n->get('label.finish'),'enable'=>!$enable_start)); + $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()) { @@ -215,18 +237,20 @@ // Prohibit creating entries in future. if (!$user->isOptionEnabled('future_entries')) { - $browser_today = new DateAndTime(DB_DATEFORMAT, $request->getParameter('browser_today', null)); - if ($selected_date->after($browser_today)) + // $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($selected_date)) + if ($user->isDateLocked($date_today)) $err->add($i18n->get('error.range_locked')); // Prohibit creating another uncompleted record. - if ($err->no() && $uncompleted) { - $err->add($i18n->get('error.uncompleted_exists')." ".$i18n->get('error.goto_uncompleted').""); + if ($err->no() && $uncompleted && $oneUncompleted) { + $err->add($i18n->get('error.uncompleted_exists')." ".$i18n->get('error.goto_uncompleted').""); } // Prohibit creating an overlapping record. @@ -254,7 +278,7 @@ } if ($id && $result) { - header('Location: timer.php'); + header('Location: puncher.php'); exit(); } $err->add($i18n->get('error.db')); @@ -262,7 +286,7 @@ } if ($request->getParameter('btn_stop')) { // Stop button clicked. We need to finish an uncompleted record in progress. - $record = ttTimeHelper::getRecord($uncompleted['id']); + $record = ttTimeHelper::getRecord($uncompletedToday['id']); // Can we complete this record? if (ttTimeHelper::isValidInterval($record['start'], $cl_finish) // finish time is greater than start time @@ -278,7 +302,7 @@ 'note'=>$record['comment'], 'billable'=>$record['billable'])); if ($res) { - header('Location: timer.php'); + header('Location: puncher.php'); exit(); } else $err->add($i18n->get('error.db')); @@ -290,22 +314,26 @@ } } // isPost -$week_total = ttTimeHelper::getTimeForWeek($cl_date); +$week_total = ttTimeHelper::getTimeForWeek($date_today); +$timeRecords = ttTimeHelper::getRecords($cl_date); + $smarty->assign('week_total', $week_total); -$smarty->assign('uncompleted', $uncompleted); +$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('time_records', ttTimeHelper::getRecords($cl_date)); $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', $selected_date->toString($user->date_format)); -$smarty->assign('title', $i18n->get('title.time')); -$smarty->assign('content_page_name', 'timer2.tpl'); -$smarty->display('index2.tpl'); +$smarty->assign('timestring', $date_today->toString($user->getDateFormat())); +$smarty->assign('title', $i18n->get('title.puncher')); +$smarty->assign('content_page_name', 'puncher.tpl'); +$smarty->display('index.tpl'); diff --git a/puncher_conf.php b/puncher_conf.php new file mode 100644 index 000000000..51610f693 --- /dev/null +++ b/puncher_conf.php @@ -0,0 +1,37 @@ +getConfigHelper(); + +if ($request->isPost()) { + $cl_puncher_menu = (bool)$request->getParameter('puncher_menu'); +} else { + $cl_puncher_menu = $config->getDefinedValue('puncher_menu'); +} + +$form = new Form('puncherConfigForm'); +$form->addInput(array('type'=>'checkbox','name'=>'puncher_menu','value'=>$cl_puncher_menu)); +$form->addInput(array('type'=>'submit','name'=>'btn_save','value'=>$i18n->get('button.save'))); + +if ($request->isPost()){ + // Update config. + $config->setDefinedValue('puncher_menu', $cl_puncher_menu); + if (!$user->updateGroup(array('config' => $config->getConfig()))) { + $err->add($i18n->get('error.db')); + } +} + +$smarty->assign('forms', array($form->getName()=>$form->toArray())); +$smarty->assign('title', $i18n->get('title.puncher')); +$smarty->assign('content_page_name', 'puncher_conf.tpl'); +$smarty->display('index.tpl'); diff --git a/quotas.php b/quotas.php index c49fbf1f7..55214093f 100644 --- a/quotas.php +++ b/quotas.php @@ -107,5 +107,5 @@ $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', 'quotas2.tpl'); -$smarty->display('index2.tpl'); +$smarty->assign('content_page_name', 'quotas.tpl'); +$smarty->display('index.tpl'); diff --git a/readme.txt b/readme.txt index 421a18cdb..90cdc970d 100644 --- a/readme.txt +++ b/readme.txt @@ -1,9 +1,9 @@ Anuko Time Tracker Copyright (c) Anuko (https://www.anuko.com) -Project home page: https://www.anuko.com/time_tracker/index.htm +Project home page: https://www.anuko.com/time-tracker/index.htm Forum: https://www.anuko.com/forum/viewforum.php?f=4 -Info for developers: https://www.anuko.com/time_tracker/info_for_developers.htm +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 Unless otherwise noted, files in this archive are protected by the LIBERAL FREEWARE LICENSE. @@ -12,7 +12,7 @@ Read the file license.txt for details. INSTALLATION INSTRUCTIONS -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: @@ -40,7 +40,7 @@ The general installation procedure looks like this: 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 @@ -62,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 220376138..6f54ccf5b 100644 --- a/register.php +++ b/register.php @@ -36,8 +36,8 @@ } $form = new Form('groupForm'); -$form->addInput(array('type'=>'text','maxlength'=>'200','name'=>'group_name','value'=>$cl_group_name)); -$form->addInput(array('type'=>'text','maxlength'=>'7','name'=>'currency','value'=>$cl_currency)); +$form->addInput(array('type'=>'text','name'=>'group_name','value'=>$cl_group_name)); +$form->addInput(array('type'=>'text','name'=>'currency','value'=>$cl_currency)); // Prepare an array of available languages. $lang_files = I18n::getLangFileList(); @@ -57,14 +57,16 @@ $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)); +$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->get('button.submit'))); if ($request->isPost()) { + // Note: user input validation is done in ttRegistrator constructor. + // Create fields array for ttRegistrator instance. $fields = array( 'user_name' => $cl_manager_name, @@ -96,5 +98,5 @@ $smarty->assign('title', $i18n->get('title.add_group')); $smarty->assign('forms', array($form->getName()=>$form->toArray())); $smarty->assign('onload', 'onLoad="document.groupForm.group_name.focus()"'); -$smarty->assign('content_page_name', 'register2.tpl'); -$smarty->display('index2.tpl'); +$smarty->assign('content_page_name', 'register.tpl'); +$smarty->display('index.tpl'); diff --git a/report.php b/report.php index cb959d488..b8a549b69 100644 --- a/report.php +++ b/report.php @@ -18,6 +18,7 @@ // End of access checks. $config = new ttConfigHelper($user->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'])); @@ -58,7 +59,7 @@ // 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 = $bean->getAttribute('client'); +$client_id = (int)$bean->getAttribute('client'); $options = ttReportHelper::getReportOptions($bean); // Do we need to show checkboxes? We show them in the following 4 situations: @@ -298,7 +299,11 @@ 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')) $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++; @@ -316,9 +321,10 @@ $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->get('title.report').": ".$totals['start_date']." - ".$totals['end_date']); -$smarty->assign('content_page_name', 'report2.tpl'); -$smarty->display('index2.tpl'); +$smarty->assign('content_page_name', 'report.tpl'); +$smarty->display('index.tpl'); diff --git a/report_send.php b/report_send.php index 67e7136c6..b20dd6dfe 100644 --- a/report_send.php +++ b/report_send.php @@ -74,5 +74,5 @@ $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', 'mail2.tpl'); -$smarty->display('index2.tpl'); +$smarty->assign('content_page_name', 'mail.tpl'); +$smarty->display('index.tpl'); diff --git a/reports.php b/reports.php index 71f455bea..aabeec08f 100644 --- a/reports.php +++ b/reports.php @@ -5,13 +5,13 @@ require_once('initialize.php'); import('form.Form'); import('form.ActionForm'); -import('DateAndTime'); import('ttGroupHelper'); -import('Period'); import('ttProjectHelper'); import('ttFavReportHelper'); import('ttClientHelper'); import('ttReportHelper'); +import('ttDate'); +import('ttPeriod'); // Access check. if (!(ttAccessAllowed('view_own_reports') || ttAccessAllowed('view_reports') || ttAccessAllowed('view_all_reports') || ttAccessAllowed('view_client_reports'))) { @@ -73,7 +73,9 @@ $project_list = array(); if ($showProject) { if ($user->can('view_reports') || $user->can('view_all_reports')) { - $project_list = ttProjectHelper::getProjects(); // All active and inactive projects. + $config = new ttConfigHelper($user->getConfig()); + $includeInactiveProjects = $config->getDefinedValue('report_inactive_projects'); + $project_list = ttProjectHelper::getProjects($includeInactiveProjects); } elseif ($user->isClient()) { $project_list = ttProjectHelper::getProjectsForClient(); } else { @@ -82,9 +84,16 @@ if (count($project_list) == 0) $showProject = false; } if ($showProject) { - $form->addInput(array('type'=>'combobox', - 'onchange'=>'fillTaskDropdown(this.value);selectAssignedUsers(this.value);', - 'name'=>'project', + $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')))); @@ -167,7 +176,7 @@ // Add user table. $showUsers = $user->can('view_reports') || $user->can('view_all_reports') || $user->isClient(); -$user_list = array(); +$user_list = $user_list_active = $user_list_inactive = array(); if ($showUsers) { // Prepare user and assigned projects arrays. if ($user->can('view_reports') || $user->can('view_all_reports')) { @@ -206,7 +215,6 @@ 'layout'=>'V', 'groupin'=>$row_count)); - $user_list_inactive = array(); foreach ($inactive_users as $single_user) { $user_list_inactive[$single_user['id']] = $single_user['name']; $projects = ttProjectHelper::getAssignedProjects($single_user['id']); @@ -228,16 +236,19 @@ $form->addInput(array('type'=>'combobox', 'name'=>'period', 'data'=>array(INTERVAL_THIS_MONTH=>$i18n->get('dropdown.current_month'), - INTERVAL_LAST_MONTH=>$i18n->get('dropdown.previous_month'), + INTERVAL_PREVIOUS_MONTH=>$i18n->get('dropdown.previous_month'), INTERVAL_THIS_WEEK=>$i18n->get('dropdown.current_week'), - INTERVAL_LAST_WEEK=>$i18n->get('dropdown.previous_week'), + INTERVAL_PREVIOUS_WEEK=>$i18n->get('dropdown.previous_week'), INTERVAL_THIS_DAY=>$i18n->get('dropdown.current_day'), - INTERVAL_LAST_DAY=>$i18n->get('dropdown.previous_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 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')); @@ -311,6 +322,23 @@ } } +// 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)); + } +} + // Add group by control. $group_by_options['no_grouping'] = $i18n->get('form.reports.group_by_no'); $group_by_options['date'] = $i18n->get('form.reports.group_by_date'); @@ -336,6 +364,13 @@ $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)); @@ -356,9 +391,9 @@ if ($request->isGet() && !$bean->isSaved()) { // No previous form data were found in session. Use the following default values. $form->setValueByElement('users_active', array_keys((array)$user_list_active)); - $period = new Period(INTERVAL_THIS_MONTH, new DateAndTime($user->getDateFormat())); - $form->setValueByElement('start_date', $period->getStartDate()); - $form->setValueByElement('end_date', $period->getEndDate()); + $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'); @@ -389,83 +424,90 @@ $form->getElement('btn_delete')->setEnabled(false); if ($request->isPost()) { - if((!$bean->getAttribute('btn_generate') && ($request->getParameter('fav_report_changed')))) { - // User changed favorite report. We need to load new values into the form. - if ($bean->getAttribute('favorite_report')) { - // This loads new favorite report options into the bean (into our form). - ttFavReportHelper::loadReport($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); + // 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_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')); - // Save form data in session for future use. - $bean->saveBean(); - header('Location: reports.php'); - exit(); - } - } elseif ($bean->getAttribute('btn_save')) { - // User clicked the Save button. We need to save form options as new favorite report. - if (!ttValidString($bean->getAttribute('new_fav_report'))) $err->add($i18n->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); + $id = ttFavReportHelper::saveReport($bean); + if (!$id) + $err->add($i18n->get('error.db')); + if ($err->no()) { + $bean->setAttribute('favorite_report', $id); + $bean->saveBean(); + header('Location: reports.php'); + exit(); + } + } + } elseif($bean->getAttribute('btn_delete')) { + // Delete button pressed. User wants to delete a favorite report. + if ($bean->getAttribute('favorite_report')) { + ttFavReportHelper::deleteReport($bean->getAttribute('favorite_report')); + // Load default report. + $bean->setAttribute('favorite_report',''); + $bean->setAttribute('new_fav_report', $report_list[0]['name']); + ttFavReportHelper::loadReport($bean); + $form->setValueByElement('users', array_keys($user_list)); $bean->saveBean(); header('Location: reports.php'); exit(); } - } - } elseif($bean->getAttribute('btn_delete')) { - // Delete button pressed. User wants to delete a favorite report. - if ($bean->getAttribute('favorite_report')) { - ttFavReportHelper::deleteReport($bean->getAttribute('favorite_report')); - // Load default report. - $bean->setAttribute('favorite_report',''); - $bean->setAttribute('new_fav_report', $report_list[0]['name']); - ttFavReportHelper::loadReport($bean); - $form->setValueByElement('users', array_keys($user_list)); - $bean->saveBean(); - header('Location: reports.php'); - exit(); - } - } else { - // Generate button pressed. Check some values. - if (!$bean->getAttribute('period')) { - $start_date = new DateAndTime($user->getDateFormat(), $bean->getAttribute('start_date')); - - if ($start_date->isError() || !$bean->getAttribute('start_date')) - $err->add($i18n->get('error.field'), $i18n->get('label.start_date')); - - $end_date = new DateAndTime($user->getDateFormat(), $bean->getAttribute('end_date')); - if ($end_date->isError() || !$bean->getAttribute('end_date')) - $err->add($i18n->get('error.field'), $i18n->get('label.end_date')); + } 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 ($start_date->compare($end_date) > 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')); - // Check remaining values. - if (!ttReportHelper::verifyBean($bean)) $err->add($i18n->get('error.sys')); - - if ($err->no()) { - $bean->saveBean(); - // Now we can go ahead and create a report. - header('Location: report.php'); - exit(); + if ($err->no()) { + $bean->saveBean(); + // Now we can go ahead and create a report. + header('Location: report.php'); + exit(); + } } - } + } // if($err->no()) } // isPost $smarty->assign('client_list', $client_list); @@ -492,5 +534,5 @@ $smarty->assign('forms', array($form->getName()=>$form->toArray())); $smarty->assign('onload', 'onLoad="handleCheckboxes();fillDropdowns()"'); $smarty->assign('title', $i18n->get('title.reports')); -$smarty->assign('content_page_name', 'reports2.tpl'); -$smarty->display('index2.tpl'); +$smarty->assign('content_page_name', 'reports.tpl'); +$smarty->display('index.tpl'); diff --git a/role_add.php b/role_add.php index 54fd9bdb5..b4e05f903 100644 --- a/role_add.php +++ b/role_add.php @@ -12,6 +12,7 @@ exit(); } +$cl_name = $cl_description = $cl_rank = null; if ($request->isPost()) { $cl_name = trim($request->getParameter('name')); $cl_description = trim($request->getParameter('description')); @@ -55,5 +56,5 @@ $smarty->assign('forms', array($form->getName()=>$form->toArray())); $smarty->assign('title', $i18n->get('title.add_role')); -$smarty->assign('content_page_name', 'role_add2.tpl'); -$smarty->display('index2.tpl'); +$smarty->assign('content_page_name', 'role_add.tpl'); +$smarty->display('index.tpl'); diff --git a/role_delete.php b/role_delete.php index 805c89dba..e2f0805eb 100644 --- a/role_delete.php +++ b/role_delete.php @@ -46,5 +46,5 @@ $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_delete2.tpl'); -$smarty->display('index2.tpl'); +$smarty->assign('content_page_name', 'role_delete.tpl'); +$smarty->display('index.tpl'); diff --git a/role_edit.php b/role_edit.php index 03e1d2473..f2aecadee 100644 --- a/role_edit.php +++ b/role_edit.php @@ -58,6 +58,7 @@ 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); @@ -115,5 +116,5 @@ $smarty->assign('forms', array($form->getName()=>$form->toArray())); $smarty->assign('title', $i18n->get('title.edit_role')); -$smarty->assign('content_page_name', 'role_edit2.tpl'); -$smarty->display('index2.tpl'); +$smarty->assign('content_page_name', 'role_edit.tpl'); +$smarty->display('index.tpl'); diff --git a/roles.php b/roles.php index f2795fb0f..569f2649a 100644 --- a/roles.php +++ b/roles.php @@ -17,5 +17,5 @@ $smarty->assign('active_roles', ttTeamHelper::getActiveRolesForUser()); $smarty->assign('inactive_roles', ttTeamHelper::getInactiveRolesForUser()); $smarty->assign('title', $i18n->get('title.roles')); -$smarty->assign('content_page_name', 'roles2.tpl'); -$smarty->display('index2.tpl'); +$smarty->assign('content_page_name', 'roles.tpl'); +$smarty->display('index.tpl'); diff --git a/site_map.php b/site_map.php index 56a79511b..f769edd76 100644 --- a/site_map.php +++ b/site_map.php @@ -12,6 +12,5 @@ global $auth; $authenticated = $auth->isAuthenticated(); // This call assigns 'authenticated' to smarty. -// $smarty->assign('title', $i18n->get('form.label.menu')); $smarty->assign('content_page_name', 'site_map.tpl'); -$smarty->display('index2.tpl'); +$smarty->display('index.tpl'); diff --git a/success.php b/success.php index eea4687a2..6fbf8ad2a 100644 --- a/success.php +++ b/success.php @@ -8,5 +8,5 @@ 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', 'success2.tpl'); -$smarty->display('index2.tpl'); +$smarty->assign('content_page_name', 'success.tpl'); +$smarty->display('index.tpl'); diff --git a/swap_roles.php b/swap_roles.php index e49bed75b..7a036a777 100644 --- a/swap_roles.php +++ b/swap_roles.php @@ -49,5 +49,5 @@ $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_roles2.tpl'); -$smarty->display('index2.tpl'); +$smarty->assign('content_page_name', 'swap_roles.tpl'); +$smarty->display('index.tpl'); diff --git a/task_add.php b/task_add.php index 4888677f1..4a3353703 100644 --- a/task_add.php +++ b/task_add.php @@ -32,15 +32,15 @@ } $form = new Form('taskForm'); -$form->addInput(array('type'=>'text','maxlength'=>'100','name'=>'name','value'=>$cl_name)); +$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->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_name, false, MAX_NAME_CHARS)) $err->add($i18n->get('error.field'), $i18n->get('label.thing_name')); + if (!ttValidString($cl_description, true, MAX_DESCR_CHARS)) $err->add($i18n->get('error.field'), $i18n->get('label.description')); if ($err->no()) { if (!ttTaskHelper::getTaskByName($cl_name)) { @@ -62,5 +62,5 @@ $smarty->assign('show_projects', count($projects) > 0); $smarty->assign('onload', 'onLoad="document.taskForm.name.focus()"'); $smarty->assign('title', $i18n->get('title.add_task')); -$smarty->assign('content_page_name', 'task_add2.tpl'); -$smarty->display('index2.tpl'); +$smarty->assign('content_page_name', 'task_add.tpl'); +$smarty->display('index.tpl'); diff --git a/task_delete.php b/task_delete.php index 61f5e23ff..04008010f 100644 --- a/task_delete.php +++ b/task_delete.php @@ -50,5 +50,5 @@ $smarty->assign('forms', array($form->getName()=>$form->toArray())); $smarty->assign('onload', 'onLoad="document.taskDeleteForm.btn_cancel.focus()"'); $smarty->assign('title', $i18n->get('title.delete_task')); -$smarty->assign('content_page_name', 'task_delete2.tpl'); -$smarty->display('index2.tpl'); +$smarty->assign('content_page_name', 'task_delete.tpl'); +$smarty->display('index.tpl'); diff --git a/task_edit.php b/task_edit.php index 4b9a72aef..1eb8c7f5b 100644 --- a/task_edit.php +++ b/task_edit.php @@ -44,7 +44,7 @@ $form = new Form('taskForm'); $form->addInput(array('type'=>'hidden','name'=>'id','value'=>$cl_task_id)); -$form->addInput(array('type'=>'text','maxlength'=>'100','name'=>'name','value'=>$cl_name)); +$form->addInput(array('type'=>'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->get('dropdown.status_active'),INACTIVE=>$i18n->get('dropdown.status_inactive')))); @@ -54,8 +54,9 @@ 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_name, false, MAX_NAME_CHARS)) $err->add($i18n->get('error.field'), $i18n->get('label.thing_name')); + if (!ttValidString($cl_description, true, MAX_DESCR_CHARS)) $err->add($i18n->get('error.field'), $i18n->get('label.description')); + if (!ttValidStatus($cl_status)) $err->add($i18n->get('error.field'), $i18n->get('label.status')); if ($err->no()) { if ($request->getParameter('btn_save')) { @@ -96,5 +97,5 @@ $smarty->assign('forms', array($form->getName()=>$form->toArray())); $smarty->assign('show_projects', count($projects) > 0); $smarty->assign('title', $i18n->get('title.edit_task')); -$smarty->assign('content_page_name', 'task_edit2.tpl'); -$smarty->display('index2.tpl'); +$smarty->assign('content_page_name', 'task_edit.tpl'); +$smarty->display('index.tpl'); diff --git a/tasks.php b/tasks.php index 464af6159..13787a8ab 100644 --- a/tasks.php +++ b/tasks.php @@ -49,5 +49,5 @@ $smarty->assign('active_tasks', $active_tasks); $smarty->assign('inactive_tasks', $inactive_tasks); $smarty->assign('title', $i18n->get('title.tasks')); -$smarty->assign('content_page_name', 'tasks2.tpl'); -$smarty->display('index2.tpl'); +$smarty->assign('content_page_name', 'tasks.tpl'); +$smarty->display('index.tpl'); diff --git a/template_add.php b/template_add.php index 6347d22e3..8eef424ef 100644 --- a/template_add.php +++ b/template_add.php @@ -19,9 +19,11 @@ $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')); @@ -65,5 +67,5 @@ $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_add2.tpl'); -$smarty->display('index2.tpl'); +$smarty->assign('content_page_name', 'template_add.tpl'); +$smarty->display('index.tpl'); diff --git a/template_delete.php b/template_delete.php index 792cadbd0..a4996e05b 100644 --- a/template_delete.php +++ b/template_delete.php @@ -47,5 +47,5 @@ $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_delete2.tpl'); -$smarty->display('index2.tpl'); +$smarty->assign('content_page_name', 'template_delete.tpl'); +$smarty->display('index.tpl'); diff --git a/template_edit.php b/template_edit.php index 3451ff646..2ff87b721 100644 --- a/template_edit.php +++ b/template_edit.php @@ -25,9 +25,11 @@ $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')); @@ -62,6 +64,7 @@ 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. @@ -83,5 +86,5 @@ $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_edit2.tpl'); -$smarty->display('index2.tpl'); +$smarty->assign('content_page_name', 'template_edit.tpl'); +$smarty->display('index.tpl'); diff --git a/templates.php b/templates.php index 53d57b86f..636d4bc10 100644 --- a/templates.php +++ b/templates.php @@ -59,5 +59,5 @@ $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', 'templates2.tpl'); -$smarty->display('index2.tpl'); +$smarty->assign('content_page_name', 'templates.tpl'); +$smarty->display('index.tpl'); diff --git a/time.php b/time.php index d96c497e3..ee90770b8 100644 --- a/time.php +++ b/time.php @@ -10,7 +10,7 @@ import('ttClientHelper'); import('ttTimeHelper'); import('ttFileHelper'); -import('DateAndTime'); +import('ttDate'); // Access checks. if (!(ttAccessAllowed('track_own_time') || ttAccessAllowed('track_time'))) { @@ -32,6 +32,44 @@ 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. @@ -57,14 +95,13 @@ $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. @@ -99,13 +136,13 @@ if ($user->isPluginEnabled('mq')){ require_once('plugins/MonthlyQuota.class.php'); $quota = new MonthlyQuota(); - $month_quota_minutes = $quota->getUserQuota($selected_date->mYear, $selected_date->mMonth); + $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); @@ -115,10 +152,10 @@ } // 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')); +$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 ($showBillable) { if ($request->isPost()) { @@ -147,7 +184,7 @@ 'label' => $timeField['label'], 'type' => $timeField['type'], 'required' => $timeField['required'], - 'value' => trim($cl_control_name)); + 'value' => is_null($cl_control_name) ? null : trim($cl_control_name)); } } @@ -192,8 +229,10 @@ } // Billable checkbox. -if ($showBillable) +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) { @@ -326,6 +365,11 @@ 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 ($showClient && $user->isOptionEnabled('client_required') && !$cl_client) $err->add($i18n->get('error.client')); @@ -342,7 +386,7 @@ if ($showTask && $taskRequired) { if (!$cl_task) $err->add($i18n->get('error.task')); } - if (strlen($cl_duration) == 0) { + if ($cl_duration == null) { if ($cl_start || $cl_finish) { if (!ttTimeHelper::isValidTime($cl_start)) $err->add($i18n->get('error.field'), $i18n->get('label.start')); @@ -368,13 +412,14 @@ 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. // Prohibit creating entries in future. if (!$user->isOptionEnabled('future_entries')) { - $browser_today = new DateAndTime(DB_DATEFORMAT, $request->getParameter('browser_today', null)); - if ($selected_date->after($browser_today)) + $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')); } @@ -383,7 +428,7 @@ $err->add($i18n->get('error.range_locked')); // Prohibit creating another uncompleted record. - if ($err->no()) { + 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').""); } @@ -461,6 +506,8 @@ $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); @@ -468,7 +515,7 @@ $smarty->assign('day_total', ttTimeHelper::getTimeForDay($cl_date)); $smarty->assign('time_records', $timeRecords); $smarty->assign('show_record_custom_fields', $showRecordCustomFields); -$smarty->assign('show_navigation', $user->isPluginEnabled('wv') && !$user->isOptionEnabled('week_menu')); +$smarty->assign('show_navigation', $showNavigation); $smarty->assign('show_client', $showClient); $smarty->assign('show_billable', $showBillable); $smarty->assign('show_project', $showProject); @@ -482,9 +529,10 @@ $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();prepopulateNote();adjustTodayLinks()"'); $smarty->assign('timestring', $selected_date->toString($user->getDateFormat())); $smarty->assign('title', $i18n->get('title.time')); -$smarty->assign('content_page_name', 'time2.tpl'); -$smarty->display('index2.tpl'); +$smarty->assign('content_page_name', 'time.tpl'); +$smarty->display('index.tpl'); diff --git a/time_delete.php b/time_delete.php index 5b3849525..cd0af11ab 100644 --- a/time_delete.php +++ b/time_delete.php @@ -6,7 +6,7 @@ import('form.Form'); import('ttUserHelper'); import('ttTimeHelper'); -import('DateAndTime'); +import('ttDate'); // Access checks. if (!(ttAccessAllowed('track_own_time') || ttAccessAllowed('track_time'))) { @@ -33,7 +33,7 @@ if ($request->getParameter('delete_button')) { // Delete button pressed. // Determine if it's okay to delete the record. - $item_date = new DateAndTime(DB_DATEFORMAT, $time_rec['date']); + $item_date = new ttDate($time_rec['date']); // Determine if the record is uncompleted. $uncompleted = ($time_rec['duration'] == '0:00'); @@ -69,5 +69,5 @@ $smarty->assign('show_duration', $showDuration); $smarty->assign('forms', array($form->getName() => $form->toArray())); $smarty->assign('title', $i18n->get('title.delete_time_record')); -$smarty->assign('content_page_name', 'time_delete2.tpl'); -$smarty->display('index2.tpl'); +$smarty->assign('content_page_name', 'time_delete.tpl'); +$smarty->display('index.tpl'); diff --git a/time_edit.php b/time_edit.php index 8e9f31e56..35037dcaf 100644 --- a/time_edit.php +++ b/time_edit.php @@ -9,7 +9,7 @@ import('ttClientHelper'); import('ttTimeHelper'); import('ttConfigHelper'); -import('DateAndTime'); +import('ttDate'); // Access checks. if (!(ttAccessAllowed('track_own_time') || ttAccessAllowed('track_time'))) { @@ -24,6 +24,14 @@ 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(); @@ -40,6 +48,7 @@ $recordType = $user->getRecordType(); $showStart = TYPE_START_FINISH == $recordType || TYPE_ALL == $recordType; $showDuration = TYPE_DURATION == $recordType || TYPE_ALL == $recordType; +$oneUncompleted = $config->getDefinedValue('one_uncompleted'); // Use custom fields plugin if it is enabled. if ($user->isPluginEnabled('cf')) { @@ -48,15 +57,15 @@ $smarty->assign('custom_fields', $custom_fields); } -$item_date = new DateAndTime(DB_DATEFORMAT, $time_rec['date']); +$item_date = new ttDate($time_rec['date']); $confirm_save = $user->getConfigOption('confirm_save'); // Initialize variables. $cl_start = $cl_finish = $cl_duration = $cl_date = $cl_note = $cl_project = $cl_task = $cl_billable = null; if ($request->isPost()) { - $cl_start = trim($request->getParameter('start')); - $cl_finish = trim($request->getParameter('finish')); - $cl_duration = trim($request->getParameter('duration')); + $cl_start = is_null($request->getParameter('start')) ? null : trim($request->getParameter('start')); + $cl_finish = is_null($request->getParameter('finish')) ? null : trim($request->getParameter('finish')); + $cl_duration = is_null($request->getParameter('duration')) ? null : trim($request->getParameter('duration')); $cl_date = $request->getParameter('date'); $cl_note = trim($request->getParameter('note')); // If we have time custom fields - collect input. @@ -108,8 +117,8 @@ // Add an info message to the form if we are editing an uncompleted record. if (strlen($cl_start) > 0 && $cl_start == $cl_finish && $cl_duration == '0:00') { - $cl_finish = ''; - $cl_duration = ''; + $cl_finish = null; + $cl_duration = null; $msg->add($i18n->get('form.time_edit.uncompleted')); } } @@ -283,10 +292,8 @@ if ($showTask && $taskRequired) { if (!$cl_task) $err->add($i18n->get('error.task')); } - if (!$cl_duration) { - if ('0' == $cl_duration) - $err->add($i18n->get('error.field'), $i18n->get('label.duration')); - elseif ($cl_start || $cl_finish) { + if ($cl_duration == null) { + if ($cl_start || $cl_finish) { if (!ttTimeHelper::isValidTime($cl_start)) $err->add($i18n->get('error.field'), $i18n->get('label.start')); if ($cl_finish) { @@ -316,12 +323,16 @@ // Finished validating user input. // This is a new date for the time record. - $new_date = new DateAndTime($user->getDateFormat(), $cl_date); + $new_date = null; + if ($err->no()) + $new_date = new ttDate($cl_date, $user->getDateFormat()); // Prohibit creating entries in future. - if (!$user->isOptionEnabled('future_entries')) { - $browser_today = new DateAndTime(DB_DATEFORMAT, $request->getParameter('browser_today', null)); - if ($new_date->after($browser_today)) + 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')); } @@ -343,8 +354,8 @@ $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) { + $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. @@ -355,7 +366,7 @@ // Prohibit creating an overlapping record. if ($err->no()) { - if (ttTimeHelper::overlaps($user_id, $new_date->toString(DB_DATEFORMAT), $cl_start, $cl_finish, $cl_id)) + if (ttTimeHelper::overlaps($user_id, $new_date->toString(), $cl_start, $cl_finish, $cl_id)) $err->add($i18n->get('error.overlap')); } @@ -363,7 +374,7 @@ if ($err->no()) { $res = ttTimeHelper::update(array( 'id'=>$cl_id, - 'date'=>$new_date->toString(DB_DATEFORMAT), + 'date'=>$new_date->toString(), 'client'=>$cl_client, 'project'=>$cl_project, 'task'=>$cl_task, @@ -380,9 +391,10 @@ } 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')); } } @@ -400,7 +412,7 @@ // 2) Prohibit saving uncompleted unlocked entries when another uncompleted entry exists. $uncompleted = ($cl_finish == '' && $cl_duration == ''); - if ($uncompleted) { + if ($uncompleted && $oneUncompleted) { $not_completed_rec = ttTimeHelper::getUncompleted($user_id); if ($not_completed_rec) { // We have another not completed record. @@ -411,7 +423,7 @@ // Prohibit creating an overlapping record. if ($err->no()) { - if (ttTimeHelper::overlaps($user_id, $new_date->toString(DB_DATEFORMAT), $cl_start, $cl_finish)) + if (ttTimeHelper::overlaps($user_id, $new_date->toString(), $cl_start, $cl_finish)) $err->add($i18n->get('error.overlap')); } @@ -419,7 +431,7 @@ if ($err->no()) { $id = ttTimeHelper::insert(array( - 'date'=>$new_date->toString(DB_DATEFORMAT), + 'date'=>$new_date->toString(), 'client'=>$cl_client, 'project'=>$cl_project, 'task'=>$cl_task, @@ -436,7 +448,7 @@ $res = $custom_fields->insertTimeFields($id, $timeCustomFields); } if ($id && $res) { - header('Location: time.php?date='.$new_date->toString(DB_DATEFORMAT)); + header('Location: time.php?date='.$new_date->toString()); exit(); } $err->add($i18n->get('error.db')); @@ -467,5 +479,5 @@ $smarty->assign('forms', array($form->getName()=>$form->toArray())); $smarty->assign('onload', 'onLoad="fillDropdowns()"'); $smarty->assign('title', $i18n->get('title.edit_time_record')); -$smarty->assign('content_page_name', 'time_edit2.tpl'); -$smarty->display('index2.tpl'); +$smarty->assign('content_page_name', 'time_edit.tpl'); +$smarty->display('index.tpl'); diff --git a/time_files.php b/time_files.php index 210bd534c..c5dce5baa 100644 --- a/time_files.php +++ b/time_files.php @@ -62,5 +62,5 @@ $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_files2.tpl'); -$smarty->display('index2.tpl'); +$smarty->assign('content_page_name', 'entity_files.tpl'); +$smarty->display('index.tpl'); diff --git a/timesheet_add.php b/timesheet_add.php index 5fb15df6e..b52775f2f 100644 --- a/timesheet_add.php +++ b/timesheet_add.php @@ -17,6 +17,7 @@ } // 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'); @@ -80,5 +81,5 @@ $smarty->assign('show_client', $showClient); $smarty->assign('show_project', $showProject); $smarty->assign('title', $i18n->get('title.add_timesheet')); -$smarty->assign('content_page_name', 'timesheet_add2.tpl'); -$smarty->display('index2.tpl'); +$smarty->assign('content_page_name', 'timesheet_add.tpl'); +$smarty->display('index.tpl'); diff --git a/timesheet_delete.php b/timesheet_delete.php index b0b847973..ff1e4e180 100644 --- a/timesheet_delete.php +++ b/timesheet_delete.php @@ -47,5 +47,5 @@ $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_delete2.tpl'); -$smarty->display('index2.tpl'); +$smarty->assign('content_page_name', 'timesheet_delete.tpl'); +$smarty->display('index.tpl'); diff --git a/timesheet_edit.php b/timesheet_edit.php index c9676ed5a..3e46733ae 100644 --- a/timesheet_edit.php +++ b/timesheet_edit.php @@ -51,6 +51,7 @@ // 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()) { @@ -81,5 +82,5 @@ $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_edit2.tpl'); -$smarty->display('index2.tpl'); +$smarty->assign('content_page_name', 'timesheet_edit.tpl'); +$smarty->display('index.tpl'); diff --git a/timesheet_files.php b/timesheet_files.php index d09ecd57d..bec4e29d8 100644 --- a/timesheet_files.php +++ b/timesheet_files.php @@ -24,6 +24,7 @@ } // End of access checks. +$cl_description = null; if ($request->isPost()) { $cl_description = trim($request->getParameter('description')); } @@ -61,5 +62,5 @@ $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_files2.tpl'); -$smarty->display('index2.tpl'); +$smarty->assign('content_page_name', 'entity_files.tpl'); +$smarty->display('index.tpl'); diff --git a/timesheet_view.php b/timesheet_view.php index a74424a6b..b4eda31f7 100644 --- a/timesheet_view.php +++ b/timesheet_view.php @@ -35,6 +35,7 @@ $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(); @@ -116,7 +117,6 @@ } } -$smarty->assign('not_client', $notClient); $smarty->assign('group_by_header', ttReportHelper::makeGroupByHeader($options)); $smarty->assign('timesheet', $timesheet); $smarty->assign('subtotals', $subtotals); @@ -126,5 +126,5 @@ $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_view2.tpl'); -$smarty->display('index2.tpl'); +$smarty->assign('content_page_name', 'timesheet_view.tpl'); +$smarty->display('index.tpl'); diff --git a/timesheets.php b/timesheets.php index 54bbc3396..7dbbb78df 100644 --- a/timesheets.php +++ b/timesheets.php @@ -78,5 +78,5 @@ $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', 'timesheets2.tpl'); -$smarty->display('index2.tpl'); +$smarty->assign('content_page_name', 'timesheets.tpl'); +$smarty->display('index.tpl'); diff --git a/tofile.php b/tofile.php index b10bdee02..0134885f8 100644 --- a/tofile.php +++ b/tofile.php @@ -20,6 +20,8 @@ $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); @@ -95,7 +97,7 @@ print "\t\n"; if ($user->can('view_reports') || $user->can('view_all_reports') || $user->isClient()) print "\t\n"; // User custom fields. - if ($custom_fields && $custom_fields->userFields) { + 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; @@ -104,9 +106,17 @@ } 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"; // Time custom fields. - if ($custom_fields && $custom_fields->timeFields) { + 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; @@ -123,6 +133,11 @@ } 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 "\tcan('manage_invoices') || $user->isClient()) @@ -190,7 +205,7 @@ 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 ($custom_fields && $custom_fields->userFields) { + 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; @@ -199,9 +214,17 @@ } 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 ($custom_fields && $custom_fields->timeFields) { + 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; @@ -213,7 +236,11 @@ 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')) print ',"'.$i18n->get('label.cost').'"'; + 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').'"'; @@ -226,7 +253,7 @@ print '"'.$item['date'].'"'; if ($user->can('view_reports') || $user->can('view_all_reports') || $user->isClient()) print ',"'.ttNeutralizeForCsv($item['user']).'"'; // User custom fields. - if ($custom_fields && $custom_fields->userFields) { + 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; @@ -235,9 +262,17 @@ } 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 ($custom_fields && $custom_fields->timeFields) { + 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; @@ -255,10 +290,13 @@ if ($bean->getAttribute('chunits')) print ',"'.$item['units'].'"'; if ($bean->getAttribute('chnote')) print ',"'.ttNeutralizeForCsv($item['note']).'"'; if ($bean->getAttribute('chcost')) { - if ($user->can('manage_invoices') || $user->isClient()) + if ($user->can('manage_invoices') || $user->isClient()) { + if ($show_cost_per_hour) + print ',"'.$item['cost_per_hour'].'"'; print ',"'.$item['cost'].'"'; - else + } else { print ',"'.$item['expense'].'"'; + } } if ($bean->getAttribute('chapproved')) print ',"'.$item['approved'].'"'; if ($bean->getAttribute('chpaid')) print ',"'.$item['paid'].'"'; diff --git a/topdf.php b/topdf.php index aa840e106..6393d5f55 100644 --- a/topdf.php +++ b/topdf.php @@ -38,6 +38,7 @@ $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, or task and user only needs to see subtotals by group. @@ -46,6 +47,7 @@ // Obtain items for report. $options = ttReportHelper::getReportOptions($bean); $grouping = ttReportHelper::grouping($options); +$items = null; if (!$totals_only) $items = ttReportHelper::getItems($options); // Individual entries. if ($totals_only || $grouping) @@ -53,6 +55,7 @@ $totals = ttReportHelper::getTotals($options); // Totals for the entire report. // Assign variables that are used to print subtotals. +$print_subtotals = $first_pass = false; if ($items && $grouping) { $print_subtotals = true; $first_pass = true; @@ -128,7 +131,7 @@ $html .= ''.$i18n->get('label.date').''; if ($user->can('view_reports') || $user->can('view_all_reports') || $user->isClient()) { $colspan++; $html .= ''.$i18n->get('label.user').''; } // User custom field labels. - if ($custom_fields && $custom_fields->userFields) { + 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; @@ -137,9 +140,17 @@ } if ($bean->getAttribute('chclient')) { $colspan++; $html .= ''.$i18n->get('label.client').''; } if ($bean->getAttribute('chproject')) { $colspan++; $html .= ''.$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)) { $colspan++; $html .= ''.htmlspecialchars($projectField['label']).''; } + } + } if ($bean->getAttribute('chtask')) { $colspan++; $html .= ''.$i18n->get('label.task').''; } // Time custom field labels. - if ($custom_fields && $custom_fields->timeFields) { + 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; @@ -151,6 +162,7 @@ if ($bean->getAttribute('chduration')) { $colspan++; $html .= "".$i18n->get('label.duration').''; } if ($bean->getAttribute('chunits')) { $colspan++; $html .= "".$i18n->get('label.work_units_short').''; } if ($show_note_column) { $colspan++; $html .= ''.$i18n->get('label.note').''; } + if ($bean->getAttribute('chcost') && $show_cost_per_hour) { $colspan++; $html .= "".$i18n->get('form.report.per_hour').''; } if ($bean->getAttribute('chcost')) { $colspan++; $html .= "".$i18n->get('label.cost').''; } if ($bean->getAttribute('chapproved')) { $colspan++; $html .= "".$i18n->get('label.approved').''; } if ($bean->getAttribute('chpaid')) { $colspan++; $html .= "".$i18n->get('label.paid').''; } @@ -174,7 +186,7 @@ $html .= ''; } // User custom fields. - if ($custom_fields && $custom_fields->userFields) { + 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; @@ -191,13 +203,21 @@ $html .= htmlspecialchars($subtotals[$prev_grouped_by]['project']); $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 .= ''; $html .= htmlspecialchars($subtotals[$prev_grouped_by]['task']); $html .= ''; } // Time custom fields. - if ($custom_fields && $custom_fields->timeFields) { + 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; @@ -209,6 +229,7 @@ if ($bean->getAttribute('chduration')) $html .= "".$subtotals[$prev_grouped_by]['time'].''; if ($bean->getAttribute('chunits')) $html .= "".$subtotals[$prev_grouped_by]['units'].''; if ($show_note_column) $html .= ''; + if ($bean->getAttribute('chcost') && $show_cost_per_hour) $html .= ''; if ($bean->getAttribute('chcost')) { $html .= ""; if ($user->can('manage_invoices') || $user->isClient()) @@ -242,7 +263,7 @@ $html .= ''.$item['date'].''; if ($user->can('view_reports') || $user->can('view_all_reports') || $user->isClient()) $html .= ''.htmlspecialchars($item['user']).''; // User custom fields. - if ($custom_fields && $custom_fields->userFields) { + 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; @@ -251,9 +272,17 @@ } if ($bean->getAttribute('chclient')) $html .= ''.htmlspecialchars($item['client']).''; if ($bean->getAttribute('chproject')) $html .= ''.htmlspecialchars($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)) $html .= ''.htmlspecialchars($item[$field_name]).''; + } + } if ($bean->getAttribute('chtask')) $html .= ''.htmlspecialchars($item['task']).''; // Time custom fields. - if ($custom_fields && $custom_fields->timeFields) { + 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; @@ -265,6 +294,11 @@ if ($bean->getAttribute('chduration')) $html .= "".$item['duration'].''; if ($bean->getAttribute('chunits')) $html .= "".$item['units'].''; if ($show_note_column) $html .= ''.htmlspecialchars($item['note']).''; + if ($bean->getAttribute('chcost') && $show_cost_per_hour) { + $html .= ""; + $html .= $item['cost_per_hour']; + $html .= ''; + } if ($bean->getAttribute('chcost')) { $html .= ""; if ($user->can('manage_invoices') || $user->isClient()) @@ -314,7 +348,7 @@ $html .= ''; } // User custom fields. - if ($custom_fields && $custom_fields->userFields) { + 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; @@ -331,13 +365,21 @@ $html .= htmlspecialchars($subtotals[$prev_grouped_by]['project']); $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 .= ''; $html .= htmlspecialchars($subtotals[$prev_grouped_by]['task']); $html .= ''; } // Time custom fields. - if ($custom_fields && $custom_fields->timeFields) { + 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; @@ -349,6 +391,7 @@ if ($bean->getAttribute('chduration')) $html .= "".$subtotals[$prev_grouped_by]['time'].''; if ($bean->getAttribute('chunits')) $html .= "".$subtotals[$prev_grouped_by]['units'].''; if ($show_note_column) $html .= ''; + if ($bean->getAttribute('chcost') && $show_cost_per_hour) $html .= ''; if ($bean->getAttribute('chcost')) { $html .= ""; if ($user->can('manage_invoices') || $user->isClient()) @@ -371,7 +414,7 @@ $html .= ''.$i18n->get('label.total').''; if ($user->can('view_reports') || $user->can('view_all_reports') || $user->isClient()) $html .= ''; // User custom fields. - if ($custom_fields && $custom_fields->userFields) { + 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; @@ -380,9 +423,17 @@ } 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 .= ''; // Time custom fields. - if ($custom_fields && $custom_fields->timeFields) { + 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; @@ -394,6 +445,7 @@ if ($bean->getAttribute('chduration')) $html .= "".$totals['time'].''; if ($bean->getAttribute('chunits')) $html .= "".$totals['units'].''; if ($show_note_column) $html .= ''; + if ($bean->getAttribute('chcost') && $show_cost_per_hour) $html .= ''; if ($bean->getAttribute('chcost')) { $html .= "".htmlspecialchars($user->currency).' '; if ($user->can('manage_invoices') || $user->isClient()) diff --git a/user_add.php b/user_add.php index 7325682e5..e75e0901d 100644 --- a/user_add.php +++ b/user_add.php @@ -158,6 +158,9 @@ function render(&$table, $value, $row, $column, $selected = false) { 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')); + // 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. @@ -217,5 +220,5 @@ function render(&$table, $value, $row, $column, $selected = false) { $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_add2.tpl'); -$smarty->display('index2.tpl'); +$smarty->assign('content_page_name', 'user_add.tpl'); +$smarty->display('index.tpl'); diff --git a/user_delete.php b/user_delete.php index 99378fda4..e6ec7365e 100644 --- a/user_delete.php +++ b/user_delete.php @@ -59,5 +59,5 @@ $smarty->assign('forms', array($form->getName()=>$form->toArray())); $smarty->assign('title', $i18n->get('title.delete_user')); -$smarty->assign('content_page_name', 'user_delete2.tpl'); -$smarty->display('index2.tpl'); +$smarty->assign('content_page_name', 'user_delete.tpl'); +$smarty->display('index.tpl'); diff --git a/user_edit.php b/user_edit.php index dc19ad6aa..0633e8a1f 100644 --- a/user_edit.php +++ b/user_edit.php @@ -37,6 +37,7 @@ } $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; @@ -189,12 +190,16 @@ function render(&$table, $value, $row, $column, $selected = false) { 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')); + // 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. + // 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. @@ -278,5 +283,5 @@ function render(&$table, $value, $row, $column, $selected = false) { $smarty->assign('show_projects', $show_projects); $smarty->assign('user_id', $user_id); $smarty->assign('title', $i18n->get('title.edit_user')); -$smarty->assign('content_page_name', 'user_edit2.tpl'); -$smarty->display('index2.tpl'); +$smarty->assign('content_page_name', 'user_edit.tpl'); +$smarty->display('index.tpl'); diff --git a/users.php b/users.php index a0ccdc3c1..38d4e1bc3 100644 --- a/users.php +++ b/users.php @@ -30,8 +30,11 @@ $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); @@ -41,5 +44,5 @@ $smarty->assign('inactive_users', $inactive_users); $smarty->assign('show_quota', $user->isPluginEnabled('mq')); $smarty->assign('title', $i18n->get('title.users')); -$smarty->assign('content_page_name', 'users2.tpl'); -$smarty->display('index2.tpl'); +$smarty->assign('content_page_name', 'users.tpl'); +$smarty->display('index.tpl'); diff --git a/week.php b/week.php index 01597704d..3c2384b91 100644 --- a/week.php +++ b/week.php @@ -12,7 +12,7 @@ import('ttWeekViewHelper'); import('ttClientHelper'); import('ttTimeHelper'); -import('DateAndTime'); +import('ttDate'); // Access checks. if (!(ttAccessAllowed('track_own_time') || ttAccessAllowed('track_time'))) { @@ -38,6 +38,20 @@ 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. @@ -55,20 +69,21 @@ $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 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; // Determine selected week start and end dates. @@ -79,10 +94,10 @@ $startWeekBias = $weekStartDay - 7; else $startWeekBias = $weekStartDay; -$startDate = new DateAndTime(); -$startDate->setTimestamp(mktime(0,0,0,$t_arr[4]+1,$t_arr[3]-$t_arr[6]+$startWeekBias,$t_arr[5])); -$endDate = new DateAndTime(); -$endDate->setTimestamp(mktime(0,0,0,$t_arr[4]+1,$t_arr[3]-$t_arr[6]+6+$startWeekBias,$t_arr[5])); +$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. @@ -96,7 +111,7 @@ if ($user->isPluginEnabled('mq')){ require_once('plugins/MonthlyQuota.class.php'); $quota = new MonthlyQuota(); - $month_quota_minutes = $quota->getUserQuota($selected_date->mYear, $selected_date->mMonth); + $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); @@ -121,36 +136,57 @@ $_SESSION['project'] = $cl_project; $cl_task = $request->getParameter('task', ($request->isPost() ? null : @$_SESSION['task'])); $_SESSION['task'] = $cl_task; -$cl_note = $request->getParameter('note', ($request->isPost() ? null : @$_SESSION['note'])); -$_SESSION['note'] = $cl_note; +$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 ($request->isPost()) { - if ($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))); - } +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(DB_DATEFORMAT)); -$lockedDays = ttWeekViewHelper::getLockedDaysForWeek($startDate->toString(DB_DATEFORMAT)); +$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(DB_DATEFORMAT), $endDate->toString(DB_DATEFORMAT), $showFiles); +$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(DB_DATEFORMAT), $dayHeaders); + $dataArray = ttWeekViewHelper::prePopulateFromPastWeeks($startDate->toString(), $dayHeaders); // Build day totals (total durations for each day in week). $dayTotals = ttWeekViewHelper::getDayTotals($dataArray, $dayHeaders); @@ -197,7 +233,7 @@ function render(&$table, $value, $row, $column, $selected = false) { $field = new TextField($field_name); // Disable control if the date is locked. global $lockedDays; - if ($lockedDays[$column-1]) + if ($lockedDays[$column]) $field->setEnabled(false); $field->setFormName($table->getFormName()); $field->setStyle('width: 60px;'); // TODO: need to style everything properly, eventually. @@ -211,7 +247,7 @@ function render(&$table, $value, $row, $column, $selected = false) { $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($table->getValueAt($row,$column)['note']); // Tooltip to help view the entire comment. + $field->setTitle(htmlspecialchars($table->getValueAt($row,$column)['note'])); // Tooltip to help view the entire comment. } } else { $field->setValue($table->getValueAt($row,$column)['duration']); @@ -229,6 +265,7 @@ function render(&$table, $value, $row, $column, $selected = false) { // 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); @@ -241,11 +278,11 @@ function render(&$table, $value, $row, $column, $selected = false) { $form->addInput(array('type'=>'combobox', 'onchange'=>'document.weekTimeForm.user_changed.value=1;document.weekTimeForm.submit();', 'name'=>'user', - 'style'=>'width: 250px;', '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); } } @@ -258,8 +295,16 @@ function render(&$table, $value, $row, $column, $selected = false) { $table->setData($dataArray); // Add columns to table. $table->addColumn(new TableColumn('label', '', new LabelCellRenderer(), $dayTotals['label'])); -for ($i = 0; $i < 7; $i++) { - $table->addColumn(new TableColumn($dayHeaders[$i], $dayHeaders[$i], new WeekViewCellRenderer(), $dayTotals[$dayHeaders[$i]])); + +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); @@ -270,46 +315,49 @@ function render(&$table, $value, $row, $column, $selected = false) { $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->get('dropdown.select')))); + $largeScreenCalendarRowSpan += 2; // Note: in other modes the client list is filtered to relevant clients only. See below. } // Billable checkbox. -if ($user->isPluginEnabled('iv')) +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 ($custom_fields && $custom_fields->timeFields) { +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,'style'=>'width: 250px;','value'=>$timeCustomFields[$timeField['id']]['value'])); + $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, - 'style'=>'width: 250px;', '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', - 'style'=>'width: 250px;', '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) { @@ -332,38 +380,38 @@ function render(&$table, $value, $row, $column, $selected = false) { $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->get('dropdown.select')))); + $largeScreenCalendarRowSpan += 2; } } // 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->get('dropdown.select')))); + $largeScreenCalendarRowSpan += 2; } // Week note control. if ($showWeekNote) { - if (!defined('NOTE_INPUT_HEIGHT')) - define('NOTE_INPUT_HEIGHT', 40); - $form->addInput(array('type'=>'textarea','name'=>'note','style'=>'width: 250px; height:'.NOTE_INPUT_HEIGHT.'px;','value'=>$cl_note)); + $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'=>'get_date()')); // User current date, which gets filled in on btn_submit click. +$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'))); @@ -385,7 +433,7 @@ function render(&$table, $value, $row, $column, $selected = false) { if ($showClient && $user->isOptionEnabled('client_required') && !$cl_client) $err->add($i18n->get('error.client')); // Validate input in time custom fields. - if ($custom_fields && $custom_fields->timeFields) { + 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'])); @@ -438,9 +486,6 @@ function render(&$table, $value, $row, $column, $selected = false) { continue; // Posted value is different. if ($existingDuration == null) { - // Skip inserting 0 duration values. - if (0 == ttTimeHelper::toMinutes($postedDuration)) - continue; // Insert a new record. $fields = array(); $fields['row_id'] = $dataArray[$rowNumber]['row_id']; @@ -451,7 +496,7 @@ function render(&$table, $value, $row, $column, $selected = false) { $record['billable'] = $cl_billable ? '1' : '0'; $record['project_id'] = $cl_project; $record['task_id'] = $cl_task; - if ($custom_fields && $custom_fields->timeFields) { + 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) @@ -465,21 +510,23 @@ function render(&$table, $value, $row, $column, $selected = false) { // because we are doing an insert that does not affect already existing data. if ($showWeekNote) { - $fields['note'] = $request->getParameter('note'); + $fields['note'] = $request->getParameter('comment'); } } $fields['day_header'] = $dayHeader; - $fields['start_date'] = $startDate->toString(DB_DATEFORMAT); // To be able to determine date for the entry using $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; - $fields['note'] = $request->getParameter($note_control_id); + 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 || 0 == ttTimeHelper::toMinutes($postedDuration)) { + } elseif ($postedDuration == null) { // Delete an already existing record here. $result = ttTimeHelper::delete($dataArray[$rowNumber][$dayHeader]['tt_log_id']); } else { @@ -531,6 +578,7 @@ function render(&$table, $value, $row, $column, $selected = false) { $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); @@ -540,6 +588,7 @@ function render(&$table, $value, $row, $column, $selected = false) { $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); diff --git a/week_view.php b/week_view.php index d612239b0..7db10226a 100644 --- a/week_view.php +++ b/week_view.php @@ -18,11 +18,13 @@ $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'); @@ -30,6 +32,7 @@ $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()){ @@ -38,6 +41,7 @@ $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')); } @@ -45,5 +49,5 @@ $smarty->assign('forms', array($form->getName()=>$form->toArray())); $smarty->assign('title', $i18n->get('title.week_view')); -$smarty->assign('content_page_name', 'week_view2.tpl'); -$smarty->display('index2.tpl'); +$smarty->assign('content_page_name', 'week_view.tpl'); +$smarty->display('index.tpl'); diff --git a/work/admin_offer_delete.php b/work/admin_offer_delete.php deleted file mode 100644 index a1bd5fac1..000000000 --- a/work/admin_offer_delete.php +++ /dev/null @@ -1,71 +0,0 @@ -getParameter('id'); -$adminWorkHelper = new ttAdminWorkHelper($err); -$offer = $adminWorkHelper->getOffer($cl_offer_id); -if (!$offer) { - header('Location: ../access_denied.php'); - exit(); -} -// End of access checks. - -$offer_to_delete = $offer['subject']; - -$form = new Form('offerDeleteForm'); -$form->addInput(array('type'=>'hidden','name'=>'id','value'=>$cl_offer_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 ($adminWorkHelper->deleteOffer($cl_offer_id)) { - header('Location: admin_work.php'); - exit(); - } - } elseif ($request->getParameter('btn_cancel')) { - header('Location: admin_work.php'); - exit(); - } -} // isPost - -$smarty->assign('offer_to_delete', $offer_to_delete); -$smarty->assign('forms', array($form->getName()=>$form->toArray())); -$smarty->assign('onload', 'onLoad="document.offerDeleteForm.btn_cancel.focus()"'); -$smarty->assign('title', $i18n->get('title.delete_offer')); -$smarty->assign('content_page_name', 'work/offer_delete.tpl'); -$smarty->display('work/index.tpl'); diff --git a/work/admin_offer_edit.php b/work/admin_offer_edit.php deleted file mode 100644 index ab795c136..000000000 --- a/work/admin_offer_edit.php +++ /dev/null @@ -1,171 +0,0 @@ -getParameter('id'); -$adminWorkHelper = new ttAdminWorkHelper($err); -$offer = $adminWorkHelper->getOffer($cl_offer_id); -if (!$offer) { - header('Location: ../access_denied.php'); - exit(); -} -// End of access checks. - -// Is this offer associated with a work item? -$work_id = $offer['work_id']; -if ($work_id) { - $work_item = $adminWorkHelper->getWorkItem($work_id); - if (!$work_item) $err->add($i18n->get('work.error.work_not_available')); -} - -$existingStatus = $offer['status']; -$currencies = ttWorkHelper::getCurrencies(); - -if ($request->isPost()) { - $cl_name = trim($request->getParameter('offer_name')); - $cl_description = trim($request->getParameter('description')); - $cl_details = trim($request->getParameter('details')); - $cl_currency_id = $request->getParameter('currency'); - if (!$cl_currency_id && $work_item) $cl_currency_id = ttWorkHelper::getCurrencyID($work_item['currency']); - $cl_budget = $request->getParameter('budget'); - $cl_payment_info = $request->getParameter('payment_info'); - // $cl_status = $request->getParameter('status'); - $cl_moderator_comment = $request->getParameter('moderator_comment'); -} else { - $cl_name = $offer['subject']; - $cl_description = $offer['descr_short']; - $cl_details = $offer['descr_long']; - $currency = $offer['currency']; - $cl_currency_id = ttWorkHelper::getCurrencyID($offer['currency']); - $cl_budget = $offer['amount']; - $cl_payment_info = $offer['payment_info']; - $status = $offer['status']; - $status_label = $offer['status_label']; - $cl_moderator_comment = $offer['moderator_comment']; -} - -$form = new Form('offerForm'); -$form->addInput(array('type'=>'hidden','name'=>'id','value'=>$cl_offer_id)); -if ($work_id) { - $form->addInput(array('type'=>'textarea','name'=>'work_description','style'=>'width: 400px; height: 80px;','value'=>$work_item['descr_short'])); - $form->getElement('work_description')->setEnabled(false); -} -$form->addInput(array('type'=>'text','name'=>'offer_name','style'=>'width: 400px;','value'=>$cl_name)); -if ($work_id) $form->getElement('offer_name')->setEnabled(false); -$form->addInput(array('type'=>'textarea','name'=>'description','maxlength'=>'512','style'=>'width: 400px; height: 80px;','value'=>$cl_description)); -$form->addInput(array('type'=>'textarea','name'=>'details','style'=>'width: 400px; height: 200px;','value'=>$cl_details)); -$form->addInput(array('type'=>'combobox','name'=>'currency','data'=>$currencies,'datakeys'=>array('id','name'),'value'=>$cl_currency_id)); -if ($work_id) $form->getElement('currency')->setEnabled(false); -$form->addInput(array('type'=>'floatfield','maxlength'=>'10','name'=>'budget','format'=>'.2','value'=>$cl_budget)); -$form->addInput(array('type'=>'textarea','name'=>'payment_info','maxlength'=>'256','style'=>'width: 400px; height: 40px;vertical-align: middle','value'=>$cl_payment_info)); -$form->addInput(array('type'=>'text','name'=>'status','style'=>'width: 400px;','value'=>$status_label)); -$form->getElement('status')->setEnabled(false); - -// Prepare status choices. -/* -$status_options = array(); -$status_options[STATUS_PENDING_APPROVAL] = $i18n->get('dropdown.pending_approval'); -$status_options[STATUS_DISAPPROVED] = $i18n->get('dropdown.not_approved'); -$status_options[STATUS_APPROVED] = $i18n->get('dropdown.approved'); - -$form->addInput(array('type'=>'combobox','name'=>'status','value'=>$cl_status,'data'=>$status_options)); -*/ -$form->addInput(array('type'=>'textarea','name'=>'moderator_comment','style'=>'width: 400px; height: 80px;','value'=>$cl_moderator_comment)); -$form->addInput(array('type'=>'submit','name'=>'btn_save','value'=>$i18n->get('button.save'))); -if ($status == STATUS_PENDING_APPROVAL) { - $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'))); -} - -if ($request->isPost()) { - // Validate user input. - if (!ttValidString($cl_name)) $err->add($i18n->get('error.field'), $i18n->get('label.work')); - if (!ttValidString($cl_description, true)) $err->add($i18n->get('error.field'), $i18n->get('label.description')); - if (!ttValidString($cl_details, true)) $err->add($i18n->get('error.field'), $i18n->get('label.details')); - if (!ttValidString($cl_budget)) $err->add($i18n->get('error.field'), $i18n->get('label.budget')); - if (!ttValidString($cl_payment_info)) $err->add($i18n->get('error.field'), $i18n->get('label.how_to_pay')); - if (!ttValidString($cl_moderator_comment, true)) $err->add($i18n->get('error.field'), $i18n->get('label.moderator_comment')); - - // Ensure user email exists (required for workflow). - if (!$user->getEmail()) $err->add($i18n->get('error.no_email')); - - $fields = array('offer_id'=>$cl_offer_id, - 'subject'=>$cl_name, - 'descr_short' => $cl_description, - 'descr_long' => $cl_details, - 'currency' => ttWorkHelper::getCurrencyName($cl_currency_id), - 'amount' => $cl_budget, - 'payment_info' => $cl_payment_info, - 'moderator_comment' => $cl_moderator_comment); - - if ($err->no()) { - if ($request->getParameter('btn_approve')) { - // Approve offer. - if ($adminWorkHelper->approveOffer($fields)) { - header('Location: admin_work.php'); - exit(); - } - } - - if ($request->getParameter('btn_save')) { - // Update offer without changing its status. - if ($adminWorkHelper->updateOffer($fields)) { - header('Location: admin_work.php'); - exit(); - } - } - - if ($request->getParameter('btn_disapprove')) { - // Dispprove offer. - if ($adminWorkHelper->disapproveOffer($fields)) { - header('Location: admin_work.php'); - exit(); - } - } - } -} // isPost - -if ($work_id) { - $smarty->assign('work_id', $work_id); - $smarty->assign('work_name', $work_item['subject']); - $smarty->assign('work_description', $work_item['descr_short']); -} -$smarty->assign('forms', array($form->getName()=>$form->toArray())); -$smarty->assign('title', $i18n->get('title.edit_offer')); -$smarty->assign('content_page_name', 'work/admin_offer_edit.tpl'); -$smarty->display('work/index.tpl'); diff --git a/work/admin_work.php b/work/admin_work.php deleted file mode 100644 index 86a029a4a..000000000 --- a/work/admin_work.php +++ /dev/null @@ -1,53 +0,0 @@ -getItems(); -$pending_work = $adminItems['pending_work']; -$pending_offers = $adminItems['pending_offers']; -$available_work = $adminItems['available_work']; -$available_offers = $adminItems['available_offers']; - -$smarty->assign('pending_work', $pending_work); -$smarty->assign('pending_offers', $pending_offers); -$smarty->assign('available_work', $available_work); -$smarty->assign('available_offers', $available_offers); -$smarty->assign('title', $i18n->get('title.work')); -$smarty->assign('content_page_name', 'work/admin_work.tpl'); -$smarty->display('work/index.tpl'); diff --git a/work/admin_work_delete.php b/work/admin_work_delete.php deleted file mode 100644 index 11b599430..000000000 --- a/work/admin_work_delete.php +++ /dev/null @@ -1,71 +0,0 @@ -getParameter('id'); -$adminWorkHelper = new ttAdminWorkHelper($err); -$work_item = $adminWorkHelper->getWorkItem($cl_work_id); -if (!$work_item) { - header('Location: ../access_denied.php'); - exit(); -} -// End of access checks. - -$work_to_delete = $work_item['subject']; - -$form = new Form('workDeleteForm'); -$form->addInput(array('type'=>'hidden','name'=>'id','value'=>$cl_work_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 ($adminWorkHelper->deleteWorkItem($cl_work_id)) { - header('Location: admin_work.php'); - exit(); - } - } elseif ($request->getParameter('btn_cancel')) { - header('Location: admin_work.php'); - exit(); - } -} // isPost - -$smarty->assign('work_to_delete', $work_to_delete); -$smarty->assign('forms', array($form->getName()=>$form->toArray())); -$smarty->assign('onload', 'onLoad="document.workDeleteForm.btn_cancel.focus()"'); -$smarty->assign('title', $i18n->get('title.delete_work')); -$smarty->assign('content_page_name', 'work/work_delete.tpl'); -$smarty->display('work/index.tpl'); diff --git a/work/admin_work_edit.php b/work/admin_work_edit.php deleted file mode 100644 index 51169325c..000000000 --- a/work/admin_work_edit.php +++ /dev/null @@ -1,168 +0,0 @@ -getParameter('id'); -$adminWorkHelper = new ttAdminWorkHelper($err); -$work_item = $adminWorkHelper->getWorkItem($cl_work_id); -if (!$work_item) { - header('Location: ../access_denied.php'); - exit(); -} -// Do we have offer_id? -$offer_id = $work_item['offer_id']; -if ($offer_id) { - $offer = $adminWorkHelper->getOffer($offer_id); - if (!$offer) { - header('Location: ../access_denied.php'); - exit(); - } -} -// End of access checks. - -$existingStatus = $work_item['status']; -$currencies = ttWorkHelper::getCurrencies(); - -if ($request->isPost()) { - $cl_name = trim($request->getParameter('work_name')); - $cl_description = trim($request->getParameter('description')); - $cl_details = trim($request->getParameter('details')); - $cl_currency_id = $request->getParameter('currency'); - $cl_budget = $request->getParameter('budget'); - $cl_status = $request->getParameter('status'); - $cl_moderator_comment = $request->getParameter('moderator_comment'); -} else { - $cl_name = $work_item['subject']; - $cl_description = $work_item['descr_short']; - $cl_details = $work_item['descr_long']; - $currency = $work_item['currency']; - $cl_currency_id = ttWorkHelper::getCurrencyID($work_item['currency']); - $cl_budget = $work_item['amount']; - $cl_status = $work_item['status']; - $cl_moderator_comment = $work_item['moderator_comment']; -} - -$form = new Form('workForm'); -$form->addInput(array('type'=>'hidden','name'=>'id','value'=>$cl_work_id)); -$form->addInput(array('type'=>'text','name'=>'work_name','maxlength'=>'128','style'=>'width: 400px;','value'=>$cl_name)); -$form->addInput(array('type'=>'textarea','name'=>'description','maxlength'=>'512','style'=>'width: 400px; height: 80px;','value'=>$cl_description)); -$form->addInput(array('type'=>'textarea','name'=>'details','style'=>'width: 400px; height: 200px;','value'=>$cl_details)); -$form->addInput(array('type'=>'combobox','name'=>'currency','data'=>$currencies,'datakeys'=>array('id','name'),'value'=>$cl_currency_id)); -$form->addInput(array('type'=>'floatfield','maxlength'=>'10','name'=>'budget','format'=>'.2','value'=>$cl_budget)); -// Disable some controls for work on an available offer. -if ($offer) { - $form->getElement('work_name')->setEnabled(false); - // $form->getElement('work_type')->setEnabled(false); - $form->getElement('currency')->setEnabled(false); - $form->getElement('budget')->setEnabled(false); -} - -// Prepare status choices. -$status_options = array(); -$status_options[STATUS_PENDING_APPROVAL] = $i18n->get('dropdown.pending_approval'); -$status_options[STATUS_DISAPPROVED] = $i18n->get('dropdown.not_approved'); -$status_options[STATUS_APPROVED] = $i18n->get('dropdown.approved'); - -$form->addInput(array('type'=>'combobox','name'=>'status','value'=>$cl_status,'data'=>$status_options)); -$form->addInput(array('type'=>'textarea','name'=>'moderator_comment','style'=>'width: 400px; height: 80px;','value'=>$cl_moderator_comment)); -$form->addInput(array('type'=>'submit','name'=>'btn_save','value'=>$i18n->get('button.save'))); - -if ($request->isPost()) { - // Validate user input. - if (!ttValidString($cl_name)) $err->add($i18n->get('error.field'), $i18n->get('label.work')); - if (!ttValidString($cl_description, true)) $err->add($i18n->get('error.field'), $i18n->get('label.description')); - if (!ttValidString($cl_details, true)) $err->add($i18n->get('error.field'), $i18n->get('label.details')); - if (!ttValidString($cl_budget)) $err->add($i18n->get('error.field'), $i18n->get('label.budget')); - if (!ttValidString($cl_moderator_comment, true)) $err->add($i18n->get('error.field'), $i18n->get('label.moderator_comment')); - - // Ensure user email exists (required for workflow). - if (!$user->getEmail()) $err->add($i18n->get('error.no_email')); - - if ($err->no()) { - if ($request->getParameter('btn_save')) { - $fields = array('work_id'=>$cl_work_id, - 'subject'=>$cl_name, - 'descr_short' => $cl_description, - 'descr_long' => $cl_details, - 'currency' => ttWorkHelper::getCurrencyName($cl_currency_id), - 'amount' => $cl_budget, - 'moderator_comment' => $cl_moderator_comment); - - // Do things differently, depending on status control value. - if ($existingStatus == $cl_status) { - // Status not changed. Update work information. - if ($adminWorkHelper->updateWorkItem($fields)) { - header('Location: admin_work.php'); - exit(); - } - } else if ($cl_status == STATUS_DISAPPROVED) { - // Status changed to "not approved". Disapprove work. - if ($offer_id) { - if ($adminWorkHelper->disapproveWorkItemOnOffer($fields)) { - header('Location: admin_work.php'); - exit(); - } - } else { - if ($adminWorkHelper->disapproveWorkItem($fields)) { - header('Location: admin_work.php'); - exit(); - } - } - } else if ($cl_status == STATUS_APPROVED) { - // Status changed to "approved". Approve work. - if ($offer_id) { - if ($adminWorkHelper->approveWorkItemOnOffer($fields)) { - header('Location: admin_work.php'); - exit(); - } - } else { - if ($adminWorkHelper->approveWorkItem($fields)) { - header('Location: admin_work.php'); - exit(); - } - } - } - } - } -} // isPost - -$smarty->assign('offer', $offer); -$smarty->assign('forms', array($form->getName()=>$form->toArray())); -$smarty->assign('title', $i18n->get('title.edit_work')); -$smarty->assign('content_page_name', 'work/admin_work_edit.tpl'); -$smarty->display('work/index.tpl'); diff --git a/work/offer_add.php b/work/offer_add.php deleted file mode 100644 index bafdae216..000000000 --- a/work/offer_add.php +++ /dev/null @@ -1,120 +0,0 @@ -isPluginEnabled('wk')) { - header('Location: ../feature_disabled.php'); - exit(); -} -// Do we have work_id? -$cl_work_id = (int)$request->getParameter('work_id'); -if ($cl_work_id) { - $workHelper = new ttWorkHelper($err); - $work_item = $workHelper->getAvailableWorkItem($cl_work_id); - if (!$work_item) { - header('Location: ../access_denied.php'); - exit(); - } -} -// End of access checks. -if ($work_item) $cl_name = $work_item['subject']; - -if ($request->isPost()) { - if (!$work_item) - $cl_name = trim($request->getParameter('offer_name')); - $cl_description = trim($request->getParameter('description')); - $cl_details = trim($request->getParameter('details')); - $cl_currency_id = $request->getParameter('currency'); - if (!$cl_currency_id && $work_item) $cl_currency_id = ttWorkHelper::getCurrencyID($work_item['currency']); - $cl_budget = $request->getParameter('budget'); - $cl_payment_info = $request->getParameter('payment_info'); -} else { - if ($work_item) { - $cl_currency_id = ttWorkHelper::getCurrencyID($work_item['currency']); - } -} - -$form = new Form('offerForm'); -if ($cl_work_id) { - $form->addInput(array('type'=>'hidden','name'=>'work_id','value'=>$cl_work_id)); - $form->addInput(array('type'=>'textarea','name'=>'work_description','style'=>'width: 400px; height: 80px;','value'=>$work_item['descr_short'])); - $form->getElement('work_description')->setEnabled(false); -} -$form->addInput(array('type'=>'text','name'=>'offer_name','maxlength'=>'128','style'=>'width: 400px;','value'=>$cl_name)); -if ($work_item) $form->getElement('offer_name')->setEnabled(false); -$form->addInput(array('type'=>'textarea','name'=>'description','maxlength'=>'512','style'=>'width: 400px; height: 80px;','value'=>$cl_description)); -$form->addInput(array('type'=>'textarea','name'=>'details','style'=>'width: 400px; height: 200px;','value'=>$cl_details)); -// Add a dropdown for currency. -$currencies = ttWorkHelper::getCurrencies(); -$form->addInput(array('type'=>'combobox','name'=>'currency','data'=>$currencies,'datakeys'=>array('id','name'),'value'=>$cl_currency_id)); -if ($work_item) $form->getElement('currency')->setEnabled(false); // Do not allow changing currency for offers on existing work items. -$form->addInput(array('type'=>'floatfield','maxlength'=>'10','name'=>'budget','format'=>'.2','value'=>$cl_budget)); -$form->addInput(array('type'=>'textarea','name'=>'payment_info','maxlength'=>'256','style'=>'width: 400px; height: 40px;vertical-align: middle','value'=>$cl_payment_info)); -$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.offer')); - if (!ttValidString($cl_description, true)) $err->add($i18n->get('error.field'), $i18n->get('label.description')); - if (!ttValidString($cl_details, true)) $err->add($i18n->get('error.field'), $i18n->get('label.details')); - if (!ttValidString($cl_budget)) $err->add($i18n->get('error.field'), $i18n->get('label.budget')); - if (!ttValidString($cl_payment_info)) $err->add($i18n->get('error.field'), $i18n->get('label.how_to_pay')); - - // Ensure user email exists (required for workflow). - if (!$user->getEmail()) $err->add($i18n->get('error.no_email')); - - if ($err->no()) { - $workHelper = new ttWorkHelper($err); - $fields = array('work_id'=>$cl_work_id, - 'subject'=>$cl_name, - 'descr_short' => $cl_description, - 'descr_long' => $cl_details, - 'currency' => ttWorkHelper::getCurrencyName($cl_currency_id), - 'amount' => $cl_budget, - 'payment_info' => $cl_payment_info); - if ($workHelper->putOwnOffer($fields)) { - header('Location: work.php'); - exit(); - } - } -} // isPost - -$smarty->assign('work_item', $work_item); -$smarty->assign('forms', array($form->getName()=>$form->toArray())); -$smarty->assign('onload', 'onLoad="document.offerForm.work_name.focus()"'); -$smarty->assign('title', $i18n->get('title.add_offer')); -$smarty->assign('content_page_name', 'work/offer_add.tpl'); -$smarty->display('work/index.tpl'); diff --git a/work/offer_delete.php b/work/offer_delete.php deleted file mode 100644 index fd3d2e7d1..000000000 --- a/work/offer_delete.php +++ /dev/null @@ -1,75 +0,0 @@ -isPluginEnabled('wk')) { - header('Location: ../feature_disabled.php'); - exit(); -} -$cl_offer_id = (int)$request->getParameter('id'); -$workHelper = new ttWorkHelper($err); -$offer = $workHelper->getOwnOffer($cl_offer_id); -if (!$offer) { - header('Location: ../access_denied.php'); - exit(); -} -// End of access checks. - -$offer_to_delete = $offer['subject']; - -$form = new Form('offerDeleteForm'); -$form->addInput(array('type'=>'hidden','name'=>'id','value'=>$cl_offer_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 ($workHelper->deleteOwnOffer($cl_offer_id)) { - header('Location: work.php'); - exit(); - } - } elseif ($request->getParameter('btn_cancel')) { - header('Location: work.php'); - exit(); - } -} // isPost - -$smarty->assign('offer_to_delete', $offer_to_delete); -$smarty->assign('forms', array($form->getName()=>$form->toArray())); -$smarty->assign('onload', 'onLoad="document.offerDeleteForm.btn_cancel.focus()"'); -$smarty->assign('title', $i18n->get('title.delete_offer')); -$smarty->assign('content_page_name', 'work/offer_delete.tpl'); -$smarty->display('work/index.tpl'); diff --git a/work/offer_edit.php b/work/offer_edit.php deleted file mode 100644 index bd2172210..000000000 --- a/work/offer_edit.php +++ /dev/null @@ -1,135 +0,0 @@ -isPluginEnabled('wk')) { - header('Location: ../feature_disabled.php'); - exit(); -} -$cl_offer_id = (int)$request->getParameter('id'); -$workHelper = new ttWorkHelper($err); -$offer = $workHelper->getOwnOffer($cl_offer_id); -if (!$offer) { - header('Location: ../access_denied.php'); - exit(); -} -// End of access checks. - -// Is this offer associated with a work item? -$work_id = $offer['work_id']; -if ($work_id) { - $work_item = $workHelper->getAvailableWorkItem($work_id); - if (!$work_item) $err->add($i18n->get('work.error.work_not_available')); -} - -$currencies = ttWorkHelper::getCurrencies(); - -if ($request->isPost()) { - $cl_name = trim($request->getParameter('offer_name')); - $cl_description = trim($request->getParameter('description')); - $cl_details = trim($request->getParameter('details')); - $cl_currency_id = $request->getParameter('currency'); - if (!$cl_currency_id && $work_item) $cl_currency_id = ttWorkHelper::getCurrencyID($work_item['currency']); - $cl_budget = $request->getParameter('budget'); - $cl_payment_info = $request->getParameter('payment_info'); -} else { - $cl_name = $offer['subject']; - $cl_description = $offer['descr_short']; - $cl_details = $offer['descr_long']; - $cl_currency_id = ttWorkHelper::getCurrencyID($offer['currency']); - $cl_budget = $offer['amount']; - $cl_payment_info = $offer['payment_info']; - $cl_status = $offer['status_label']; - $cl_moderator_comment = $offer['moderator_comment']; -} - -$show_moderator_comment = $cl_moderator_comment != null; - -$form = new Form('offerForm'); -$form->addInput(array('type'=>'hidden','name'=>'id','value'=>$cl_offer_id)); -if ($work_id) { - $form->addInput(array('type'=>'textarea','name'=>'work_description','style'=>'width: 400px; height: 80px;','value'=>$work_item['descr_short'])); - $form->getElement('work_description')->setEnabled(false); -} -$form->addInput(array('type'=>'text','name'=>'offer_name','maxlength'=>'128','style'=>'width: 400px;','value'=>$cl_name)); -if ($work_id) $form->getElement('offer_name')->setEnabled(false); -$form->addInput(array('type'=>'textarea','name'=>'description','maxlength'=>'512','style'=>'width: 400px; height: 80px;','value'=>$cl_description)); -$form->addInput(array('type'=>'textarea','name'=>'details','style'=>'width: 400px; height: 200px;','value'=>$cl_details)); -$form->addInput(array('type'=>'combobox','name'=>'currency','data'=>$currencies,'datakeys'=>array('id','name'),'value'=>$cl_currency_id)); -if ($work_id) $form->getElement('currency')->setEnabled(false); -$form->addInput(array('type'=>'floatfield','maxlength'=>'10','name'=>'budget','format'=>'.2','value'=>$cl_budget)); -$form->addInput(array('type'=>'textarea','name'=>'payment_info','maxlength'=>'256','style'=>'width: 400px; height: 40px; vertical-align: middle','value'=>$cl_payment_info)); -$form->addInput(array('type'=>'text','name'=>'status','style'=>'width: 400px;','value'=>$cl_status)); -$form->getElement('status')->setEnabled(false); -$form->addInput(array('type'=>'textarea','name'=>'moderator_comment','style'=>'width: 400px; height: 80px;','value'=>$cl_moderator_comment)); -$form->getElement('moderator_comment')->setEnabled(false); -$form->addInput(array('type'=>'submit','name'=>'btn_save','value'=>$i18n->get('button.save'))); - -if ($request->isPost()) { - // Validate user input. - if (!ttValidString($cl_name)) $err->add($i18n->get('error.field'), $i18n->get('label.offer')); - if (!ttValidString($cl_description, true)) $err->add($i18n->get('error.field'), $i18n->get('label.description')); - if (!ttValidString($cl_details, true)) $err->add($i18n->get('error.field'), $i18n->get('label.details')); - if (!ttValidString($cl_budget)) $err->add($i18n->get('error.field'), $i18n->get('label.budget')); - if (!ttValidString($cl_payment_info)) $err->add($i18n->get('error.field'), $i18n->get('label.how_to_pay')); - - // Ensure user email exists (required for workflow). - if (!$user->getEmail()) $err->add($i18n->get('error.no_email')); - - if ($err->no()) { - if ($request->getParameter('btn_save')) { - // Update offer information. - $fields = array('offer_id'=>$cl_offer_id, - 'subject'=>$cl_name, - 'descr_short' => $cl_description, - 'descr_long' => $cl_details, - 'currency' => ttWorkHelper::getCurrencyName($cl_currency_id), - 'amount' => $cl_budget, - 'payment_info' => $cl_payment_info); - if ($workHelper->updateOwnOffer($fields)) { - header('Location: work.php'); - exit(); - } - } - } -} // isPost - -$smarty->assign('work_item', $work_item); -$smarty->assign('show_moderator_comment', $show_moderator_comment); -$smarty->assign('forms', array($form->getName()=>$form->toArray())); -$smarty->assign('title', $i18n->get('title.edit_offer')); -$smarty->assign('content_page_name', 'work/offer_edit.tpl'); -$smarty->display('work/index.tpl'); diff --git a/work/offer_message.php b/work/offer_message.php deleted file mode 100644 index ff324808e..000000000 --- a/work/offer_message.php +++ /dev/null @@ -1,90 +0,0 @@ -isPluginEnabled('wk')) { - header('Location: ../feature_disabled.php'); - exit(); -} -$cl_offer_id = (int)$request->getParameter('offer_id'); -if (!$cl_offer_id) { - header('Location: ../access_denied.php'); - exit(); -} -$workHelper = new ttWorkHelper($err); -$offer = $workHelper->getAvailableOffer($cl_offer_id); -if (!$offer) { - header('Location: ../access_denied.php'); - exit(); -} -// End of access checks. - -$cl_name = $offer['subject']; - -if ($request->isPost()) { - $cl_message_body = trim($request->getParameter('message_body')); -} - -$form = new Form('messageForm'); -$form->addInput(array('type'=>'hidden','name'=>'offer_id','value'=>$cl_offer_id)); -$form->addInput(array('type'=>'textarea','name'=>'message_body','style'=>'width: 400px; height: 80px;vertical-align: middle','value'=>$cl_message_body)); -$form->addInput(array('type'=>'submit','name'=>'btn_send','value'=>$i18n->get('work.button.send_message'))); - -if ($request->isPost()) { - // Validate user input. - if (!ttValidString($cl_message_body)) $err->add($i18n->get('error.field'), $i18n->get('work.label.message')); - - // Ensure user email exists (required for workflow). - if (!$user->getEmail()) $err->add($i18n->get('error.no_email')); - - if ($err->no()) { - $workHelper = new ttWorkHelper($err); - $fields = array('offer_id'=>$cl_offer_id, - 'message_body' => $cl_message_body); - if ($workHelper->sendMessageToOfferOwner($fields)) { - $msg->add($i18n->get('work.msg.message_sent')); - // header('Location: work.php'); // TODO: where to redirect? - // exit(); - } - } -} // isPost - -$smarty->assign('offer', $offer); -$smarty->assign('forms', array($form->getName()=>$form->toArray())); -$smarty->assign('onload', 'onLoad="document.offerForm.work_name.focus()"'); -$smarty->assign('title', $i18n->get('work.title.send_message')); -$smarty->assign('content_page_name', 'work/offer_message.tpl'); -$smarty->display('work/index.tpl'); diff --git a/work/offer_view.php b/work/offer_view.php deleted file mode 100644 index 1e045bf20..000000000 --- a/work/offer_view.php +++ /dev/null @@ -1,81 +0,0 @@ -isPluginEnabled('wk')) { - header('Location: ../feature_disabled.php'); - exit(); -} -if (!ttAccessAllowed('manage_work')) { - header('Location: ../access_denied.php'); - exit(); -} -$offer_id = (int)$request->getParameter('id'); -$workHelper = new ttWorkHelper($err); -$offer = $workHelper->getAvailableOffer($offer_id); -if (!$offer) { - header('Location: ../access_denied.php'); - exit(); -} -// End of access checks. - -// Is this offer associated with a work item? -$work_id = $offer['work_id']; -if ($work_id) { - $work_item = $workHelper->getAvailableWorkItem($work_id); - if (!$work_item) $err->add($i18n->get('work.error.work_not_available')); -} - -$cl_contractor = $offer['group_name']; -$cl_name = $offer['subject']; -$cl_description = $offer['descr_short']; -$cl_details = $offer['descr_long']; -$cl_budget = $offer['amount_with_currency']; - -$form = new Form('offerForm'); -$form->addInput(array('type'=>'text','name'=>'contractor','value'=>$cl_contractor)); -$form->getElement('contractor')->setEnabled(false); -$form->addInput(array('type'=>'text','name'=>'offer_name','style'=>'width: 400px;','value'=>$cl_name)); -$form->getElement('offer_name')->setEnabled(false); -$form->addInput(array('type'=>'textarea','name'=>'description','style'=>'width: 400px; height: 80px;','value'=>$cl_description)); -$form->getElement('description')->setEnabled(false); -$form->addInput(array('type'=>'textarea','name'=>'details','style'=>'width: 400px; height: 200px;','value'=>$cl_details)); -$form->getElement('details')->setEnabled(false); -$form->addInput(array('type'=>'text','name'=>'budget','value'=>$cl_budget)); -$form->getElement('budget')->setEnabled(false); - - -$smarty->assign('offer_id', $offer_id); -$smarty->assign('forms', array($form->getName()=>$form->toArray())); -$smarty->assign('title', $i18n->get('title.offer')); -$smarty->assign('content_page_name', 'work/offer_view.tpl'); -$smarty->display('work/index.tpl'); diff --git a/work/offer_view_own.php b/work/offer_view_own.php deleted file mode 100644 index 57b1b95b3..000000000 --- a/work/offer_view_own.php +++ /dev/null @@ -1,77 +0,0 @@ -isPluginEnabled('wk')) { - header('Location: ../feature_disabled.php'); - exit(); -} -if (!ttAccessAllowed('manage_work')) { - header('Location: ../access_denied.php'); - exit(); -} -$offer_id = (int)$request->getParameter('id'); -$workHelper = new ttWorkHelper($err); -$offer = $workHelper->getOwnOffer($offer_id); -if (!$offer) { - header('Location: ../access_denied.php'); - exit(); -} -// End of access checks. - -// Is this offer associated with a work item? -$work_id = $offer['work_id']; -if ($work_id) { - $work_item = $workHelper->getAvailableWorkItem($work_id); - if (!$work_item) $err->add($i18n->get('work.error.work_not_available')); -} - -$cl_name = $offer['subject']; -$cl_description = $offer['descr_short']; -$cl_details = $offer['descr_long']; -$cl_budget = $offer['amount_with_currency']; - -$form = new Form('offerForm'); -$form->addInput(array('type'=>'text','name'=>'offer_name','style'=>'width: 400px;','value'=>$cl_name)); -$form->getElement('offer_name')->setEnabled(false); -$form->addInput(array('type'=>'textarea','name'=>'description','style'=>'width: 400px; height: 80px;','value'=>$cl_description)); -$form->getElement('description')->setEnabled(false); -$form->addInput(array('type'=>'textarea','name'=>'details','style'=>'width: 400px; height: 200px;','value'=>$cl_details)); -$form->getElement('details')->setEnabled(false); -$form->addInput(array('type'=>'text','name'=>'budget','value'=>$cl_budget)); -$form->getElement('budget')->setEnabled(false); - -$smarty->assign('work_item', $work_item); -$smarty->assign('forms', array($form->getName()=>$form->toArray())); -$smarty->assign('title', $i18n->get('title.offer')); -$smarty->assign('content_page_name', 'work/offer_view_own.tpl'); -$smarty->display('work/index.tpl'); diff --git a/work/work.php b/work/work.php deleted file mode 100644 index b043690b4..000000000 --- a/work/work.php +++ /dev/null @@ -1,65 +0,0 @@ -isPluginEnabled('wk')) { - header('Location: ../feature_disabled.php'); - exit(); -} -// End of access checks. - -$workHelper = new ttWorkHelper($err); -$groupItems = $workHelper->getGroupItems(); // All group items. -if($user->can('manage_work')) { - $own_work_items = $groupItems['own_work_items']; // Own work items this group is outsourcing. - $available_offers = $groupItems['available_offers']; // Available offers from other organizations. -} -if($user->can('bid_on_work')) { - $available_work_items = $groupItems['available_work_items']; // Currently available work items from other orgs. - $own_offers = $groupItems['own_offers']; // Own offers this group makes available to other groups. -} -if($user->can('update_work')) { - // $in_progress_work = ttWorkHelper::getInProgressWork(); // Work items in progress for other groups. - // $completed_work = ttWorkHelper::getCompletedWork(); // Completed work items for other groups. -} -// TODO: review access rights for the code above. - -$smarty->assign('own_work_items', $own_work_items); -$smarty->assign('available_work_items', $available_work_items); -$smarty->assign('own_offers', $own_offers); -$smarty->assign('available_offers', $available_offers); -$smarty->assign('title', $i18n->get('title.work')); -$smarty->assign('content_page_name', 'work/work.tpl'); -$smarty->display('work/index.tpl'); diff --git a/work/work_add.php b/work/work_add.php deleted file mode 100644 index df12c9155..000000000 --- a/work/work_add.php +++ /dev/null @@ -1,122 +0,0 @@ -isPluginEnabled('wk')) { - header('Location: ../feature_disabled.php'); - exit(); -} -// Do we have offer_id? -$cl_offer_id = (int)$request->getParameter('offer_id'); -if ($cl_offer_id) { - $workHelper = new ttWorkHelper($err); - $offer = $workHelper->getAvailableOffer($cl_offer_id); - if (!$offer) { - header('Location: ../access_denied.php'); - exit(); - } -} -// End of access checks. - -if ($request->isPost()) { - $cl_name = trim($request->getParameter('work_name')); - $cl_work_type = $request->getParameter('work_type'); - $cl_description = trim($request->getParameter('description')); - $cl_details = trim($request->getParameter('details')); - $cl_currency_id = $request->getParameter('currency'); - $cl_budget = $request->getParameter('budget'); -} -// Override some fields for work being created on an available offer. -if ($offer) { - $cl_name = $offer['subject']; - $cl_work_type = 0; // one-time work - $cl_currency_id = ttWorkHelper::getCurrencyID($offer['currency']); - $cl_budget = $offer['amount']; -} - -$form = new Form('workForm'); -if ($offer) - $form->addInput(array('type'=>'hidden','name'=>'offer_id','value'=>$cl_offer_id)); -$form->addInput(array('type'=>'text','name'=>'work_name','maxlength'=>'128','style'=>'width: 400px;','value'=>$cl_name)); -$WORK_TYPE_OPTIONS = array('0'=>$i18n->get('work.type.one_time'),'1'=>$i18n->get('work.type.ongoing')); -$form->addInput(array('type'=>'combobox','name'=>'work_type','data'=>$WORK_TYPE_OPTIONS,'value'=>$cl_work_type)); -$form->addInput(array('type'=>'textarea','name'=>'description','maxlength'=>'512','style'=>'width: 400px; height: 80px;','value'=>$cl_description)); -$form->addInput(array('type'=>'textarea','name'=>'details','style'=>'width: 400px; height: 200px;','value'=>$cl_details)); -// Add a dropdown for currency. -$currencies = ttWorkHelper::getCurrencies(); -$form->addInput(array('type'=>'combobox','name'=>'currency','data'=>$currencies,'datakeys'=>array('id','name'),'value'=>$cl_currency_id)); -$form->addInput(array('type'=>'floatfield','maxlength'=>'10','name'=>'budget','format'=>'.2','value'=>$cl_budget)); -$form->addInput(array('type'=>'submit','name'=>'btn_add','value'=>$i18n->get('button.add'))); -// Disable some controls for work being created on an available offer. -if ($offer) { - $form->getElement('work_name')->setEnabled(false); - $form->getElement('work_type')->setEnabled(false); - $form->getElement('currency')->setEnabled(false); - $form->getElement('budget')->setEnabled(false); -} - -if ($request->isPost()) { - // Validate user input. - if (!ttValidString($cl_name)) $err->add($i18n->get('error.field'), $i18n->get('label.work')); - if (!ttValidString($cl_description, true)) $err->add($i18n->get('error.field'), $i18n->get('label.description')); - if (!ttValidString($cl_details, true)) $err->add($i18n->get('error.field'), $i18n->get('label.details')); - if (!ttValidString($cl_budget)) $err->add($i18n->get('error.field'), $i18n->get('label.budget')); - - // Ensure user email exists (required for workflow). - if (!$user->getEmail()) $err->add($i18n->get('error.no_email')); - - if ($err->no()) { - $workHelper = new ttWorkHelper($err); - $fields = array('offer_id' => $cl_offer_id, - 'subject' => $cl_name, - 'type' => $cl_work_type, - 'descr_short' => $cl_description, - 'descr_long' => $cl_details, - 'currency' => ttWorkHelper::getCurrencyName($cl_currency_id), - 'amount' => $cl_budget); - if ($workHelper->putOwnWorkItem($fields)) { - header('Location: work.php'); - exit(); - } - } -} // isPost - -$smarty->assign('offer', $offer); -$smarty->assign('forms', array($form->getName()=>$form->toArray())); -$smarty->assign('onload', 'onLoad="document.workForm.work_name.focus()"'); -$smarty->assign('title', $i18n->get('title.add_work')); -$smarty->assign('content_page_name', 'work/work_add.tpl'); -$smarty->display('work/index.tpl'); diff --git a/work/work_delete.php b/work/work_delete.php deleted file mode 100644 index 1d94603ab..000000000 --- a/work/work_delete.php +++ /dev/null @@ -1,75 +0,0 @@ -isPluginEnabled('wk')) { - header('Location: ../feature_disabled.php'); - exit(); -} -$cl_work_id = (int)$request->getParameter('id'); -$workHelper = new ttWorkHelper($err); -$work_item = $workHelper->getOwnWorkItem($cl_work_id); -if (!$work_item) { - header('Location: ../access_denied.php'); - exit(); -} -// End of access checks. - -$work_to_delete = $work_item['subject']; - -$form = new Form('workDeleteForm'); -$form->addInput(array('type'=>'hidden','name'=>'id','value'=>$cl_work_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 ($workHelper->deleteOwnWorkItem($cl_work_id)) { - header('Location: work.php'); - exit(); - } - } elseif ($request->getParameter('btn_cancel')) { - header('Location: work.php'); - exit(); - } -} // isPost - -$smarty->assign('work_to_delete', $work_to_delete); -$smarty->assign('forms', array($form->getName()=>$form->toArray())); -$smarty->assign('onload', 'onLoad="document.workDeleteForm.btn_cancel.focus()"'); -$smarty->assign('title', $i18n->get('title.delete_work')); -$smarty->assign('content_page_name', 'work/work_delete.tpl'); -$smarty->display('work/index.tpl'); diff --git a/work/work_edit.php b/work/work_edit.php deleted file mode 100644 index 7e27106b8..000000000 --- a/work/work_edit.php +++ /dev/null @@ -1,151 +0,0 @@ -isPluginEnabled('wk')) { - header('Location: ../feature_disabled.php'); - exit(); -} -$cl_work_id = (int)$request->getParameter('id'); -$workHelper = new ttWorkHelper($err); -$work_item = $workHelper->getOwnWorkItem($cl_work_id); -if (!$work_item) { - header('Location: ../access_denied.php'); - exit(); -} -// Do we have offer_id? -$offer_id = $work_item['offer_id']; -if ($offer_id) { - $offer = $workHelper->getAvailableOffer($offer_id); - if (!$offer) { - header('Location: ../access_denied.php'); - exit(); - } -} -// End of access checks. - -$currencies = ttWorkHelper::getCurrencies(); - -if ($request->isPost()) { - $cl_name = trim($request->getParameter('work_name')); - $cl_work_type = $request->getParameter('work_type'); - $cl_description = trim($request->getParameter('description')); - $cl_details = trim($request->getParameter('details')); - $cl_currency_id = $request->getParameter('currency'); - $cl_budget = $request->getParameter('budget'); -} else { - $cl_name = $work_item['subject']; - $cl_work_type = $work_item['type']; - $cl_description = $work_item['descr_short']; - $cl_details = $work_item['descr_long']; - $cl_currency_id = ttWorkHelper::getCurrencyID($work_item['currency']); - $cl_budget = $work_item['amount']; - $cl_status = $work_item['status_label']; - $cl_moderator_comment = $work_item['moderator_comment']; -} -// Override some fields for work on an available offer. -if ($offer) { - $cl_name = $offer['subject']; - $cl_work_type = 0; // one-time work - $cl_currency_id = ttWorkHelper::getCurrencyID($offer['currency']); - $cl_budget = $offer['amount']; -} - -$show_moderator_comment = $cl_moderator_comment != null; - -$form = new Form('workForm'); -$form->addInput(array('type'=>'hidden','name'=>'id','value'=>$cl_work_id)); -$form->addInput(array('type'=>'text','name'=>'work_name','maxlength'=>'128','style'=>'width: 400px;','value'=>$cl_name)); -$WORK_TYPE_OPTIONS = array('0'=>$i18n->get('work.type.one_time'),'1'=>$i18n->get('work.type.ongoing')); -$form->addInput(array('type'=>'combobox','name'=>'work_type','data'=>$WORK_TYPE_OPTIONS,'value'=>$cl_work_type)); -$form->addInput(array('type'=>'textarea','name'=>'description','maxlength'=>'512','style'=>'width: 400px; height: 80px;','value'=>$cl_description)); -$form->addInput(array('type'=>'textarea','name'=>'details','style'=>'width: 400px; height: 200px;','value'=>$cl_details)); -$form->addInput(array('type'=>'combobox','name'=>'currency','data'=>$currencies,'datakeys'=>array('id','name'),'value'=>$cl_currency_id)); -$form->addInput(array('type'=>'floatfield','maxlength'=>'10','name'=>'budget','format'=>'.2','value'=>$cl_budget)); -$form->addInput(array('type'=>'text','name'=>'status','style'=>'width: 400px;','value'=>$cl_status)); -$form->getElement('status')->setEnabled(false); -$form->addInput(array('type'=>'textarea','name'=>'moderator_comment','style'=>'width: 400px; height: 80px;','value'=>$cl_moderator_comment)); -$form->getElement('moderator_comment')->setEnabled(false); -$form->addInput(array('type'=>'submit','name'=>'btn_save','value'=>$i18n->get('button.save'))); -// Disable some controls for work on an available offer. -if ($offer) { - $form->getElement('work_name')->setEnabled(false); - $form->getElement('work_type')->setEnabled(false); - $form->getElement('currency')->setEnabled(false); - $form->getElement('budget')->setEnabled(false); -} - -if ($request->isPost()) { - // Validate user input. - if (!ttValidString($cl_name)) $err->add($i18n->get('error.field'), $i18n->get('label.work')); - if (!ttValidString($cl_description, true)) $err->add($i18n->get('error.field'), $i18n->get('label.description')); - if (!ttValidString($cl_details, true)) $err->add($i18n->get('error.field'), $i18n->get('label.details')); - if (!ttValidString($cl_budget)) $err->add($i18n->get('error.field'), $i18n->get('label.budget')); - - // Ensure user email exists (required for workflow). - if (!$user->getEmail()) $err->add($i18n->get('error.no_email')); - - if ($err->no()) { - if ($request->getParameter('btn_save')) { - // Update work information. - $fields = array('work_id'=>$cl_work_id, - 'type'=>$cl_work_type, - 'subject'=>$cl_name, - 'descr_short' => $cl_description, - 'descr_long' => $cl_details, - 'currency' => ttWorkHelper::getCurrencyName($cl_currency_id), - 'amount' => $cl_budget); - if ($offer_id > 0) { - if ($workHelper->updateOwnWorkItemOnOffer($fields)) { - header('Location: work.php'); - exit(); - } - } else { - if ($workHelper->updateOwnWorkItem($fields)) { - header('Location: work.php'); - exit(); - } - } - } - } -} // isPost - -$smarty->assign('offer', $offer); -$smarty->assign('show_moderator_comment', $show_moderator_comment); -$smarty->assign('forms', array($form->getName()=>$form->toArray())); -$smarty->assign('title', $i18n->get('title.edit_work')); -$smarty->assign('content_page_name', 'work/work_edit.tpl'); -$smarty->display('work/index.tpl'); diff --git a/work/work_message.php b/work/work_message.php deleted file mode 100644 index 91d02b5cb..000000000 --- a/work/work_message.php +++ /dev/null @@ -1,90 +0,0 @@ -isPluginEnabled('wk')) { - header('Location: ../feature_disabled.php'); - exit(); -} -$cl_work_id = (int)$request->getParameter('work_id'); -if (!$cl_work_id) { - header('Location: ../access_denied.php'); - exit(); -} -$workHelper = new ttWorkHelper($err); -$work_item = $workHelper->getAvailableWorkItem($cl_work_id); -if (!$work_item) { - header('Location: ../access_denied.php'); - exit(); -} -// End of access checks. - -$cl_name = $work_item['subject']; - -if ($request->isPost()) { - $cl_message_body = trim($request->getParameter('message_body')); -} - -$form = new Form('messageForm'); -$form->addInput(array('type'=>'hidden','name'=>'work_id','value'=>$cl_work_id)); -$form->addInput(array('type'=>'textarea','name'=>'message_body','style'=>'width: 400px; height: 80px;vertical-align: middle','value'=>$cl_message_body)); -$form->addInput(array('type'=>'submit','name'=>'btn_send','value'=>$i18n->get('work.button.send_message'))); - -if ($request->isPost()) { - // Validate user input. - if (!ttValidString($cl_message_body)) $err->add($i18n->get('error.field'), $i18n->get('work.label.message')); - - // Ensure user email exists (required for workflow). - if (!$user->getEmail()) $err->add($i18n->get('error.no_email')); - - if ($err->no()) { - $workHelper = new ttWorkHelper($err); - $fields = array('work_id'=>$cl_work_id, - 'message_body' => $cl_message_body); - if ($workHelper->sendMessageToWorkOwner($fields)) { - $msg->add($i18n->get('work.msg.message_sent')); - // header('Location: work.php'); // TODO: where to redirect? - // exit(); - } - } -} // isPost - -$smarty->assign('work_item', $work_item); -$smarty->assign('forms', array($form->getName()=>$form->toArray())); -$smarty->assign('onload', 'onLoad="document.offerForm.work_name.focus()"'); -$smarty->assign('title', $i18n->get('work.title.send_message')); -$smarty->assign('content_page_name', 'work/work_message.tpl'); -$smarty->display('work/index.tpl'); diff --git a/work/work_offer_view.php b/work/work_offer_view.php deleted file mode 100644 index c0dd5ee1a..000000000 --- a/work/work_offer_view.php +++ /dev/null @@ -1,123 +0,0 @@ -isPluginEnabled('wk')) { - header('Location: ../feature_disabled.php'); - exit(); -} -if (!ttAccessAllowed('manage_work')) { - header('Location: ../access_denied.php'); - exit(); -} -$cl_offer_id = (int)$request->getParameter('id'); -$workHelper = new ttWorkHelper($err); -$offer = $workHelper->getOwnWorkItemOffer($cl_offer_id); -if (!$offer) { - header('Location: ../access_denied.php'); - exit(); -} -// End of access checks. - -// Get an associated work item. -$work_item = $workHelper->getOwnWorkItem($offer['work_id']); -if (!$work_item) $err->add($i18n->get('work.error.work_not_available')); - -if ($request->isPost()) { - $cl_client_comment = $request->getParameter('client_comment'); -} else { - $cl_client_comment = $offer['client_comment']; -} - -$cl_contractor = $offer['group_name']; -$cl_name = $offer['subject']; -$cl_description = $offer['descr_short']; -$cl_details = $offer['descr_long']; -$cl_budget = $offer['amount_with_currency']; -$cl_status = $offer['status_label']; - -$form = new Form('offerForm'); -$form->addInput(array('type'=>'hidden','name'=>'id','value'=>$cl_offer_id)); -$form->addInput(array('type'=>'text','name'=>'contractor','value'=>$cl_contractor)); -$form->getElement('contractor')->setEnabled(false); -$form->addInput(array('type'=>'text','name'=>'offer_name','style'=>'width: 400px;','value'=>$cl_name)); -$form->getElement('offer_name')->setEnabled(false); -$form->addInput(array('type'=>'textarea','name'=>'description','style'=>'width: 400px; height: 80px;','value'=>$cl_description)); -$form->getElement('description')->setEnabled(false); -$form->addInput(array('type'=>'textarea','name'=>'details','style'=>'width: 400px; height: 200px;','value'=>$cl_details)); -$form->getElement('details')->setEnabled(false); -$form->addInput(array('type'=>'text','name'=>'budget','value'=>$cl_budget)); -$form->getElement('budget')->setEnabled(false); -$form->addInput(array('type'=>'text','name'=>'status','style'=>'width: 400px;','value'=>$cl_status)); -$form->getElement('status')->setEnabled(false); -if ($offer['status'] == STATUS_APPROVED) { - $form->addInput(array('type'=>'submit','name'=>'btn_accept','value'=>$i18n->get('work.button.accept'))); - $form->addInput(array('type'=>'submit','name'=>'btn_decline','value'=>$i18n->get('work.button.decline'))); -} -$form->addInput(array('type'=>'textarea','name'=>'client_comment','style'=>'width: 400px; height: 80px;','value'=>$cl_client_comment)); - -if ($request->isPost()) { - // Validate user input. - if (!ttValidString($cl_client_comment, true)) $err->add($i18n->get('error.field'), $i18n->get('label.comment')); - - // Ensure user email exists (required for workflow). - if (!$user->getEmail()) $err->add($i18n->get('error.no_email')); - - if ($err->no()) { - $workHelper = new ttWorkHelper($err); - $fields = array('offer_id'=>$cl_offer_id, - 'client_comment'=>$cl_client_comment); - - if ($request->getParameter('btn_accept')) { - // Accept offer. - if ($workHelper->acceptOwnWorkItemOffer($fields)) { - header('Location: work_offer_view.php?id='.$cl_offer_id); - exit(); - } - } - - if ($request->getParameter('btn_decline')) { - // Decline offer. - if ($workHelper->declineOwnWorkItemOffer($fields)) { - header('Location: work_offer_view.php?id='.$cl_offer_id); - exit(); - } - } - } -} // isPost - -$smarty->assign('work_item', $work_item); -$smarty->assign('forms', array($form->getName()=>$form->toArray())); -$smarty->assign('title', $i18n->get('title.offer')); -$smarty->assign('content_page_name', 'work/work_offer_view.tpl'); -$smarty->display('work/index.tpl'); diff --git a/work/work_offers.php b/work/work_offers.php deleted file mode 100644 index eae9841bb..000000000 --- a/work/work_offers.php +++ /dev/null @@ -1,56 +0,0 @@ -isPluginEnabled('wk')) { - header('Location: ../feature_disabled.php'); - exit(); -} -$work_id = (int)$request->getParameter('id'); -$workHelper = new ttWorkHelper($err); -$work_item = $workHelper->getOwnWorkItem($work_id); -if (!$work_item) { - header('Location: ../access_denied.php'); - exit(); -} -// End of access checks. - -$work_item_offers = $workHelper->getOwnWorkItemOffers($work_id); - -$smarty->assign('work_item', $work_item); -$smarty->assign('work_item_offers', $work_item_offers); -$smarty->assign('title', $i18n->get('work.label.offers')); -$smarty->assign('content_page_name', 'work/work_offers.tpl'); -$smarty->display('work/index.tpl'); diff --git a/work/work_view.php b/work/work_view.php deleted file mode 100644 index cb73f2eed..000000000 --- a/work/work_view.php +++ /dev/null @@ -1,74 +0,0 @@ -isPluginEnabled('wk')) { - header('Location: ../feature_disabled.php'); - exit(); -} -if (!ttAccessAllowed('bid_on_work')) { - header('Location: ../access_denied.php'); - exit(); -} -$cl_work_id = (int)$request->getParameter('id'); -$workHelper = new ttWorkHelper($err); -$work_item = $workHelper->getAvailableWorkItem($cl_work_id); -if (!$work_item) { - header('Location: ../access_denied.php'); - exit(); -} -// End of access checks. - -$cl_client = $work_item['group_name']; -$cl_name = $work_item['subject']; -$cl_description = $work_item['descr_short']; -$cl_details = $work_item['descr_long']; -$cl_budget = $work_item['amount_with_currency']; - -$form = new Form('workForm'); -$form->addInput(array('type'=>'hidden','name'=>'id','value'=>$cl_work_id)); -$form->addInput(array('type'=>'text','name'=>'client','value'=>$cl_client)); -$form->getElement('client')->setEnabled(false); -$form->addInput(array('type'=>'textarea','name'=>'work_name','style'=>'width: 400px;','value'=>$cl_name)); -$form->getElement('work_name')->setEnabled(false); -$form->addInput(array('type'=>'textarea','name'=>'description','style'=>'width: 400px; height: 80px;','value'=>$cl_description)); -$form->getElement('description')->setEnabled(false); -$form->addInput(array('type'=>'textarea','name'=>'details','style'=>'width: 400px; height: 200px;','value'=>$cl_details)); -$form->getElement('details')->setEnabled(false); -$form->addInput(array('type'=>'text','name'=>'budget','value'=>$cl_budget)); -$form->getElement('budget')->setEnabled(false); - -$smarty->assign('work_id', $cl_work_id); -$smarty->assign('forms', array($form->getName()=>$form->toArray())); -$smarty->assign('title', $i18n->get('title.work')); -$smarty->assign('content_page_name', 'work/work_view.tpl'); -$smarty->display('work/index.tpl'); diff --git a/work/work_view_own.php b/work/work_view_own.php deleted file mode 100644 index c4b887e7e..000000000 --- a/work/work_view_own.php +++ /dev/null @@ -1,79 +0,0 @@ -isPluginEnabled('wk')) { - header('Location: ../feature_disabled.php'); - exit(); -} -if (!ttAccessAllowed('manage_work')) { - header('Location: ../access_denied.php'); - exit(); -} -$work_id = (int)$request->getParameter('id'); -$workHelper = new ttWorkHelper($err); -$work_item = $workHelper->getOwnWorkItem($work_id); -if (!$work_item) { - header('Location: ../access_denied.php'); - exit(); -} -// Do we have offer_id? -$offer_id = $work_item['offer_id']; -if ($offer_id) { - $offer = $workHelper->getAvailableOffer($offer_id); - if (!$offer) { - header('Location: ../access_denied.php'); - exit(); - } -} -// End of access checks. - -$cl_name = $work_item['subject']; -$cl_description = $work_item['descr_short']; -$cl_details = $work_item['descr_long']; -$cl_budget = $work_item['amount_with_currency']; - -$form = new Form('workForm'); -$form->addInput(array('type'=>'textarea','name'=>'work_name','style'=>'width: 400px;','value'=>$cl_name)); -$form->getElement('work_name')->setEnabled(false); -$form->addInput(array('type'=>'textarea','name'=>'description','style'=>'width: 400px; height: 80px;','value'=>$cl_description)); -$form->getElement('description')->setEnabled(false); -$form->addInput(array('type'=>'textarea','name'=>'details','style'=>'width: 400px; height: 200px;','value'=>$cl_details)); -$form->getElement('details')->setEnabled(false); -$form->addInput(array('type'=>'text','name'=>'budget','value'=>$cl_budget)); -$form->getElement('budget')->setEnabled(false); - -$smarty->assign('offer', $offer); -$smarty->assign('forms', array($form->getName()=>$form->toArray())); -$smarty->assign('title', $i18n->get('title.work')); -$smarty->assign('content_page_name', 'work/work_view_own.tpl'); -$smarty->display('work/index.tpl'); diff --git a/work_units.php b/work_units.php index ab2ac81af..b8a4a747e 100644 --- a/work_units.php +++ b/work_units.php @@ -53,5 +53,5 @@ $smarty->assign('forms', array($form->getName()=>$form->toArray())); $smarty->assign('title', $i18n->get('title.work_units')); -$smarty->assign('content_page_name', 'work_units2.tpl'); -$smarty->display('index2.tpl'); +$smarty->assign('content_page_name', 'work_units.tpl'); +$smarty->display('index.tpl');