/** * Prepare the response. * * @return void * * @since 1.0 */ protected function prepareResponse() { /* @type \JTracker\Application $application */ $application = $this->getContainer()->get('app'); $application->getUser()->authorize('manage'); $number = $application->input->getUint('number'); $title = $application->input->getString('title'); $state = $application->input->getCmd('state'); $description = $application->input->getString('description'); $due_on = $application->input->getString('due_on') ?: null; $project = $application->getProject(); // Look if we have a bot user configured. if ($project->getGh_Editbot_User() && $project->getGh_Editbot_Pass()) { $gitHub = GithubFactory::getInstance($application, true, $project->getGh_Editbot_User(), $project->getGh_Editbot_Pass()); } else { $gitHub = GithubFactory::getInstance($application); } if ($number) { // Update milestone $gitHub->issues->milestones->edit($project->gh_user, $project->gh_project, $number, $title, $state, $description, $due_on); } else { // Create the milestone. $gitHub->issues->milestones->create($project->gh_user, $project->gh_project, $title, $state, $description, $due_on); } // Get the current milestones list. $this->response->data = $this->getList($project); }
/** * Execute the command. * * @return void * * @since 1.0 */ public function execute() { $this->getApplication()->outputTitle(g11n3t('Retrieve Issues')); // This class has actions that depend on a bot account, fetch a GitHub instance as a bot $this->githubBot = GithubFactory::getInstance($this->getApplication(), true); $this->logOut(g11n3t('Start retrieve Issues'))->selectProject()->setupGitHub()->fetchData()->processData()->out()->logOut(g11n3t('Finished')); }
/** * Registers the service provider with a DI container. * * @param Container $container The DI container. * * @return Container Returns the container to support chaining. * * @since 1.0 * @throws \RuntimeException */ public function register(Container $container) { $container->share('JTracker\\Github\\Github', function () use($container) { // Call the Github factory's getInstance method and inject the application; it handles the rest of the configuration return GithubFactory::getInstance($container->get('app')); }, true); // Alias the object $container->alias('gitHub', 'JTracker\\Github\\Github'); }
/** * Set the GitHub object with the credentials from the project or, * if not found, with those from the configuration file. * * @param TrackerProject $project The Project object. * * @since 1.0 * @return $this */ protected function setProjectGitHubBot(TrackerProject $project) { // If there is a bot defined for the project, prefer it over the config credentials. if ($project->gh_editbot_user && $project->gh_editbot_pass) { $this->github = GithubFactory::getInstance($this->getContainer()->get('app'), true, $project->gh_editbot_user, $project->gh_editbot_pass); } else { $this->github = GithubFactory::getInstance($this->getContainer()->get('app')); } return $this; }
/** * Prepare the response. * * @return void * * @since 1.0 */ protected function prepareResponse() { /* @type \JTracker\Application $application */ $application = $this->getContainer()->get('app'); $application->getUser()->authorize('manage'); $name = $application->input->getCmd('name'); $project = $application->getProject(); // Look if we have a bot user configured. if ($project->getGh_Editbot_User() && $project->getGh_Editbot_Pass()) { $gitHub = GithubFactory::getInstance($application, true, $project->getGh_Editbot_User(), $project->getGh_Editbot_Pass()); } else { $gitHub = GithubFactory::getInstance($application); } // Delete the label $gitHub->issues->labels->delete($project->gh_user, $project->gh_project, $name); // Get the current labels list. $this->response->data = $gitHub->issues->labels->getList($project->gh_user, $project->gh_project); }
/** * Get a list of milestones for a project. * * @param \App\Projects\TrackerProject $project The project. * * @return array * * @since 1.0 */ protected function getList($project) { $gitHub = GithubFactory::getInstance($this->getContainer()->get('app')); $data = array_merge($gitHub->issues->milestones->getList($project->gh_user, $project->gh_project), $gitHub->issues->milestones->getList($project->gh_user, $project->gh_project, 'closed')); $milestones = []; foreach ($data as $item) { // This is to keep request data short.. $milestone = new \stdClass(); $milestone->number = $item->number; $milestone->title = $item->title; $milestone->state = $item->state; $milestone->description = $item->description; $milestone->due_on = $item->due_on; $milestones[] = $milestone; } // Sort milestones by their number usort($milestones, function ($a, $b) { return $a->number > $b->number; }); return $milestones; }
/** * Update the issue on GitHub. * * The method will first try to perform the action with the logged in user credentials and then, if it fails, perform * the action using a configured "edit bot". If the GitHub status changes (e.g. open <=> close), a comment will be * created automatically stating that the action has been performed by a bot. * * @param integer $issueNumber The issue number. * @param array $data The issue data. * @param string $state The issue state (either 'open' or 'closed). * @param string $oldState The previous issue state. * * @throws \Exception * @throws \JTracker\Github\Exception\GithubException * * * @return $this * * @since 1.0 */ private function updateGitHub($issueNumber, array $data, $state, $oldState) { /* @type \JTracker\Application $application */ $application = $this->getContainer()->get('app'); $project = $application->getProject(); try { // Try to update the project on GitHub using thew current user credentials $gitHubResponse = GithubFactory::getInstance($application)->issues->edit($project->gh_user, $project->gh_project, $issueNumber, $state, $data['title'], $data['description_raw']); } catch (GithubException $exception) { // GitHub will return a "404 - not found" in case there is a permission problem. if (404 != $exception->getCode()) { throw $exception; } // Look if we have a bot user configured. if (!$project->getGh_Editbot_User() || !$project->getGh_Editbot_Pass()) { throw $exception; } // Try to perform the action on behalf of an authorized bot. $gitHubBot = GithubFactory::getInstance($application, true, $project->getGh_Editbot_User(), $project->getGh_Editbot_Pass()); // Update the project on GitHub $gitHubResponse = $gitHubBot->issues->edit($project->gh_user, $project->gh_project, $issueNumber, $state, $data['title'], $data['description_raw']); // Add a comment stating that this action has been performed by a MACHINE !! // (only if the "state" has changed - open <=> closed) if ($state != $oldState) { $uri = $application->get('uri')->base->full; $body = sprintf('Set to "%s" on behalf of @%s by %s at %s', $state, $application->getUser()->username, sprintf('The <a href="%s">%s</a>', 'https://github.com/joomla/jissues', 'JTracker Application'), sprintf('<a href="%s">%s</a>', $uri, trim(str_replace(['http:', 'https:'], '', $uri), '/'))); $gitHubBot->issues->comments->create($project->gh_user, $project->gh_project, $issueNumber, $body); } } if (!isset($gitHubResponse->id)) { throw new \Exception('Invalid response from GitHub'); } return $this; }
/** * Initialize the controller. * * @return $this Method allows chiaining * * @since 1.0 * @throws \RuntimeException */ public function initialize() { $this->debug = $this->getContainer()->get('app')->get('debug.hooks'); // Initialize the logger $this->logger = new Logger('JTracker'); $this->logger->pushHandler(new StreamHandler($this->getContainer()->get('app')->get('debug.log-path') . '/github_' . strtolower($this->type) . '.log')); // Get the event dispatcher $this->dispatcher = $this->getContainer()->get('app')->getDispatcher(); // Get a database object $this->db = $this->getContainer()->get('db'); // Get the payload data $data = $this->getContainer()->get('app')->input->post->get('payload', null, 'raw'); if (!$data) { $this->logger->error('No data received.'); $this->getContainer()->get('app')->close(); } // Decode it $this->hookData = json_decode($data); // Get the project data $this->getProjectData(); // If we have a bot defined for the project, prefer it over the DI object if ($this->project->gh_editbot_user && $this->project->gh_editbot_pass) { $this->github = GithubFactory::getInstance($this->getContainer()->get('app'), true, $this->project->gh_editbot_user, $this->project->gh_editbot_pass); } else { $this->github = $this->getContainer()->get('gitHub'); } // Check the request is coming from GitHub $validIps = $this->github->meta->getMeta(); if (isset($_SERVER['HTTP_X_FORWARDED_FOR'])) { $parts = explode(',', $_SERVER['HTTP_X_FORWARDED_FOR']); $myIP = $parts[0]; } elseif (strpos($_SERVER['SCRIPT_NAME'], 'cli/tracker.php') !== false) { $myIP = '127.0.0.1'; } else { $myIP = $this->getContainer()->get('app')->input->server->getString('REMOTE_ADDR'); } if (!IpHelper::ipInRange($myIP, $validIps->hooks, 'cidr') && '127.0.0.1' != $myIP) { // Log the unauthorized request $this->logger->error('Unauthorized request from ' . $myIP); $this->getContainer()->get('app')->close(); } // Set up the event listener $this->addEventListener(); return $this; }
/** * Update the issue on GitHub. * * The method will first try to perform the action with the logged in user credentials and then, if it fails, perform * the action using a configured "edit bot". If the GitHub status changes (e.g. open <=> close), a comment will be * created automatically stating that the action has been performed by a bot. * * @param integer $issueNumber The issue number. * @param array $data The issue data. * @param string $state The issue state (either 'open' or 'closed). * @param string $oldState The previous issue state. * @param string $assignee The login for the GitHub user that this issue should be assigned to. * @param integer $milestone The milestone to associate this issue with. * @param array $labels The labels to associate with this issue. * * @throws \JTracker\Github\Exception\GithubException * @throws \RuntimeException * * @return object The issue data * * @since 1.0 */ private function updateGitHub($issueNumber, array $data, $state, $oldState, $assignee, $milestone, $labels) { /* @type \JTracker\Application $application */ $application = $this->getContainer()->get('app'); $project = $application->getProject(); try { // Try to perform the action on behalf of current user $gitHub = GithubFactory::getInstance($application); // Look if we have a bot user configured if ($project->getGh_Editbot_User() && $project->getGh_Editbot_Pass()) { // Try to perform the action on behalf of an authorized bot $gitHubBot = GithubFactory::getInstance($application, true, $project->getGh_Editbot_User(), $project->getGh_Editbot_Pass()); } } catch (\RuntimeException $exception) { throw new \RuntimeException('Error retrieving an instance of the Github object'); } try { $gitHubResponse = $gitHub->issues->edit($project->gh_user, $project->gh_project, $issueNumber, $state, $data['title'], $data['description_raw'], $assignee, $milestone, $labels); $needUpdate = false; $isAllowed = $application->getUser()->check('manage'); // The milestone and labels are silently dropped, // so try to update the milestone and/or labels if they are not set. if (!empty($milestone) && empty($gitHubResponse->milestone) || !empty($milestone) && $milestone != $gitHubResponse->milestone) { $needUpdate = true; } else { // Allow only specific group to reset milestone if (!empty($gitHubResponse->milestone) && $isAllowed) { $milestone = ''; $needUpdate = true; } } if (!empty($labels)) { if (empty($gitHubResponse->labels)) { $needUpdate = true; } if (!empty($gitHubResponse->labels)) { foreach ($gitHubResponse->labels as $ghLabel) { // If labels differ then need to update if (!in_array($ghLabel->name, $labels)) { $needUpdate = true; break; } } } } else { // Allow only specific group to reset labels if (!empty($gitHubResponse->labels) && $isAllowed) { $needUpdate = true; } } if ($needUpdate && isset($gitHubBot)) { $gitHubBot->issues->edit($project->gh_user, $project->gh_project, $gitHubResponse->number, 'open', $data['title'], $data['description_raw'], $assignee, $milestone, $labels); } } catch (GithubException $exception) { // GitHub will return a "404 - not found" in case there is a permission problem. if (404 != $exception->getCode()) { throw $exception; } if (!isset($gitHubBot)) { throw $exception; } $gitHubResponse = $gitHubBot->issues->edit($project->gh_user, $project->gh_project, $issueNumber, $state, $data['title'], $data['description_raw'], $assignee, $milestone, $labels); $needUpdate = false; // The milestone and labels are silently dropped, // so try to update the milestone and/or labels if they are not set. if (!empty($milestone) && empty($gitHubResponse->milestone) || !empty($milestone) && $milestone != $gitHubResponse->milestone) { $needUpdate = true; } else { if (!empty($gitHubResponse->milestone)) { $milestone = ''; $needUpdate = true; } } if (!empty($labels)) { if (empty($gitHubResponse->labels)) { $needUpdate = true; } if (!empty($gitHubResponse->labels)) { foreach ($gitHubResponse->labels as $ghLabel) { // If labels differ then need to update if (!in_array($ghLabel->name, $labels)) { $needUpdate = true; break; } } } } else { if (!empty($gitHubResponse->labels)) { $needUpdate = true; } } // Try to update the milestone and/or labels if ($needUpdate) { $gitHubBot->issues->edit($project->gh_user, $project->gh_project, $gitHubResponse->number, 'open', $data['title'], $data['description_raw'], $assignee, $milestone, $labels); } // Add a comment stating that this action has been performed by a MACHINE !! // (only if the "state" has changed - open <=> closed) if ($state != $oldState) { $uri = $application->get('uri')->base->full; $body = sprintf('Set to "%s" on behalf of @%s by %s at %s', $state, $application->getUser()->username, sprintf('The <a href="%s">%s</a>', 'https://github.com/joomla/jissues', 'JTracker Application'), sprintf('<a href="%s">%s</a>', $uri . 'tracker/' . $project->alias . '/' . $issueNumber, str_replace(['http://', 'https://'], '', $uri) . $project->alias . '/' . $issueNumber)); $gitHub->issues->comments->create($project->gh_user, $project->gh_project, $issueNumber, $body); } } if (!isset($gitHubResponse->id)) { throw new \RuntimeException('Invalid response from GitHub'); } return $gitHubResponse; }
/** * Update the issue on GitHub. * * The method will first try to perform the action with the logged in user credentials and then, if it fails, perform * the action using a configured "edit bot". If the GitHub status changes (e.g. open <=> close), a comment will be * created automatically stating that the action has been performed by a bot. * * @param string $title The title of the issue. * @param string $body The contents of the issue. * @param string $assignee The login for the GitHub user that this issue should be assigned to. * @param integer $milestone The milestone to associate this issue with. * @param array $labels The labels to associate with this issue. * * @return object The issue data * * @since 1.0 * @throws \RuntimeException */ private function updateGitHub($title, $body, $assignee, $milestone, $labels) { /* @type \JTracker\Application $application */ $application = $this->getContainer()->get('app'); $project = $application->getProject(); try { // Try to perform the action on behalf of current user $gitHub = GithubFactory::getInstance($application); // Look if we have a bot user configured if ($project->getGh_Editbot_User() && $project->getGh_Editbot_Pass()) { // Try to perform the action on behalf of an authorized bot $gitHubBot = GithubFactory::getInstance($application, true, $project->getGh_Editbot_User(), $project->getGh_Editbot_Pass()); } } catch (\RuntimeException $exception) { throw new \RuntimeException('Error retrieving an instance of the Github object'); } try { $gitHubResponse = $gitHub->issues->create($project->gh_user, $project->gh_project, $title, $body, $assignee, $milestone, $labels); } catch (GithubException $exception) { $this->getContainer()->get('app')->getLogger()->error(sprintf('Error code %1$s received from GitHub when creating an issue with the following data:' . ' GitHub User: %2$s; GitHub Repo: %3$s; Title: %4$s; Body Text: %5$s' . ' The error message returned was: %6$s', $exception->getCode(), $project->gh_user, $project->gh_project, $title, $body, $exception->getMessage())); throw new \RuntimeException('Invalid response from GitHub'); } /** * Try to update the milestone and/or labels. * We are not throwing any error because the issue is already created. */ if (isset($gitHubBot)) { if (!empty($milestone) && empty($gitHubResponse->milestone) || !empty($labels) && empty($gitHubResponse->labels)) { $gitHubBot->issues->edit($project->gh_user, $project->gh_project, $gitHubResponse->number, 'open', $title, $body, $assignee, $milestone, $labels); } } return $gitHubResponse; }
/** * Method to process the list of issues and inject into the database as needed * * @return $this * * @since 1.0 * @throws \RuntimeException */ protected function processData() { $ghIssues = $this->issues; $dbIssues = $this->getDbIssues(); if (!$ghIssues) { throw new \UnexpectedValueException('No issues received...'); } $added = 0; $updated = 0; $milestones = $this->getMilestones(); $this->out(g11n3t('Adding issues to the database...'), false); $progressBar = $this->getProgressBar(count($ghIssues)); $this->usePBar ? $this->out() : null; $gitHubBot = null; if ($this->project->gh_editbot_user && $this->project->gh_editbot_pass) { $gitHubBot = new GitHubHelper(GithubFactory::getInstance($this->getApplication(), true, $this->project->gh_editbot_user, $this->project->gh_editbot_pass)); } // Start processing the pulls now foreach ($ghIssues as $count => $ghIssue) { $this->usePBar ? $progressBar->update($count + 1) : $this->out($ghIssue->number . '...', false); if (!$this->checkInRange($ghIssue->number)) { // Not in range $this->usePBar ? null : $this->out('NiR ', false); continue; } $id = 0; foreach ($dbIssues as $dbIssue) { if ($ghIssue->number == $dbIssue->issue_number) { if ($this->force) { // Force update $this->usePBar ? null : $this->out('F ', false); $id = $dbIssue->id; break; } $d1 = new Date($ghIssue->updated_at); $d2 = new Date($dbIssue->modified_date); if ($d1 == $d2) { // No update required $this->usePBar ? null : $this->out('- ', false); continue 2; } $id = $dbIssue->id; break; } } // Store the item in the database $table = new IssuesTable($this->getContainer()->get('db')); if ($id) { $table->load($id); } $table->issue_number = $ghIssue->number; $table->title = $ghIssue->title; if ($table->description_raw != $ghIssue->body) { $table->description = $this->github->markdown->render($ghIssue->body, 'gfm', $this->project->gh_user . '/' . $this->project->gh_project); $this->checkGitHubRateLimit($this->github->markdown->getRateLimitRemaining()); $table->description_raw = $ghIssue->body; } $statusTable = new StatusTable($this->getContainer()->get('db')); // Get the list of status IDs based on the GitHub issue state $state = $ghIssue->state == 'open' ? false : true; $stateIds = $statusTable->getStateStatusIds($state); // Check if the issue status is in the array; if it is, then the item didn't change open state and we don't need to change the status if (!in_array($table->status, $stateIds)) { $table->status = $state ? 10 : 1; } $table->opened_date = (new Date($ghIssue->created_at))->format('Y-m-d H:i:s'); $table->opened_by = $ghIssue->user->login; $table->modified_date = (new Date($ghIssue->updated_at))->format('Y-m-d H:i:s'); $table->modified_by = $ghIssue->user->login; $table->project_id = $this->project->project_id; $table->milestone_id = $ghIssue->milestone && isset($milestones[$ghIssue->milestone->number]) ? $milestones[$ghIssue->milestone->number] : null; // We do not have a data about the default branch // @todo We need to retrieve repository somehow $table->build = 'master'; // If the issue has a diff URL, it is a pull request. if (isset($ghIssue->pull_request->diff_url)) { $table->has_code = 1; // Get the pull request corresponding to an issue. $this->debugOut('Get PR for the issue'); $pullRequest = $this->github->pulls->get($this->project->gh_user, $this->project->gh_project, $ghIssue->number); $table->build = $pullRequest->base->ref; // If the $pullRequest->head->user object is not set, the repo/branch had been deleted by the user. $table->pr_head_user = isset($pullRequest->head->user) ? $pullRequest->head->user->login : '******'; $table->pr_head_ref = $pullRequest->head->ref; if ($gitHubBot && $table->pr_head_sha && $table->pr_head_sha != $pullRequest->head->sha) { // The PR has been updated. $testers = (new IssueModel($this->getContainer()->get('db')))->getAllTests($table->id); if ($testers) { // Send a notification. $comment = "This PR has received new commits.\n\n**CC:** @" . implode(', @', $testers); // @todo send application comment - find a way to set a WEB URL in CLI scripts. // @todo $comment .= $gitHubBot->getApplicationComment($this->getContainer()->get('app'), $this->project, $table->issue_number); $gitHubBot->addComment($this->project, $table->issue_number, $comment, $this->project->gh_editbot_user, $this->getContainer()->get('db')); } } $table->pr_head_sha = $pullRequest->head->sha; $status = $this->GetMergeStatus($pullRequest); if (!$status->state) { // No status found. Let's create one! $status->state = 'pending'; $status->targetUrl = 'http://issues.joomla.org/gagaga'; $status->description = 'JTracker Bug Squad working on it...'; $status->context = 'jtracker'; // @todo Project based status messages // @$this->createStatus($ghIssue, 'pending', 'http://issues.joomla.org/gagaga', 'JTracker Bug Squad working on it...', 'CI/JTracker'); } else { // Save the merge status to database $table->merge_state = $status->state; $table->gh_merge_status = json_encode($status); } // Get commits $commits = (new GitHubHelper(GithubFactory::getInstance($this->getApplication())))->getCommits($this->project, $table->issue_number); $table->commits = json_encode($commits); } // Add the closed date if the status is closed if ($ghIssue->closed_at) { $table->closed_date = (new Date($ghIssue->closed_at))->format('Y-m-d H:i:s'); } // If the title has a [# in it, assume it's a JoomlaCode Tracker ID if (preg_match('/\\[#([0-9]+)\\]/', $ghIssue->title, $matches)) { $table->foreign_number = $matches[1]; } elseif (preg_match('/tracker_item_id=([0-9]+)/', $ghIssue->body, $matches)) { $table->foreign_number = $matches[1]; } $table->labels = implode(',', $this->getLabelIds($ghIssue->labels)); $table->check()->store(true); if (!$table->id) { // Bad coder :( - @todo when does this happen ?? throw new \RuntimeException(sprintf('Invalid issue id for issue: %1$d in project id %2$s', $ghIssue->number, $this->project->project_id)); } /* @todo see issue #194 Add an open record to the activity table $activity = new ActivitiesTable($db); $activity->project_id = $this->project->project_id; $activity->issue_number = (int) $table->issue_number; $activity->user = $issue->user->login; $activity->event = 'open'; $activity->created_date = $table->opened_date; $activity->store(); / Add a close record to the activity table if the status is closed if ($issue->closed_at) { $activity = new ActivitiesTable($db); $activity->project_id = $this->project->project_id; $activity->issue_number = (int) $table->issue_number; $activity->event = 'close'; $activity->created_date = $issue->closed_at; $activity->store(); } */ // Store was successful, update status if ($id) { ++$updated; } else { ++$added; } $this->changedIssueNumbers[] = $ghIssue->number; } // Output the final result $this->out()->logOut(sprintf(g11n3t('<ok>%1$d added, %2$d updated.</ok>'), $added, $updated)); return $this; }
/** * Triggers an event if a listener is set. * * @param string $eventName Name of the event to trigger. * @param array $arguments Associative array of arguments for the event. * * @return void * * @since 1.0 */ protected function triggerEvent($eventName, array $arguments) { if (!$this->listenerSet) { return; } /* @type \JTracker\Application $application */ $application = $this->getContainer()->get('app'); $event = new Event($eventName); // Add default event arguments. $event->addArgument('github', $this->github ?: GithubFactory::getInstance($application))->addArgument('logger', $application->getLogger())->addArgument('project', $application->getProject()); // Add event arguments passed as parameters. foreach ($arguments as $name => $value) { $event->addArgument($name, $value); } // Trigger the event. $this->dispatcher->triggerEvent($event); }
/** * Method to update data for an issue from GitHub * * @return boolean True on success * * @since 1.0 */ protected function updateData() { $table = new IssuesTable($this->db); try { $table->load(array('issue_number' => $this->data->number, 'project_id' => $this->project->project_id)); } catch (\Exception $e) { $this->setStatusCode($e->getCode()); $logMessage = sprintf('Error loading GitHub issue %s/%s #%d in the tracker: %s', $this->project->gh_user, $this->project->gh_project, $this->data->number, $e->getMessage()); $this->response->error = $logMessage; $this->logger->error($logMessage); return false; } // Figure out the state based on the action $action = $this->hookData->action; $status = $this->processStatus($action, $table->status); // Try to render the description with GitHub markdown $parsedText = $this->parseText($this->data->body); // Prepare the dates for insertion to the database $dateFormat = $this->db->getDateFormat(); $modified = new Date($this->data->updated_at); // Only update fields that may have changed, there's no API endpoint to show that so make some guesses $data = array(); $data['id'] = $table->id; $data['title'] = $this->data->title; $data['description'] = $parsedText; $data['description_raw'] = $this->data->body; $data['status'] = is_null($status) ? $table->status : $status; $data['modified_date'] = $modified->format($dateFormat); $data['modified_by'] = $this->hookData->sender->login; // Add the closed date if the status is closed if ($this->data->closed_at) { $closed = new Date($this->data->closed_at); $data['closed_date'] = $closed->format($dateFormat); } // Process labels for the item $data['labels'] = $this->processLabels($this->data->number); // Grab some data based on the existing record $data['priority'] = $table->priority; $data['build'] = $table->build; $data['rel_number'] = $table->rel_number; $data['rel_type'] = $table->rel_type; $data['milestone_id'] = $table->milestone_id; if (empty($data['build'])) { $data['build'] = $this->hookData->repository->default_branch; } $model = (new IssueModel($this->db))->setProject(new TrackerProject($this->db, $this->project)); // Check if the state has changed (e.g. open/closed) $oldState = $model->getOpenClosed($table->status); $state = is_null($status) ? $oldState : $model->getOpenClosed($data['status']); $data['old_state'] = $oldState; $data['new_state'] = $state; $gitHubBot = null; if ($this->project->gh_editbot_user && $this->project->gh_editbot_pass) { $gitHubBot = new GitHubHelper(GithubFactory::getInstance($this->getContainer()->get('app'), true, $this->project->gh_editbot_user, $this->project->gh_editbot_pass)); } if ($gitHubBot && $table->pr_head_sha && $table->pr_head_sha != $this->data->head->sha) { // The PR has been updated. $testers = (new IssueModel($this->getContainer()->get('db')))->getAllTests($table->id); if ($testers) { // Send a notification. $comment = "This PR has received new commits.\n\n**CC:** @" . implode(', @', $testers); $comment .= $gitHubBot->getApplicationComment($this->getContainer()->get('app'), $this->project, $table->issue_number); $gitHubBot->addComment($this->project, $table->issue_number, $comment, $this->project->gh_editbot_user, $this->getContainer()->get('db')); } } $data['pr_head_sha'] = $this->data->head->sha; $commits = (new GitHubHelper($this->github))->getCommits($this->project, $this->data->number); $data['commits'] = json_encode($commits); try { $model->save($data); } catch (\Exception $e) { $this->setStatusCode($e->getCode()); $logMessage = sprintf('Error updating GitHub pull request %s/%s #%d (Database ID #%d) to the tracker: %s', $this->project->gh_user, $this->project->gh_project, $this->data->number, $table->id, $e->getMessage()); $this->response->error = $logMessage; $this->logger->error($logMessage); return false; } // Refresh the table object for the listeners $table->load($data['id']); $this->triggerEvent('onPullAfterUpdate', ['table' => $table]); // Add a reopen record to the activity table if the status is reopened if ($action == 'reopened') { $this->addActivityEvent('reopen', $this->data->updated_at, $this->hookData->sender->login, $this->project->project_id, $this->data->number); } // Add a synchronize record to the activity table if the action is synchronized if ($action == 'synchronize') { $this->addActivityEvent('synchronize', $this->data->updated_at, $this->hookData->sender->login, $this->project->project_id, $this->data->number); } // Add a close record to the activity table if the status is closed if ($this->data->closed_at) { $this->addActivityEvent('close', $this->data->closed_at, $this->hookData->sender->login, $this->project->project_id, $this->data->number); } // Store was successful, update status $this->logger->info(sprintf('Updated GitHub pull request %s/%s #%d (Database ID #%d) to the tracker.', $this->project->gh_user, $this->project->gh_project, $this->data->number, $table->id)); return true; }