PartialSubmissionJob   A
last analyzed

Complexity

Total Complexity 40

Size/Duplication

Total Lines 293
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 13

Test Coverage

Coverage 100%

Importance

Changes 0
Metric Value
wmc 40
lcom 1
cbo 13
dl 0
loc 293
ccs 131
cts 131
cp 1
rs 9.2
c 0
b 0
f 0

15 Methods

Rating   Name   Duplication   Size   Complexity  
A setup() 0 6 1
A validateEmails() 0 19 5
A getTitle() 0 4 1
A process() 0 31 4
A getParents() 0 25 5
A buildCSV() 0 22 2
A processSubmissions() 0 27 5
A sendEmail() 0 17 4
A afterComplete() 0 15 4
A cleanupSubmissions() 0 14 3
A createNewJob() 0 8 1
A getMessages() 0 4 1
A getAddresses() 0 4 1
A addAddress() 0 6 2
A getConfig() 0 4 1

How to fix   Complexity   

Complex Class

Complex classes like PartialSubmissionJob often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use PartialSubmissionJob, and based on these observations, apply Extract Interface, too.

1
<?php
2
3
namespace Firesphere\PartialUserforms\Jobs;
4
5
use Exception;
6
use Firesphere\PartialUserforms\Models\PartialFieldSubmission;
7
use Firesphere\PartialUserforms\Models\PartialFormSubmission;
8
use Firesphere\PartialUserforms\Services\DateService;
9
use SilverStripe\Control\Director;
10
use SilverStripe\Control\Email\Email;
11
use SilverStripe\Core\Extensible;
12
use SilverStripe\Core\Injector\Injector;
13
use SilverStripe\ORM\ArrayList;
14
use SilverStripe\ORM\DataList;
15
use SilverStripe\ORM\FieldType\DBDatetime;
16
use SilverStripe\ORM\ValidationException;
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
    use Extensible;
29
30
    /**
31
     * The generated CSV files
32
     * @var array
33
     */
34
    protected $files = [];
35
36
    /**
37
     * @var SiteConfig
38
     */
39
    protected $config;
40
41
    /**
42
     * @var array
43
     */
44
    protected $addresses;
45
46
47
    /**
48
     * Prepare the data
49
     */
50 16
    public function setup()
51
    {
52 16
        parent::setup();
53 16
        $this->config = SiteConfig::current_site_config();
54 16
        $this->validateEmails();
55 16
    }
56
57
    /**
58
     * Only add valid email addresses
59
     */
60 16
    protected function validateEmails()
61
    {
62 16
        $email = $this->config->SendMailTo;
63 16
        $result = Email::is_valid_address($email);
64 16
        if ($result) {
65 10
            $this->addresses[] = $email;
66
        }
67 16
        if (strpos($email, ',') !== false) {
68 2
            $emails = explode(',', $email);
69 2
            foreach ($emails as $address) {
70 2
                $result = Email::is_valid_address(trim($address));
71 2
                if ($result) {
72 2
                    $this->addresses[] = trim($address);
73
                } else {
74 2
                    $this->addMessage($address . _t(__CLASS__ . '.invalidMail', ' is not a valid email address'));
75
                }
76
            }
77
        }
78 16
    }
79
80
    /**
81
     * @return string
82
     */
83 15
    public function getTitle()
84
    {
85 15
        return _t(__CLASS__ . '.Title', 'Export partial submissions to Email');
86
    }
87
88
    /**
89
     * Do some processing yourself!
90
     */
91 14
    public function process()
92
    {
93 14
        if (!$this->config->SendDailyEmail) {
94 1
            $this->addMessage(_t(__CLASS__ . '.NotActive', 'Daily exports are not enabled'));
95 1
            $this->isComplete = true;
96
97 1
            return;
98
        }
99 13
        if (!count($this->addresses)) {
100 1
            $this->addMessage(_t(__CLASS__ . '.EmailError', 'Can not process without valid email'));
101 1
            $this->isComplete = true;
102
103 1
            return;
104
        }
105
106 12
        $userDefinedForms = $this->getParents();
107
108
        /** @var UserDefinedForm $form */
109 12
        foreach ($userDefinedForms as $form) {
110 9
            $fileName = _t(__CLASS__ . '.Export', 'Export of ') .
111 9
                $form->Title . ' - ' .
112 9
                DBDatetime::now()->Format(DBDatetime::ISO_DATETIME);
113 9
            $file = '/tmp/' . $fileName . '.csv';
114 9
            $this->files[] = $file;
115 9
            $this->buildCSV($file, $form);
116
        }
117
118 12
        $this->sendEmail();
119
120 12
        $this->isComplete = true;
121 12
    }
122
123
    /**
124
     * @return ArrayList
125
     */
126 12
    protected function getParents()
127
    {
128
        /** @var DataList|PartialFormSubmission[] $exportForms */
129 12
        $allSubmissions = PartialFormSubmission::get()->filter(['IsSend' => false]);
130
        /** @var ArrayList|UserDefinedForm[] $parents */
131 12
        $userDefinedForms = ArrayList::create();
132
133
        /** @var PartialFormSubmission $submission */
134
        /** @noinspection ForeachSourceInspection */
135 12
        foreach ($allSubmissions as $submission) {
136
            // Due to having to support Elemental ElementForm, we need to manually get the parent
137
            // It's a bit a pickle, but it works
138 9
            $parentClass = $submission->ParentClass;
139 9
            $parent = $parentClass::get()->byID($submission->UserDefinedFormID);
140 9
            if ($parent &&
141 9
                $parent->ExportPartialSubmissions &&
142 9
                !$userDefinedForms->find('ID', $parent->ID)
143
            ) {
144 9
                $userDefinedForms->push($parent);
145
            }
146 9
            $submission->destroy();
147
        }
148
149 12
        return $userDefinedForms;
150
    }
