Пример #1
0
/**
 * Execute an sql query in the database. The correct database connection
 * will be chosen and the query will be logged with the success status.
 *
 * As third parameter an alternative query can be passed, which should be
 * displayed instead of the executed query. This prevents leakage of
 * confidential information like password salts. The logfile will still
 * contain the executed query.
 *
 * @param $query query to execute as string
 * @param bool $errorProcessing true if it's an error when the query fails.
 * @param null $displayQuery
 */
function exec_query($query, $errorProcessing = true, $displayQuery = null)
{
    global $database, $kga, $errors, $executed_queries;
    $conn = $database->getConnectionHandler();
    $executed_queries++;
    $success = $conn->Query($query);
    Kimai_Logger::logfile($query);
    $err = $conn->Error();
    $query = htmlspecialchars($query);
    $displayQuery = htmlspecialchars($displayQuery);
    if ($success) {
        $level = 'green';
    } else {
        if ($errorProcessing) {
            $level = 'red';
            $errors++;
        } else {
            $level = 'orange';
            // something went wrong but it's not an error
        }
    }
    printLine($level, $displayQuery == null ? $query : $displayQuery, $err);
    if (!$success) {
        Kimai_Logger::logfile("An error has occured in query [{$query}]: " . $conn->Error());
    }
}
Пример #2
0
 /**
  * @param string $name
  * @return string
  * @throws \Zend_Mail_Exception
  */
 public function forgotPassword($name)
 {
     $kga = $this->getKga();
     $database = $this->getDatabase();
     $is_customer = $database->is_customer_name($name);
     $mail = new Zend_Mail('utf-8');
     $mail->setFrom($kga['conf']['adminmail'], 'Kimai - Open Source Time Tracking');
     $mail->setSubject($kga['lang']['passwordReset']['mailSubject']);
     $transport = new Zend_Mail_Transport_Sendmail();
     $passwordResetHash = str_shuffle(MD5(microtime()));
     if ($is_customer) {
         $customerId = $database->customer_nameToID($name);
         $customer = $database->customer_get_data($customerId);
         $database->customer_edit($customerId, array('passwordResetHash' => $passwordResetHash));
         $mail->addTo($customer['mail']);
     } else {
         $userId = $database->user_name2id($name);
         $user = $database->user_get_data($userId);
         $database->user_edit($userId, array('passwordResetHash' => $passwordResetHash));
         $mail->addTo($user['mail']);
     }
     Kimai_Logger::logfile('password reset: ' . $name . ($is_customer ? ' as customer' : ' as user'));
     $ssl = !empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] != 'off';
     $url = ($ssl ? 'https://' : 'http://') . $_SERVER['SERVER_NAME'] . dirname($_SERVER['SCRIPT_NAME']) . '/forgotPassword.php?name=' . urlencode($name) . '&key=' . $passwordResetHash;
     $message = $kga['lang']['passwordReset']['mailMessage'];
     $message = str_replace('%{URL}', $url, $message);
     $mail->setBodyText($message);
     try {
         $mail->send($transport);
         return $kga['lang']['passwordReset']['mailConfirmation'];
     } catch (Zend_Mail_Transport_Exception $e) {
         return $e->getMessage();
     }
 }
Пример #3
0
/**
 * Execute an sql query in the database. The correct database connection
 * will be chosen and the query will be logged with the success status.
 *
 * @param $query query to execute as string
 */
function exec_query($query)
{
    global $conn, $kga, $errors, $executed_queries;
    $success = $conn->Query($query);
    $errorInfo = serialize($conn->Error());
    Kimai_Logger::logfile($query);
    if (!$success) {
        Kimai_Logger::logfile($errorInfo);
        $errors = true;
    }
}
Пример #4
0
/**
 * Execute an sql query in the database. The correct database connection
 * will be chosen and the query will be logged with the success status.
 *
 * @param string $query string query to execute
 */
