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

heimrichhannot / contao-utils-bundle / 3766097959

pending completion
3766097959

push

github

Dennis Patzer
see cl 2.223.3

2 of 2 new or added lines in 1 file covered. (100.0%)

1120 of 5238 relevant lines covered (21.38%)

1.55 hits per line

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

0.0
/src/Image/ImageUtil.php
1
<?php
2

3
/*
4
 * Copyright (c) 2022 Heimrich & Hannot GmbH
5
 *
6
 * @license LGPL-3.0-or-later
7
 */
8

9
namespace HeimrichHannot\UtilsBundle\Image;
10

11
use Contao\Config;
12
use Contao\Controller;
13
use Contao\CoreBundle\Framework\ContaoFramework;
14
use Contao\CoreBundle\Monolog\ContaoContext;
15
use Contao\File;
16
use Contao\FilesModel;
17
use Contao\Frontend;
18
use Contao\FrontendTemplate;
19
use Contao\StringUtil;
20
use Contao\System;
21
use Contao\Validator;
22
use Psr\Log\LogLevel;
23
use Symfony\Component\DependencyInjection\ContainerInterface;
24

25
class ImageUtil
26
{
27
    /**
28
     * @var ContaoFramework
29
     */
30
    protected $framework;
31
    /**
32
     * @var ContainerInterface
33
     */
34
    protected $container;
35

36
    public function __construct(ContainerInterface $container)
37
    {
38
        $this->framework = $container->get('contao.framework');
×
39
        $this->container = $container;
×
40
    }
41

42
    /**
43
     * Add an image to a template.
44
     *
45
     * Advanced version of Controller::addImageToTemplate
46
     * with custom imageField and imageSelectorField and array instead of FrontendTemplate.
47
     *
48
     * @param string          $imageField         the image field name (typical singleSRC)
49
     * @param string          $imageSelectorField the image selector field indicated if an image is added (typical addImage)
50
     * @param array           $templateData       An array to add the generated data to
51
     * @param array           $item               The source data containing the imageField and imageSelectorField
52
     * @param int|null        $maxWidth           An optional maximum width of the image
53
     * @param string|null     $lightboxId         An optional lightbox ID
54
     * @param string|null     $lightboxName       An optional lightbox name
55
     * @param FilesModel|null $model              an optional file model used to read meta data
56
     */
57
    public function addToTemplateData(
58
        string $imageField,
59
        string $imageSelectorField,
60
        array &$templateData,
61
        array $item,
62
        int $maxWidth = null,
×
63
        string $lightboxId = null,
×
64
        string $lightboxName = null,
×
65
        FilesModel $model = null
×
66
    ) {
67
        $containerUtil = $this->container->get('huh.utils.container');
×
68
        $rootDir = $this->container->getParameter('kernel.project_dir');
×
69

70
        try {
71
            if (Validator::isUuid($item[$imageField])) {
×
72
                $file = $this->container->get('huh.utils.file')->getFileFromUuid($item[$imageField]);
×
73

74
                if (null === $file) {
×
75
                    return;
×
76
                }
77
            } else {
78
                $file = new File($item[$imageField]);
×
79
            }
80
            $imgSize = $file->imageSize;
×
81
        } catch (\Exception $e) {
×
82
            return;
×
83
        }
84

85
        if (null === $model) {
×
86
            $model = $file->getModel();
×
87
        }
88

89
        $size = StringUtil::deserialize($item['size'] ?? '');
×
90

91
        if (is_numeric($size)) {
×
92
            $size = [0, 0, (int) $size];
×
93
        } elseif (!\is_array($size)) {
×
94
            $size = [];
×
95
        }
96

97
        $size += [0, 0, 'crop'];
×
98

99
        if (null === $maxWidth) {
×
100
            $maxWidth = ($containerUtil->isBackend()) ? 320 : Config::get('maxImageWidth');
×
101
        }
102

103
        $marginArray = ($containerUtil->isBackend()) ? '' : StringUtil::deserialize($item['imagemargin'] ?? '');
×
104

105
        // Store the original dimensions
106
        $templateData['width'] = $imgSize[0] ?? 0;
×
107
        $templateData['height'] = $imgSize[1] ?? 0;
×
108

109
        // Adjust the image size
110
        if ($maxWidth > 0) {
×
111
            // Subtract the margins before deciding whether to resize (see #6018)
112
            if (\is_array($marginArray) && 'px' == $marginArray['unit']) {
×
113
                $margin = (int) $marginArray['left'] + (int) $marginArray['right'];
×
114

115
                // Reset the margin if it exceeds the maximum width (see #7245)
116
                if ($maxWidth - $margin < 1) {
×
117
                    $marginArray['left'] = '';
×
118
                    $marginArray['right'] = '';
×
119
                } else {
120
                    $maxWidth -= $margin;
×
121
                }
122
            }
123

124
            if ($size[0] > $maxWidth || (!$size[0] && !$size[1] && (!$imgSize[0] || $imgSize[0] > $maxWidth))) {
×
125
                // See #2268 (thanks to Thyon)
126
                $ratio = ($size[0] && $size[1]) ? $size[1] / $size[0] : (($imgSize[0] && $imgSize[1]) ? $imgSize[1] / $imgSize[0] : 0);
×
127

128
                $size[0] = $maxWidth;
×
129
                $size[1] = floor($maxWidth * $ratio);
×
130
            }
131
        }
132

133
        // Disable responsive images in the back end (see #7875)
134
        if ($containerUtil->isBackend()) {
×
135
            unset($size[2]);
×
136
        }
137

138
        $imageFile = $file;
×
139

140
        try {
141
            $src = $this->container->get('contao.image.image_factory')->create($rootDir.'/'.$file->path, $size)->getUrl($rootDir);
×
142
            $picture = $this->container->get('contao.image.picture_factory')->create($rootDir.'/'.$file->path, $size);
×
143

144
            $picture = [
×
145
                'img' => $picture->getImg($rootDir, TL_FILES_URL),
×
146
                'sources' => $picture->getSources($rootDir, TL_FILES_URL),
×
147
                'ratio' => '1.0',
×
148
                'copyright' => $file->getModel()->copyright,
×
149
            ];
×
150

151
            if ($src !== $file->path) {
×
152
                $imageFile = new File(rawurldecode($src));
×
153
            }
154
        } catch (\Exception $e) {
×
155
            $this->container->get('monolog.logger.contao')->log(
×
156
                LogLevel::ERROR,
×
157
                'Image "'.$file->path.'" could not be processed: '.$e->getMessage(),
×
158
                ['contao' => new ContaoContext(__METHOD__, TL_ERROR)]
×
159
            );
×
160

161
            $src = '';
×
162
            $picture = ['img' => ['src' => '', 'srcset' => ''], 'sources' => []];
×
163
        }
164

165
        // Image dimensions
166
        if (false !== ($imgSize = $imageFile->imageSize) && $imageFile->exists()) {
×
167
            $templateData['arrSize'] = $imgSize;
×
168
            $templateData['imgSize'] = ' width="'.$imgSize[0].'" height="'.$imgSize[1].'"';
×
169

170
            $picture['size'] = $imgSize;
×
171
            $picture['width'] = $imgSize[0];
×
172
            $picture['height'] = $imgSize[1];
×
173
            $picture['ratio'] = $imgSize[1] > 0 ? ($imgSize[0] / $imgSize[1]) : '1.0';
×
174
        }
175

176
        $meta = [];
×
177

178
        // Load the meta data
179
        if ($model instanceof FilesModel) {
×
180
            if ($containerUtil->isFrontend()) {
×
181
                global $objPage;
×
182

183
                $meta = Frontend::getMetaData($model->meta, $objPage->language);
×
184

185
                if (empty($meta) && null !== $objPage->rootFallbackLanguage) {
×
186
                    $meta = Frontend::getMetaData($model->meta, $objPage->rootFallbackLanguage);
×
187
                }
188
            } else {
189
                $meta = Frontend::getMetaData($model->meta, $GLOBALS['TL_LANGUAGE']);
×
190
            }
191

192
            $this->container->get('contao.framework')->getAdapter(Controller::class)->loadDataContainer('tl_files');
×
193

194
            // Add any missing fields
195
            foreach (array_keys($GLOBALS['TL_DCA']['tl_files']['fields']['meta']['eval']['metaFields']) as $k) {
×
196
                if (!isset($meta[$k])) {
×
197
                    $meta[$k] = '';
×
198
                }
199
            }
200

201
            $meta['imageTitle'] = $meta['title'];
×
202
            $meta['imageUrl'] = $meta['link'];
×
203
            unset($meta['title'], $meta['link']);
×
204

205
            // Add the meta data to the item
206
            if (!($item['overwriteMeta'] ?? false)) {
×
207
                foreach ($meta as $k => $v) {
×
208
                    switch ($k) {
209
                        case 'alt':
×
210
                        case 'imageTitle':
×
211
                            $item[$k] = StringUtil::specialchars($v);
×
212

213
                            break;
×
214

215
                        default:
216
                            $item[$k] = $v;
×
217

218
                            break;
×
219
                    }
220
                }
221
            }
222
        }
223

224
        $picture['alt'] = StringUtil::specialchars($item['alt']);
×
225

226
        $fullsize = (bool) ($item['fullsize'] ?? false);
×
227

228
        // Move the title to the link tag so it is shown in the lightbox
229
        if ($fullsize && ($item['imageTitle'] ?? false) && !($item['linkTitle'] ?? false)) {
×
230
            $item['linkTitle'] = $item['imageTitle'];
×
231
            unset($item['imageTitle']);
×
232
        }
233

234
        if (isset($item['imageTitle'])) {
×
235
            $picture['title'] = StringUtil::specialchars($item['imageTitle']);
×
236
        }
237

238
        // empty the attributes in order to avoid passing the link attributes to the img element
239
        $picture['attributes'] = '';
×
240

241
        $templateData['picture'] = $picture;
×
242

243
        // Provide an ID for single lightbox images in HTML5 (see #3742)
244
        if (null === $lightboxId && $fullsize) {
×
245
            $lightboxId = substr(md5($lightboxName.'_'.$item['id']), 0, 6);
×
246
        }
247

248
        // Float image
249
        if ($item['floating'] ?? false) {
×
250
            $templateData['floatClass'] = ' float_'.$item['floating'];
×
251
        }
252

253
        // Do not override the "href" key (see #6468)
254
        $hrefKey = (isset($templateData['href']) && '' != $templateData['href']) ? 'imageHref' : 'href';
×
255

256
        // Image link
257
        if ($item['imageUrl'] && $containerUtil->isFrontend()) {
×
258
            $templateData[$hrefKey] = $item['imageUrl'];
×
259
            $templateData['attributes'] = '';
×
260

261
            if ($fullsize) {
×
262
                // Open images in the lightbox
263
                if (preg_match('/\.(jpe?g|gif|png)$/', $item['imageUrl'])) {
×
264
                    // Do not add the TL_FILES_URL to external URLs (see #4923)
265
                    if (0 !== strncmp($item['imageUrl'], 'http://', 7) && 0 !== strncmp($item['imageUrl'], 'https://', 8)) {
×
266
                        $templateData[$hrefKey] = TL_FILES_URL.System::urlEncode($item['imageUrl']);
×
267
                    }
268

269
                    $templateData['attributes'] = ' data-lightbox="'.$lightboxId.'"';
×
270
                } else {
271
                    $templateData['attributes'] = ' target="_blank"';
×
272
                }
273
            }
274
        } // Fullsize view
275
        elseif ($fullsize && $containerUtil->isFrontend()) {
×
276
            $templateData[$hrefKey] = TL_FILES_URL.System::urlEncode($file->path);
×
277
            $templateData['attributes'] = ' data-lightbox="'.$lightboxId.'"';
×
278
        }
279

280
        // Add the meta data to the template
281
        foreach (array_keys($meta) as $k) {
×
282
            $templateData[$k] = $item[$k];
×
283
        }
284

285
        // Do not urlEncode() here because getImage() already does (see #3817)
286
        $templateData['src'] = TL_FILES_URL.$src;
×
287
        $templateData[$imageField] = $file->path;
×
288
        $templateData['linkTitle'] = $item['linkTitle'] ?? ($item['title'] ?? null);
×
289
        $templateData['fullsize'] = $fullsize;
×
290
        $templateData['addBefore'] = ('below' != ($item['floating'] ?? ''));
×
291
        $templateData['margin'] = Controller::generateMargin($marginArray);
×
292
        $templateData[$imageSelectorField] = true;
×
293

294
        // HOOK: modify image template data
295
        if (isset($GLOBALS['TL_HOOKS']['addImageToTemplateData']) && \is_array($GLOBALS['TL_HOOKS']['addImageToTemplateData'])) {
×
296
            foreach ($GLOBALS['TL_HOOKS']['addImageToTemplateData'] as $callback) {
×
297
                $templateData = System::importStatic($callback[0])->{$callback[1]}($templateData, $imageField, $imageSelectorField, $item, $maxWidth, $lightboxId, $lightboxName, $model);
×
298
            }
299
        }
300
    }
301

302
    /**
303
     * Convert sizes like 2em, 10cm or 12pt to pixels.
304
     *
305
     * @param string $size The size string
306
     *
307
     * @return int The pixel value
308
     */
309
    public function getPixelValue(string $size)
310
    {
311
        $value = preg_replace('/[^0-9.-]+/', '', $size);
×
312
        $unit = preg_replace('/[^acehimnprtvwx%]/', '', $size);
×
313

314
        // Convert 16px = 1em = 2ex = 12pt = 1pc = 1/6in = 2.54/6cm = 25.4/6mm = 100%
315
        switch ($unit) {
316
            case '':
×
317
            case 'px':
×
318
                return (int) round($value);
×
319

320
                break;
×
321

322
            case 'em':
×
323
                return (int) round($value * 16);
×
324

325
                break;
×
326

327
            case 'ex':
×
328
                return (int) round($value * 16 / 2);
×
329

330
                break;
×
331

332
            case 'pt':
×
333
                return (int) round($value * 16 / 12);
×
334

335
                break;
×
336

337
            case 'pc':
×
338
                return (int) round($value * 16);
×
339

340
                break;
×
341

342
            case 'in':
×
343
                return (int) round($value * 16 * 6);
×
344

345
                break;
×
346

347
            case 'cm':
×
348
                return (int) round($value * 16 / (2.54 / 6));
×
349

350
                break;
×
351

352
            case 'mm':
×
353
                return (int) round($value * 16 / (25.4 / 6));
×
354

355
                break;
×
356

357
            case '%':
×
358
                return (int) round($value * 16 / 100);
×
359

360
                break;
×
361
        }
362

363
        return 0;
×
364
    }
365

366
    /**
367
     * Prepares one image for a typical Contao template.
368
     *
369
     * Possible option keys:
370
     * - imageField: (string) The name of the field containers the image uuid. Default: singleSRC
371
     * - imageSelectorField: (?string) The name of the field that indicated if an images is added. Set to null to skip check. Default: addImage
372
     * - maxWidth: (int) An optional maximum width of the image. Passed directly to Controller::addImageToTemplate()
373
     * - lightboxId: (string) An optional lightbox ID. Passed directly to Controller::addImageToTemplate()
374
     *
375
     * @param array $data    the model/module/element data
376
     * @param array $options Additional options
377
     *
378
     * @return array
379
     */
380
    public function prepareImage(array $data, array $options = []): ?array
381
    {
382
        $defaultOptions = [
×
383
            'imageField' => 'singleSRC',
×
384
            'imageSelectorField' => 'addImage',
×
385
        ];
×
386
        $options = array_merge($defaultOptions, $options);
×
387

388
        if (null !== $options['imageSelectorField'] && (!isset($data[$options['imageSelectorField']]) || !$data[$options['imageSelectorField']])) {
×
389
            return null;
×
390
        }
391

392
        $fileModel = FilesModel::findByUuid($data[$options['imageField']]);
×
393

394
        if (!$fileModel || !is_file($this->container->getParameter('kernel.project_dir').'/'.$fileModel->path)) {
×
395
            return null;
×
396
        }
397

398
        $image = $data;
×
399
        $image['singleSRC'] = $fileModel->path;
×
400

401
        $template = new FrontendTemplate();
×
402

403
        Controller::addImageToTemplate(
×
404
            $template,
×
405
            $image,
×
406
            isset($options['maxWidth']) ? $options['maxWidth'] : null,
×
407
            isset($options['lightboxId']) ? $options['lightboxId'] : null
×
408
        );
×
409

410
        return $template->getData();
×
411
    }
412
}
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