Completed
Push — master ( 3eebf6...0c0dcf )
by Bernhard
01:47 queued 10s
created

Push::getPasswdFromKeychain()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 13

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 13
rs 9.8333
c 0
b 0
f 0
cc 3
nc 3
nop 2
1
<?php
2
3
/**
4
 * \Wicked\Timely\Command\Push
5
 *
6
 * NOTICE OF LICENSE
7
 *
8
 * This source file is subject to the Open Software License (OSL 3.0)
9
 * that is available through the world-wide-web at this URL:
10
 * http://opensource.org/licenses/osl-3.0.php
11
 *
12
 * PHP version 5
13
 *
14
 * @author    Bernhard Wick <[email protected]>
15
 * @copyright 2016 Bernhard Wick
16
 * @license   http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0)
17
 * @link      https://github.com/wick-ed/timely
18
 */
19
20
namespace Wicked\Timely\Command;
21
22
use JiraRestApi\Configuration\ConfigurationInterface;
23
use Wicked\Timely\DotEnvConfiguration;
24
use JiraRestApi\Issue\IssueService;
25
use JiraRestApi\Issue\Worklog;
26
use JiraRestApi\JiraException;
27
use Symfony\Component\Console\Input\InputInterface;
28
use Symfony\Component\Console\Output\OutputInterface;
29
use Wicked\Timely\Entities\TaskFactory;
30
use Wicked\Timely\Formatter\FormatterFactory;
31
use Wicked\Timely\Helper\Date;
32
use Wicked\Timely\Storage\StorageFactory;
33
34
/**
35
 * Class for the "track" command. Command is used to track time bookings
36
 *
37
 * @author    Bernhard Wick <[email protected]>
38
 * @copyright 2016 Bernhard Wick
39
 * @license   http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0)
40
 * @link      https://github.com/wick-ed/timely
41
 */