function exec_query($query)
{
    global $database, $errors;
    $conn = $database->getConnectionHandler();
    $success = $conn->Query($query);
    //Kimai_Logger::logfile($query);
    if (!$success) {
        $errorInfo = serialize($conn->Error());
        Kimai_Logger::logfile('[ERROR] in [' . $query . '] => ' . $errorInfo);
        $errors = true;
    }
}
Пример #5
0
 /**
  * Adds the translations for the given language.
  *
  * @param $language
  * @throws Exception
  */
 public function addTranslations($language)
 {
     // no need to load the default or already requested language again!
     $default = Kimai_Config::getDefault(Kimai_Config::DEFAULT_LANGUAGE);
     if (empty($language) || $language == $default || $language == $this->language) {
         return;
     }
     $languageFile = WEBROOT . 'language/' . $language . '.php';
     if (!file_exists($languageFile)) {
         Kimai_Logger::logfile('Requested translation is missing: ' . $language);
         return;
     }
     $this->language = $language;
     $data = array_replace_recursive($this->getArrayCopy(), include $languageFile);
     $this->exchangeArray($data);
 }
Пример #6
0
function expenseAccessAllowed($entry, $action, &$errors)
{
    global $database, $kga;
    if (!isset($kga['user'])) {
        $errors[''] = $kga['lang']['errorMessages']['permissionDenied'];
        return false;
    }
    // check if expense is too far in the past to allow editing (or deleting)
    if ($kga->isEditLimit() && time() - $entry['timestamp'] > $kga->getEditLimit()) {
        $errors[''] = $kga['lang']['editLimitError'];
        return false;
    }
    $groups = $database->getGroupMemberships($entry['userID']);
    if ($entry['userID'] == $kga['user']['userID']) {
        $permissionName = 'ki_expenses-ownEntry-' . $action;
        if ($database->global_role_allows($kga['user']['globalRoleID'], $permissionName)) {
            return true;
        } else {
            Kimai_Logger::logfile("missing global permission {$permissionName} for user " . $kga['user']['name'] . " to access expense");
            $errors[''] = $kga['lang']['errorMessages']['permissionDenied'];
            return false;
        }
    }
    $assignedOwnGroups = array_intersect($groups, $database->getGroupMemberships($kga['user']['userID']));
    if (count($assignedOwnGroups) > 0) {
        $permissionName = 'ki_expenses-otherEntry-ownGroup-' . $action;
        if ($database->checkMembershipPermission($kga['user']['userID'], $assignedOwnGroups, $permissionName)) {
            return true;
        } else {
            Kimai_Logger::logfile("missing membership permission {$permissionName} of own group(s) " . implode(", ", $assignedOwnGroups) . " for user " . $kga['user']['name']);
            $errors[''] = $kga['lang']['errorMessages']['permissionDenied'];
            return false;
        }
    }
    $permissionName = 'ki_expenses-otherEntry-otherGroup-' . $action;
    if ($database->global_role_allows($kga['user']['globalRoleID'], $permissionName)) {
        return true;
    } else {
        Kimai_Logger::logfile("missing global permission {$permissionName} for user " . $kga['user']['name'] . " to access expense");
        $errors[''] = $kga['lang']['errorMessages']['permissionDenied'];
        return false;
    }
}
Пример #7
0
/**
 * create exp entry 
 *
 * @param $userID
 * @param array $data  array with record data
 * @return bool|int
 */
