Completed
Push — master ( 64551a...f3ef88 )
by Simon
02:13
created

PartialSubmissionJob   B

Complexity

Total Complexity 38

Size/Duplication

Total Lines 281
Duplicated Lines 3.56 %

Coupling/Cohesion

Components 1
Dependencies 11

Importance

Changes 3
Bugs 0 Features 0
Metric Value
wmc 38
c 3
b 0
f 0
lcom 1
cbo 11
dl 10
loc 281
rs 8.3999

16 Methods

Rating   Name   Duplication   Size   Complexity  
A setup() 0 6 1
A getTitle() 0 4 1
B process() 0 31 4
B validateEmails() 0 19 5
B getParents() 0 25 5
A buildCSV() 0 16 2
A processSubmissions() 0 19 4
A afterComplete() 0 15 4
A cleanupSubmissions() 0 12 2
A createNewJob() 0 8 1
A getTomorrow() 10 10 1
A getMessages() 0 4 1
A getAddresses() 0 4 1
A addAddress() 0 6 2
A getConfig() 0 4 1
A sendEmail() 0 15 3

How to fix   Duplicated Code   

Duplicated Code

Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.

Common duplication problems, and corresponding solutions are:

1
<?php
2
3
namespace Firesphere\PartialUserforms\Jobs;
4
5
use DateInterval;
6
use DateTime;
7
use DNADesign\ElementalUserForms\Model\ElementForm;
8
use Firesphere\PartialUserforms\Models\PartialFieldSubmission;
9
use Firesphere\PartialUserforms\Models\PartialFormSubmission;
10
use SilverStripe\Control\Director;
11
use SilverStripe\Control\Email\Email;
12
use SilverStripe\Core\Injector\Injector;
13
use SilverStripe\Dev\Debug;
14
use SilverStripe\ORM\ArrayList;
15
use SilverStripe\ORM\DataList;
16
use SilverStripe\ORM\FieldType\DBDatetime;
17
use SilverStripe\SiteConfig\SiteConfig;
18
use SilverStripe\UserForms\Model\UserDefinedForm;
19
use Symbiote\QueuedJobs\Services\AbstractQueuedJob;
20
use Symbiote\QueuedJobs\Services\QueuedJobService;
21
22
/**
23
 * Class PartialSubmissionJob
24
 * @package Firesphere\PartialUserforms\Jobs
25
 */
