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

Freegle / iznik-server / 2e982f03-a250-43d3-a894-4819c5574e24

03 Jun 2024 04:37PM UTC coverage: 94.863% (-0.006%) from 94.869%
2e982f03-a250-43d3-a894-4819c5574e24

push

circleci

edwh
Test fixes.

25429 of 26806 relevant lines covered (94.86%)

31.54 hits per line

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

82.94
/include/message/Attachment.php
1
<?php
2

3
namespace Freegle\Iznik;
4

5

6
use Jenssegers\ImageHash\ImageHash;
7

8
//use Google\Cloud\VideoIntelligence\V1\VideoIntelligenceServiceClient;
9
//use Google\Cloud\VideoIntelligence\V1\Feature;
10

11
# This is a base class
12
class Attachment {
13
    /** @var  $dbhr LoggedPDO */
14
    private $dbhr;
15
    /** @var  $dbhm LoggedPDO */
16
    private $dbhm;
17
    private $id, $table, $hash, $archived, $externalurl, $externaluid, $externalmods;
18

19
    /**
20
     * @return null
21
     */
22
    public function getId() {
23
        return $this->id;
1✔
24
    }
25

26
    const TYPE_MESSAGE = 'Message';
27
    const TYPE_GROUP = 'Group';
28
    const TYPE_NEWSLETTER = 'Newsletter';
29
    const TYPE_COMMUNITY_EVENT = 'CommunityEvent';
30
    const TYPE_CHAT_MESSAGE = 'ChatMessage';
31
    const TYPE_USER = 'User';
32
    const TYPE_NEWSFEED = 'Newsfeed';
33
    const TYPE_VOLUNTEERING = 'Volunteering';
34
    const TYPE_STORY = 'Story';
35
    const TYPE_NOTICEBOARD = 'Noticeboard';
36

37
    /**
38
     * @return mixed
39
     */
40
    public function getHash() {
41
        return $this->hash;
11✔
42
    }
43

44
    public function getPath($thumb = false, $id = null, $archived = false) {
45
        if ($this->externalurl) {
54✔
46
            return $this->externalurl;
2✔
47
        }
48

49
        # We serve up our attachment names as though they are files.
50
        # When these are fetched it will go through image.php
51
        $id = $id ? $id : $this->id;
52✔
52

53
        switch ($this->type) {
52✔
54
            case Attachment::TYPE_MESSAGE:
55
                $name = 'img';
34✔
56
                break;
34✔
57
            case Attachment::TYPE_GROUP:
58
                $name = 'gimg';
2✔
59
                break;
2✔
60
            case Attachment::TYPE_NEWSLETTER:
61
                $name = 'nimg';
2✔
62
                break;
2✔
63
            case Attachment::TYPE_COMMUNITY_EVENT:
64
                $name = 'cimg';
2✔
65
                break;
2✔
66
            case Attachment::TYPE_VOLUNTEERING:
67
                $name = 'oimg';
2✔
68
                break;
2✔
69
            case Attachment::TYPE_CHAT_MESSAGE:
70
                $name = 'mimg';
5✔
71
                break;
5✔
72
            case Attachment::TYPE_USER:
73
                $name = 'uimg';
1✔
74
                break;
1✔
75
            case Attachment::TYPE_NEWSFEED:
76
                $name = 'fimg';
1✔
77
                break;
1✔
78
            case Attachment::TYPE_STORY:
79
                $name = 'simg';
1✔
80
                break;
1✔
81
            case Attachment::TYPE_NOTICEBOARD:
82
                $name = 'bimg';
2✔
83
                break;
2✔
84
        }
85

86
        $name = $thumb ? "t$name" : $name;
52✔
87
        $domain = ($this->archived || $archived) ? IMAGE_ARCHIVED_DOMAIN : IMAGE_DOMAIN;
52✔
88

89
        return ("https://$domain/{$name}_$id.jpg");
52✔
90
    }
91

92
    public function getPublic() {
93
        $ret = array(
13✔
94
            'id' => $this->id,
13✔
95
            'hash' => $this->hash,
13✔
96
            $this->idatt => $this->{$this->idatt}
13✔
97
        );
13✔
98

99
        $ret['path'] = $this->getPath(false);
13✔
100
        $ret['paththumb'] = $this->getPath(true);
13✔
101
        $ret['mods'] = $this->externalmods;
13✔
102

103
        return ($ret);
13✔
104
    }
105

106
    function __construct(LoggedPDO $dbhr, LoggedPDO $dbhm, $id = null, $type = Attachment::TYPE_MESSAGE, $atts = null) {
107
        $this->dbhr = $dbhr;
368✔
108
        $this->dbhm = $dbhm;
368✔
109
        $this->id = $id;
368✔
110
        $this->type = $type;
368✔
111
        $this->archived = false;
368✔
112
        $url = '';
368✔
113
        $uid = 'externaluid';
368✔
114
        $mods = '';
368✔
115

116
        switch ($type) {
117
            case Attachment::TYPE_MESSAGE:
118
            {
256✔
119
                $this->table = 'messages_attachments';
256✔
120
                $this->idatt = 'msgid';
256✔
121
                $this->externalurlname = 'externalurl';
256✔
122
                $this->uidname = 'externaluid';
256✔
123
                $this->modsname = 'externalmods';
256✔
124
                $url = ', externalurl';
256✔
125
                $uid = ', externaluid';
256✔
126
                $mods = ', externalmods';
256✔
127
                break;
256✔
128
            }
256✔
129
            case Attachment::TYPE_GROUP:
130
                $this->table = 'groups_images';
216✔
131
                $this->idatt = 'groupid';
216✔
132
                break;
216✔
133
            case Attachment::TYPE_NEWSLETTER:
134
                $this->table = 'newsletters_images';
3✔
135
                $this->idatt = 'articleid';
3✔
136
                break;
3✔
137
            case Attachment::TYPE_COMMUNITY_EVENT:
138
                $this->table = 'communityevents_images';
2✔
139
                $this->idatt = 'eventid';
2✔
140
                break;
2✔
141
            case Attachment::TYPE_VOLUNTEERING:
142
                $this->table = 'volunteering_images';
2✔
143
                $this->idatt = 'opportunityid';
2✔
144
                break;
2✔
145
            case Attachment::TYPE_CHAT_MESSAGE:
146
                $this->table = 'chat_images';
6✔
147
                $this->idatt = 'chatmsgid';
6✔
148
                break;
6✔
149
            case Attachment::TYPE_USER:
150
            {
2✔
151
                $this->table = 'users_images';
2✔
152
                $this->idatt = 'userid';
2✔
153
                $this->externalurlname = 'externalurl';
2✔
154
                $url = ', url';
2✔
155
                break;
2✔
156
            }
2✔
157
            case Attachment::TYPE_NEWSFEED:
158
                $this->table = 'newsfeed_images';
2✔
159
                $this->idatt = 'newsfeedid';
2✔
160
                break;
2✔
161
            case Attachment::TYPE_STORY:
162
                $this->table = 'users_stories_images';
1✔
163
                $this->idatt = 'storyid';
1✔
164
                break;
1✔
165
            case Attachment::TYPE_NOTICEBOARD:
166
                $this->table = 'noticeboards_images';
2✔
167
                $this->idatt = 'noticeboardid';
2✔
168
                break;
2✔
169
        }
170

171
        if ($id) {
368✔
172
            $sql = "SELECT {$this->idatt}, hash, archived $url $uid $mods FROM {$this->table} WHERE id = ?;";
44✔
173
            $as = $atts ? [$atts] : $this->dbhr->preQuery($sql, [$id]);
44✔
174
            foreach ($as as $att) {
44✔
175
                $this->hash = $att['hash'];
34✔
176
                $this->archived = $att['archived'];
34✔
177
                $this->externalurl = Utils::presdef($this->externalurlname, $att, null);
34✔
178
                $this->externaluid = Utils::presdef($this->uidname, $att, null);
34✔
179
                $this->externalmods = Utils::presdef($this->modsname, $att, null);
34✔
180
                $this->{$this->idatt} = $att[$this->idatt];
34✔
181
            }
182
        }
183
    }
184

185
    public function create($id, $data, $uid = null, $url = null, $stripExif = TRUE, $mods = NULL) {
186
        if ($url) {
67✔
187
            if ($stripExif && UPLOADCARE_PUBLIC_KEY) {
2✔
188
                // Image data is held externally on uploadcare.  The uploaded photo will contain EXIF data, and there
189
                // isn't currently a way to strip that out on upload.  So we have to copy the image to a new one which
190
                // "bakes in" the removal of the EXIF data.
191
                $uc = new UploadCare();
×
192
                list($uid, $url) = $uc->stripExif($uid, $url);
×
193
                $this->hash = $uc->getPerceptualHash($uid);
×
194
            }
195

196
            $rc = $this->dbhm->preExec(
2✔
197
                "INSERT INTO {$this->table} (`{$this->idatt}`, `{$this->uidname}`, `{$this->externalurlname}`, `{$this->modsname}`, `hash`) VALUES (?, ?, ?, ?, ?);",
2✔
198
                [
2✔
199
                    $id,
2✔
200
                    $uid,
2✔
201
                    $url,
2✔
202
                    json_encode($mods),
2✔
203
                    $this->hash,
2✔
204
                ]
2✔
205
            );
2✔
206

207
            $imgid = $rc ? $this->dbhm->lastInsertId() : null;
2✔
208

209
            if ($imgid) {
2✔
210
                $this->id = $imgid;
2✔
211
                $this->externaluid = $uid;
2✔
212
                $this->externalmods = $mods;
2✔
213
                $this->externalurl = $url;
2✔
214
            }
215

216
            return ([$imgid, $uid, $url]);
2✔
217
        } else {
218
            # We generate a perceptual hash.  This allows us to spot duplicate or similar images later.
219
            $hasher = new ImageHash;
65✔
220
            $img = @imagecreatefromstring($data);
65✔
221
            $hash = $img ? $hasher->hash($img) : null;
65✔
222

223
            $rc = $this->dbhm->preExec(
65✔
224
                "INSERT INTO {$this->table} (`{$this->idatt}`, `data`, `hash`) VALUES (?, ?, ?);",
65✔
225
                [
65✔
226
                    $id,
65✔
227
                    $data,
65✔
228
                    $hash
65✔
229
                ]
65✔
230
            );
65✔
231

232
            $imgid = $rc ? $this->dbhm->lastInsertId() : null;
65✔
233

234
            if ($imgid) {
65✔
235
                $this->id = $imgid;
65✔
236
                $this->hash = $hash;
65✔
237
            }
238

239
            return ($imgid);
65✔
240
        }
241
    }
242

243
    public function getById($id) {
244
        $sql = "SELECT id FROM {$this->table} WHERE {$this->idatt} = ? AND ((data IS NOT NULL AND LENGTH(data) > 0) OR archived = 1) ORDER BY id;";
34✔
245
        $atts = $this->dbhr->preQuery($sql, [$id]);
34✔
246
        $ret = [];
34✔
247
        foreach ($atts as $att) {
34✔
248
            $ret[] = new Attachment($this->dbhr, $this->dbhm, $att['id']);
6✔
249
        }
250

251
        return ($ret);
34✔
252
    }
253

254
    public function getByIds($ids) {
255
        $ret = [];
105✔
256

257
        $extstr = '';
105✔
258
        $extwhere = '';
105✔
259

260
        switch ($this->type) {
105✔
261
            case Attachment::TYPE_MESSAGE:
262
                $extstr = ', externalurl, externaluid, externalmods ';
105✔
263
                $extwhere = ' OR externaluid IS NOT NULL';
105✔
264
                break;
105✔
265
        }
266

267
        if (count($ids)) {
105✔
268
            $sql = "SELECT id, {$this->idatt}, hash, archived $extstr FROM {$this->table} 
102✔
269
                       WHERE {$this->idatt} IN (" . implode(',', $ids) . ") 
102✔
270
                       AND ((data IS NOT NULL AND LENGTH(data) > 0) OR archived = 1 $extwhere) 
102✔
271
                       ORDER BY `primary` DESC, id;";
102✔
272
            #error_log($sql);
273
            $atts = $this->dbhr->preQuery($sql);
102✔
274
            foreach ($atts as $att) {
102✔
275
                $ret[] = new Attachment($this->dbhr, $this->dbhm, $att['id'], $this->type, $att);
9✔
276
            }
277
        }
278

279
        return ($ret);
105✔
280
    }
281

282
    public function getByImageIds($ids) {
283
        $ret = [];
2✔
284

285
        if (count($ids)) {
2✔
286
            $sql = "SELECT id, {$this->idatt}, hash, archived FROM {$this->table} WHERE id IN (" . implode(
2✔
287
                    ',',
2✔
288
                    $ids
2✔
289
                ) . ") AND ((data IS NOT NULL AND LENGTH(data) > 0) OR archived = 1) ORDER BY id;";
2✔
290
            $atts = $this->dbhr->preQuery($sql);
2✔
291
            foreach ($atts as $att) {
2✔
292
                $ret[] = new Attachment($this->dbhr, $this->dbhm, $att['id'], $this->type, $att);
1✔
293
            }
294
        }
295

296
        return ($ret);
2✔
297
    }
298

299
    public function scp($host, $data, $fn, &$failed) {
300
        $connection = @ssh2_connect($host, 22);
×
301
        $failed = true;
×
302

303
        if ($connection) {
×
304
            if (@ssh2_auth_pubkey_file(
×
305
                $connection,
×
306
                CDN_SSH_USER,
×
307
                CDN_SSH_PUBLIC_KEY,
×
308
                CDN_SSH_PRIVATE_KEY
×
309
            )) {
×
310
                $temp = tempnam(sys_get_temp_dir(), "img_archive_$fn");
×
311
                file_put_contents($temp, $data);
×
312
                $rem = "/var/www/iznik/images/$fn";
×
313
                $retry = 0;
×
314

315
                do {
316
                    $rc = ssh2_scp_send($connection, $temp, $rem, 0644);
×
317

318
                    if (!$rc) {
×
319
                        $msg = "SCP of $rem failed, retry $retry";
×
320
                        \Sentry\captureMessage($msg);
×
321
                        error_log($msg);
×
322
                        sleep(1);
×
323
                    }
324

325
                    $retry++;
×
326
                } while (!$rc && $retry < 5);
×
327
                $failed = !$rc;
×
328
                unlink($temp);
×
329
                error_log("scp $temp to $host $rem returned $rc failed? $failed");
×
330
            }
331

332
            # Exit gracefully - might help with file truncation.
333
            ssh2_exec($connection, 'exit');
×
334
        }
335
    }
336

337
    public function archive() {
338
        if ($this->externalurl) {
11✔
339
            // We don't archive external URLs.
340
            return;
1✔
341
        }
342

343
        # We archive out of the DB onto our two CDN image hosts.  This reduces load on the servers because we don't
344
        # have to serve the images up, and it also reduces the disk space we need within the DB (which is not an ideal
345
        # place to store large amounts of image data);
346
        #
347
        # If we fail then we leave it unchanged for next time.
348
        $data = $this->getData();
10✔
349
        $rc = true;
10✔
350

351
        if ($data) {
10✔
352
            $rc = false;
10✔
353

354
            try {
355
                $name = null;
10✔
356

357
                # Only these types are in archive_attachments.
358
                switch ($this->type) {
10✔
359
                    case Attachment::TYPE_MESSAGE:
360
                        $tname = 'timg';
1✔
361
                        $name = 'img';
1✔
362
                        break;
1✔
363
                    case Attachment::TYPE_CHAT_MESSAGE:
364
                        $tname = 'tmimg';
1✔
365
                        $name = 'mimg';
1✔
366
                        break;
1✔
367
                    case Attachment::TYPE_NEWSFEED:
368
                        $tname = 'tfimg';
1✔
369
                        $name = 'fimg';
1✔
370
                        break;
1✔
371
                    case Attachment::TYPE_COMMUNITY_EVENT:
372
                        $tname = 'tcimg';
1✔
373
                        $name = 'cimg';
1✔
374
                        break;
1✔
375
                    case Attachment::TYPE_NOTICEBOARD:
376
                        $tname = 'tbimg';
1✔
377
                        $name = 'bimg';
1✔
378
                        break;
1✔
379
                }
380

381
                if ($name) {
10✔
382
                    $failed = false;
5✔
383

384
                    foreach ([CDN_HOST_1, CDN_HOST_2] as $host) {
5✔
385
                        # Upload the thumbnail.  If this fails we'll leave it untouched.
386
                        $i = new Image($data);
5✔
387
                        if ($i->img) {
5✔
388
                            $i->scale(250, 250);
5✔
389
                            $thumbdata = $i->getData(100);
5✔
390
                            $this->scp($host, $thumbdata, "{$tname}_{$this->id}.jpg", $failed2);
5✔
391
                            $failed |= $failed2;
5✔
392
                            $this->scp($host, $data, "{$name}_{$this->id}.jpg", $failed3);
5✔
393
                            $failed |= $failed3;
5✔
394
                        } else {
395
                            error_log("...failed to create image {$this->id}");
×
396
                        }
397
                    }
398

399
                    $rc = !$failed;
10✔
400
                }
401
            } catch (\Exception $e) {
×
402
                error_log("Archive failed " . $e->getMessage());
×
403
            }
404
        }
405

406
        if ($rc) {
10✔
407
            # Remove from the DB.
408
            $sql = "UPDATE {$this->table} SET archived = 1, data = NULL WHERE id = {$this->id};";
5✔
409
            $this->archived = true;
5✔
410
            $this->dbhm->exec($sql);
5✔
411
        }
412

413
        return ($rc);
10✔
414
    }
415

416
    public function setData($data) {
417
        $this->dbhm->preExec("UPDATE {$this->table} SET archived = 0, data = ? WHERE id = ?;", [
2✔
418
            $data,
2✔
419
            $this->id
2✔
420
        ]);
2✔
421
    }
422

423
    public function fgc($url, $use_include_path, $ctx) {
424
        return @file_get_contents($url, $use_include_path, $ctx);
1✔
425
    }
426

427
    public function canRedirect() {
428
        if ($this->externalurl) {
46✔
429
            return $this->externalurl;
2✔
430
        } else {
431
            if ($this->archived) {
44✔
432
                # Only these types are in archive_attachments.
433
                switch ($this->type) {
5✔
434
                    case Attachment::TYPE_MESSAGE:
435
                        $tname = 'timg';
1✔
436
                        $name = 'img';
1✔
437
                        break;
1✔
438
                    case Attachment::TYPE_CHAT_MESSAGE:
439
                        $tname = 'tmimg';
1✔
440
                        $name = 'mimg';
1✔
441
                        break;
1✔
442
                    case Attachment::TYPE_NEWSFEED:
443
                        $tname = 'tfimg';
1✔
444
                        $name = 'fimg';
1✔
445
                        break;
1✔
446
                    case Attachment::TYPE_COMMUNITY_EVENT:
447
                        $tname = 'tcimg';
1✔
448
                        $name = 'cimg';
1✔
449
                        break;
1✔
450
                    case Attachment::TYPE_NOTICEBOARD:
451
                        $tname = 'tbimg';
1✔
452
                        $name = 'bimg';
1✔
453
                        break;
1✔
454
                }
455

456
                return 'https://' . IMAGE_ARCHIVED_DOMAIN . "/{$name}_{$this->id}.jpg";
5✔
457
            }
458
        }
459

460
        return false;
44✔
461
    }
462

463
    public function getData() {
464
        $ret = null;
45✔
465

466
        $url = $this->canRedirect();
45✔
467

468
        if ($url) {
45✔
469
            # This attachment has been archived out of our database, to a CDN.  Normally we would expect
470
            # that we wouldn't come through here, because we'd serve up an image link directly to the CDN, but
471
            # there is a timing window where we could archive after we've served up a link, so we have
472
            # to handle it.
473
            #
474
            # We fetch the data - not using SSL as we don't need to, and that host might not have a cert.  And
475
            # we put it back in the DB, because we are probably going to fetch it again.
476
            #
477
            # Apply a short timeout to avoid hanging the server if Azure is down.
478
            $ctx = stream_context_create(
6✔
479
                array(
6✔
480
                    'http' =>
6✔
481
                        array(
6✔
482
                            'timeout' => 2,
6✔
483
                        )
6✔
484
                )
6✔
485
            );
6✔
486

487
            $ret = $this->fgc($url, false, $ctx);
6✔
488
        } else {
489
            $sql = "SELECT * FROM {$this->table} WHERE id = ?;";
44✔
490
            $datas = $this->dbhr->preQuery($sql, [$this->id]);
44✔
491

492
            foreach ($datas as $data) {
44✔
493
                $ret = $data['data'];
44✔
494
            }
495
        }
496

497
        return ($ret);
45✔
498
    }
499

500
    public function identify() {
501
        # Identify objects in an attachment using Google Vision API.  Only for messages.
502
        $items = [];
3✔
503
        if ($this->type == Attachment::TYPE_MESSAGE) {
3✔
504
            $data = $this->getData();
3✔
505
            $base64 = base64_encode($data);
3✔
506

507
            $r_json = '{
3✔
508
                "requests": [
509
                    {
510
                      "image": {
511
                        "content":"' . $base64 . '"
3✔
512
                      },
513
                      "features": [
514
                          {
515
                            "type": "LABEL_DETECTION",
516
                            "maxResults": 20
517
                          }
518
                      ]
519
                    }
520
                ]
521
            }';
3✔
522

523
            $curl = curl_init();
3✔
524
            curl_setopt(
3✔
525
                $curl,
3✔
526
                CURLOPT_URL,
3✔
527
                'https://vision.googleapis.com/v1/images:annotate?key=' . GOOGLE_VISION_KEY
3✔
528
            );
3✔
529
            curl_setopt($curl, CURLOPT_RETURNTRANSFER, true);
3✔
530
            curl_setopt($curl, CURLOPT_HTTPHEADER, array("Content-type: application/json"));
3✔
531
            curl_setopt($curl, CURLOPT_POST, true);
3✔
532
            curl_setopt($curl, CURLOPT_POSTFIELDS, $r_json);
3✔
533
            $json_response = curl_exec($curl);
3✔
534
            $status = curl_getinfo($curl, CURLINFO_HTTP_CODE);
3✔
535

536
            if ($status) {
3✔
537
                $rsp = json_decode($json_response, true);
3✔
538
                #error_log("Identified {$this->id} by Google $json_response for $r_json");
539

540
                if ($rsp && array_key_exists('responses', $rsp) && count($rsp['responses']) > 0 && array_key_exists(
3✔
541
                        'labelAnnotations',
3✔
542
                        $rsp['responses'][0]
3✔
543
                    )) {
3✔
544
                    $rsps = $rsp['responses'][0]['labelAnnotations'];
3✔
545
                    $i = new Item($this->dbhr, $this->dbhm);
3✔
546

547
                    foreach ($rsps as $rsp) {
3✔
548
                        $found = $i->findByName($rsp['description']);
3✔
549
                        $wasfound = false;
3✔
550
                        foreach ($found as $item) {
3✔
551
                            $this->dbhm->background(
2✔
552
                                "INSERT INTO messages_attachments_items (attid, itemid) VALUES ({$this->id}, {$item['id']});"
2✔
553
                            );
2✔
554
                            $wasfound = true;
2✔
555
                        }
556

557
                        if (!$wasfound) {
3✔
558
                            # Record items which were suggested but not considered as items by us.  This allows us to find common items which we ought to
559
                            # add.
560
                            #
561
                            # This is usually because they're too vague.
562
                            $url = "https://" . IMAGE_DOMAIN . "/img_{$this->id}.jpg";
3✔
563
                            $this->dbhm->background(
3✔
564
                                "INSERT INTO items_non (name, lastexample) VALUES (" . $this->dbhm->quote(
3✔
565
                                    $rsp['description']
3✔
566
                                ) . ", " . $this->dbhm->quote(
3✔
567
                                    $url
3✔
568
                                ) . ") ON DUPLICATE KEY UPDATE popularity = popularity + 1, lastexample = " . $this->dbhm->quote(
3✔
569
                                    $url
3✔
570
                                ) . ";"
3✔
571
                            );
3✔
572
                        }
573

574
                        $items = array_merge($items, $found);
3✔
575
                    }
576
                }
577
            }
578

579
            curl_close($curl);
3✔
580
        }
581

582
        return ($items);
3✔
583
    }