function expense_create($userID, $data)
{
    global $kga, $database;
    $conn = $database->getConnectionHandler();
    $data = $database->clean_data($data);
    $values['projectID'] = MySQL::SQLValue($data['projectID'], MySQL::SQLVALUE_NUMBER);
    $values['designation'] = MySQL::SQLValue($data['designation']);
    $values['comment'] = MySQL::SQLValue($data['comment']);
    $values['commentType'] = MySQL::SQLValue($data['commentType'], MySQL::SQLVALUE_NUMBER);
    $values['timestamp'] = MySQL::SQLValue($data['timestamp'], MySQL::SQLVALUE_NUMBER);
    $values['multiplier'] = MySQL::SQLValue($data['multiplier'], MySQL::SQLVALUE_NUMBER);
    $values['value'] = MySQL::SQLValue($data['value'], MySQL::SQLVALUE_NUMBER);
    $values['userID'] = MySQL::SQLValue($userID, MySQL::SQLVALUE_NUMBER);
    $values['refundable'] = MySQL::SQLValue($data['refundable'], MySQL::SQLVALUE_NUMBER);
    $table = $kga['server_prefix'] . "expenses";
    $result = $conn->InsertRow($table, $values);
    if (!$result) {
        Kimai_Logger::logfile('expense_create: ' . $conn->Error());
        return false;
    }
    return $result;
}
Пример #8
0
    Kimai_Logger::logfile("-- update to r1392");
    $charset = '';
    if ($kga['utf8']) {
        $charset = 'utf8';
    }
    $success = write_config_file($kga['server_database'], $kga['server_hostname'], $kga['server_username'], $kga['server_password'], $charset, $kga['server_prefix'], $kga['language'], $kga['password_salt'], $kga['defaultTimezone']);
    if ($success) {
        $level = 'green';
        $additional = 'charset: ' . $charset;
    } else {
        $level = 'red';
        $additional = 'Unable to write config file.';
    }
    printLine($level, 'Store charset in configuration file <i>autoconf.php</i>.', $additional);
}
if ((int) $revisionDB < 1393) {
    Kimai_Logger::logfile("-- update to r1393");
    exec_query("ALTER TABLE `{$p}users` CHANGE `mail` `mail` VARCHAR(160) NULL");
    exec_query("ALTER TABLE `{$p}timeSheet` CHANGE `fixedRate` `fixedRate` DECIMAL(10,2) NULL");
}
// ================================================================================
// FINALIZATION: update DB version number
// ================================================================================
if ((int) $revisionDB < $kga['revision'] && !$errors) {
    $query = sprintf("UPDATE `{$p}configuration` SET value = '%s' WHERE `option` = 'version';", $kga['version']);
    exec_query($query, 0);
    $query = sprintf("UPDATE `{$p}configuration` SET value = '%d' WHERE `option` = 'revision';", $kga['revision']);
    exec_query($query, 0);
}
Kimai_Logger::logfile("-- update finished --------------------------------");
require_once 'update_footer.php';
Пример #9
0
$view = new Zend_View();
$view->setBasePath(WEBROOT . '/templates');
$authPlugin = Kimai_Registry::getAuthenticator();
$view->assign('kga', $kga);
// current database setup correct?
checkDBversion(".");
// processing login and displaying either login screen or errors
$name = htmlspecialchars(trim($name));
$is_customer = $database->is_customer_name($name);
if ($is_customer) {
    $id = $database->customer_nameToID($name);
    $customer = $database->customer_get_data($id);
    $keyCorrect = $key === $customer['passwordResetHash'];
} else {
    $id = $database->user_name2id($name);
    $user = $database->user_get_data($id);
    $keyCorrect = $key === $user['passwordResetHash'];
}
switch ($_REQUEST['a']) {
    case "request":
        Kimai_Logger::logfile("password reset: " . $name . ($is_customer ? " as customer" : " as user"));
        break;
        // Show password reset page
    // Show password reset page
    default:
        $view->assign('devtimespan', '2006-' . date('y'));
        $view->assign('keyCorrect', $keyCorrect);
        $view->assign('requestData', array('key' => $key, 'name' => $name));
        echo $view->render('login/forgotPassword.php');
        break;
}
Пример #10
0
$view->assign('kga', $kga);
// ==================
// = security check =
// ==================
if (isset($_REQUEST['axAction']) && !is_array($_REQUEST['axAction']) && $_REQUEST['axAction'] != "") {
    $axAction = strip_tags($_REQUEST['axAction']);
} else {
    $axAction = '';
}
$axValue = isset($_REQUEST['axValue']) ? strip_tags($_REQUEST['axValue']) : '';
$id = isset($_REQUEST['id']) ? strip_tags($_REQUEST['id']) : null;
// ============================================
// = initialize currently displayed timeframe =
// ============================================
$timeframe = get_timeframe();
$in = $timeframe[0];
$out = $timeframe[1];
if (isset($_REQUEST['first_day'])) {
    $in = (int) $_REQUEST['first_day'];
}
if (isset($_REQUEST['last_day'])) {
    $out = mktime(23, 59, 59, date("n", $_REQUEST['last_day']), date("j", $_REQUEST['last_day']), date("Y", $_REQUEST['last_day']));
}
if ($axAction != "reloadLogfile") {
    Kimai_Logger::logfile("KSPI axAction (" . (array_key_exists('customer', $kga) ? $kga['customer']['name'] : $kga['user']['name']) . "): " . $axAction);
}
// prevent IE from caching the response
header("Last-Modified: " . gmdate("D, d M Y H:i:s") . " GMT");
header("Cache-Control: no-store, no-cache, must-revalidate");
header("Cache-Control: post-check=0, pre-check=0", false);
header("Pragma: no-cache");
Пример #11
0
 public static function errorHandler($errno, $errstr, $errfile, $errline)
 {
     // If the @ error-control operator is set don't log the error.
     if (error_reporting() === 0) {
         return false;
     }
     $line = '';
     switch ($errno) {
         case E_WARNING:
             $line .= 'E_WARNING';
             break;
         case E_NOTICE:
             $line .= 'E_NOTICE';
             break;
         case E_USER_ERROR:
             $line .= 'E_USER_ERROR';
             break;
         case E_USER_WARNING:
             $line .= 'E_USER_WARNING';
             break;
         case E_USER_NOTICE:
             $line .= 'E_USER_NOTICE';
             break;
         case E_STRICT:
             $line .= 'E_STRICT';
             break;
         case E_RECOVERABLE_ERROR:
             $line .= 'E_RECOVERABLE_ERROR';
             break;
     }
     $line .= ' ' . $errstr;
     $line .= " @{$errfile} line {$errline}";
     Kimai_Logger::logfile($line);
     return false;
 }