26
class PartialSubmissionJob extends AbstractQueuedJob
27
{
28
29
    /**
30
     * The generated CSV files
31
     * @var array
32
     */
33
    protected $files = [];
34
35
    /**
36
     * @var SiteConfig
37
     */
38
    protected $config;
39
40
    /**
41
     * @var array
42
     */
43
    protected $addresses;
44
45
46
    /**
47
     * Prepare the data
48
     */
49
    public function setup()
50
    {
51
        parent::setup();
52
        $this->config = SiteConfig::current_site_config();
53
        $this->validateEmails();
54
    }
55
56
    /**
57
     * @return string
58
     */
59
    public function getTitle()
60
    {
61
        return _t(__CLASS__ . '.Title', 'Export partial submissions to Email');
62
    }
63
64
    /**
65
     * Do some processing yourself!
66
     */
67
    public function process()
68
    {
69
        if (!$this->config->SendDailyEmail) {
70
            $this->addMessage(_t(__CLASS__ . '.NotActive', 'Daily exports are not enabled'));
71
            $this->isComplete = true;
72
73
            return;
74
        }
75
        if (!count($this->addresses)) {
76
            $this->addMessage(_t(__CLASS__ . '.EmailError', 'Can not process without valid email'));
77
            $this->isComplete = true;
78
79
            return;
80
        }
81
82
        $userDefinedForms = $this->getParents();
83
84
        /** @var UserDefinedForm $form */
85
        foreach ($userDefinedForms as $form) {
86
            $fileName = _t(__CLASS__ . '.Export', 'Export of ') .
87
                $form->Title . ' - ' .
88
                DBDatetime::now()->Format(DBDatetime::ISO_DATETIME);
89
            $file = '/tmp/' . $fileName . '.csv';
90
            $this->files[] = $file;
91
            $this->buildCSV($file, $form);
92
        }
93
94
        $this->sendEmail();
95
96
        $this->isComplete = true;
97
    }
98
99
    /**
100
     * Only add valid email addresses
101
     */
102
    protected function validateEmails()
103
    {
104
        $email = $this->config->SendMailTo;
105
        $result = Email::is_valid_address($email);
106
        if ($result) {
107
            $this->addresses[] = $email;
108
        }
109
        if (strpos($email, ',') !== false) {
110
            $emails = explode(',', $email);
111
            foreach ($emails as $address) {
112
                $result = Email::is_valid_address(trim($address));
113
                if ($result) {
114
                    $this->addresses[] = trim($address);
115
                } else {
116
                    $this->addMessage($address . _t(__CLASS__ . '.invalidMail', ' is not a valid email address'));
117
                }
118
            }
119
        }
120
    }
121
122
    /**
123
     * @return ArrayList
124
     */
125
    protected function getParents()
126
    {
127
        /** @var DataList|PartialFormSubmission[] $exportForms */
128
        $allSubmissions = PartialFormSubmission::get()->filter(['IsSend' => false]);
129
        /** @var ArrayList|UserDefinedForm[]|ElementForm[] $parents */
130
        $userDefinedForms = ArrayList::create();
131
132
        /** @var PartialFormSubmission $submission */
133
        /** @noinspection ForeachSourceInspection */
134
        foreach ($allSubmissions as $submission) {
135
            // Due to having to support Elemental ElementForm, we need to manually get the parent
136
            // It's a bit a pickle, but it works
137
            $parentClass = $submission->ParentClass;
138
            $parent = $parentClass::get()->byID($submission->UserDefinedFormID);
139
            if ($parent &&
140
                $parent->ExportPartialSubmissions &&
141
                !$userDefinedForms->find('ID', $parent->ID)
142
            ) {
143
                $userDefinedForms->push($parent);
144
            }
145
            $submission->destroy();
146
        }
147
148
        return $userDefinedForms;
149
    }
150
151
    /**
152
     * @param $file
153
     * @param $form
154
     */
155
    protected function buildCSV($file, $form)
156
    {
157
        $resource = fopen($file, 'w+');
158
        /** @var PartialFormSubmission $submissions */
159
        $submissions = PartialFormSubmission::get()->filter(['UserDefinedFormID' => $form->ID]);
160
        $headerFields = $form
161
            ->Fields()
162
            ->exclude(['Name:PartialMatch' => 'EditableFormStep'])
163
            ->column('Title');
164
        fputcsv($resource, $headerFields);
165
166
        if ($submissions->count()) {
0 ignored issues
show
Documentation Bug introduced by
The method count does not exist on object<Firesphere\Partia...\PartialFormSubmission>? Since you implemented __call, maybe consider adding a @method annotation.

If you implement __call and you know which methods are available, you can improve IDE auto-completion and static analysis by adding a @method annotation to the class.

This is often the case, when __call is implemented by a parent class and only the child class knows which methods exist:

class ParentClass {
    private $data = array();

    public function __call($method, array $args) {
        if (0 === strpos($method, 'get')) {
            return $this->data[strtolower(substr($method, 3))];
        }

        throw new \LogicException(sprintf('Unsupported method: %s', $method));
    }
}

/**
 * If this class knows which fields exist, you can specify the methods here:
 *
 * @method string getName()
 */
class SomeClass extends ParentClass { }
Loading history...
167
            $this->processSubmissions($form, $submissions, $resource);
168
        }
169
        fclose($resource);
170
    }
171
172
    /**
173
     * @param $form
174
     * @param $submissions
175
     * @param $resource
176
     */
177
    protected function processSubmissions($form, $submissions, $resource)
178
    {
179
        $editableFields = $form->Fields()->map('Name', 'Title')->toArray();
180
        $submitted = [];
181
        foreach ($submissions as $submission) {
182
            $values = $submission->PartialFields()->map('Name', 'Value')->toArray();
183
            $i = 0;
184
            foreach ($editableFields as $field => $title) {
185
                $submitted[] = '';
186
                if (isset($values[$field])) {
187
                    $submitted[] = $values[$field];
188
                }
189
                $i++;
190
            }
191
            fputcsv($resource, $submitted);
192
            $submission->IsSend = true;
193
            $submission->write();
194
        }
195
    }
196
197
    /**
198
     * @throws \Exception
199
     */
200
    public function afterComplete()
201
    {
202
        // Remove the files created in the process
203
        foreach ($this->files as $file) {
204
            unlink($file);
205
        }
206
207
        parent::afterComplete();
208
        if ($this->config->CleanupAfterSend) {
209
            $this->cleanupSubmissions();
210
        }
211
        if ($this->config->SendDailyEmail) {
212
            $this->createNewJob();
213
        }
214
    }
215
216
    /**
217
     * Remove submissions that have been sent out
218
     */
219
    protected function cleanupSubmissions()
220
    {
221
        /** @var DataList|PartialFormSubmission[] $forms */
222
        $forms = PartialFormSubmission::get()->filter(['IsSend' => true]);
223
        foreach ($forms as $form) {
224
            /** @var DataList|PartialFieldSubmission[] $fields */
225
            $fields = PartialFieldSubmission::get()->filter(['ID' => $form->PartialFields()->column('ID')]);
226
            $fields->removeAll();
227
            $form->delete();
228
            $form->destroy();
229
        }
230
    }
231
232
    /**
233
     * Create a new queued job for tomorrow
234
     * @throws \Exception
235
     */
236
    protected function createNewJob()
237
    {
238
        $job = new self();
239
        /** @var QueuedJobService $queuedJob */
240
        $queuedJob = Injector::inst()->get(QueuedJobService::class);
241
        $tomorrow = $this->getTomorrow();
242
        $queuedJob->queueJob($job, $tomorrow);
243
    }
244
245
    /**
246
     * @return DBDatetime|static
247
     * @throws \Exception
248
     */
249 View Code Duplication
    protected function getTomorrow()
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
250
    {
251
        $dateTime = new DateTime(DBDatetime::now());
252
        $interval = new DateInterval('P1D');
253
        $tomorrow = $dateTime->add($interval);
254
        $dbDateTime = DBDatetime::create();
255
        $dbDateTime->setValue($tomorrow->format('Y-m-d 00:00:00'));
256
257
        return $dbDateTime;
258
    }
259
260
    /**
261
     * @return array
262
     */
263
    public function getMessages()
264
    {
265
        return $this->messages;
266
    }
267
268
    /**
269
     * @return array
270
     */
271
    public function getAddresses()
272
    {
273
        return $this->addresses;
274
    }
275
276
    /**
277
     * @param string $address
278
     */
279
    public function addAddress($address)
280
    {
281
        if (Email::is_valid_address($address)) {
282
            $this->addresses[] = $address;
283
        }
284
    }
285
286
    public function getConfig()
287
    {
288
        return $this->config;
289
    }
290
291
    protected function sendEmail()
292
    {
293
        /** @var Email $mail */
294
        $mail = Email::create();
295
        $mail->setSubject('Partial form submissions of ' . DBDatetime::now()->Format(DBDatetime::ISO_DATETIME));
296
        foreach ($this->files as $file) {
297
            $mail->addAttachment($file);
298
        }
299
        $from = $this->config->SendMailFrom ?: 'site@' . Director::host();
300
301
        $mail->setFrom($from);
302
        $mail->setTo($this->addresses);
303
        $mail->setBody('Please see attached CSV files');
304
        $mail->send();
305
    }
306
}
307