File size: 4,644 Bytes
d2897cd
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
<?php

namespace Mautic\ApiBundle\Helper;

class BatchIdToEntityHelper
{
    /**
     * @var array
     */
    private $ids = [];

    private array $originalKeys = [];

    private array $errors = [];

    private bool $isAssociative = false;

    /**
     * @param string $idKey
     */
    public function __construct(
        array $parameters,
        private $idKey = 'id'
    ) {
        $this->extractIds($parameters);
    }

    public function hasIds(): bool
    {
        return !empty($this->ids);
    }

    /**
     * @return array
     */
    public function getIds()
    {
        return $this->ids;
    }

    public function hasErrors(): bool
    {
        return !empty($this->errors);
    }

    /**
     * @return array
     */
    public function getErrors()
    {
        return $this->errors;
    }

    /**
     * Reorder the entities based on the original keys
     * BC allowed a request to have associative keys (don't ask why; yes it's terrible implementation but we're keeping BC here)
     * The issue this solves is the response should match the format given by the request. If the request had associative keys, the response
     * will return with associative keys (json object). If the request was a sequential numeric array starting with 0, the response will
     * be a simple array (json array).
     */
    public function orderByOriginalKey(array $entities): array
    {
        if (!$this->isAssociative) {
            // The request was keyed by sequential numbers starting with 0
            return array_values($entities);
        }

        // Ensure entities are keyed by ID in order to find the original keys assuming that some entities are missing if the ID was not found
        $entitiesKeyedById = [];
        foreach ($entities as $entity) {
            $entitiesKeyedById[$entity->getId()] = $entity;
        }

        $orderedEntities = [];
        foreach ($this->ids as $key => $id) {
            if (!isset($entitiesKeyedById[$id])) {
                continue;
            }

            $originalKey                   = $this->originalKeys[$key];
            $orderedEntities[$originalKey] = $entitiesKeyedById[$id];
        }

        return $orderedEntities;
    }

    private function extractIds(array $parameters): void
    {
        $this->ids = [];

        if (isset($parameters['ids'])) {
            $this->extractIdsFromIdKey($parameters['ids']);

            return;
        }

        $this->extractIdsFromParams($parameters);
    }

    /**
     * @param mixed $ids
     */
    private function extractIdsFromIdKey($ids): void
    {
        // ['ids' => [1,2,3]]
        if (is_array($ids)) {
            $this->isAssociative = $this->isAssociativeArray($ids);
            $this->ids           = array_values($ids);
            $this->originalKeys  = array_keys($ids);

            return;
        }

        // ['ids' => '1,2,3'] OR ['ids' => '1']
        if (str_contains($ids, ',') || is_numeric($ids)) {
            $this->ids           = str_getcsv($ids);
            $this->originalKeys  = array_keys($this->ids);
            $this->isAssociative = false;

            return;
        }

        // Couldn't parse the 'ids' key; not throwing an exception in order to keep BC with
        // the old CommonApiController code and the use of a foreach in extractIdsFromParams
        $this->errors[] = 'mautic.api.call.id_missing';
    }

    private function extractIdsFromParams(array $parameters): void
    {
        $this->isAssociative = $this->isAssociativeArray($parameters);
        $this->originalKeys  = array_keys($parameters);

        // [1,2,3]
        $firstKey            = array_key_first($parameters);
        if (!is_array($parameters[$firstKey])) {
            $this->ids = array_values($parameters);

            return;
        }

        // [ ['id' => 1, 'foo' => 'bar'], ['id' => 2, 'bar' => 'foo'] ]
        foreach ($parameters as $key => $params) {
            // Missing id column key in the array; terrible but keep BC
            if (!isset($params[$this->idKey])) {
                $this->errors[$key] = 'mautic.api.call.id_missing';

                continue;
            }

            $this->ids[] = $params[$this->idKey];
        }
    }

    private function isAssociativeArray(array $array): bool
    {
        if (empty($array)) {
            return false;
        }
        $firstKey = array_key_first($array);

        return array_keys($array) !== range(0, count($array) - 1) && 0 !== $firstKey;
    }

    public function setIsAssociative(bool $isAssociative): void
    {
        $this->isAssociative = $isAssociative;
    }
}