151
152
    /**
153
     * @param string $file
154
     * @param UserDefinedForm $form
155
     * @throws ValidationException
156
     */
157 9
    protected function buildCSV($file, $form)
158
    {
159
        $excludeFields = [
160 9
            'Name:PartialMatch' => 'EditableFormStep'
161
        ];
162
        $filter = [
163 9
            'UserDefinedFormID' => $form->ID
164
        ];
165 9
        $resource = fopen($file, 'w+');
166
        /** @var DataList|PartialFormSubmission[] $submissions */
167 9
        $submissions = PartialFormSubmission::get()->filter($filter);
168
        $headerFields = $form
169 9
            ->Fields()
170 9
            ->exclude($excludeFields)
171 9
            ->column('Title');
172 9
        fputcsv($resource, $headerFields);
173
174 9
        if ($submissions->count()) {
175 9
            $this->processSubmissions($form, $submissions, $resource);
176
        }
177 9
        fclose($resource);
178 9
    }
179
180
    /**
181
     * @param UserDefinedForm $form
182
     * @param DataList|PartialFormSubmission[] $submissions
183
     * @param resource $resource
184
     * @throws ValidationException
185
     */
186 9
    protected function processSubmissions($form, $submissions, $resource)
187
    {
188
        $editableFields = $form
189 9
            ->Fields()
190 9
            ->exclude(['Name:PartialMatch' => 'EditableFormStep'])
191 9
            ->map('Name', 'Title')
192 9
            ->toArray();
193 9
        $i = 0;
194 9
        $submitted = [];
195 9
        foreach ($submissions as $submission) {
196 9
            $values = $submission->PartialFields()->map('Name', 'Value')->toArray();
197 9
            foreach ($editableFields as $field => $title) {
198 9
                if (isset($values[$field])) {
199 9
                    $submitted[$i][] = $values[$field];
200
                } else {
201 9
                    $submitted[$i][] = '';
202
                }
203
            }
204 9
            $i++;
205 9
            $submission->IsSend = true;
206 9
            $submission->write();
207
        }
208 9
        $this->extend('updateCSVRecords', $submitted, $editableFields);
209 9
        foreach ($submitted as $submitItem) {
210 9
            fputcsv($resource, $submitItem);
211
        }
212 9
    }
213
214
    /**
215
     * Send out the email(s)
216
     */
217 12
    protected function sendEmail()
218
    {
219
        /** @var Email $mail */
220 12
        $mail = Email::create();
221 12
        $mail->setSubject('Partial form submissions of ' . DBDatetime::now()->Format(DBDatetime::ISO_DATETIME));
222 12
        foreach ($this->files as $file) {
223 9
            $mail->addAttachment($file);
224
        }
225 12
        $from = $this->config->SendMailFrom ?: 'site@' . Director::host();
226
227 12
        $mail->setFrom($from);
228 12
        foreach ($this->addresses as $address) {
229 12
            $mail->setTo($address);
230 12
            $mail->setBody('Please see attached CSV files');
231 12
            $mail->send();
232
        }
233 12
    }
234
235
    /**
236
     * @throws Exception
237
     */
238 3
    public function afterComplete()
239
    {
240
        // Remove the files created in the process
241 3
        foreach ($this->files as $file) {
242 3
            unlink($file);
243
        }
244
245 3
        parent::afterComplete();
246 3
        if ($this->config->CleanupAfterSend) {
247 3
            $this->cleanupSubmissions();
248
        }
249 3
        if ($this->config->SendDailyEmail) {
250 3
            $this->createNewJob();
251
        }
252 3
    }
253
254
    /**
255
     * Remove submissions that have been sent out
256
     */
257 3
    protected function cleanupSubmissions()
258
    {
259
        /** @var DataList|PartialFormSubmission[] $forms */
260 3
        $forms = PartialFormSubmission::get()->filter(['IsSend' => true]);
261 3
        foreach ($forms as $form) {
262
            /** @var DataList|PartialFieldSubmission[] $fields */
263 3
            if ($form->PartialFields()->column('ID')) {
264 3
                $fields = PartialFieldSubmission::get()->filter(['ID' => $form->PartialFields()->column('ID')]);
265 3
                $fields->removeAll();
266
            }
267 3
            $form->delete();
268 3
            $form->destroy();
269
        }
270 3
    }
271
272
    /**
273
     * Create a new queued job for tomorrow
274
     * @throws Exception
275
     */
276 3
    protected function createNewJob()
277
    {
278 3
        $job = new self();
279
        /** @var QueuedJobService $queuedJob */
280 3
        $queuedJob = Injector::inst()->get(QueuedJobService::class);
281 3
        $tomorrow = DateService::getTomorrow();
282 3
        $queuedJob->queueJob($job, $tomorrow->Format(DBDatetime::ISO_DATETIME));
283 3
    }
284
285
    /**
286
     * @return array
287
     */
288 2
    public function getMessages()
289
    {
290 2
        return $this->messages;
291
    }
292
293
    /**
294
     * @return array
295
     */
296 1
    public function getAddresses()
297
    {
298 1
        return $this->addresses;
299
    }
300
301
    /**
302
     * @param string $address
303
     */
304 3
    public function addAddress($address)
305
    {
306 3
        if (Email::is_valid_address($address)) {
307 3
            $this->addresses[] = $address;
308
        }
309 3
    }
310
311
    /**
312
     * @return SiteConfig
313
     */
314 1
    public function getConfig()
315
    {
316 1
        return $this->config;
317
    }
318
}
319