| @@ -1,8 +1,9 @@ | ||
| 1 | 1 | <?php |
| 2 | 2 | |
| 3 | 3 | namespace Give\Donors\Models; |
| 4 | 4 | |
| 5 | +use Give\Donors\ValueObjects\DonorAddress; | |
| 5 | 6 | use Give\Framework\Database\DB; |
| 6 | 7 | use Give\Framework\Models\ModelQueryBuilder; |
| 7 | 8 | |
| 8 | 9 | class DonorModelQueryBuilder extends ModelQueryBuilder |
| @@ -13,8 +14,10 @@ | ||
| 13 | 14 | * |
| 14 | 15 | * @since 2.24.0 |
| 15 | 16 | * |
| 16 | 17 | * @return M|null |
| 18 | + * | |
| 19 | + * @param int $output For inheritance compatibility only, unused. | |
| 17 | 20 | */ |
| 18 | 21 | public function get($output = OBJECT) |
| 19 | 22 | { |
| 20 | 23 | $row = DB::get_row($this->getSQL(), OBJECT); |
| @@ -23,8 +26,9 @@ | ||
| 23 | 26 | return null; |
| 24 | 27 | } |
| 25 | 28 | |
| 26 | 29 | $row = $this->attachAdditionalEmails($row); |
| 30 | + $row = $this->attachAddresses($row); | |
| 27 | 31 | |
| 28 | 32 | return $this->getRowAsModel($row); |
| 29 | 33 | } |
| 30 | 34 | |
| @@ -33,8 +37,10 @@ | ||
| 33 | 37 | * |
| 34 | 38 | * @since 2.24.0 |
| 35 | 39 | * |
| 36 | 40 | * @return M[]|null |
| 41 | + * | |
| 42 | + * @param int $output For inheritance compatibility only, unused. | |
| 37 | 43 | */ |
| 38 | 44 | public function getAll($output = OBJECT) |
| 39 | 45 | { |
| 40 | 46 | $results = DB::get_results($this->getSQL(), OBJECT); |
| @@ -43,8 +49,9 @@ | ||
| 43 | 49 | return null; |
| 44 | 50 | } |
| 45 | 51 | |
| 46 | 52 | $results = $this->attachAdditionalEmails($results); |
| 53 | + $results = $this->attachAddresses($results); | |
| 47 | 54 | |
| 48 | 55 | if (isset($this->model)) { |
| 49 | 56 | return $this->getAllAsModel($results); |
| 50 | 57 | } |
| @@ -107,6 +114,109 @@ | ||
| 107 | 114 | return $additionalEmails[$donorIds[0]]; |
| 108 | 115 | } |
| 109 | 116 | |
| 110 | 117 | return $additionalEmails; |
| 118 | + } | |
| 119 | + | |
| 120 | + /** | |
| 121 | + * Attach addresses to query results later so that we can avoid additional Group By on the main query | |
| 122 | + * | |
| 123 | + * @since 4.4.0 | |
| 124 | + * | |
| 125 | + * @param array|object $queryResults | |
| 126 | + * | |
| 127 | + * @return array|object | |
| 128 | + */ | |
| 129 | + private function attachAddresses($queryResults) | |
| 130 | + { | |
| 131 | + if (is_array($queryResults)) { | |
| 132 | + $donorIds = wp_list_pluck($queryResults, 'id'); | |
| 133 | + $addresses = $this->getAddresses($donorIds); | |
| 134 | + $queryResults = array_map(function ($donor) use ($addresses) { | |
| 135 | + $donor->addresses = $addresses[$donor->id] ?? []; | |
| 136 | + | |
| 137 | + return $donor; | |
| 138 | + }, $queryResults); | |
| 139 | + } else { | |
| 140 | + $queryResults->addresses = $this->getAddresses([$queryResults->id], true) ?? []; | |
| 141 | + } | |
| 142 | + | |
| 143 | + return $queryResults; | |
| 144 | + } | |
| 145 | + | |
| 146 | + /** | |
| 147 | + * @since 4.4.0 | |
| 148 | + * | |
| 149 | + * @param array $donorIds Array of donor ids | |
| 150 | + * @param bool $single Return addresses for the first donor id | |
| 151 | + * | |
| 152 | + * @return array|null | |
| 153 | + */ | |
| 154 | + private function getAddresses(array $donorIds, bool $single = false) | |
| 155 | + { | |
| 156 | + // Get all address-related meta for the donors | |
| 157 | + $results = DB::table('give_donormeta') | |
| 158 | + ->select(['donor_id', 'donorId'], ['meta_key', 'metaKey'], ['meta_value', 'metaValue']) | |
| 159 | + ->whereIn('donor_id', $donorIds) | |
| 160 | + ->where(function($query) { | |
| 161 | + $query->where('meta_key', '_give_donor_address_billing_line1_%', 'LIKE') | |
| 162 | + ->orWhere('meta_key', '_give_donor_address_billing_line2_%', 'LIKE') | |
| 163 | + ->orWhere('meta_key', '_give_donor_address_billing_city_%', 'LIKE') | |
| 164 | + ->orWhere('meta_key', '_give_donor_address_billing_state_%', 'LIKE') | |
| 165 | + ->orWhere('meta_key', '_give_donor_address_billing_country_%', 'LIKE') | |
| 166 | + ->orWhere('meta_key', '_give_donor_address_billing_zip_%', 'LIKE'); | |
| 167 | + }) | |
| 168 | + ->getAll(); | |
| 169 | + | |
| 170 | + if (!$results) { | |
| 171 | + return null; | |
| 172 | + } | |
| 173 | + | |
| 174 | + // Group by donor and index to build addresses | |
| 175 | + $addressData = []; | |
| 176 | + foreach ($results as $result) { | |
| 177 | + $donorId = (int)$result->donorId; | |
| 178 | + | |
| 179 | + // Extract the address field type and index from the meta key | |
| 180 | + if (preg_match('/_give_donor_address_billing_(.+)_(\d+)$/', $result->metaKey, $matches)) { | |
| 181 | + $field = $matches[1]; | |
| 182 | + $index = (int)$matches[2]; | |
| 183 | + | |
| 184 | + if (!isset($addressData[$donorId][$index])) { | |
| 185 | + $addressData[$donorId][$index] = []; | |
| 186 | + } | |
| 187 | + | |
| 188 | + // Map field names to Address property names | |
| 189 | + $fieldMap = [ | |
| 190 | + 'line1' => 'address1', | |
| 191 | + 'line2' => 'address2', | |
| 192 | + 'city' => 'city', | |
| 193 | + 'state' => 'state', | |
| 194 | + 'country' => 'country', | |
| 195 | + 'zip' => 'zip' | |
| 196 | + ]; | |
| 197 | + | |
| 198 | + if (isset($fieldMap[$field])) { | |
| 199 | + $addressData[$donorId][$index][$fieldMap[$field]] = $result->metaValue; | |
| 200 | + } | |
| 201 | + } | |
| 202 | + } | |
| 203 | + | |
| 204 | + // Convert to Address objects | |
| 205 | + $addresses = []; | |
| 206 | + foreach ($addressData as $donorId => $donorAddresses) { | |
| 207 | + $addresses[$donorId] = []; | |
| 208 | + foreach ($donorAddresses as $addressArray) { | |
| 209 | + // Only create address if we have at least one field | |
| 210 | + if (!empty(array_filter($addressArray))) { | |
| 211 | + $addresses[$donorId][] = DonorAddress::fromArray($addressArray); | |
| 212 | + } | |
| 213 | + } | |
| 214 | + } | |
| 215 | + | |
| 216 | + if ($single) { | |
| 217 | + return $addresses[$donorIds[0]] ?? []; | |
| 218 | + } | |
| 219 | + | |
| 220 | + return $addresses; | |
| 111 | 221 | } |
| 112 | 222 | } |