42
class Push extends AbstractReadCommand
43
{
44
45
    const COMMAND_NAME = 'push';
46
    const KEYCHAIN_NAME = 'osxkeychain';
47
    const KEYCHAIN_SAVE = 'timely jira access';
48
49
    /**
50
     * Configures the "track" command
51
     *
52
     * @return void
53
     *
54
     * {@inheritDoc}
55
     * @see \Symfony\Component\Console\Command\Command::configure()
56
     */
57
    protected function configure()
58
    {
59
        $this
60
        ->setName(static::COMMAND_NAME)
61
        ->setDescription('Pushes booked times against the configured remote');
62
63
        // add all the read options from the abstract super class
64
        parent::configure();
65
    }
66
67
    /**
68
     * Execute the command
69
     *
70
     * @param \Symfony\Component\Console\Input\InputInterface $input The command input
71
     * @param \Symfony\Component\Console\Output\OutputInterface $output The command output
72
     *
73
     * @return void
74
     *
75
     * {@inheritDoc}
76
     * @throws JiraException
77
     * @throws \JsonMapper_Exception
78
     * @see \Symfony\Component\Console\Command\Command::execute()
79
     */
80
    protected function execute(InputInterface $input, OutputInterface $output)
81
    {
82
//        $passwd = $this->prompt_silent($output);
83
//        $output->writeln('fertig für heute:'.$passwd.'#');
84
85
        // get the ticket
86
        $ticket = $input->getArgument('ticket');
87
88
        // we might need framing dates
89
        $toDate = null;
90
        $fromDate = null;
91
92
        // there might be a limit
93
        $limit = null;
94
95
        // prepare all the input options
96
        $this->prepareInputParams($input, $ticket, $fromDate, $toDate, $limit);
97
98
        // filter by ticket if given
99
        /** @var \Wicked\Timely\Storage\StorageInterface $storage */
100
        $storage = StorageFactory::getStorage();
101
        $bookings = $storage->retrieve($ticket, $toDate, $fromDate, $limit);
102
103
        // return if we did not find any bookings
104
        if (empty($bookings)) {
105
            $output->write('No bookings found, nothing to push ...', true);
106
            return;
107
        }
108
109
        // get tasks from bookings
110
        $tasks = TaskFactory::getTasksFromBookings($bookings);
111
112
        // retrieve configuration
113
        $configuration = new DotEnvConfiguration();
114
        $password = $configuration->getJiraPassword();
115
        if (empty($password) || strtolower($password) === self::KEYCHAIN_NAME) {
116
            $password = $this->getPasswdFromKeychain($output, $configuration);
117
            if (empty($password)) {
118
                return;
119
            }
120
            $configuration->setJiraPassword($password);
121
122
        }
123
        unset($password);
124
        $bookingsPushed = array();
125
        // get our issue service and push the tasks
126
        $issueService = new IssueService($configuration);
127
        foreach ($tasks as $task) {
128
            // Allready pushed to jira? take next one
129
            if ($task->isPushed()) {
130
                continue;
131
            }
132
            // create a worklog from the task
133
            $workLog = new Worklog();
134
            $workLog->setComment($task->getComment())
135
                ->setStarted($task->getStartTime())
136
                ->setTimeSpent(Date::secondsToUnits(Date::roundByInterval($task->getDuration(), 900)));
137
138
            // check the sanity of our worklog and discard it if there is something missing
139
            if (!$task->getTicketId() || empty($workLog->timeSpent) || empty($workLog->comment))
140
            {
141
                $output->writeln('Not pushing one worklog as it misses vital information');
142
                continue;
143
            }
144
145
            // log the worklog about to being pushed if output is verbose
146
            if ($output->getVerbosity() >= OutputInterface::VERBOSITY_VERBOSE) {
147
                $output->writeln(
148
                    sprintf(
149
                        '%s : %s > %s',
150
                        $task->getTicketId(),
151
                        $workLog->timeSpent,
152
                        $workLog->comment
153
                    )
154
                );
155
            }
156
157
            try {
158
                // push to remote
159
                $issueService->addWorklog($task->getTicketId(), $workLog);
160
161
                $output->writeln(
162
                    sprintf(
163
                        'PUSHED %s : %s > %s',
164
                        $task->getTicketId(),
165
                        $workLog->timeSpent,
166
                        $workLog->comment
167
                    )
168
                );
169
170
                $bookingsPushed[] = $task->getStartBooking();
171
172
            } catch (JiraException $e) {
173
                $output->write(
174
                    sprintf(
175
                        'Error while pushing. Status %s, with message: "%s"',
176
                        $e->getCode(),
177
                        $e->getMessage()
178
                    )
179
                );
180
            }
181
        }
182
183
        foreach ($bookingsPushed as $booking) {
184
            $storage->storePush($booking);
185
            $formatter = FormatterFactory::getFormatter();
186
            $bookString = $formatter->toString($booking);
187
            $output->write($bookString, true);
188
        }
189
190
        // write output
191
        $output->write(sprintf('Successfully pushed %s tasks.', count($bookingsPushed)), true);
192
193
    }
194
195
    /**
196
     * Prompt silent
197
     *
198
     * Interactively prompts for input without echoing to the terminal.
199
     * Requires a bash shell or Windows and won't work with
200
     * safe_mode settings (Uses `shell_exec`)
201
     *
202
     * Source: http://www.sitepoint.com/interactive-cli-password-prompt-in-php/
203
     *
204
     * @param OutputInterface $output
205
     * @param string $prompt
206
     * @return string
207
     */
208
    function prompt_silent(OutputInterface $output, $prompt = "Enter Password:")
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

Adding explicit visibility (private, protected, or public) is generally recommend to communicate to other developers how, and from where this method is intended to be used.

Loading history...
209
    {
210
        $command = "/usr/bin/env bash -c 'echo OK'";
211
        if (rtrim(shell_exec($command)) !== 'OK') {
212
            trigger_error("Can't invoke bash");
213
            return '';
214
        }
215
        $command = "/usr/bin/env bash -c 'read -s -p \""
216
                   . addslashes($prompt)
217
                   . "\" mypassword && echo \$mypassword'";
218
        $password = rtrim(shell_exec($command));
219
        $output->writeln('');
220
        return $password;
221
    }
222
223
    private function getPasswdFromKeychain(OutputInterface $output, ConfigurationInterface $configuration)
224
    {
225
        $password = rtrim(shell_exec("security find-generic-password -s '".self::KEYCHAIN_SAVE."' -w"));
226
        if (empty($password)) {
227
            $password = $this->prompt_silent($output, 'Please enter password for your jira account "'.$configuration->getJiraUser().'":');
228
            if (empty($password)) {
229
                $output->writeln('Empty password is not possible. Stop push ...');
230
                return '';
231
            }
232
            shell_exec('security add-generic-password -a "'.$configuration->getJiraUser().'" -s "'.self::KEYCHAIN_SAVE.'" -w "'.$password.'"');
233
        }
234
        return $password;
235
    }
236
}
237