Completed
Push — development ( 43bb99...2fa2b9 )
by Thomas
03:02 queued 02:41
created

htdocs/lib2/logic/picture.class.php (3 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 = picture::pictureIdFromUUID($uuid);
29
        if ($pictureId == 0) {
30
            return null;
31
        }
32
33
        return new picture($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
        } else {
108
            return false;
109
        }
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 boolean $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
        } else {
241
            return false;
242
        }
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 integer $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
        } else {
377
            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...
378
        }
379
    }
380
381
    /**
382
     * @return bool|null
383
     */
384
    public function isVisibleOnCachePage()
385
    {
386
        if ($this->getObjectType() != OBJECT_CACHELOG) {
387
            return null;
388
        } else {
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
        } else {
421
            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...
422
        }
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
        } else {
477
            return false;
478
        }
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
        } else {
0 ignored issues
show
This else statement is empty and can be removed.

This check looks for the else branches of if statements that have no statements or where all statements have been commented out. This may be the result of changes for debugging or the code may simply be obsolete.

These else branches can be removed.

if (rand(1, 6) > 3) {
print "Check failed";
} else {
    //print "Check succeeded";
}

could be turned into

if (rand(1, 6) > 3) {
    print "Check failed";
}

This is much more concise to read.

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