/** * Require a user to be logged in. Redirects to /login if a session is not found. * @param int $rank * @return int|bool */ protected function _requireLogin($rank = \Model\User::RANK_CLIENT) { $f3 = \Base::instance(); if ($id = $f3->get("user.id")) { if ($f3->get("user.rank") >= $rank) { return $id; } else { $f3->error(403); $f3->unload(); return false; } } else { if ($f3->get("site.demo") && is_numeric($f3->get("site.demo"))) { $user = new \Model\User(); $user->load($f3->get("site.demo")); if ($user->id) { $session = new \Model\Session($user->id); $session->setCurrent(); $f3->reroute("/"); return; } else { $f3->set("error", "Auto-login failed, demo user was not found."); } } if (empty($_GET)) { $f3->reroute("/login?to=" . urlencode($f3->get("PATH"))); } else { $f3->reroute("/login?to=" . urlencode($f3->get("PATH")) . urlencode("?" . http_build_query($_GET))); } $f3->unload(); return false; } }
/** * Login Procedure * @param $f3 * @param $params */ public function login($f3, $params) { if ($f3->exists('POST.username') && $f3->exists('POST.password')) { sleep(3); // login should take a while to kick-ass brute force attacks $user = new \Model\User(); $user->load(array('username = ?', $f3->get('POST.username'))); if (!$user->dry()) { // check hash engine $hash_engine = $f3->get('password_hash_engine'); $valid = false; if ($hash_engine == 'bcrypt') { $valid = \Bcrypt::instance()->verify($f3->get('POST.password'), $user->password); } elseif ($hash_engine == 'md5') { $valid = md5($f3->get('POST.password') . $f3->get('password_md5_salt')) == $user->password; } if ($valid) { @$f3->clear('SESSION'); //recreate session id $f3->set('SESSION.user_id', $user->_id); if ($f3->get('CONFIG.ssl_backend')) { $f3->reroute('https://' . $f3->get('HOST') . $f3->get('BASE') . '/'); } else { $f3->reroute('/cnc'); } } } \Flash::instance()->addMessage('Wrong Username/Password', 'danger'); } $this->response->setTemplate('templates/login.html'); }
public function install($db_type) { $f3 = \Base::instance(); $db_type = strtoupper($db_type); if ($db = storage::instance()->get($db_type)) { $f3->set('DB', $db); } else { $f3->error(256, 'no valid DB specified'); } // setup the models \Model\Post::setup(); \Model\Tag::setup(); \Model\Comment::setup(); \Model\User::setup(); // create demo admin user $user = new \Model\User(); $user->load(array('username = ?', 'admin')); if ($user->dry()) { $user->username = '******'; $user->name = 'Administrator'; $user->password = '******'; $user->save(); \Flash::instance()->addMessage('Admin User created,' . ' username: admin, password: fabulog', 'success'); } \Flash::instance()->addMessage('Setup complete', 'success'); }
/** * Require an API key. Sends an HTTP 401 if one is not supplied. * @return int|bool */ protected function _requireAuth() { $f3 = \Base::instance(); $user = new \Model\User(); // Use the logged in user if there is one if ($f3->get("user.api_key")) { $key = $f3->get("user.api_key"); } else { $key = false; } // Check all supported key methods if (!empty($_GET["key"])) { $key = $_GET["key"]; } elseif ($f3->get("HEADERS.X-Redmine-API-Key")) { $key = $f3->get("HEADERS.X-Redmine-API-Key"); } elseif ($f3->get("HEADERS.X-API-Key")) { $key = $f3->get("HEADERS.X-API-Key"); } elseif ($f3->get("HEADERS.X-Api-Key")) { $key = $f3->get("HEADERS.X-Api-Key"); } $user->load(array("api_key = ?", $key)); if ($key && $user->id && $user->api_key) { $f3->set("user", $user->cast()); $f3->set("user_obj", $user); return $user->id; } else { $f3->error(401); return false; } }
public function single_email($f3, $params) { $user = new \Model\User(); $user->load(array("email = ? AND deleted_date IS NULL", $params["email"])); if ($user->id) { $this->_printJson($this->user_array($user)); } else { $f3->error(404); } }
/** * Convert a user ID to a user name * @param int $id * @return string */ public function convertUserId($id) { if (isset($this->cache['user.' . $id])) { $user = $this->cache['user.' . $id]; } else { $user = new \Model\User(); $user->load($id); $this->cache['user.' . $id] = $user; } return $user->name; }
/** * Installs tables with default user * @param $db_type */ public function install($db_type) { $f3 = \Base::instance(); $db_type = strtoupper($db_type); if ($db = DBHandler::instance()->get($db_type)) { $f3->set('DB', $db); } else { $f3->error(256, 'no valid Database Type specified'); } // setup the models \Model\User::setup(); \Model\Payload::setup(); \Model\Webot::setup(); // create demo admin user $user = new \Model\User(); $user->load(array('username = ?', 'mth3l3m3nt')); if ($user->dry()) { $user->username = '******'; $user->name = 'Framework Administrator'; $user->password = '******'; $user->email = '*****@*****.**'; $user->save(); //migrate payloads successfully $payload_file = $f3->ROOT . $f3->BASE . '/db_dump_optional/mth3l3m3nt_payload'; if (file_exists($payload_file)) { $payload = new \Model\Payload(); $payload_file_data = $f3->read($payload_file); $payloadarray = json_decode($payload_file_data, true); foreach ($payloadarray as $payloaddata) { $payload->pName = $payloaddata['pName']; $payload->pType = $payloaddata['pType']; $payload->pCategory = $payloaddata['pCategory']; $payload->pDescription = $payloaddata['pDescription']; $payload->payload = $payloaddata['payload']; $payload->save(); //ensures values set to null before continuing update $payload->reset(); } //migtate payloads \Flash::instance()->addMessage('Payload StarterPack: ,' . 'All Starter Pack Payloads added New database', 'success'); } else { \Flash::instance()->addMessage('Payload StarterPack: ,' . 'StarterPack Database not Found no payloads installed ', 'danger'); } \Flash::instance()->addMessage('Admin User created,' . ' username: mth3l3m3nt, password: mth3l3m3nt', 'success'); } \Flash::instance()->addMessage('New Database Setup Completed', 'success'); }
/** * POST /login * @param \Base $fw * @return void */ public function login(\Base $fw) { if ($this->_getUser()) { $fw->reroute('/dashboard'); } $username = $fw->get('POST.username'); $password = $fw->get('POST.password'); $user = new \Model\User(); $user->load(array('username = ?', $username)); if ($user->id) { if (password_verify($password, $user->password)) { $fw->set('SESSION.user_id', $user->id); $fw->reroute('/dashboard'); } } $fw->set('error', 'Invalid username or password.'); $this->_render('index.html'); }
/** * Get a list of users from a filter * @param string $params URL Parameters * @return array */ protected function _filterUsers($params) { if ($params["filter"] == "groups") { $group_model = new \Model\User\Group(); $groups_result = $group_model->find(array("user_id = ?", $this->_userId)); $filter_users = array($this->_userId); foreach ($groups_result as $g) { $filter_users[] = $g["group_id"]; } $groups = implode(",", $filter_users); $users_result = $group_model->find("group_id IN ({$groups})"); foreach ($users_result as $u) { $filter_users[] = $u["user_id"]; } } elseif ($params["filter"] == "me") { $filter_users = array($this->_userId); } elseif (is_numeric($params["filter"])) { $user = new \Model\User(); $user->load($params["filter"]); if ($user->role == 'group') { $group_model = new \Model\User\Group(); $users_result = $group_model->find(array("group_id = ?", $user->id)); $filter_users = array(intval($params["filter"])); foreach ($users_result as $u) { $filter_users[] = $u["user_id"]; } } else { $filter_users = array($params["filter"]); } } elseif ($params["filter"] == "all") { return array(); } else { return array($this->_userId); } return $filter_users; }
public function group_setmanager($f3, $params) { $db = $f3->get("db.instance"); $group = new \Model\User(); $group->load(array("id = ? AND deleted_date IS NULL AND role = 'group'", $params["id"])); if (!$group->id) { $f3->error(404); return; } // Remove Manager status from all members and set manager status on specified user $db->exec("UPDATE user_group SET manager = 0 WHERE group_id = ?", $group->id); $db->exec("UPDATE user_group SET manager = 1 WHERE id = ?", $params["user_group_id"]); $f3->reroute("/admin/groups/" . $group->id); }
public function atom($f3) { // Authenticate user if ($f3->get("GET.key")) { $user = new \Model\User(); $user->load(array("api_key = ?", $f3->get("GET.key"))); if (!$user->id) { $f3->error(403); return; } } else { $f3->error(403); return; } // Get requested array substituting defaults $get = $f3->get("GET") + array("type" => "assigned", "user" => $user->username); unset($user); // Load target user $user = new \Model\User(); $user->load(array("username = ?", $get["user"])); if (!$user->id) { $f3->error(404); return; } // Load issues $issue = new \Model\Issue\Detail(); $options = array("order" => "created_date DESC"); if ($get["type"] == "assigned") { $issues = $issue->find(array("author_id = ? AND status_closed = 0 AND deleted_date IS NULL", $user->id), $options); } elseif ($get["type"] == "created") { $issues = $issue->find(array("owner = ? AND status_closed = 0 AND deleted_date IS NULL", $user->id), $options); } elseif ($get["type"] == "all") { $issues = $issue->find("status_closed = 0 AND deleted_date IS NULL", $options + array("limit" => 50)); } else { $f3->error(400, "Invalid feed type"); return; } // Render feed $f3->set("get", $get); $f3->set("feed_user", $user); $f3->set("issues", $issues); $this->_render("index/atom.xml", "application/atom+xml"); }
} } else { $issue = \Model\Issue::create(array('name' => $header->subject, 'description' => $text, 'author_id' => $from_user->id, 'owner_id' => $owner, 'status' => 1, 'type_id' => 1)); $log->write(sprintf("Created issue #%s - %s", $issue->id, $issue->name)); } // Add other recipients as watchers if (!empty($header->cc) || count($header->to) > 1) { if (!empty($header->cc)) { $watchers = array_merge($header->to, $header->cc); } else { $watchers = $header->to; } foreach ($watchers as $more_people) { $watcher_email = $more_people->mailbox . '@' . $more_people->host; $watcher = new \Model\User(); $watcher->load(array('email=? AND deleted_date IS NULL', $watcher_email)); if (!empty($watcher->id)) { $watching = new \Model\Issue\Watcher(); // Loads just in case the user is already a watcher $watching->load(array("issue_id = ? AND user_id = ?", $issue->id, $watcher->id)); $watching->issue_id = $issue->id; $watching->user_id = $watcher->id; $watching->save(); } } } foreach ($attachments as $item) { // Skip big files if ($item['size'] > $f3->get("files.maxsize")) { continue; }
$issue->save(); $log->write('Saved issue ' . $issue->id); } } if (!empty($issue->id)) { // add other recipients as watchers if (!empty($header->cc) || count($header->to) > 1) { if (!empty($header->cc)) { $watchers = array_merge($header->to, $header->cc); } else { $watchers = $header->to; } foreach ($watchers as $more_people) { $watcher_email = $more_people->mailbox . "@" . $more_people->host; $watcher = new \Model\User(); $watcher->load(array('email=? AND (deleted_date IS NULL OR deleted_date != ?)', $watcher_email, '0000-00-00 00:00:00')); if (!empty($watcher->id)) { $watching = new \Model\Issue\Watcher(); // Loads just in case the user is already a watcher $watching->load(array("issue_id = ? AND user_id = ?", $issue->id, $watcher->id)); $watching->issue_id = $issue->id; $watching->user_id = $watcher->id; $watching->save(); } } } // Copy Attachments as Files /* Mod from http://www.codediesel.com/php/downloading-gmail-attachments-using-php/ */ /* get mail structure */ $structure = imap_fetchstructure($inbox, $email_number); $attachments = array();
/** * @param \Base $f3 * @param array $params * @throws \Exception */ public function avatar($f3, $params) { // Ensure proper content-type for JPEG images if ($params["format"] == "jpg") { $params["format"] = "jpeg"; } $user = new \Model\User(); $user->load($params["id"]); if ($user->avatar_filename && is_file("uploads/avatars/" . $user->avatar_filename)) { // Use local file $img = new \Image($user->avatar_filename, null, "uploads/avatars/"); $img->resize($params["size"], $params["size"]); // Render and output image header("Content-type: image/" . $params["format"]); $img->render($params["format"]); } else { // Send user to Gravatar $f3->reroute($f3->get("SCHEME") . ":" . \Helper\View::instance()->gravatar($user->email, $params["size"]), true); } }
/** * POST /auth.json */ public function auth($f3) { switch ($f3->get('POST.action')) { case 'salt': $user = new \Model\User(); $user->load(['username = ?', $f3->get('POST.username')]); if ($user->id) { $this->_json(['salt' => $user->password_salt]); } else { $this->_json(['salt' => null, 'error' => 'User does not exist.']); } break; case 'auth': // Verify login $user = new \Model\User(); $user->load(['username = ?', $f3->get('POST.username')]); if ($user->id && password_verify($f3->get('POST.password_hash'), $user->password_hash)) { // Re-hash passphrase if it doesn't meet the current security settings if (password_needs_rehash($user->password_hash, PASSWORD_DEFAULT, ['cost' => \App::config()['security']['bcrypt_cost']])) { $user->password_hash = password_hash($f3->get('POST.password_hash'), PASSWORD_DEFAULT, ['cost' => \App::config()['security']['bcrypt_cost']]); $user->save(); } // Generate and return session token $token = \Helper\Security::generateToken($user->id); $this->_json(['user_id' => $user->id, 'token' => $token]); } else { $this->_json(['error' => 'Invalid username or password.']); } break; } }
/** * GET /issues/@id * View an issue * * @param \Base $f3 * @param array $params * @throws \Exception */ public function single($f3, $params) { $issue = new \Model\Issue\Detail(); $issue->load(array("id=?", $params["id"])); $user = $f3->get("user_obj"); if (!$issue->id || $issue->deleted_date && !($user->role == 'admin' || $user->rank >= \Model\User::RANK_MANAGER || $issue->author_id == $user->id)) { $f3->error(404); return; } $type = new \Model\Issue\Type(); $type->load($issue->type_id); $f3->set("title", $type->name . " #" . $issue->id . ": " . $issue->name); $f3->set("menuitem", "browse"); $author = new \Model\User(); $author->load($issue->author_id); $owner = new \Model\User(); if ($issue->owner_id) { $owner->load($issue->owner_id); } $files = new \Model\Issue\File\Detail(); $f3->set("files", $files->find(array("issue_id = ? AND deleted_date IS NULL", $issue->id))); if ($issue->sprint_id) { $sprint = new \Model\Sprint(); $sprint->load($issue->sprint_id); $f3->set("sprint", $sprint); } $watching = new \Model\Issue\Watcher(); $watching->load(array("issue_id = ? AND user_id = ?", $issue->id, $this->_userId)); $f3->set("watching", !!$watching->id); $f3->set("issue", $issue); $f3->set("ancestors", $issue->getAncestors()); $f3->set("type", $type); $f3->set("author", $author); $f3->set("owner", $owner); $comments = new \Model\Issue\Comment\Detail(); $f3->set("comments", $comments->find(array("issue_id = ?", $issue->id), array("order" => "created_date DESC"))); // Extra data needed for inline edit form $status = new \Model\Issue\Status(); $f3->set("statuses", $status->find(null, null, $f3->get("cache_expire.db"))); $priority = new \Model\Issue\Priority(); $f3->set("priorities", $priority->find(null, array("order" => "value DESC"), $f3->get("cache_expire.db"))); $sprint = new \Model\Sprint(); $f3->set("sprints", $sprint->find(array("end_date >= ? OR id = ?", $this->now(false), $issue->sprint_id), array("order" => "start_date ASC"))); $users = new \Model\User(); $f3->set("users", $users->find("deleted_date IS NULL AND role != 'group'", array("order" => "name ASC"))); $f3->set("groups", $users->find("deleted_date IS NULL AND role = 'group'", array("order" => "name ASC"))); $this->_render("issues/single.html"); }
<?php // Init Composer autoloader require_once 'vendor/autoload.php'; // Init app $fw = Base::instance(); $fw->mset(array('AUTOLOAD' => 'app/', 'CACHE' => true, 'ESCAPE' => false, 'PREFIX' => 'dict.', 'PACKAGE' => 'Reader', 'UI' => 'app/view/')); // Init config if (is_file('config.php')) { $fw->mset(require 'config.php'); } else { throw new Exception('No config.php file found.'); } // Init db $db = new DB\SQL('mysql:host=' . $fw->get('db.host') . ';port=3306;dbname=' . $fw->get('db.database'), $fw->get('db.user'), $fw->get('db.password')); $fw->set('db.instance', $db); // Initialize user $userId = $fw->get('SESSION.user_id'); if ($userId) { $user = new \Model\User(); $user->load($userId); if ($user->id) { $fw->set('user', $user->cast()); $fw->set('user_obj', $user); } }
/** * GET /user/@username/tree * * @param \Base $f3 * @param array $params * @throws \Exception */ public function single_tree($f3, $params) { $this->_requireLogin(); $user = new \Model\User(); $user->load(array("username = ? AND deleted_date IS NULL", $params["username"])); if ($user->id) { $f3->set("title", $user->name); $f3->set("this_user", $user); $tree = \Helper\Dashboard::instance()->issue_tree(); $f3->set("issues", $tree); $this->_render($f3->get("AJAX") ? "user/single/tree/ajax.html" : "user/single/tree.html"); } else { $f3->error(404); } }
$f3->mset(array("site.url" => $f3->get("SCHEME") . "://" . $f3->get("HOST") . $f3->get("BASE") . "/", "revision" => "")); $f3->config($homedir . "app/routes.ini"); $f3->config($homedir . "app/dict/en.ini"); $test = new Test(); // No output for routes $f3->set("QUIET", true); $f3->set("HALT", false); $f3->mock("GET /login"); $test->expect(!$f3->get("ERROR"), "GET /login"); $f3->mock("POST /login", array("username" => "admin", "password" => "admin")); $test->expect(!$f3->get("ERROR"), "POST /login"); $f3->mock("GET /ping"); $test->expect(!$f3->get("ERROR"), "GET /ping (no session)"); // Build a fake session $user = new Model\User(); $user->load(1); $types = new \Model\Issue\Type(); $f3->mset(array("user" => $user->cast(), "user_obj" => $user, "plugins" => array(), "issue_types" => $types->find())); $test->expect($user->id == 1, "Force user authentication"); $f3->mock("GET /ping"); $test->expect(!$f3->get("ERROR"), "GET /ping (active session)"); $f3->mock("GET /"); $test->expect(!$f3->get("ERROR"), "GET /"); $f3->mock("GET /issues/1"); $test->expect($f3->get("PARAMS.id") == 1 && !$f3->get("ERROR"), "GET /issues/1"); $f3->mock("GET /issues/1/history"); $test->expect($f3->get("PARAMS.id") == 1 && !$f3->get("ERROR"), "GET /issues/1/history"); $f3->mock("GET /issues/1/watchers"); $test->expect($f3->get("PARAMS.id") == 1 && !$f3->get("ERROR"), "GET /issues/1/watchers"); $f3->mock("GET /issues/1/related"); $test->expect($f3->get("PARAMS.id") == 1 && !$f3->get("ERROR"), "GET /issues/1/related");
/** * @param \Base $f3 * @param array $params * @throws \Exception */ public function single($f3, $params) { $issue = new \Model\Issue\Detail(); $issue->load(array("id=?", $f3->get("PARAMS.id"))); $user = $f3->get("user_obj"); if (!$issue->id || $issue->deleted_date && !($user->role == 'admin' || $user->rank >= \Model\User::RANK_MANAGER || $issue->author_id == $user->id)) { $f3->error(404); return; } $type = new \Model\Issue\Type(); $type->load($issue->type_id); // Run actions if passed $post = $f3->get("POST"); if (!empty($post)) { switch ($post["action"]) { case "add_watcher": $watching = new \Model\Issue\Watcher(); // Loads just in case the user is already a watcher $watching->load(array("issue_id = ? AND user_id = ?", $issue->id, $post["user_id"])); if (!$watching->id) { $watching->issue_id = $issue->id; $watching->user_id = $post["user_id"]; $watching->save(); } if ($f3->get("AJAX")) { return; } break; case "remove_watcher": $watching = new \Model\Issue\Watcher(); $watching->load(array("issue_id = ? AND user_id = ?", $issue->id, $post["user_id"])); $watching->delete(); if ($f3->get("AJAX")) { return; } break; case "add_dependency": $dependencies = new \Model\Issue\Dependency(); // Loads just in case the task is already a dependency $dependencies->load(array("issue_id = ? AND dependency_id = ?", $issue->id, $post["id"])); $dependencies->issue_id = $issue->id; $dependencies->dependency_id = $post["id"]; $dependencies->dependency_type = $post["type_id"]; $dependencies->save(); if ($f3->get("AJAX")) { return; } break; case "add_dependent": $dependencies = new \Model\Issue\Dependency(); // Loads just in case the task is already a dependency $dependencies->load(array("issue_id = ? AND dependency_id = ?", $post["id"], $issue->id)); $dependencies->dependency_id = $issue->id; $dependencies->issue_id = $post["id"]; $dependencies->dependency_type = $post["type_id"]; $dependencies->save(); if ($f3->get("AJAX")) { return; } break; case "remove_dependency": $dependencies = new \Model\Issue\Dependency(); $dependencies->load($post["id"]); $dependencies->delete(); if ($f3->get("AJAX")) { return; } break; } } $f3->set("title", $type->name . " #" . $issue->id . ": " . $issue->name); $f3->set("menuitem", "browse"); $author = new \Model\User(); $author->load($issue->author_id); $owner = new \Model\User(); if ($issue->owner_id) { $owner->load($issue->owner_id); } $files = new \Model\Issue\File\Detail(); $f3->set("files", $files->find(array("issue_id = ? AND deleted_date IS NULL", $issue->id))); if ($issue->sprint_id) { $sprint = new \Model\Sprint(); $sprint->load($issue->sprint_id); $f3->set("sprint", $sprint); } $watching = new \Model\Issue\Watcher(); $watching->load(array("issue_id = ? AND user_id = ?", $issue->id, $this->_userId)); $f3->set("watching", !!$watching->id); $f3->set("issue", $issue); $f3->set("ancestors", $issue->getAncestors()); $f3->set("type", $type); $f3->set("author", $author); $f3->set("owner", $owner); $comments = new \Model\Issue\Comment\Detail(); $f3->set("comments", $comments->find(array("issue_id = ?", $issue->id), array("order" => "created_date DESC"))); // Extra data needed for inline edit form $status = new \Model\Issue\Status(); $f3->set("statuses", $status->find(null, null, $f3->get("cache_expire.db"))); $priority = new \Model\Issue\Priority(); $f3->set("priorities", $priority->find(null, array("order" => "value DESC"), $f3->get("cache_expire.db"))); $sprint = new \Model\Sprint(); $f3->set("sprints", $sprint->find(array("end_date >= ? OR id = ?", $this->now(false), $issue->sprint_id), array("order" => "start_date ASC"))); $users = new \Model\User(); $f3->set("users", $users->find("deleted_date IS NULL AND role != 'group'", array("order" => "name ASC"))); $f3->set("groups", $users->find("deleted_date IS NULL AND role = 'group'", array("order" => "name ASC"))); $this->_render("issues/single.html"); }
/** * Send a user a password reset email * @param int $user_id */ public function user_reset($user_id) { $f3 = \Base::instance(); if ($f3->get("mail.from")) { $user = new \Model\User(); $user->load($user_id); if (!$user->id) { throw new Exception("User does not exist."); } // Render message body $f3->set("user", $user); $text = $this->_render("notification/user_reset.txt"); $body = $this->_render("notification/user_reset.html"); // Send email to user $subject = "Reset your password - " . $f3->get("site.name"); $this->utf8mail($user->email, $subject, $body, $text); } }
public function single_tree($f3, $params) { $this->_requireLogin(); $user = new \Model\User(); $user->load(array("username = ? AND deleted_date IS NULL", $params["username"])); if ($user->id) { $f3->set("title", $user->name); $f3->set("this_user", $user); // Load assigned issues $issue = new \Model\Issue\Detail(); $assigned = $issue->find(array("closed_date IS NULL AND deleted_date IS NULL AND owner_id = ?", $user->id)); // Build issue list $issues = array(); $assigned_ids = array(); $missing_ids = array(); foreach ($assigned as $iss) { $issues[] = $iss->cast(); $assigned_ids[] = $iss->id; } foreach ($issues as $iss) { if ($iss["parent_id"] && !in_array($iss["parent_id"], $assigned_ids)) { $missing_ids[] = $iss["parent_id"]; } } while (!empty($missing_ids)) { $parents = $issue->find("id IN (" . implode(",", $missing_ids) . ")"); foreach ($parents as $iss) { if (($key = array_search($iss->id, $missing_ids)) !== false) { unset($missing_ids[$key]); } $issues[] = $iss->cast(); $assigned_ids[] = $iss->id; if ($iss->parent_id && !in_array($iss->parent_id, $assigned_ids)) { $missing_ids[] = $iss->parent_id; } } } // Convert list to tree $tree = $this->_buildTree($issues); /** * Helper function for recursive tree rendering * @param Issue $issue * @var callable $renderTree This function, required for recursive calls */ $renderTree = function (&$issue, $level = 0) use(&$renderTree) { if (!empty($issue['id'])) { $f3 = \Base::instance(); $hive = array("issue" => $issue, "dict" => $f3->get("dict"), "BASE" => $f3->get("BASE"), "level" => $level, "issue_type" => $f3->get("issue_type")); echo \Helper\View::instance()->render("issues/project/tree-item.html", "text/html", $hive); if (!empty($issue['children'])) { foreach ($issue['children'] as $item) { $renderTree($item, $level + 1); } } } }; $f3->set("renderTree", $renderTree); // Render view $f3->set("issues", $tree); $this->_render($f3->get("AJAX") ? "user/single/tree/ajax.html" : "user/single/tree.html"); } else { $f3->error(404); } }