584

585
    public function findWebReferences() {
586
        # Find a web page containing this imge, if any.
587
        $ret = null;
×
588

589
        if ($this->type == Attachment::TYPE_MESSAGE) {
×
590
            $data = $this->getData();
×
591
            $base64 = base64_encode($data);
×
592

593
            $r_json = '{
×
594
                "requests": [
595
                    {
596
                      "image": {
597
                        "content":"' . $base64 . '"
×
598
                      },
599
                      "features": [
600
                          {
601
                            "type": "WEB_DETECTION",
602
                            "maxResults": 1
603
                          }
604
                      ]
605
                    }
606
                ]
607
            }';
×
608

609
            $curl = curl_init();
×
610
            curl_setopt(
×
611
                $curl,
×
612
                CURLOPT_URL,
×
613
                'https://vision.googleapis.com/v1/images:annotate?key=' . GOOGLE_VISION_KEY
×
614
            );
×
615
            curl_setopt($curl, CURLOPT_RETURNTRANSFER, true);
×
616
            curl_setopt($curl, CURLOPT_HTTPHEADER, array("Content-type: application/json"));
×
617
            curl_setopt($curl, CURLOPT_POST, true);
×
618
            curl_setopt($curl, CURLOPT_POSTFIELDS, $r_json);
×
619
            $json_response = curl_exec($curl);
×
620
            $status = curl_getinfo($curl, CURLINFO_HTTP_CODE);
×
621

622
            if ($status) {
×
623
                $rsp = json_decode($json_response, true);
×
624
                #error_log("Identified {$this->id} by Google $json_response for $r_json");
625
                error_log("Matching " . var_export($rsp, true));
×
626

627
                if ($rsp &&
×
628
                    array_key_exists('responses', $rsp) &&
×
629
                    count($rsp['responses']) > 0 &&
×
630
                    array_key_exists('webDetection', $rsp['responses'][0]) &&
×
631
                    array_key_exists('pagesWithMatchingImages', $rsp['responses'][0]['webDetection'])) {
×
632
                    $rsps = $rsp['responses'][0]['webDetection']['pagesWithMatchingImages'];
×
633

634
                    foreach ($rsps as $r) {
×
635
                        if (array_key_exists('fullMatchingImages', $r) && strpos($r['url'], USER_SITE) === false) {
×
636
                            $ret = $r['url'];
×
637
                        }
638
                    }
639
                    error_log(var_export($rsps, true));
×
640
                }
641
            }
642

643
            curl_close($curl);
×
644
        }
