1<?php
2// (c) Copyright by authors of the Tiki Wiki CMS Groupware Project
3//
4// All Rights Reserved. See copyright.txt for details and a complete list of authors.
5// Licensed under the GNU LESSER GENERAL PUBLIC LICENSE. See license.txt for details.
6// $Id$
7
8namespace Tiki\Test\Files;
9
10use org\bovigo\vfs\vfsStream;
11use Tiki\Files\CheckAttachmentGallery;
12use TikiLib;
13
14class CheckAttachmentGalleryTest extends \PHPUnit_Framework_TestCase
15{
16	protected $file_root;
17	protected $files_dir;
18	protected $default_file_content = 'this is a test';
19
20	protected function setUp()
21	{
22		global $prefs;
23
24		$prefs['feature_user_watches'] = 'n';
25		$this->refreshDirectory();
26		// Remove existing attachments
27		$this->removeAttachmentsFromDb();
28	}
29
30	protected function tearDown()
31	{
32		$this->removeAttachmentsFromDb();
33	}
34
35	/**
36	 * @dataProvider getTypes
37	 * @param $type
38	 */
39	public function testAttachmentEmptyAttachmentsNoProblemOnDB($type)
40	{
41		$to_assert = [
42			'usesDatabase' => true,
43			'path' => [''],
44			'mixedLocation' => false,
45			'count' => 0,
46			'countFilesDb' => 0,
47			'countFilesDisk' => 0,
48			'issueCount' => 0,
49			'missing' => [],
50			'mismatch' => [],
51			'unknown' => [],
52		];
53
54		$this->configToStoreFiles($type, true);
55		$checkInstance = new CheckAttachmentGallery($type);
56
57		$result = $checkInstance->analyse();
58
59		$this->assertEquals($to_assert, $result);
60	}
61
62	/**
63	 * @dataProvider getTypes()
64	 */
65	public function testAttachmentEmptyAttachmentsNoProblemOnDisk($type)
66	{
67		$to_assert = [
68			'usesDatabase' => false,
69			'path' => [$this->files_dir],
70			'mixedLocation' => false,
71			'count' => 0,
72			'countFilesDb' => 0,
73			'countFilesDisk' => 0,
74			'issueCount' => 0,
75			'missing' => [],
76			'mismatch' => [],
77			'unknown' => [],
78		];
79
80		$this->configToStoreFiles($type, false);
81		$checkInstance = new CheckAttachmentGallery($type);
82
83		$result = $checkInstance->analyse();
84
85		$this->assertEquals($to_assert, $result);
86	}
87
88	/**
89	 * @dataProvider getTypes
90	 * @throws \Exception
91	 */
92	public function testAttachmentWithOneFileOnDisk($type)
93	{
94		$this->configToStoreFiles($type, false);
95
96		$this->insertAttachment(['name' => uniqid(), 'type' => $type]);
97
98		$checkInstance = new CheckAttachmentGallery($type);
99		$result = $checkInstance->analyse();
100
101		$this->assertFalse($result['usesDatabase']);
102		$this->assertFalse($result['mixedLocation']);
103		$this->assertEquals(1, $result['count']);
104		$this->assertEquals(0, $result['issueCount']);
105	}
106
107	/**
108	 * @dataProvider getTypes()
109	 * @param $type
110	 * @throws \Exception
111	 */
112	public function testAttachmentWithOneFileOnDb($type)
113	{
114		$this->configToStoreFiles($type, true);
115		$checkInstance = new CheckAttachmentGallery($type);
116		$this->insertAttachment([
117			'name' => 'testAttachmentWithOneFileOnDb',
118			'type' => $type,
119			'db' => true
120		]);
121		$result = $checkInstance->analyse();
122
123		$this->assertFalse($result['mixedLocation']);
124		$this->assertEquals(1, $result['count']);
125		$this->assertEquals(0, $result['issueCount']);
126	}
127
128	/**
129	 * @dataProvider getTypes
130	 * @param $type
131	 */
132	public function testAttachmentUnknownFile($type)
133	{
134		global $tikilib;
135
136		$this->configToStoreFiles($type, false);
137		$filename = md5($tikilib->now);
138		$this->createFileOnDisk($filename, 'Invalid file');
139
140		$checkInstance = new CheckAttachmentGallery($type);
141
142		$result = $checkInstance->analyse();
143
144		$this->assertFalse($result['usesDatabase']);
145		$this->assertFalse($result['mixedLocation']);
146		$this->assertEquals(0, $result['count'], 'Count'); // Attachment not registered in db
147		$this->assertEquals(1, $result['issueCount'], 'Issue Count');
148		$this->assertEquals(
149			[
150				[
151					'name' => $filename,
152					'path' => $this->files_dir,
153					'size' => strlen('Invalid file')
154				]
155			],
156			$result['unknown']
157		);
158	}
159
160	/**
161	 * @dataProvider getTypes
162	 * @param $type
163	 * @throws \Exception
164	 */
165	public function testAttachmentMismatchFile($type)
166	{
167		global $tikilib;
168
169		$this->configToStoreFiles($type, false);
170
171		$filename = md5($tikilib->now);
172
173		$id = $this->insertAttachment([
174			'name' => $filename,
175			'fhash' => $filename,
176			'type' => $type,
177			'size' => 1 // the size will create the mismatch
178		]);
179
180		$checkInstance = new CheckAttachmentGallery($type);
181
182		$result = $checkInstance->analyse();
183
184		$this->assertFalse($result['usesDatabase']);
185		$this->assertFalse($result['mixedLocation']);
186		$this->assertEquals(1, $result['count'], 'Count');
187		$this->assertEquals(1, $result['issueCount'], 'Issue Count');
188		$this->assertEquals(
189			[
190				[
191					'name' => $filename,
192					'path' => $this->files_dir,
193					'size' => 1,
194					'id' => $id
195				]
196			],
197			$result['mismatch']
198		);
199	}
200
201	/**
202	 * Test that a file that should be stored in filesystem is missing
203	 *
204	 * @dataProvider getTypes
205	 * @param $type
206	 * @throws \Exception
207	 */
208	public function testAttachmentMissingFile($type)
209	{
210		global $tikilib;
211
212		$this->configToStoreFiles($type, false);
213
214		$filename = md5($tikilib->now);
215		$id = $this->insertAttachment([
216			'name' => $filename,
217			'fhash' => $filename,
218			'type' => $type,
219		]);
220
221		if (file_exists($this->files_dir . $filename)) {
222			unlink($this->files_dir . $filename);
223		}
224
225		$checkInstance = new CheckAttachmentGallery($type);
226
227		$result = $checkInstance->analyse();
228
229		$this->assertFalse($result['usesDatabase']);
230		$this->assertFalse($result['mixedLocation']);
231		$this->assertEquals(1, $result['count'], 'Count');
232		$this->assertEquals(1, $result['issueCount'], 'Issue Count');
233		$this->assertEquals([
234			[
235				'name' => $filename,
236				'path' => $this->files_dir,
237				'size' => (int)strlen($this->default_file_content),
238				'id' => $id,
239			]
240		], $result['missing']);
241	}
242
243	/**
244	 * Configures the preferences to set the storage in the disk for a specific type
245	 * @param $type
246	 */
247	protected function configToStoreFiles($type, $use_db = false)
248	{
249		global $prefs;
250		$prefs[$type . '_use_db'] = $use_db ? 'y' : 'n';
251		$prefs[$type . '_use_dir'] = $use_db ? '' : $this->files_dir;
252	}
253
254	/**
255	 * Inserts a TXT file attachment for a specific type
256	 * @param $base_name
257	 * @param $type
258	 * @return mixed
259	 * @throws \Exception
260	 */
261	protected function insertAttachment($file)
262	{
263		global $tikilib;
264
265		$id = null;
266		$base_name = $file['name'];
267		$useDB = isset($file['db']) && $file['db'];
268
269		$string = $this->default_file_content;
270		$size = isset($file['size']) ? $file['size'] : strlen($string);
271		$type = $file['type'];
272
273		$lib = $this->getLib($type);
274		$dir = $useDB ? '' : $this->files_dir;
275		$fhash = null;
276
277		if (! $useDB) {
278			$fhash = isset($file['fhash']) ? $file['fhash'] : md5($base_name . $tikilib->now);
279			$this->createFileOnDisk($fhash, $string);
280			$string = null;
281		}
282
283		switch ($type) {
284			case 'w':
285				$id = $lib->wiki_attach_file('test', $base_name, '.txt', (int)$size, $string, 0, 0, $fhash, time());
286				break;
287			case 't':
288				$id = $lib->replace_item_attachment(null, $base_name, '.txt', (int)$size, $string, 0, 0, $fhash, '', '', 0, 0, [], []);
289				break;
290			case 'f':
291				$id = $lib->forum_attach_file(0, 0, $base_name, '.txt', (int)$size, $string, $fhash, $dir, 0);
292				break;
293		}
294
295		return $id;
296	}
297
298	/**
299	 * Function to create a local file with a specific content
300	 * @param $file_name
301	 * @param $content
302	 * @param string $extension
303	 */
304	protected function createFileOnDisk($file_name, $content)
305	{
306		$full_path = $this->files_dir . $file_name;
307		$myfile = fopen($full_path, "w") or die("Unable to open file!");
308		fwrite($myfile, $content);
309		fclose($myfile);
310	}
311
312	/**
313	 * Clears and prepares the DB to run the tests
314	 * @throws \Exception
315	 */
316	protected function removeAttachmentsFromDb()
317	{
318		$types = ['f', 't', 'w'];
319
320		foreach ($types as $type) {
321			$lib = $this->getLib($type);
322			$attachments = $lib->list_all_attachements();
323
324			foreach ($attachments['data'] as $attachment) {
325				$this->removeAttachment($type, $attachment['attId']);
326			}
327		}
328	}
329
330	/**
331	 * Gets the lib related to a specific attachment type
332	 * @param $type
333	 * @return \WikiLib|\TrackerLib|\Comments
334	 * @throws \Exception
335	 */
336	protected function getLib($type)
337	{
338		switch ($type) {
339			case 'w':
340				return TikiLib::lib('wiki');
341			case 't':
342				return TikiLib::lib('trk');
343			case 'f':
344				return TikiLib::lib('comments');
345		}
346	}
347
348	/**
349	 * Gets the method responsible for removing an attachment based on the type
350	 * @param $type
351	 * @throws \Exception
352	 */
353	protected function removeAttachment($type, $id)
354	{
355		$lib = $this->getLib($type);
356
357		switch ($type) {
358			case 'w':
359				$lib->remove_wiki_attachment($id);
360				break;
361			case 't':
362				$lib->remove_item_attachment($id);
363				break;
364			case 'f':
365				$lib->remove_thread_attachment($id);
366				break;
367		}
368	}
369
370	/**
371	 * Refreshes mock directory to store files for test purposes
372	 */
373	protected function refreshDirectory()
374	{
375		$this->file_root = vfsStream::setup(uniqid('', true), null);
376		$this->files_dir = $this->file_root->url() . '/test/';
377		mkdir($this->files_dir);
378	}
379
380	/**
381	 * Data provider to test all attachment galleries
382	 *
383	 * @return array
384	 */
385	public function getTypes()
386	{
387		return [
388			'forum' => ['f'],
389			'tracker' => ['t'],
390			'wiki' => ['w'],
391		];
392	}
393}
394