/** * Sync a single harvest time entry. * * @param \Harvest\Model\DayEntry $harvest_entry * * @return bool */ protected function syncEntry(DayEntry $harvest_entry) { // Check spelling. $words = explode(' ', preg_replace('/[^a-z]+/i', ' ', $harvest_entry->get('notes'))); $spelling_errors = array(); foreach ($words as $word) { if (!pspell_check($this->pspellLink, $word)) { $spelling_errors[] = $word; } } if ($spelling_errors) { $this->userTimeEntryErrors[$harvest_entry->get('user-id')]['spelling'][] = ['entry' => $harvest_entry, 'spelling-errors' => $spelling_errors]; } $redmine_issue = $this->getRedmineIssue($harvest_entry); if (!$redmine_issue) { return false; } $existing_redmine_time_entries = $this->getExistingRedmineIssueTimeEntries($redmine_issue, $harvest_entry); // If there are existing Redmine time entries matching this harvest entry and we are not updating, skip. if (count($existing_redmine_time_entries) > 0 && !$this->input->getOption('update')) { return false; } // Or if there is more than one matching redmine time entry, throw an error and continue. if (count($existing_redmine_time_entries) > 1) { $this->output->writeln(sprintf('<error>Multiple Redmine time entries matching harvest time entry %d. See entries %s</error>', $harvest_entry->get('id'), json_encode($existing_redmine_time_entries))); $this->errors = true; return false; } // If Harvest user is not mapped to a redmine user, throw an error and continue. if (!isset($this->userMap[$harvest_entry->get('user-id')])) { $this->output->writeln(sprintf('<error>No mapping is defined for user %d</error>', $harvest_entry->get('user-id'))); $this->errors = true; return false; } // Log the entry. $redmine_entry_params = $this->populateRedmineTimeEntry($redmine_issue, $harvest_entry); // Check rounding. if ($redmine_entry_params['hours'] != $harvest_entry->get('hours')) { $this->userTimeEntryErrors[$harvest_entry->get('user-id')]['rounding'][] = ['entry' => $harvest_entry, 'rounded-hours' => $redmine_entry_params['hours']]; } $save_entry_result = false; $this->setRedmineClient(); if (!$this->input->getOption('dry-run')) { try { $this->redmineClient->setImpersonateUser($this->userMap[$harvest_entry->get('user-id')]); $save_entry_result = $this->saveHarvestTimeEntryToRedmine($redmine_entry_params, $existing_redmine_time_entries); } catch (\Exception $e) { $this->output->writeln(sprintf('<error>Failed to create time entry for redmine issue #%d, harvest id %d, exception %s</error>', $redmine_issue['issue']['id'], $harvest_entry->get('id'), $e->getMessage())); } finally { $this->redmineClient->setImpersonateUser(null); } } if ($save_entry_result || $this->input->getOption('dry-run')) { $this->output->writeln(sprintf('<comment>%s time entry for issue #%d with %s hours (Harvest hours: %s)</comment>', count($existing_redmine_time_entries) > 0 ? 'Updated' : 'Created', $redmine_issue['issue']['id'], $redmine_entry_params['hours'], $harvest_entry->get('hours'))); } return $save_entry_result; }
/** * @covers Redmine\Client * @test */ public function testPrepareDeleteUploadRequestWithSslAndImpersonateUser() { // Create the object under test $client = new Client('https://test.local', 'USER_API-KEY159'); $client->setImpersonateUser('test_user'); $client->setUseHttpAuth(false); $client->setCheckSslCertificate(true); $client->setCheckSslHost(true); // Perform the tests $data = array(1 => 'post_1', '25' => 'post_25'); $client->prepareRequest('/uploads.xml', 'DELETE', $data); $curlOptions = $client->getCurlOptions(); $this->assertArrayNotHasKey(CURLOPT_USERPWD, $curlOptions); $this->assertArrayNotHasKey(CURLOPT_HTTPAUTH, $curlOptions); $this->assertSame('https://test.local/uploads.xml', $curlOptions[CURLOPT_URL]); $this->assertSame(0, $curlOptions[CURLOPT_VERBOSE]); $this->assertSame(0, $curlOptions[CURLOPT_HEADER]); $this->assertSame(1, $curlOptions[CURLOPT_RETURNTRANSFER]); $this->assertSame(443, $curlOptions[CURLOPT_PORT]); $this->assertSame(1, $curlOptions[CURLOPT_SSL_VERIFYPEER]); $this->assertSame(2, $curlOptions[CURLOPT_SSL_VERIFYHOST]); $this->assertContains('Expect: ', $curlOptions[CURLOPT_HTTPHEADER]); $this->assertContains('Content-Type: application/octet-stream', $curlOptions[CURLOPT_HTTPHEADER]); $this->assertContains('X-Redmine-Switch-User: test_user', $curlOptions[CURLOPT_HTTPHEADER]); $this->assertContains('X-Redmine-API-Key: USER_API-KEY159', $curlOptions[CURLOPT_HTTPHEADER]); $this->assertSame('DELETE', $curlOptions[CURLOPT_CUSTOMREQUEST]); }