Proyectos de Subversion Moodle

Rev

| Ultima modificación | Ver Log |

Rev Autor Línea Nro. Línea
1441 ariadna 1
<?php
2
// This file is part of Moodle - http://moodle.org/
3
//
4
// Moodle is free software: you can redistribute it and/or modify
5
// it under the terms of the GNU General Public License as published by
6
// the Free Software Foundation, either version 3 of the License, or
7
// (at your option) any later version.
8
//
9
// Moodle is distributed in the hope that it will be useful,
10
// but WITHOUT ANY WARRANTY; without even the implied warranty of
11
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12
// GNU General Public License for more details.
13
//
14
// You should have received a copy of the GNU General Public License
15
// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
16
 
17
namespace core_files\redactor\services;
18
 
19
/**
20
 * Tests for the EXIF remover service.
21
 *
22
 * If you wish to use these unit tests all you need to do is add the following definition to
23
 * your config.php file:
24
 *
25
 * define('TEST_PATH_TO_EXIFTOOL', '/usr/bin/exiftool');
26
 *
27
 * @package   core_files
28
 * @copyright Meirza <meirza.arson@moodle.com>
29
 * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
30
 *
31
 * @covers \core_files\redactor\services\exifremover_service
32
 */
33
final class exifremover_service_test extends \advanced_testcase {
34
    /**
35
     * Tests the `exifremover_service` functionality using PHP GD.
36
     *
37
     * This test verifies the ability of the `exifremover_service` to remove all EXIF
38
     * tags from an image file when using PHP GD. It ensures that all tags, including
39
     * GPSLatitude, GPSLongitude, and Orientation, are removed from the EXIF data.
40
     *
41
     * @return void
42
     */
43
    public function test_exifremover_service_with_gd(): void {
44
        $this->resetAfterTest(true);
45
 
46
        // Ensure that the exif remover tool path is not set.
47
        set_config('exifremovertoolpath', null, 'core_files');
48
 
49
        $sourcepath = self::get_fixture_path('core_files', 'redactor/dummy.jpg');
50
 
51
        // Get the EXIF data from the original file.
52
        $currentexif = $this->get_exif_data_from_file($sourcepath);
53
        $this->assertStringContainsString('GPSLatitude', $currentexif);
54
        $this->assertStringContainsString('GPSLongitude', $currentexif);
55
        $this->assertStringContainsString('Orientation', $currentexif);
56
 
57
        // Redact the file.
58
        $service = new exifremover_service();
59
        $newfile = $service->redact_file_by_path('image/jpeg', $sourcepath);
60
 
61
        // Get the EXIF data from the new file.
62
        $newexif = $this->get_exif_data_from_file($newfile);
63
 
64
        // Removing the "all" tags will result in removing all existing tags.
65
        $this->assertStringNotContainsString('GPSLatitude', $newexif);
66
        $this->assertStringNotContainsString('GPSLongitude', $newexif);
67
        $this->assertStringNotContainsString('Orientation', $newexif);
68
    }
69
 
70
    /**
71
     * Tests the `exifremover_service` functionality to flip orientation.
72
     *
73
     * @dataProvider exifremover_service_flip_orientation_provider
74
     * @param string $sourcepath the path to the source image.
75
     * @param string $expectedpath the path to the expected image.
76
     * @param bool $expectedresult the expected result of the comparison.
77
     */
78
    public function test_exifremover_service_flip_orientation_with_gd(
79
        string $sourcepath,
80
        string $expectedpath,
81
        bool $expectedresult
82
    ): void {
83
        $this->resetAfterTest(true);
84
 
85
        // Ensure that the exif remover tool path is not set.
86
        set_config('exifremovertoolpath', null, 'core_files');
87
 
88
        // Flip the orientation.
89
        $service = new exifremover_service();
90
        $newfile = $service->redact_file_by_path('image/jpeg', $sourcepath);
91
 
92
        // Compare the actual and expected images.
93
        $this->assertEquals($expectedresult, $this->compare_images($newfile, $expectedpath));
94
    }
95
 
96
    /**
97
     * Data provider for test_exifremover_service_flip_orientation().
98
     *
99
     * @return array
100
     */
101
    public static function exifremover_service_flip_orientation_provider(): array {
102
        return [
103
            'Flip right-top' => [
104
                'sourcepath' => self::get_fixture_path('core_files', 'redactor/righttop.jpg'),
105
                'expectedpath' => self::get_fixture_path('core_files', 'redactor/topleft.jpg'),
106
                'expectedresult' => true,
107
            ],
108
            'The image will not be the same after the flip process' => [
109
                'sourcepath' => self::get_fixture_path('core_files', 'redactor/righttop.jpg'),
110
                'expectedpath' => self::get_fixture_path('core_files', 'redactor/righttop.jpg'),
111
                'expectedresult' => false,
112
            ],
113
        ];
114
    }
115
 
116
    /**
117
     * Compares two images pixel by pixel.
118
     *
119
     * @param string $image1path the path to the first image.
120
     * @param string $image2path the path to the second image.
121
     * @return bool True if the images are identical, false otherwise.
122
     */
123
    private function compare_images(string $image1path, string $image2path): bool {
124
        $image1 = imagecreatefromjpeg($image1path);
125
        $image2 = imagecreatefromjpeg($image2path);
126
 
127
        if (!$image1 || !$image2) {
128
            return false;
129
        }
130
 
131
        $width1 = imagesx($image1);
132
        $height1 = imagesy($image1);
133
        $width2 = imagesx($image2);
134
        $height2 = imagesy($image2);
135
 
136
        if ($width1 !== $width2 || $height1 !== $height2) {
137
            return false;
138
        }
139
 
140
        for ($x = 0; $x < $width1; $x++) {
141
            for ($y = 0; $y < $height1; $y++) {
142
                if (imagecolorat($image1, $x, $y) !== imagecolorat($image2, $x, $y)) {
143
                    return false;
144
                }
145
            }
146
        }
147
 
148
        return true;
149
    }
150
 
151
    /**
152
     * Tests the `exifremover_service` functionality using ExifTool.
153
     *
154
     * This test verifies the ability of the `exifremover_service` to remove specific
155
     * EXIF tags from an image file when configured to use ExifTool. The test includes
156
     * scenarios for removing all EXIF tags and for removing only GPS tags.
157
     */
158
    public function test_exifremover_service_with_exiftool(): void {
159
        $this->require_exiftool();
160
        $this->resetAfterTest(true);
161
 
162
        $sourcepath = self::get_fixture_path('core_files', 'redactor/dummy.jpg');
163
        set_config('file_redactor_exifremovertoolpath', TEST_PATH_TO_EXIFTOOL);
164
 
165
        // Remove All tags.
166
        set_config('file_redactor_exifremoverremovetags', 'all');
167
        $service = new exifremover_service();
168
        $newfile = $service->redact_file_by_path('image/jpeg', $sourcepath);
169
 
170
        // Get the EXIF data from the new file.
171
        $newexif = $this->get_exif_data_from_file($newfile);
172
 
173
        // Removing the "all" tags will result in removing all existing tags.
174
        $this->assertStringNotContainsString('GPSLatitude', $newexif);
175
        $this->assertStringNotContainsString('GPSLongitude', $newexif);
176
        $this->assertStringNotContainsString('Aperture', $newexif);
177
 
178
        // Orientation is a preserve tag. Ensure it always exists.
179
        $this->assertStringContainsString('Orientation', $newexif);
180
 
181
        // Remove the GPS tag only.
182
        set_config('file_redactor_exifremoverremovetags', 'gps');
183
 
184
        $service = new exifremover_service();
185
        $newfile = $service->redact_file_by_path('image/jpeg', $sourcepath);
186
 
187
        // Get the EXIF data from the new file.
188
        $newexif = $this->get_exif_data_from_file($newfile);
189
 
190
        // The GPS tag only removal will remove the tag containing "GPS" keyword.
191
        $this->assertStringNotContainsString('GPSLatitude', $newexif);
192
        $this->assertStringNotContainsString('GPSLongitude', $newexif);
193
 
194
        // And keep the other tags remaining.
195
        $this->assertStringContainsString('Aperture', $newexif);
196
 
197
        // Orientation is a preserve tag. Ensure it always exists.
198
        $this->assertStringContainsString('Orientation', $newexif);
199
    }
200
 
201
    /**
202
     * Tests the `is_mimetype_supported` method.
203
     *
204
     * This test initializes the `exifremover_service` and verifies if the given
205
     * MIME types are supported for EXIF removal using both PHP GD and ExifTool.
206
     */
207
    public function test_exifremover_service_is_mimetype_supported_generic(): void {
208
        $service = new exifremover_service();
209
 
210
        // Ensure that an unsupported mimetype is not accepted.
211
        $this->assertFalse($service->is_mimetype_supported('application/binary'));
212
 
213
        // An unsupported mimetype will just return null.
214
        $sourcepath = self::get_fixture_path('core_files', 'redactor/dummy.jpg');
215
        $this->assertNull($service->redact_file_by_path('application/binary', $sourcepath));
216
        $this->assertNull($service->redact_file_by_content('application/binary', $sourcepath));
217
    }
218
 
219
    /**
220
     * Tests the `is_mimetype_supported` method.
221
     *
222
     * This test initializes the `exifremover_service` and verifies if the given
223
     * MIME types are supported for EXIF removal using both PHP GD and ExifTool.
224
     */
225
    public function test_exifremover_service_is_mimetype_supported_gd(): void {
226
        $this->resetAfterTest(true);
227
 
228
        // Ensure that the exif remover tool path is not set.
229
        set_config('file_redactor_exifremovertoolpath', null);
230
 
231
        $service = new exifremover_service();
232
 
233
        // The default MIME type is supported.
234
        $this->assertTrue($service->is_mimetype_supported(exifremover_service::DEFAULT_MIMETYPE));
235
 
236
        // Other than the default, the function will returns false.
237
        $this->assertFalse($service->is_mimetype_supported('image/tiff'));
238
    }
239
 
240
    /**
241
     * Tests the `is_mimetype_supported` method.
242
     *
243
     * This test initializes the `exifremover_service` and verifies if the given
244
     * MIME types are supported for EXIF removal using both PHP GD and ExifTool.
245
     */
246
    public function test_exifremover_service_is_mimetype_supported_exiftool(): void {
247
        $this->require_exiftool();
248
        $this->resetAfterTest(true);
249
 
250
        set_config('file_redactor_exifremovertoolpath', TEST_PATH_TO_EXIFTOOL);
251
 
252
        // Set the supported mime types to only redact image/tiff.
253
        set_config('file_redactor_exifremovermimetype', 'image/tiff');
254
        $service = new exifremover_service();
255
 
256
        $this->assertTrue($service->is_mimetype_supported('image/tiff'));
257
 
258
        // Other image formats are not supported.
259
        $this->assertFalse($service->is_mimetype_supported('image/png'));
260
    }
261
 
262
    /**
263
     * Tests the EXIF remover service with an unknown filename and an invalid EXIF tool path.
264
     */
265
    public function test_exiftool_notfound_filename_unknown(): void {
266
        $this->resetAfterTest(true);
267
        set_config('file_redactor_exifremovertoolpath', 'fakeexiftool');
268
 
269
        $service = new exifremover_service();
270
        $this->expectException(\core\exception\moodle_exception::class);
271
        $this->expectExceptionMessage(get_string('redactor:exifremover:failedprocessgd', 'core_files'));
272
        $service->redact_file_by_content('image/jpeg', 'content');
273
    }
274
 
275
    /**
276
     * Retrieves the EXIF metadata of a file.
277
     *
278
     * @param string $content the content of file.
279
     * @return string The EXIF metadata as a string.
280
     */
281
    private function get_exif_data_from_content(string $content): string {
282
        $logpath = make_request_directory() . '/temp.jpg';
283
        file_put_contents($logpath, $content);
284
 
285
        return $this->get_exif_data_from_file($logpath);
286
    }
287
 
288
    /**
289
     * Retrieves the EXIF metadata of a file.
290
     *
291
     * @param string $filepath the path to the file.
292
     * @return string The EXIF metadata as a string.
293
     */
294
    private function get_exif_data_from_file(string $filepath): string {
295
        $exif = exif_read_data($filepath);
296
 
297
        $string = "";
298
        foreach ($exif as $key => $value) {
299
            if (is_array($value)) {
300
                foreach ($value as $subkey => $subvalue) {
301
                    $string .= "$subkey: $subvalue\n";
302
                }
303
            } else {
304
                $string .= "$key: $value\n";
305
            }
306
        }
307
        return $string;
308
    }
309
 
310
    /**
311
     * Data provider for test_exifremover_service_clean_filename().
312
     *
313
     * @return array
314
     */
315
    public static function exifremover_service_clean_filename_provider(): array {
316
        return [
317
            'Hyphen minus &#x002D' => [
318
                'filename' => '-if \'$LensModel eq "18-35mm"\'',
319
                'expected' => 'if $LensModel eq 18-35mm',
320
            ],
321
            'Minus &#x2212;' => [
322
                'filename' => '−filename.jpg',
323
                'expected' => 'filename.jpg',
324
            ],
325
        ];
326
    }
327
 
328
    /**
329
     * Helper to require valid testing exiftool configuration.
330
     */
331
    private function require_exiftool(): void {
332
        if (!defined('TEST_PATH_TO_EXIFTOOL')) {
333
            $this->markTestSkipped('Could not test the EXIF remover service, missing configuration.');
334
        }
335
 
336
        if (!TEST_PATH_TO_EXIFTOOL) {
337
            $this->markTestSkipped('Could not test the EXIF remover service, configuration invalid.');
338
        }
339
 
340
        if (!is_executable(TEST_PATH_TO_EXIFTOOL)) {
341
            $this->markTestSkipped('Could not test the EXIF remover service, exiftool not executable.');
342
        }
343
    }
344
}