Пример #12
0
 * ==================
 *
 * Called via AJAX from the Kimai user interface. Depending on $axAction
 * actions are performed, e.g. editing preferences or returning a list
 * of customers.
 */
// insert KSPI
$isCoreProcessor = 1;
$dir_templates = "templates/core/";
require "../includes/kspi.php";
switch ($axAction) {
    /**
     * Append a new entry to the logfile.
     */
    case 'logfile':
        Kimai_Logger::logfile("JavaScript: " . $axValue);
        break;
        /**
         * Remember which project and activity the user has selected for
         * the quick recording via the buzzer.
         */
    /**
     * Remember which project and activity the user has selected for
     * the quick recording via the buzzer.
     */
    case 'saveBuzzerPreselection':
        if (!isset($kga['user'])) {
            return;
        }
        $data = array();
        if (isset($_REQUEST['project'])) {
Пример #13
0
 /**
  * Check if a parset string matches with the following time-formatting: 20.08.2008-19:00:00.
  *
  * @param string $timestring
  * @return boolean
  */
 public static function check_time_format($timestring)
 {
     if (!preg_match("/([0-9]{1,2})\\.([0-9]{1,2})\\.([0-9]{2,4})-([0-9]{1,2}):([0-9]{1,2}):([0-9]{1,2})/", $timestring)) {
         return false;
         // WRONG format
     }
     $ok = 1;
     $hours = substr($timestring, 11, 2);
     $minutes = substr($timestring, 14, 2);
     $seconds = substr($timestring, 17, 2);
     if ((int) $hours >= 24) {
         $ok = 0;
     }
     if ((int) $minutes >= 60) {
         $ok = 0;
     }
     if ((int) $seconds >= 60) {
         $ok = 0;
     }
     Kimai_Logger::logfile("timecheck: " . $ok);
     $day = substr($timestring, 0, 2);
     $month = substr($timestring, 3, 2);
     $year = substr($timestring, 6, 4);
     if (!checkdate((int) $month, (int) $day, (int) $year)) {
         $ok = 0;
     }
     Kimai_Logger::logfile("time/datecheck: " . $ok);
     if ($ok) {
         return true;
     }
     return false;
 }
Пример #14
0
         $logdatei = fopen(WEBROOT . "temporary/logfile.txt", "w");
         fwrite($logdatei, "");
         fclose($logdatei);
         echo $kga['lang']['log_delete'];
     } else {
         die;
     }
     break;
     /**
      * Write some message to the logfile.
      */
 /**
  * Write some message to the logfile.
  */
 case "shoutbox":
     Kimai_Logger::logfile("[" . Kimai_Registry::getUser()->getName() . "] " . $axValue);
     break;
     /**
      * Return the $kga variable (Kimai Global Array). Strip out some sensitive
      * information if not configured otherwise.
      */
 /**
  * Return the $kga variable (Kimai Global Array). Strip out some sensitive
  * information if not configured otherwise.
  */
 case "reloadKGA":
     $output = $kga;
     $filter = array('server_hostname' => "xxx", 'server_database' => "xxx", 'server_username' => "xxx", 'server_password' => "xxx", 'password_salt' => "xxx", 'user' => array('secure' => "xxx", 'userID' => "xxx", 'pw' => "xxx", 'password' => "xxx", 'apikey' => "xxx"));
     switch ($axValue) {
         case 'plain':
             $output = $kga;
Пример #15
0
        $database->setGroupMemberships($userId, array($authPlugin->getDefaultGroups()));
    }
    $userData = $database->user_get_data($userId);
    $loginKey = random_code(30);
    setcookie('kimai_key', $loginKey);
    setcookie('kimai_user', $userData['name']);
    $database->user_loginSetKey($userId, $loginKey);
    header('Location: core/kimai.php');
}
// =================================================================
// = processing login and displaying either login screen or errors =
// =================================================================
switch ($_REQUEST['a']) {
    case 'checklogin':
        $is_customer = $database->is_customer_name($name);
        Kimai_Logger::logfile('login: '******' as customer' : ' as user'));
        if ($is_customer) {
            // perform login of customer
            $passCrypt = encode_password($password);
            $customerId = $database->customer_nameToID($name);
            $data = $database->customer_get_data($customerId);
            // TODO: add BAN support
            if ($data['password'] == $passCrypt && $name != '' && $passCrypt != '') {
                $loginKey = random_code(30);
                setcookie('kimai_key', $loginKey);
                setcookie('kimai_user', 'customer_' . $name);
                $database->customer_loginSetKey($customerId, $loginKey);
                header('Location: core/kimai.php');
            } else {
                setcookie('kimai_key', '0');
                setcookie('kimai_user', '0');
Пример #16
0
 /**
  * Query the database for the best fitting rate for the given user, project and activity.
  *
  * @author sl
  * @param $userID
  * @param $projectID
  * @param $activityID
  * @return bool
  */
 public function get_best_fitting_rate($userID, $projectID, $activityID)
 {
     // validate input
     if ($userID == null || !is_numeric($userID)) {
         $userID = "NULL";
     }
     if ($projectID == null || !is_numeric($projectID)) {
         $projectID = "NULL";
     }
     if ($activityID == null || !is_numeric($activityID)) {
         $activityID = "NULL";
     }
     $query = "SELECT rate FROM " . $this->kga['server_prefix'] . "rates WHERE\n    (userID = {$userID} OR userID IS NULL)  AND\n    (projectID = {$projectID} OR projectID IS NULL)  AND\n    (activityID = {$activityID} OR activityID IS NULL)\n    ORDER BY userID DESC, activityID DESC, projectID DESC\n    LIMIT 1;";
     $result = $this->conn->Query($query);
     if ($result === false) {
         $this->logLastError('get_best_fitting_rate');
         return false;
     }
     if ($this->conn->RowCount() == -1) {
         // no error, but no best fitting rate, return default value
         Kimai_Logger::logfile("get_best_fitting_rate - using default rate 0.00");
         return 0.0;
     }
     $data = $this->conn->rowArray(0, MYSQLI_ASSOC);
     return $data['rate'];
 }
Пример #17
0
$beginDate = $in;
$endDate = $out;
$invoiceID = $customer['name'] . "-" . date("y", $in) . "-" . date("m", $in);
$today = time();
$dueDate = mktime(0, 0, 0, date("m") + 1, date("d"), date("Y"));
$round = 0;
// do we have to round the time ?
if (isset($_REQUEST['roundValue']) && (double) $_REQUEST['roundValue'] > 0) {
    $round = (double) $_REQUEST['roundValue'];
    $time_index = 0;
    $amount = count($invoiceArray);
    while ($time_index < $amount) {
        if ($invoiceArray[$time_index]['type'] == 'timeSheet') {
            $rounded = ext_invoice_round_value($invoiceArray[$time_index]['hour'], $round / 10);
            // Write a logfile entry for each value that is rounded.
            Kimai_Logger::logfile("Round " . $invoiceArray[$time_index]['hour'] . " to " . $rounded . " with " . $round);
            if ($invoiceArray[$time_index]['hour'] == 0) {
                // make sure we do not raise a "divison by zero" - there might be entries with the zero seconds
                $rate = 0;
            } else {
                $rate = ext_invoice_round_value($invoiceArray[$time_index]['amount'] / $invoiceArray[$time_index]['hour'], 0.05);
            }
            $invoiceArray[$time_index]['hour'] = $rounded;
            $invoiceArray[$time_index]['amount'] = $invoiceArray[$time_index]['hour'] * $rate;
        }
        $time_index++;
    }
}
// calculate invoice sums
$ttltime = 0;
$rawTotalTime = 0;
Пример #18
0
/**
 * @brief Check the permission to access an object.
 *
 * This method is meant to check permissions for adding, editing and deleting customers,
 * projects, activities and users. The input is not checked whether it falls within those boundaries since
 * it can also work with others, if the permissions match the pattern.
 *
 * @param string $objectTypeName name of the object type being edited (e.g. Project)
 * @param string $action the action being performed (e.g. add)
 * @param array $oldGroups the old groups of the object (empty array for new objects)
 * @param array $newGroups the new groups of the object (same as oldGroups if nothing should be changed in group assignment)
 * @return boolean if the permission is granted, false otherwise
 */
function checkGroupedObjectPermission($objectTypeName, $action, $oldGroups, $newGroups)
{
    global $database, $kga;
    if (!isset($kga['user'])) {
        return false;
    }
    $assignedOwnGroups = array_intersect($oldGroups, $database->getGroupMemberships($kga['user']['userID']));
    $assignedOtherGroups = array_diff($oldGroups, $database->getGroupMemberships($kga['user']['userID']));
    if (count($assignedOtherGroups) > 0) {
        $permissionName = "core-{$objectTypeName}-otherGroup-{$action}";
        if (!$database->global_role_allows($kga['user']['globalRoleID'], $permissionName)) {
            Kimai_Logger::logfile("missing global permission {$permissionName} for user " . $kga['user']['name'] . " to access {$objectTypeName}");
            return false;
        }
    }
    if (count($assignedOwnGroups) > 0) {
        $permissionName = "core-{$objectTypeName}-{$action}";
        if (!$database->checkMembershipPermission($kga['user']['userID'], $assignedOwnGroups, $permissionName)) {
            Kimai_Logger::logfile("missing membership permission {$permissionName} of current own group(s) " . implode(", ", $assignedOwnGroups) . " for user " . $kga['user']['name'] . " to access {$objectTypeName}");
            return false;
        }
    }
    if (count($oldGroups) != array_intersect($oldGroups, $newGroups)) {
        // group assignment has changed
        $addToGroups = array_diff($newGroups, $oldGroups);
        $removeFromGroups = array_diff($oldGroups, $newGroups);
        $addToOtherGroups = array_diff($addToGroups, $database->getGroupMemberships($kga['user']['userID']));
        $addToOwnGroups = array_intersect($addToGroups, $database->getGroupMemberships($kga['user']['userID']));
        $removeFromOtherGroups = array_diff($removeFromGroups, $database->getGroupMemberships($kga['user']['userID']));
        $removeFromOwnGroups = array_intersect($removeFromGroups, $database->getGroupMemberships($kga['user']['userID']));
        $action = 'assign';
        if (count($addToOtherGroups) > 0) {
            $permissionName = "core-{$objectTypeName}-otherGroup-{$action}";
            if (!$database->global_role_allows($kga['user']['globalRoleID'], $permissionName)) {
                Kimai_Logger::logfile("missing global permission {$permissionName} for user " . $kga['user']['name'] . " to access {$objectTypeName}");
                return false;
            }
        }
        if (count($addToOwnGroups) > 0) {
            $permissionName = "core-{$objectTypeName}-{$action}";
            if (!$database->checkMembershipPermission($kga['user']['userID'], $addToOwnGroups, $permissionName)) {
                Kimai_Logger::logfile("missing membership permission {$permissionName} of new own group(s) " . implode(", ", $addToOwnGroups) . " for user " . $kga['user']['name'] . " to access {$objectTypeName}");
                return false;
            }
        }
        $action = 'unassign';
        if (count($removeFromOtherGroups) > 0) {
            $permissionName = "core-{$objectTypeName}-otherGroup-{$action}";
            if (!$database->global_role_allows($kga['user']['globalRoleID'], $permissionName)) {
                Kimai_Logger::logfile("missing global permission {$permissionName} for user " . $kga['user']['name'] . " to access {$objectTypeName}");
                return false;
            }
        }
        if (count($removeFromOwnGroups) > 0) {
            $permissionName = "core-{$objectTypeName}-{$action}";
            if (!$database->checkMembershipPermission($kga['user']['userID'], $removeFromOwnGroups, $permissionName)) {
                Kimai_Logger::logfile("missing membership permission {$permissionName} of old own group(s) " . implode(", ", $removeFromOwnGroups) . " for user " . $kga['user']['name'] . " to access {$objectTypeName}");
                return false;
            }
        }
    }
    return true;
}