645

646
        return ($ret);
×
647
    }
648

649
    public function ocr($data = null, $returnfull = false, $video = false) {
650
        # Identify text in an attachment using Google Vision API.
651
        $base64 = $data ? $data : base64_encode($this->getData());
1✔
652

653
        if ($video) {
1✔
654
//            "videoContext": {
655
//                "textDetectionConfig": {
656
//                    "languageHints": ["en"]
657
//                }
658
//              }
659
            $r_json = '{
×
660
              "inputContent": "' . $base64 . '",
×
661
              "features": ["TEXT_DETECTION"],
662
            }';
×
663
        } else {
664
            $r_json = '{
1✔
665
                "requests": [
666
                    {
667
                      "image": {
668
                        "content":"' . $base64 . '",
1✔
669
                      },
670
                      "features": [
671
                          {
672
                            "type": "TEXT_DETECTION"
673
                          }
674
                      ],
675
                      "imageContext": {
676
                        "languageHints": [
677
                          "en"
678
                        ]
679
                      }
680
                    }
681
                ]
682
            }';
1✔
683
        }
684

685
        $url = 'https://vision.googleapis.com/v1/images:annotate?key=' . GOOGLE_VISION_KEY;
1✔
686
        $curl = curl_init();
1✔
687
        curl_setopt($curl, CURLOPT_URL, $url);
1✔
688
        curl_setopt($curl, CURLOPT_RETURNTRANSFER, true);
1✔
689
        curl_setopt($curl, CURLOPT_HTTPHEADER, array("Content-type: application/json"));
1✔
690
        curl_setopt($curl, CURLOPT_POST, true);
1✔
691
        curl_setopt($curl, CURLOPT_POSTFIELDS, $r_json);
1✔
692

693
        if ($video) {
1✔
694
            curl_setopt($curl, CURLOPT_HTTPHEADER, array("Authorization: Bearer " . GOOGLE_VIDEO_KEY));
×
695
        }
696

697
        $json_response = curl_exec($curl);
1✔
698
        $status = curl_getinfo($curl, CURLINFO_HTTP_CODE);
1✔
699

700
        $text = '';
1✔
701
        $rsps = null;
1✔
702

703
        if ($status) {
1✔
704
            error_log("Rsp $json_response");
1✔
705
            $rsp = json_decode($json_response, true);
1✔
706

707
            if ($rsp && array_key_exists('responses', $rsp) && count($rsp['responses']) > 0 && array_key_exists(
1✔
708
                    'textAnnotations',
1✔
709
                    $rsp['responses'][0]
1✔
710
                )) {
1✔
711
                $rsps = $rsp['responses'][0]['textAnnotations'];
1✔
712

713
                foreach ($rsps as $rsp) {
1✔
714
                    $text .= $rsp['description'] . "\n";
1✔
715
                    break;
1✔
716
                }
717
            }
718
        }
719

720
        curl_close($curl);
1✔
721

722
        return ($returnfull ? $rsps : $text);
1✔
723
    }
724

725
    public function objects($data = null) {
726
        # Identify objects in an attachment using Google Vision API.
727
        $base64 = $data ? $data : base64_encode($this->getData());
1✔
728

729
        $r_json = '{
1✔
730
            "requests": [
731
                {
732
                  "image": {
733
                    "content":"' . $base64 . '"
1✔
734
                  },
735
                  "features": [
736
                      {
737
                        "type": "OBJECT_LOCALIZATION"
738
                      }
739
                  ]
740
                }
741
            ]
742
        }';
1✔
743

744
        $curl = curl_init();
1✔
745
        curl_setopt($curl, CURLOPT_URL, 'https://vision.googleapis.com/v1/images:annotate?key=' . GOOGLE_VISION_KEY);
1✔
746
        curl_setopt($curl, CURLOPT_RETURNTRANSFER, true);
1✔
747
        curl_setopt($curl, CURLOPT_HTTPHEADER, array("Content-type: application/json"));
1✔
748
        curl_setopt($curl, CURLOPT_POST, true);
1✔
749
        curl_setopt($curl, CURLOPT_POSTFIELDS, $r_json);
1✔
750
        $json_response = curl_exec($curl);
