Completed
Push — master ( 535b09...0667cb )
by
unknown
56:23
created

isDisqualifyAndConvertAllowed()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 6
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 1 Features 0
Metric Value
dl 0
loc 6
rs 9.4285
c 1
b 1
f 0
cc 3
eloc 4
nc 3
nop 1
1
<?php
2
3
namespace OroCRM\Bundle\SalesBundle\Provider;
4
5
use Symfony\Component\PropertyAccess\PropertyAccess;
6
use Symfony\Component\PropertyAccess\PropertyAccessor;
7
8
use Oro\Bundle\EntityBundle\Provider\EntityFieldProvider;
9
use Oro\Bundle\WorkflowBundle\Model\WorkflowRegistry;
10
11
use OroCRM\Bundle\AccountBundle\Entity\Account;
12
use OroCRM\Bundle\SalesBundle\Entity\B2bCustomer;
13
use OroCRM\Bundle\SalesBundle\Entity\Opportunity;
14
use OroCRM\Bundle\SalesBundle\Entity\Lead;
15
use OroCRM\Bundle\SalesBundle\Model\B2bGuesser;
16
use OroCRM\Bundle\ContactBundle\Entity\Contact;
17
use OroCRM\Bundle\ContactBundle\Entity\ContactEmail;
18
use OroCRM\Bundle\ContactBundle\Entity\ContactPhone;
19
use OroCRM\Bundle\ContactBundle\Entity\ContactAddress;
20
use OroCRM\Bundle\SalesBundle\Model\ChangeLeadStatus;
21
22
class LeadToOpportunityProvider
23
{
24
    /**
25
     * @var PropertyAccessor
26
     */
27
    protected $accessor;
28
29
    /**
30
     * @var B2bGuesser
31
     */
32
    protected $b2bGuesser;
33
34
    /**
35
     * @var ChangeLeadStatus
36
     */
37
    protected $changeLeadStatus;
38
39
    /** @var bool */
40
    protected $isLeadWorkflowEnabled;
41
42
    /**
43
     * @var EntityFieldProvider
44
     */
45
    protected $entityFieldProvider;
46
47
    /**
48
     * @var array
49
     */
50
    protected $addressFields = [
51
        'properties' => [
52
            'firstName' => 'firstName',
53
            'lastName' => 'lastName',
54
            'middleName' => 'middleName',
55
            'namePrefix' => 'namePrefix',
56
            'nameSuffix' => 'nameSuffix',
57
            'city' => 'city',
58
            'country' => 'country',
59
            'label' => 'label',
60
            'organization' => 'organization',
61
            'postalCode' => 'postalCode',
62
            'region' => 'region',
63
            'regionText' => 'regionText',
64
            'street' => 'street',
65
            'street2' => 'street2',
66
            'primary' => 'primary'
67
        ]
68
    ];
69
70
    /**
71
     * @var array
72
     */
73
    protected $contactFields = [
74
        'properties' => [
75
            'firstName' => 'firstName',
76
            'jobTitle' => 'jobTitle',
77
            'lastName' => 'lastName',
78
            'middleName' => 'middleName',
79
            'namePrefix' => 'namePrefix',
80
            'nameSuffix' => 'nameSuffix',
81
            'twitter' => 'twitter',
82
            'linkedIn' => 'linkedIn',
83
            'owner' => 'owner',
84
            'source' => 'source'
85
        ],
86
        'extended_properties' => [
87
            'source' => 'enum'
88
        ]
89
    ];
90
91
    /**
92
     * @param B2bGuesser $b2bGuesser
93
     * @param EntityFieldProvider $entityFieldProvider
94
     * @param ChangeLeadStatus $changeLeadStatus
95
     * @param WorkflowRegistry $workflowRegistry
96
     */
97
    public function __construct(
98
        B2bGuesser $b2bGuesser,
99
        EntityFieldProvider $entityFieldProvider,
100
        ChangeLeadStatus $changeLeadStatus,
101
        WorkflowRegistry $workflowRegistry
102
    ) {
103
        $this->b2bGuesser = $b2bGuesser;
104
        $this->accessor = PropertyAccess::createPropertyAccessor();
105
        $this->entityFieldProvider = $entityFieldProvider;
106
        $this->changeLeadStatus = $changeLeadStatus;
107
        $this->validateContactFields();
108
        $this->isLeadWorkflowEnabled = $workflowRegistry
109
            ->hasActiveWorkflowByEntityClass('OroCRM\Bundle\SalesBundle\Entity\Lead');
110
    }
111
112
    /**
113
     * @return array
114
     */
115
    protected function prepareEntityFields()
116
    {
117
        $rawFields = $this->entityFieldProvider->getFields(
118
            'OroCRMSalesBundle:Lead',
119
            true,
120
            true,
121
            false,
122
            false,
123
            true,
124
            true
125
        );
126
        $fields = [];
127
        foreach ($rawFields as $field) {
128
            $fields[$field['name']] = $field['type'];
129
        }
130
131
        return $fields;
132
    }
133
134
    protected function validateContactFields()
135
    {
136
        $fields = $this->prepareEntityFields();
137
        foreach ($this->contactFields['extended_properties'] as $propertyName => $type) {
138
            $fieldValid = false;
139
            if (key_exists($propertyName, $fields) && $fields[$propertyName] !== $type) {
140
                $fieldValid = true;
141
            }
142
143
            if (!$fieldValid) {
144
                unset($this->contactFields['properties'][$propertyName]);
145
            }
146
        }
147
    }
148
149
    /**
150
     * @param object $filledEntity
151
     * @param array $properties
152
     * @param object $sourceEntity
153
     */
154
    protected function fillEntityProperties($filledEntity, array $properties, $sourceEntity)
155
    {
156
        foreach ($properties as $key => $value) {
157
            $propertyValue = is_array($value) ? $value['value'] : $this->accessor->getValue($sourceEntity, $value);
158
            if ($propertyValue) {
159
                $this->accessor->setValue($filledEntity, $key, $propertyValue);
160
            }
161
        }
162
    }
163
164
    /**
165
     * @param Lead $lead
166
     *
167
     * @return Contact
168
     */
169
    protected function prepareContactToOpportunity(Lead $lead)
170
    {
171
        $contact = $lead->getContact();
172
173
        if (!$contact instanceof Contact) {
174
            $contact = new Contact();
175
176
            $this->fillEntityProperties(
177
                $contact,
178
                $this->contactFields['properties'],
179
                $lead
180
            );
181
182
            $emails = $lead->getEmails();
183
            foreach ($emails as $email) {
184
                $contactEmail = new ContactEmail($email->getEmail());
185
                $contactEmail->setPrimary($email->isPrimary());
186
                $contact->addEmail($contactEmail);
187
            }
188
189
            $phones = $lead->getPhones();
190
            foreach ($phones as $phone) {
191
                $contactPhone = new ContactPhone($phone->getPhone());
192
                $contactPhone->setPrimary($phone->isPrimary());
193
                $contact->addPhone($contactPhone);
194
            }
195
196
            $addresses = $lead->getAddresses();
197
            foreach ($addresses as $address) {
198
                $contactAddress = new ContactAddress();
199
                $contactAddress->setPrimary($address->isPrimary());
200
                $this->fillEntityProperties(
201
                    $contactAddress,
202
                    $this->addressFields['properties'],
203
                    $address
204
                );
205
                $contact->addAddress($contactAddress);
206
            }
207
        }
208
209
        return $contact;
210
    }
211
212
    /**
213
     * @param Lead $lead
214
     * @param bool $isGetRequest
215
     *
216
     * @return Opportunity
217
     */
218
    public function prepareOpportunityForForm(Lead $lead, $isGetRequest = true)
219
    {
220
        $opportunity = new Opportunity();
221
        $opportunity->setLead($lead);
222
223
        if ($isGetRequest) {
224
            $contact = $this->prepareContactToOpportunity($lead);
225
            $opportunity
226
                ->setContact($contact)
227
                ->setName($lead->getName());
228
229
            $this->b2bGuesser->setCustomer($opportunity, $lead);
230
231
        } else {
232
            $opportunity
233
                // Set predefined contact entity to have proper validation
234
                // of addresses sub-form in case when user submit empty address
235
                ->setContact(new Contact());
236
        }
237
238
        return $opportunity;
239
    }
240
241
    /**
242
     * @param Opportunity $opportunity
243
     * @param callable    $errorMessageCallback
244
     *
245
     * @return bool
246
     */
247
    public function saveOpportunity(Opportunity $opportunity, callable $errorMessageCallback)
248
    {
249
        $lead = $opportunity->getLead();
250
        $customer = $opportunity->getCustomer();
251
252
        $this->setContactAndAccountToLeadFromOpportunity($lead, $opportunity);
253
        $this->prepareCustomerToSave($customer, $opportunity);
254
255
        $saveResult = $this->changeLeadStatus->qualify($lead);
256
257
        if (!$saveResult && is_callable($errorMessageCallback)) {
258
            call_user_func($errorMessageCallback);
259
        }
260
261
        return $saveResult;
262
    }
263
264
    /**
265
     * @param B2bCustomer $customer
266
     * @param Opportunity $opportunity
267
     */
268
    protected function prepareCustomerToSave(B2bCustomer $customer, Opportunity $opportunity)
269
    {
270
        $contact = $opportunity->getContact();
271
        if (!$customer->getContact() instanceof Contact) {
272
            $customer->setContact($contact);
273
        }
274
275
        if ($customer->getAccount() instanceof Account) {
276
            $customer->getAccount()->addContact($contact);
277
        }
278
    }
279
280
    /**
281
     * @param Lead        $lead
282
     * @param Opportunity $opportunity
283
     */
284
    protected function setContactAndAccountToLeadFromOpportunity(Lead $lead, Opportunity $opportunity)
285
    {
286
        $lead->setContact($opportunity->getContact());
287
        $lead->setCustomer($opportunity->getCustomer());
288
    }
289
290
    /**
291
     * @param Lead $lead
292
     *
293
     * @return bool
294
     */
295
    public function isDisqualifyAndConvertAllowed(Lead $lead)
296
    {
297
        return $lead->getStatus()->getId() !== ChangeLeadStatus::STATUS_DISQUALIFY &&
0 ignored issues
show
Bug introduced by
It seems like you code against a specific sub-type and not the parent class OroCRM\Bundle\SalesBundle\Entity\Lead as the method getStatus() does only exist in the following sub-classes of OroCRM\Bundle\SalesBundle\Entity\Lead: OroCRM\Bundle\SalesBundl...s\Unit\Fixture\LeadStub. Maybe you want to instanceof check for one of these explicitly?

Let’s take a look at an example:

abstract class User
{
    /** @return string */
    abstract public function getPassword();
}

class MyUser extends User
{
    public function getPassword()
    {
        // return something
    }

    public function getDisplayName()
    {
        // return some name.
    }
}

class AuthSystem
{
    public function authenticate(User $user)
    {
        $this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
        // do something.
    }
}

In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different sub-classes of User which does not have a getDisplayName() method, the code will break.

Available Fixes

  1. Change the type-hint for the parameter:

    class AuthSystem
    {
        public function authenticate(MyUser $user) { /* ... */ }
    }
    
  2. Add an additional type-check:

    class AuthSystem
    {
        public function authenticate(User $user)
        {
            if ($user instanceof MyUser) {
                $this->logger->info(/** ... */);
            }
    
            // or alternatively
            if ( ! $user instanceof MyUser) {
                throw new \LogicException(
                    '$user must be an instance of MyUser, '
                   .'other instances are not supported.'
                );
            }
    
        }
    }
    
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types inside the if block in such a case.
  1. Add the method to the parent class:

    abstract class User
    {
        /** @return string */
        abstract public function getPassword();
    
        /** @return string */
        abstract public function getDisplayName();
    }
    
Loading history...
298
        !$this->isLeadWorkflowEnabled &&
299
        $lead->getOpportunities()->count() === 0;
300
    }
301
}
302