• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

Freegle / iznik-server / #2581

26 Jan 2026 03:17PM UTC coverage: 85.587% (+0.004%) from 85.583%
#2581

push

edwh
feat: Show phone consent status in cake list

Check modcakedate setting and show [Phone consent given] for users
who opted in after 2026-01-26 (when phone consent text was added).

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

25563 of 29868 relevant lines covered (85.59%)

30.51 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

93.17
/include/misc/PAF.php
1
<?php
2
namespace Freegle\Iznik;
3

4

5
require_once(IZNIK_BASE . '/include/misc/Location.php');
4✔
6
require_once(IZNIK_BASE . '/lib/Address.php');
4✔
7

8
class PAF
9
{
10
    # Most fields are foreign key references to other tables.
11
    private $idfields1 = [
12
        'posttown',
13
        'dependentlocality',
14
        'doubledependentlocality',
15
        'thoroughfaredescriptor',
16
        'dependentthoroughfaredescriptor',
17
    ];
18
    
19
    private $idfields2 = [
20
        'buildingname',
21
        'subbuildingname',
22
        'pobox',
23
        'departmentname',
24
        'organisationname'
25
    ];
26

27
    private $cache = [];
28

29
    function __construct(LoggedPDO $dbhr, LoggedPDO $dbhm)
30
    {
31
        $this->dbhr = $dbhr;
6✔
32
        $this->dbhm = $dbhm;
6✔
33
    }
34

35
    private function getRefId($table, $att, $val) {
36
        # Get an id from the appropriate table, inserting a new value if required.
37
        $id = NULL;
1✔
38

39
        if ($val && strlen(trim($val))) {
1✔
40
            if (!array_key_exists($table, $this->cache)) {
1✔
41
                $this->cache[$table] = [];
1✔
42
            }
43

44
            if (Utils::pres($val, $this->cache[$table])) {
1✔
45
                $id = $this->cache[$table][$val];
×
46
                #error_log("Got cached $val for $att = $val in $table");
47
            } else {
48
                $vals = $this->dbhr->preQuery("SELECT id FROM $table WHERE $att = ?;", [ $val ]);
1✔
49

50
                if (count($vals) > 0) {
1✔
51
                    $id = $vals[0]['id'];
×
52
                } else {
53
                    $this->dbhm->preExec("INSERT INTO $table ($att) VALUES (?);" , [ $val ], NULL, FALSE);
1✔
54
                    $id = $this->dbhm->lastInsertId();
1✔
55
                }
56

57
                $this->cache[$table][$val] = $id;
1✔
58
            }
59
        }
60

61
        return($id);
1✔
62
    }
63

64
    public function load($fn, $foutpref) {
65
        error_log("Input $fn, output to $foutpref");
1✔
66
        $l = new Location($this->dbhr, $this->dbhm);
1✔
67
        $fh = fopen($fn,'r');
1✔
68
        $count = 0;
1✔
69
        $unknowns = [];
1✔
70
        $csvfile = 0;
1✔
71
        $fhout = NULL;
1✔
72

73
        while ($row = fgets($fh)){
1✔
74
            # We generate CSV files so that we can use LOAD DATA INFILE.
75
            if ($count % 100000 == 0) {
1✔
76
                $fname = $foutpref . sprintf('%010d', $csvfile++) . ".csv";
1✔
77
                $fhout = fopen($fname, "w");
1✔
78

79
                # Clear cache to keep memory sane.
80
                $this->cache = [];
1✔
81
            }
82

83
            $fields = str_getcsv($row);
1✔
84
            $pcid = $l->findByName($fields[0]);
1✔
85
            #error_log("UDPRN {$fields[12]} found postcode $pcid from {$fields[0]}");
86

87
            if (!$pcid) {
1✔
88
                if (!in_array($fields[0], $unknowns)) {
×
89
                    error_log("Unknown postcode {$fields[0]}");
×
90
                    $unknowns[] = $fields[0];
×
91
                }
92
            } else {
93
                $csv = [ $pcid, $fields[12], $fields[6], $fields[13], $fields[14], $fields[15] ];
1✔
94

95
                $ix = 1;
1✔
96

97
                foreach ($this->idfields1 as $field) {
1✔
98
                    $csv[] = $this->getRefId("paf_$field", $field, $fields[$ix++]);
1✔
99
                }
100

101
                foreach ($this->idfields2 as $field) {
1✔
102
                    $csv[] = $this->getRefId("paf_$field", $field, $fields[$ix++]);
1✔
103
                }
104

105
                fputcsv($fhout, $csv);
1✔
106
            }
107

108
            $count++;
1✔
109

110
            if ($count % 1000 === 0) { error_log("...$count"); }
1✔
111
        }
112
    }
113

114
    public function update($fn) {
115
        $l = new Location($this->dbhr, $this->dbhm);
1✔
116
        $fh = fopen($fn,'r');
1✔
117
        $count = 0;
1✔
118
        $differs = 0;
1✔
119

120
        while ($row = fgets($fh)){
1✔
121
            # Parse the line.
122
            $fields = str_getcsv($row);
1✔
123
            $postcode = $l->findByName($fields[0]);
1✔
124
            #error_log("Found $postcode from {$fields[0]}");
125

126
            if (!$postcode) {
1✔
127
                error_log("Failed to find postcode for {$fields[0]} - is Doogal cron running OK?");
×
128
            } else {
129
                $udprn = $fields[12];
1✔
130
                $buildingnumber = $fields[6];
1✔
131
                $postcodetype = $fields[13];
1✔
132
                $suorganisationindicator = $fields[14];
1✔
133
                $deliverypointsuffix = $fields[15];
1✔
134

135
                $ix = 1;
1✔
136

137
                foreach ($this->idfields1 as $field) {
1✔
138
                    $$field = $this->getRefId("paf_$field", $field, $fields[$ix++]);
1✔
139
                }
140

141
                foreach ($this->idfields2 as $field) {
1✔
142
                    $$field = $this->getRefId("paf_$field", $field, $fields[$ix++]);
1✔
143
                }
144

145
                $addresses = $this->dbhr->preQuery("SELECT * FROM paf_addresses WHERE udprn = ?;", [ $udprn ]);
1✔
146
                foreach ($addresses as $address) {
1✔
147
                    # Compare the values
148
                    foreach ($address as $key => $val) {
1✔
149
                        if (!is_int($key) && $key != 'id') {
1✔
150
                            $v = str_replace('id', '', $key);
1✔
151
                            $$v = $$v == ' ' ? NULL : $$v;
1✔
152
                            $$v = $$v == '' ? NULL : $$v;
1✔
153

154
                            if ($val != $$v) {
1✔
155
                                error_log("UDPRN $udprn differs in $key $val => {$$v}");
1✔
156
                                $differs++;
1✔
157
                                $this->dbhm->preExec("UPDATE paf_addresses SET $key = ? WHERE id = ?;", [
1✔
158
                                    $$v,
1✔
159
                                    $address['id']
1✔
160
                                ]);
1✔
161
                            }
162
                        }
163
                    }
164
                }
165

166
                if (count($addresses) === 0) {
1✔
167
                    # This is a new entry.
168
                    $sql = "INSERT INTO paf_addresses (postcodeid, buildingnumber, postcodetype, suorganisationindicator, deliverypointsuffix";
1✔
169
                    $values = [];
1✔
170
                    $ix = 1;
1✔
171

172
                    foreach ($this->idfields1 as $field) {
1✔
173
                        $sql .= ", {$field}id";
1✔
174
                        $v = $$field;
1✔
175
                        $v = $v == ' ' ? NULL : $v;
1✔
176
                        $v = $v == '' ? NULL : $v;
1✔
177

178
                        $values[] = $v;
1✔
179
                    }
180

181
                    foreach ($this->idfields2 as $field) {
1✔
182
                        $sql .= ", {$field}id";
1✔
183
                        $v = $$field;
1✔
184
                        $v = $v == ' ' ? NULL : $v;
1✔
185
                        $v = $v == '' ? NULL : $v;
1✔
186

187
                        $values[] = $v;
1✔
188
                    }
189

190
                    $sql .= ") VALUES ($postcode, " . $this->dbhm->quote($buildingnumber) . ", " . $this->dbhm->quote($postcodetype) . ", " . $this->dbhm->quote($suorganisationindicator) . ", " . $this->dbhm->quote($deliverypointsuffix);
1✔
191
                    $ix = 0;
1✔
192

193
                    foreach ($this->idfields1 as $field) {
1✔
194
                        $val = $values[$ix++];
1✔
195
                        $sql .= ", " . ($val ? $this->dbhm->quote($val) : 'NULL');
1✔
196
                    }
197

198
                    foreach ($this->idfields2 as $field) {
1✔
199
                        $val = $values[$ix++];
1✔
200
                        $sql .= ", " . ($val ? $this->dbhm->quote($val) : 'NULL');
1✔
201
                    }
202

203
                    $sql .= ");";
1✔
204
                    $differs++;
1✔
205
                    $this->dbhm->preExec($sql);
1✔
206
                }
207
            }
208

209
            $count++;
1✔
210

211
            if ($count % 1000 === 0) { error_log("...$count"); }
1✔
212
        }
213

214
        return($differs);
1✔
215
    }
216

217
    public function listForPostcode($pc) {
218
        $ret = [];
1✔
219
        $l = new Location($this->dbhr, $this->dbhm);
1✔
220
        $pcid = $l->findByName($pc);
1✔
221

222
        error_log("$pcid for $pc");
1✔
223

224
        $ids = $this->dbhr->preQuery("SELECT id FROM paf_addresses WHERE postcodeid = $pcid;");
1✔
225
        foreach ($ids as $id) {
1✔
226
            $ret[] = $id['id'];
1✔
227
        }
228

229
        return($ret);
1✔
230
    }
231

232
    public function listForPostcodeId($pcid) {
233
        $ret = [];
1✔
234

235
        $ids = $this->dbhr->preQuery("SELECT id FROM paf_addresses WHERE postcodeid = $pcid;");
1✔
236
        foreach ($ids as $id) {
1✔
237
            $ret[] = $id['id'];
1✔
238
        }
239

240
        return($ret);
1✔
241
    }
242

243
    public function getSingleLine($id) {
244
        return($this->getFormatted($id, ', '));
6✔
245
    }
246

247
    private function tweak(&$addr) {
248
        # The third-party lib we use doesn't get it quite right.
249
        if (count($addr) >= 2 && strpos($addr[1], "{$addr[0]} ") === 0) {
6✔
250
            # House number which appears in the first line and the second line.
251
            $addr = array_slice($addr, 1);
×
252
        }
253

254
        if (count($addr) > 0) {
6✔
255
            $addr[0] = str_replace('PO Box Flat', 'Flat', $addr[0]);
×
256
            $addr[0] = str_replace('PO Box ', '', $addr[0]);
×
257
        }
258

259
        if (count($addr) >= 3 && strpos($addr[2], "{$addr[1]} ") === 0) {
6✔
260
            # House number which appears in the third line and the fourth line.
261
            unset($addr[1]);
×
262
        }
263
    }
264

265
    public function getFormatted($id, $delimiter) {
266
        $str = NULL;
6✔
267
        $a = new \AllenJB\PafUtils\Address;
6✔
268
        $sql = "SELECT locations.name AS postcode, paf_addresses.buildingnumber";
6✔
269

270
        $fields = array_merge($this->idfields1, $this->idfields2);
6✔
271

272
        foreach ($fields as $field) {
6✔
273
            $sql .= ", paf_$field.$field ";
6✔
274
        }
275

276
        $sql .= " FROM paf_addresses LEFT JOIN locations ON locations.id = paf_addresses.postcodeid ";
6✔
277

278
        foreach ($fields as $field) {
6✔
279
            $sql .= " LEFT JOIN paf_$field ON paf_$field.id = paf_addresses.{$field}id ";
6✔
280
        }
281

282
        $sql .= " WHERE paf_addresses.id = $id";
6✔
283

284
        $addresses = $this->dbhr->preQuery($sql);
6✔
285
        foreach ($addresses as $address) {
6✔
286
            foreach ($address as $key => $val) {
6✔
287
                switch ($key) {
288
                    case 'postcode': $a->setPostcode($val); break;
6✔
289
                    case 'buildingnumber':
6✔
290
                        if ($val != Utils::presdef('buildingname', $address, NULL)) {
6✔
291
                            $a->setBuildingNumber($val);
×
292
                        }
293
                        break;
6✔
294
                    case 'posttown': $a->setPostTown($val); break;
6✔
295
                    case 'dependentlocality': $a->setDependentLocality($val); break;
6✔
296
                    case 'doubledependentlocality': $a->setDoubleDependentLocality($val); break;
6✔
297
                    case 'thoroughfaredescriptor': $a->setThoroughfare($val); break;
6✔
298
                    case 'dependentthoroughfaredescriptor': $a->setDependentThoroughfare($val); break;
6✔
299
                    case 'buildingname': $a->setBuildingName($val); break;
6✔
300
                    case 'subbuildingname': $a->setSubBuildingName($val); break;
6✔
301
                    case 'pobox': $a->setPoBox($val); break;
6✔
302
                    case 'departmentname': $a->setDepartmentName($val); break;
6✔
303
                    case 'organisationname': $a->setOrganizationName($val); break;
6✔
304
                }
305
            }
306

307
            $addr = $a->getAddressLines();
6✔
308
            $this->tweak($addr);
6✔
309

310
            $str = implode($delimiter, $addr) . $delimiter . $a->getPostTown() . " " . $address['postcode'];
6✔
311
        }
312

313
        $str = strpos($str, ", ") === 0 ? substr($str, 2) : $str;
6✔
314

315
        return($str);
6✔
316
    }
317
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc