Completed
Push — development ( 1b87d2...43bb99 )
by Thomas
06:02
created

htdocs/lib2/logic/picture.class.php (2 issues)

Upgrade to new PHP Analysis Engine

These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more

1
<?php
2
/***************************************************************************
3
 * for license information see LICENSE.md
4
 *
5
 *
6
 *   get/set has to be commited with save
7
 *   add/remove etc. is executed instantly
8
 ***************************************************************************/
9
10
require_once __DIR__ . '/const.inc.php';
11
12
class picture
13
{
14
    public $nPictureId = 0;
15
    public $rePicture;
16
    public $sFileExtension = '';
17
    public $bFilenamesSet = false;
18
19
    private $originalPosition;
20
21
    public static function pictureIdFromUUID($uuid)
22
    {
23
        return sql_value("SELECT `id` FROM `pictures` WHERE `uuid`='&1'", 0, $uuid);
24
    }
25
26
    public static function fromUUID($uuid)
27
    {
28
        $pictureId = self::pictureIdFromUUID($uuid);
29
        if ($pictureId == 0) {
30
            return null;
31
        }
32
33
        return new self($pictureId);
34
    }
35
36
    public function __construct($nNewPictureId = ID_NEW)
37
    {
38
        global $opt;
39
40
        $this->rePicture = new rowEditor('pictures');
41
        $this->rePicture->addPKInt('id', null, false, RE_INSERT_AUTOINCREMENT);
42
        $this->rePicture->addString('uuid', '', false, RE_INSERT_AUTOUUID);
43
        $this->rePicture->addInt('node', 0, false);
44
        $this->rePicture->addDate('date_created', time(), true, RE_INSERT_IGNORE);
45
        $this->rePicture->addDate('last_modified', time(), true, RE_INSERT_IGNORE);
46
        $this->rePicture->addString('url', '', false);
47
        $this->rePicture->addString('title', '', false);
48
        $this->rePicture->addDate('last_url_check', 0, true);
49
        $this->rePicture->addInt('object_id', null, false);
50
        $this->rePicture->addInt('object_type', null, false);
51
        $this->rePicture->addString('thumb_url', '', false);
52
        $this->rePicture->addDate('thumb_last_generated', 0, false);
53
        $this->rePicture->addInt('spoiler', 0, false);
54
        $this->rePicture->addInt('local', 0, false);
55
        $this->rePicture->addInt('unknown_format', 0, false);
56
        $this->rePicture->addInt('display', 1, false);
57
        $this->rePicture->addInt('mappreview', 0, false);
58
        $this->rePicture->addInt('seq', 0, false);
59
60
        $this->nPictureId = $nNewPictureId + 0;
61
62
        if ($nNewPictureId == ID_NEW) {
63
            $this->rePicture->addNew(null);
64
65
            $sUUID = mb_strtoupper(sql_value('SELECT UUID()', ''));
66
            $this->rePicture->setValue('uuid', $sUUID);
67
            $this->rePicture->setValue('node', $opt['logic']['node']['id']);
68
            $this->originalPosition = false;
69
        } else {
70
            $this->rePicture->load($this->nPictureId);
71
            $this->originalPosition = $this->getPosition();
72
73
            $sFilename = $this->getFilename();
74
            $fna = mb_split('\\.', $sFilename);
75
            $this->sFileExtension = mb_strtolower($fna[count($fna) - 1]);
76
            $this->bFilenamesSet = true;
77
        }
78
    }
79
80
    /**
81
     * @return bool
82
     */
83
    public function exist()
84
    {
85
        return $this->rePicture->exist();
86
    }
87
88
    /**
89
     * @param $sFilename
90
     * @return bool
91
     */
92
    public static function allowedExtension($sFilename)
93
    {
94
        global $opt;
95
96
        if (strpos($sFilename, ';') !== false) {
97
            return false;
98
        }
99
        if (strpos($sFilename, '.') === false) {
100
            return false;
101
        }
102
103
        $sExtension = mb_strtolower(substr($sFilename, strrpos($sFilename, '.') + 1));
104
105
        if (strpos(';' . $opt['logic']['pictures']['extensions'] . ';', ';' . $sExtension . ';') !== false) {
106
            return true;
107
        }
108
109
        return false;
110
    }
111
112
    /**
113
     * @param string $sFilename
114
     */
115
    public function setFilenames($sFilename)
116
    {
117
        global $opt;
118
119
        if ($this->bFilenamesSet == true) {
120
            return;
121
        }
122
        if (strpos($sFilename, '.') === false) {
123
            return;
124
        }
125
126
        $sExtension = mb_strtolower(substr($sFilename, strrpos($sFilename, '.') + 1));
127
        $this->sFileExtension = $sExtension;
128
129
        $sUUID = $this->getUUID();
130
131
        $this->setUrl($opt['logic']['pictures']['url'] . $sUUID . '.' . $sExtension);
132
133
        $this->bFilenamesSet = true;
134
    }
135
136
    /**
137
     * @return int
138
     */
139
    public function getPictureId()
140
    {
141
        return $this->nPictureId;
142
    }
143
144
    /**
145
     * @param bool $bRestoring
146
     * @param int $original_id
147
     */
148
    private function setArchiveFlag($bRestoring, $original_id = 0)
149
    {
150
        global $login;
151
152
        // This function determines if an insert, update oder deletion at pictures table
153
        // ist to be recorded for vandalism recovery, depending on WHO OR WHY the
154
        // operation is done. Other conditions, depending on the data, are handled
155
        // by triggers.
156
        //
157
        // Data is passed by ugly global DB variables, so try call this function as
158
        // close before the targetet DB operation as possible.
159
160
        if ($this->getObjectType() == 1) {
161
            $logger_id = sql_value(
162
                "SELECT
163
                    IFNULL((SELECT `user_id` FROM `cache_logs` WHERE `id`='&1'),
164
                           (SELECT `user_id` FROM `cache_logs_archived` WHERE `id`='&1'))",
165
                0,
166
                $this->getObjectId()
167
            );
168
            $archive = ($bRestoring || $login->userid != $logger_id);
169
        } else {
170
            $archive = true;
171
        }
172
173
        sql('SET @archive_picop=' . ($archive ? 'TRUE' : 'FALSE'));
174
        sql_slave('SET @archive_picop=' . ($archive ? 'TRUE' : 'FALSE'));
175
176
        sql("SET @original_picid='&1'", $original_id);
177
        sql_slave("SET @original_picid='&1'", $original_id);
178
179
        // @archive_picop and @original_picid are evaluated by trigger functions
180
    }
181
182
    private function resetArchiveFlag()
183
    {
184
        sql('SET @archive_picop=FALSE');
185
        sql('SET @original_picid=0');
186
        sql_slave('SET @archive_picop=FALSE');
187
        sql_slave('SET @original_picid=0');
188
    }
189
190
    /**
191
     * @return string
192
     */
193
    public function getUrl()
194
    {
195
        return $this->rePicture->getValue('url');
196
    }
197
198
    /**
199
     * @param string $value
200
     * @return bool
201
     */
202
    public function setUrl($value)
203
    {
204
        return $this->rePicture->setValue('url', $value);
205
    }
206
207
    /**
208
     * @return mixed
209
     */
210
    public function getThumbUrl()
211
    {
212
        return $this->rePicture->getValue('thumb_url');
213
    }
214
215
    /**
216
     * @param $value
217
     * @return bool
218
     */
219
    public function setThumbUrl($value)
220
    {
221
        return $this->rePicture->setValue('thumb_url', $value);
222
    }
223
224
    /**
225
     * @return mixed
226
     */
227
    public function getTitle()
228
    {
229
        return $this->rePicture->getValue('title');
230
    }
231
232
    /**
233
     * @param $value
234
     * @return bool
235
     */
236
    public function setTitle($value)
237
    {
238
        if ($value != '') {
239
            return $this->rePicture->setValue('title', $value);
240
        }
241
242
        return false;
243
    }
244
245
    /**
246
     * @return bool
247
     */
248
    public function getSpoiler()
249
    {
250
        return $this->rePicture->getValue('spoiler') != 0;
251
    }
252
253
    /**
254
     * @param $value
255
     * @return bool
256
     */
257
    public function setSpoiler($value)
258
    {
259
        return $this->rePicture->setValue('spoiler', $value ? 1 : 0);
260
    }
261
262
    /**
263
     * @return bool
264
     */
265
    public function getLocal()
266
    {
267
        return $this->rePicture->getValue('local') != 0;
268
    }
269
270
    /**
271
     * @param int $value
272
     * @return bool
273
     */
274
    public function setLocal($value)
275
    {
276
        return $this->rePicture->setValue('local', $value ? 1 : 0);
277
    }
278
279
    /**
280
     * @return bool
281
     */
282
    public function getUnknownFormat()
283
    {
284
        return $this->rePicture->getValue('unknown_format') != 0;
285
    }
286
287
    /**
288
     * @param $value
289
     * @return bool
290
     */
291
    public function setUnknownFormat($value)
292
    {
293
        return $this->rePicture->setValue('unknown_format', $value ? 1 : 0);
294
    }
295
296
    /**
297
     * @return bool
298
     */
299
    public function getDisplay()
300
    {
301
        return $this->rePicture->getValue('display') != 0;
302
    }
303
304
    /**
305
     * @param $value
306
     * @return bool
307
     */
308
    public function setDisplay($value)
309
    {
310
        return $this->rePicture->setValue('display', $value ? 1 : 0);
311
    }
312
313
    /**
314
     * @return bool
315
     */
316
    public function getMapPreview()
317
    {
318
        return $this->rePicture->getValue('mappreview') != 0;
319
    }
320
321
    /**
322
     * @param $value
323
     * @return bool
324
     */
325
    public function setMapPreview($value)
326
    {
327
        return $this->rePicture->setValue('mappreview', $value ? 1 : 0);
328
    }
329
330
    /**
331
     * @return string
332
     */
333
    public function getFilename()
334
    {
335
        // works intendently before bFilenameSet == true !
336
        global $opt;
337
338 View Code Duplication
        if (mb_substr($opt['logic']['pictures']['dir'], -1, 1) != '/') {
339
            $opt['logic']['pictures']['dir'] .= '/';
340
        }
341
342
        $url = $this->getUrl();
343
        $fna = mb_split('\\/', $url);
344
345
        return $opt['logic']['pictures']['dir'] . end($fna);
346
    }
347
348
    /**
349
     * @return string
350
     */
351
    public function getThumbFilename()
352
    {
353
        global $opt;
354
355 View Code Duplication
        if (mb_substr($opt['logic']['pictures']['thumb_dir'], -1, 1) != '/') {
356
            $opt['logic']['pictures']['thumb_dir'] .= '/';
357
        }
358
359
        $url = $this->getUrl();
360
        $fna = mb_split('\\/', $url);
361
        $filename = end($fna);
362
363
        $dir1 = mb_strtoupper(mb_substr($filename, 0, 1));
364
        $dir2 = mb_strtoupper(mb_substr($filename, 1, 1));
365
366
        return $opt['logic']['pictures']['thumb_dir'] . $dir1 . '/' . $dir2 . '/' . $filename;
367
    }
368
369
    /**
370
     * @return string
371
     */
372
    public function getLogId()
373
    {
374
        if ($this->getObjectType() == OBJECT_CACHELOG) {
375
            return $this->getObjectId();
376
        }
377
378
        return false;
0 ignored issues
show
Bug Best Practice introduced by
The return type of return false; (false) is incompatible with the return type documented by picture::getLogId of type string.

If you return a value from a function or method, it should be a sub-type of the type that is given by the parent type f.e. an interface, or abstract method. This is more formally defined by the Lizkov substitution principle, and guarantees that classes that depend on the parent type can use any instance of a child type interchangably. This principle also belongs to the SOLID principles for object oriented design.

Let’s take a look at an example:

class Author {
    private $name;

    public function __construct($name) {
        $this->name = $name;
    }

    public function getName() {
        return $this->name;
    }
}

abstract class Post {
    public function getAuthor() {
        return 'Johannes';
    }
}

class BlogPost extends Post {
    public function getAuthor() {
        return new Author('Johannes');
    }
}

class ForumPost extends Post { /* ... */ }

function my_function(Post $post) {
    echo strtoupper($post->getAuthor());
}

Our function my_function expects a Post object, and outputs the author of the post. The base class Post returns a simple string and outputting a simple string will work just fine. However, the child class BlogPost which is a sub-type of Post instead decided to return an object, and is therefore violating the SOLID principles. If a BlogPost were passed to my_function, PHP would not complain, but ultimately fail when executing the strtoupper call in its body.

Loading history...
379
    }
380
381
    /**
382
     * @return bool|null
383
     */
384
    public function isVisibleOnCachePage()
385
    {
386
        if ($this->getObjectType() != OBJECT_CACHELOG) {
387
            return null;
388
        }
389
        $rs = sql(
390
                "SELECT `id`
391
                 FROM `cache_logs`
392
                 WHERE `cache_id`='&1'
393
                 ORDER BY `date`, `id` DESC
394
                 LIMIT &2",
395
                $this->getCacheId(),
396
                MAX_LOGENTRIES_ON_CACHEPAGE
397
            );
398
        
399
        $firstlogs = false;
400
        while ($r = sql_fetch_assoc($rs)) {
401
            if ($r['id'] == $this->getLogId()) {
402
                $firstlogs = true;
403
            }
404
        }
405
406
        sql_free_result($rs);
407
408
        return $firstlogs;
409
    }
410
411
    /**
412
     * @return string
413
     */
414 View Code Duplication
    public function getCacheId()
415
    {
416
        if ($this->getObjectType() == OBJECT_CACHELOG) {
417
            return sql_value("SELECT `cache_id` FROM `cache_logs` WHERE `id`='&1'", false, $this->getObjectId());
418
        } elseif ($this->getObjectType() == OBJECT_CACHE) {
419
            return $this->getObjectId();
420
        }
421
422
        return false;
0 ignored issues
show
Bug Best Practice introduced by
The return type of return false; (false) is incompatible with the return type documented by picture::getCacheId of type string.

If you return a value from a function or method, it should be a sub-type of the type that is given by the parent type f.e. an interface, or abstract method. This is more formally defined by the Lizkov substitution principle, and guarantees that classes that depend on the parent type can use any instance of a child type interchangably. This principle also belongs to the SOLID principles for object oriented design.

Let’s take a look at an example:

class Author {
    private $name;

    public function __construct($name) {
        $this->name = $name;
    }

    public function getName() {
        return $this->name;
    }
}

abstract class Post {
    public function getAuthor() {
        return 'Johannes';
    }
}

class BlogPost extends Post {
    public function getAuthor() {
        return new Author('Johannes');
    }
}

class ForumPost extends Post { /* ... */ }

function my_function(Post $post) {
    echo strtoupper($post->getAuthor());
}

Our function my_function expects a Post object, and outputs the author of the post. The base class Post returns a simple string and outputting a simple string will work just fine. However, the child class BlogPost which is a sub-type of Post instead decided to return an object, and is therefore violating the SOLID principles. If a BlogPost were passed to my_function, PHP would not complain, but ultimately fail when executing the strtoupper call in its body.

Loading history...
423
    }
424
425
    /**
426
     * @return mixed
427
     */
428
    public function getObjectId()
429
    {
430
        return $this->rePicture->getValue('object_id');
431
    }
432
433
    /**
434
     * @param $value
435
     * @return bool
436
     */
437
    public function setObjectId($value)
438
    {
439
        return $this->rePicture->setValue('object_id', $value + 0);
440
    }
441
442
    /**
443
     * @return mixed
444
     */
445
    public function getObjectType()
446
    {
447
        return $this->rePicture->getValue('object_type');
448
    }
449
450
    /**
451
     * @param $value
452
     * @return bool
453
     */
454
    public function setObjectType($value)
455
    {
456
        return $this->rePicture->setValue('object_type', $value + 0);
457
    }
458
459
    /**
460
     * @return bool|mixed
461
     */
462 View Code Duplication
    public function getUserId()
463
    {
464
        if ($this->getObjectType() == OBJECT_CACHE) {
465
            return sql_value(
466
                "SELECT `caches`.`user_id` FROM `caches` WHERE `caches`.`cache_id`='&1'",
467
                false,
468
                $this->getObjectId()
469
            );
470
        } elseif ($this->getObjectType() == OBJECT_CACHELOG) {
471
            return sql_value(
472
                "SELECT `cache_logs`.`user_id` FROM `cache_logs` WHERE `cache_logs`.`id`='&1'",
473
                false,
474
                $this->getObjectId()
475
            );
476
        }
477
478
        return false;
479
    }
480
481
    /**
482
     * @return mixed
483
     */
484
    public function getNode()
485
    {
486
        return $this->rePicture->getValue('node');
487
    }
488
489
    /**
490
     * @param $value
491
     * @return bool
492
     */
493
    public function setNode($value)
494
    {
495
        return $this->rePicture->setValue('node', $value);
496
    }
497
498
    /**
499
     * @return mixed
500
     */
501
    public function getUUID()
502
    {
503
        return $this->rePicture->getValue('uuid');
504
    }
505
506
    /**
507
     * @return mixed
508
     */
509
    public function getLastModified()
510
    {
511
        return $this->rePicture->getValue('last_modified');
512
    }
513
514
    /**
515
     * @return mixed
516
     */
517
    public function getDateCreated()
518
    {
519
        return $this->rePicture->getValue('date_created');
520
    }
521
522
    /**
523
     * @return mixed
524
     */
525
    public function getPosition()
526
    {
527
        return $this->rePicture->getValue('seq');
528
    }
529
530
    /**
531
     * @param int $position
532
     */
533
    public function setPosition($position)
534
    {
535
        if ($this->originalPosition === false) {
536
            // position numbers are always >= 1
537
            $this->rePicture->setValue('seq', max(1, $position));
538
        }
539
        // repositioning existing pictures is not implemented yet
540
    }
541
542
    /**
543
     * @return bool|null
544
     */
545
    public function getAnyChanged()
546
    {
547
        return $this->rePicture->getAnyChanged();
548
    }
549
550
    // Test if the picture can be discarded as duplicate.
551
    // This is a quick test for Ocprop dups and may be extended for any
552
    // picture uploads by comparing the file sizes and contents.
553
554
    /**
555
     * @return bool
556
     */
557
    public function is_duplicate()
558
    {
559
        global $ocpropping;
560
561
        return $ocpropping &&
562
        sql_value(
563
            "
564
            SELECT COUNT(*) FROM `pictures`
565
            WHERE `object_type`='&1' AND `object_id`='&2' AND `title`='&3'",
566
            0,
567
            $this->getObjectType(),
568
            $this->getObjectId(),
569
            $this->getTitle()
570
        ) > 0;
571
    }
572
573
    /**
574
     * return true if successful (with insert)
575
     *
576
     * @param bool $restore
577
     * @param int $original_id
578
     * @param string $original_url
579
     * @return bool
580
     */
581
    public function save($restore = false, $original_id = 0, $original_url = '')
582
    {
583
        $undelete = ($original_id != 0);
584
585
        if ($undelete) {
586
            if ($this->bFilenamesSet == true) {
587
                return false;
588
            }
589
            // restore picture file
590
                $this->setUrl($original_url);     // set the url, so that we can
591
                $filename = $this->getFilename(); // .. retrieve the file path+name
592
                $this->setFilenames($filename);   // now set url(s) from the new uuid
593
                try {
594
                    rename($this->deletedFilename($filename), $this->getFilename());
595
                } catch (Exception $e) {
596
                    // @todo implement logging
597
                }
598
        }
599
600
        if ($this->bFilenamesSet == false) {
601
            return false;
602
        }
603
604
        // produce a position number gap for inserting
605
        sql(
606
            "UPDATE `pictures`
607
             SET `seq`=`seq`+1
608
             WHERE `object_type`='&1' AND `object_id`='&2' AND `seq` >= '&3'
609
             ORDER BY `seq` DESC",
610
            $this->getObjectType(),
611
            $this->getObjectId(),
612
            $this->getPosition()
613
        );
614
615
        // The seq numbers need not to be consecutive, so it dosen't matter
616
        // if something fails here and leaves the gap open.
617
618
        $this->setArchiveFlag($restore, $original_id);
619
        $bRetVal = $this->rePicture->save();
620
        $this->resetArchiveFlag();
621
622
        if ($bRetVal) {
623
            $this->nPictureId = $this->rePicture->getValue('id');
624
            if ($this->getObjectType() == OBJECT_CACHE && $this->getMapPreview()) {
625
                sql(
626
                    "UPDATE `pictures` SET `mappreview`= 0
627
                     WHERE `object_type`='&1' AND `object_id`='&2' AND `id`!='&3'",
628
                    OBJECT_CACHE,
629
                    $this->getObjectId(),
630
                    $this->getPictureId()
631
                );
632
            }
633
            sql_slave_exclude();
634
        }
635
636
        return $bRetVal;
637
    }
638
639
    /**
640
     * @param bool $restore
641
     * @return bool
642
     */
643
    public function delete($restore = false)
644
    {
645
        // see also removelog.php, 'remove log pictures'
646
        // delete record, image and thumb
647
        $this->setArchiveFlag($restore);
648
        sql("DELETE FROM `pictures` WHERE `id`='&1'", $this->nPictureId);
649
        $this->resetArchiveFlag();
650
        $filename = $this->getFilename();
651
652
        // archive picture if picture record has been archived
653
        if (sql_value("SELECT `id` FROM `pictures_modified` WHERE `id`='&1'", 0, $this->getPictureId()) != 0) {
654
            try {
655
                rename($filename, $this->deletedFilename($filename));
656
            } catch (Exception $e) {
657
                // @todo implement logging
658
            }
659
        } else {
660
            try {
661
                unlink($filename);
662
            } catch (Exception $e) {
663
                // @todo implement logging
664
            }
665
        }
666
667
        try {
668
            unlink($this->getThumbFilename());
669
        } catch (Exception $e) {
670
            // @todo implement logging
671
        }
672
673
        return true;
674
    }
675
676
    /**
677
     * @param string $filename
678
     *
679
     * @return string
680
     */
681
    private function deletedFilename($filename)
682
    {
683
        $fna = mb_split('\\/', $filename);
684
        $fna[] = end($fna);
685
        $fna[count($fna) - 2] = 'deleted';
686
        $dp = '';
687
        foreach ($fna as $fp) {
688
            $dp .= '/' . $fp;
689
        }
690
691
        return substr($dp, 1);
692
    }
693
694 View Code Duplication
    public function allowEdit()
695
    {
696
        global $login;
697
698
        $login->verify();
699
700
        if (sql_value(
701
            "SELECT COUNT(*)
702
            FROM `caches`
703
            INNER JOIN `cache_status` ON `caches`.`status`=`cache_status`.`id`
704
            WHERE (`cache_status`.`allow_user_view`=1 OR `caches`.`user_id`='&1')
705
            AND `caches`.`cache_id`='&2'",
706
            0,
707
            $login->userid,
708
            $this->getCacheId()
709
        ) == 0) {
710
            return false;
711
        } elseif ($this->getUserId() == $login->userid) {
712
            return true;
713
        }
714
715
        return false;
716
    }
717
718
    /**
719
     * @return bool|string
720
     */
721
    public function getPageLink()
722
    {
723
        if ($this->getObjectType() == OBJECT_CACHELOG) {
724
            $pl = 'viewcache.php?cacheid=' . urlencode($this->getCacheId());
725
            if (!$this->isVisibleOnCachePage()) {
726
                $pl .= '&log=A';
727
            }
728
            $pl .= '#log' . urlencode($this->getLogId());
729
        } elseif ($this->getObjectType() == OBJECT_CACHE) {
730
            $pl = 'editcache.php?cacheid=' . urlencode($this->getCacheId()) . '#pictures';
731
        } else {
732
            $pl = false;
733
        }
734
735
        return $pl;
736
    }
737
738
    /*
739
        Shrink picture to a specified maximum size. If present Imagemagick extension will be used, if not gd.
740
        Imagick is sharper, faster, need less memory and supports more types.
741
        For gd size is limited to 5000px (memory consumption).
742
        i prefer FILTER_CATROM because its faster but similiar to lanczos see http://de1.php.net/manual/de/imagick.resizeimage.php
743
        parameter:
744
        $tmpfile: full name of uploaded file
745
        $longSideSize:  if longer side of picture > $longSideSize, then it will be prop. shrinked to
746
        returns: true if no error occur, otherwise false
747
748
     * @param $tmpFile
749
     * @param $longSideSize
750
     * @return bool
751
     */
752
    public function rotate_and_shrink($tmpFile, $longSideSize)
753
    {
754
        global $opt;
755
        if (extension_loaded('imagick')) {
756
            try {
757
                $image = new Imagick();
758
                $image->readImage($tmpFile);
759
                $this->imagick_rotate($image);
760
                $w = $image->getImageWidth();
761
                $h = $image->getImageHeight();
762
                $image->setImageResolution(PICTURE_RESOLUTION, PICTURE_RESOLUTION);
763
                $image->setImageCompression(Imagick::COMPRESSION_JPEG);
764
                $image->setImageCompressionQuality(PICTURE_QUALITY);
765
                $image->stripImage(); //clears exif, private data
766
                //$newSize=$w<$h?array($w*$longSideSize/$h,$longSideSize):array($longSideSize,$h*$longSideSize/$w);
767
                if (max($w, $h) > $longSideSize) {
768
                    $image->resizeImage($longSideSize, $longSideSize, imagick::FILTER_CATROM, 1, true);
769
                }
770
                $result = $image->writeImage($this->getFilename());
771
                $image->clear();
772
            } catch (Exception $e) {
773
                if ($image) {
774
                    $image->clear();
775
                }
776
                if ($opt['debug'] & DEBUG_DEVELOPER) {
777
                    die($e);
778
                }
779
                $result = false;
780
            }
781
782
            return $result;
783
        } elseif (extension_loaded('gd')) {
784
            $imageNew = null;
785
            try {
786
                $image = imagecreatefromstring(file_get_contents($tmpFile));
787
                $w = imagesx($image);
788
                $h = imagesy($image);
789
                if (max($w, $h) > 5000) {
790
                    throw new Exception('Image too large >5000px');
791
                }
792
                if (max($w, $h) <= $longSideSize) {
793
                    $result = imagejpeg($image, $this->getFilename(), PICTURE_QUALITY);
794
                } else {
795
                    $newSize = $w < $h ? [
796
                        $w * $longSideSize / $h,
797
                        $longSideSize,
798
                    ] : [
799
                        $longSideSize,
800
                        $h * $longSideSize / $w,
801
                    ];
802
                    $imageNew = imagecreatetruecolor($newSize[0], $newSize[1]);
803
                    imagecopyresampled($imageNew, $image, 0, 0, 0, 0, $newSize[0], $newSize[1], $w, $h);
804
                    $result = imagejpeg($imageNew, $this->getFilename(), PICTURE_QUALITY);
805
                    imagedestroy($imageNew);
806
                }
807
                imagedestroy($image);
808
            } catch (Exception $e) {
809
                if ($image) {
810
                    imagedestroy($image);
811
                }
812
                if ($imageNew) {
813
                    imagedestroy($imageNew);
814
                }
815
                if ($opt['debug'] & DEBUG_DEVELOPER) {
816
                    die($e);
817
                }
818
                $result = false;
819
            }
820
821
            return $result;
822
        }
823
824
        return false;
825
    }
826
827
    /**
828
     * rotate image according to EXIF orientation
829
     *
830
     * @param $tmpFile
831
     * @return bool
832
     */
833
    public function rotate($tmpFile)
834
    {
835
        if (extension_loaded('imagick')) {
836
            try {
837
                $image = new Imagick();
838
                $image->readImage($tmpFile);
839
                if ($this->imagick_rotate($image)) {
840
                    $image->stripImage(); // clears exif, private data
841
                    $image->writeImage($this->getFilename());
842
                    $image->clear();
843
844
                    return true;
845
                }
846
                $image->clear();
847
            } catch (Exception $e) {
848
                if ($image) {
849
                    $image->clear();
850
                }
851
852
                return false;
853
            }
854
        }
855
856
        return move_uploaded_file($tmpFile, $this->getFilename());
857
    }
858
859
    /**
860
     * @param Imagick $image
861
     *
862
     * @return bool
863
     */
864
    public function imagick_rotate(&$image)
865
    {
866
        $exif = $image->getImageProperties();
867
        if (isset($exif['exif:Orientation'])) {
868
            switch ($exif['exif:Orientation']) {
869
                case 3:
870
                    return $image->rotateImage(new ImagickPixel(), 180);
871
                case 6:
872
                    return $image->rotateImage(new ImagickPixel(), 90);
873
                case 8:
874
                    return $image->rotateImage(new ImagickPixel(), -90);
875
            }
876
        }
877
878
        return false;
879
    }
880
881
    /**
882
     * @return bool
883
     */
884
    public function up()
885
    {
886
        // TODO: protect the following operations by a transaction
887
888
        $prevPos = sql_value(
889
            "
890
            SELECT MAX(`seq`)
891
            FROM `pictures`
892
            WHERE `object_type`='&1' AND `object_id`='&2' AND `seq`<'&3'",
893
            0,
894
            $this->getObjectType(),
895
            $this->getObjectId(),
896
            $this->getPosition()
897
        );
898
899
        if ($prevPos) {
900
            $maxPos = sql_value(
901
                "
902
                SELECT MAX(`seq`)
903
                FROM `pictures`
904
                WHERE `object_type`='&1' AND `object_id`='&2'",
905
                0,
906
                $this->getObjectType(),
907
                $this->getObjectId()
908
            );
909
910
            // swap positions with the previous pic
911
            sql(
912
                "
913
                UPDATE `pictures`
914
                SET `seq`='&2'
915
                WHERE `id`='&1'",
916
                $this->getPictureId(),
917
                $maxPos + 1
918
            );
919
            // If something goes wrong from here (i.e. DB server failure) and
920
            // we are moving anything else but the last picture, it will
921
            // produce a wrong picture order. No big deal, the user can easily
922
            // correct that, but still a transaction would be nice.
923
            sql(
924
                "
925
                UPDATE `pictures` SET `seq`='&4'
926
                WHERE `object_type`='&1' AND `object_id`='&2' AND `seq`='&3'",
927
                $this->getObjectType(),
928
                $this->getObjectId(),
929
                $prevPos,
930
                $this->getPosition()
931
            );
932
            sql(
933
                "
934
                UPDATE `pictures`
935
                SET `seq`='&2'
936
                WHERE `id`='&1'",
937
                $this->getPictureId(),
938
                $prevPos
939
            );
940
            $this->rePicture->setValue('seq', $prevPos);
941
942
            return true;
943
        }
944
945
        return false;
946
    }
947
}
948