1✔
751
        $status = curl_getinfo($curl, CURLINFO_HTTP_CODE);
1✔
752

753
        $rsp = null;
1✔
754

755
        if ($status) {
1✔
756
            $rsp = json_decode($json_response, true);
1✔
757
        }
758

759
        curl_close($curl);
1✔
760

761
        return ($rsp);
1✔
762
    }
763

764
    public function setPrivate($att, $val) {
765
        $this->dbhm->preExec("UPDATE {$this->table} SET `$att` = ? WHERE id = {$this->id};", [$val]);
14✔
766
    }
767

768
    public function delete() {
769
        $this->dbhm->preExec("DELETE FROM {$this->table} WHERE id = {$this->id};");
10✔
770
    }
771

772
    public function getIdAtt() {
773
        return $this->idatt;
×
774
    }
775

776
    function rotate($rotate) {
777
        if ($this->externaluid) {
2✔
778
            # We can rotate this by changing external mods.
779
            $mods = json_decode($this->externalmods, TRUE);
×
780
            $mods['rotate'] = $rotate;
×
781
            $this->setPrivate('externalmods', json_encode($mods));
×
782
        } else {
783
            $data = $this->getData();
2✔
784
            $i = new Image($data);
2✔
785
            $i->rotate($rotate);
2✔
786
            $newdata = $i->getData(100);
2✔
787
            $this->setData($newdata);
2✔
788
        }
789

790
        if ($this->type == Attachment::TYPE_MESSAGE) {
2✔
791
            # Only some kinds of attachments record whether they are rotated.
792
            $this->recordRotate();
2✔
793
        }
794
    }
795

796
    public function recordRotate() {
797
        $this->setPrivate('rotated', 1);
2✔
798
    }
799
}
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