Overview
Comment:Modernisation objet fichiers
Downloads: Tarball | ZIP archive | SQL archive
Timelines: family | ancestors | descendants | both | dev
Files: files | file ages | folders
SHA1: 18ca9332f51e1970591ae5dab4eca9766643e18f
User & Date: bohwaz on 2017-05-17 07:08:19
Other Links: branch diff | manifest | tags
Context
2017-05-18
07:29
Modernisation admin wiki et fichiers check-in: 9f46f9067f user: bohwaz tags: dev
2017-05-17
07:08
Modernisation objet fichiers check-in: 18ca9332f5 user: bohwaz tags: dev
07:07
Ajout méthode openBlob check-in: 2b37df9aad user: bohwaz tags: dev
Changes

Modified src/include/lib/Garradin/Fichiers.php from [6317211824] to [249ae8890d].

69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
...
112
113
114
115
116
117
118

119
120



121
122
123


124




125
126
127
128
129
130
131
132
133
134
135
136
137
138
139


140
141
142
143

144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
...
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
...
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374

375
376
377
378
379
380
381
382
383


384
385
386
387
388
389
390
...
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
...
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
...
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
...
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
	 * Constructeur de l'objet pour un fichier
	 * @param integer $id Numéro unique du fichier
	 */
	public function __construct($id, $data = null)
	{
		if (is_null($data))
		{
			$data = DB::getInstance()->simpleQuerySingle('SELECT fichiers.*, fc.hash, fc.taille,
				strftime(\'%s\', datetime) AS datetime
				FROM fichiers INNER JOIN fichiers_contenu AS fc ON fc.id = fichiers.id_contenu
				WHERE fichiers.id = ?;', true, (int)$id);
		}

		if (!$data)
		{
			throw new \InvalidArgumentException('Ce fichier n\'existe pas.');
		}

................................................................................
		$check = [self::LIEN_MEMBRES, self::LIEN_WIKI, self::LIEN_COMPTA];

		if (!in_array($type, $check))
		{
			throw new \LogicException('Type de lien de fichier inconnu.');
		}


		unset($check[array_search($type, $check)]);
		



		foreach ($check as $check_type)
		{
			if ($db->simpleQuerySingle('SELECT 1 FROM fichiers_' . $check_type . ' WHERE fichier = ?;', false, (int)$this->id))


			{




				throw new \LogicException('Ce fichier est déjà lié à un autre contenu : ' . $check_type);
			}
		}

		return $db->simpleExec('INSERT OR IGNORE INTO fichiers_' . $type . ' (fichier, id) VALUES (?, ?);',
			(int)$this->id, (int)$foreign_id);
	}

	/**
	 * Vérifie que l'utilisateur a bien le droit d'accéder à ce fichier
	 * @param  mixed   $user Tableau contenant les infos sur l'utilisateur connecté, provenant de Membres::getLoggedUser, ou false
	 * @return boolean       TRUE si l'utilisateur a le droit d'accéder au fichier, sinon FALSE
	 */
	public function checkAccess($user = false)
	{


		// On regarde déjà si le fichier n'est pas lié au wiki
		$wiki = DB::getInstance()->simpleQuerySingle('SELECT wp.droit_lecture FROM fichiers_' . self::LIEN_WIKI . ' AS link
			INNER JOIN wiki_pages AS wp ON wp.id = link.id
			WHERE link.fichier = ? LIMIT 1;', false, (int)$this->id);


		// Page wiki publique, aucune vérification à faire, seul cas d'accès à un fichier en dehors de l'espace admin
		if ($wiki !== false && $wiki == Wiki::LECTURE_PUBLIC)
		{
			return true;
		}
			
		// Pas d'utilisateur connecté, pas d'accès aux fichiers de l'espace admin
		if (empty($user['droits']))
		{
			return false;
		}

		if ($wiki !== false)
		{
			// S'il n'a même pas droit à accéder au wiki c'est mort
			if ($user['droits']['wiki'] < Membres::DROIT_ACCES)
			{
				return false;
			}

			// On renvoie à l'objet Wiki pour savoir si l'utilisateur a le droit de lire ce fichier
			$_w = new Wiki;
			$_w->setRestrictionCategorie($user['id_categorie'], $user['droits']['wiki']);
			return $_w->canReadPage($wiki);
		}

		// On regarde maintenant si le fichier est lié à la compta
		$compta = DB::getInstance()->simpleQuerySingle('SELECT 1 
			FROM fichiers_' . self::LIEN_COMPTA . ' WHERE fichier = ? LIMIT 1;', false, (int)$this->id);

		if ($compta && $user['droits']['compta'] >= Membres::DROIT_ACCES)
		{
			// OK si accès à la compta
			return true;
		}

		// Enfin, si le fichier est lié à un membre
		$membre = DB::getInstance()->simpleQuerySingle('SELECT id 
			FROM fichiers_' . self::LIEN_MEMBRES . ' WHERE fichier = ? LIMIT 1;', false, (int)$this->id);

		if ($membre !== false)
		{
			// De manière évidente, l'utilisateur a le droit d'accéder aux fichiers liés à son profil
			if ((int)$membre == $user['id'])
			{
				return true;
			}

			// Pour voir les fichiers des membres il faut pouvoir les gérer
			if ($user['droits']['membres'] >= Membres::DROIT_ECRITURE)
			{
				return true;
			}
		}

		return false;
	}
................................................................................
	 * Supprime le fichier
	 * @return boolean TRUE en cas de succès
	 */
	public function remove()
	{
		$db = DB::getInstance();
		$db->begin();
		$db->simpleExec('DELETE FROM fichiers_compta_journal WHERE fichier = ?;', (int)$this->id);
		$db->simpleExec('DELETE FROM fichiers_wiki_pages WHERE fichier = ?;', (int)$this->id);
		$db->simpleExec('DELETE FROM fichiers_membres WHERE fichier = ?;', (int)$this->id);

		$db->simpleExec('DELETE FROM fichiers WHERE id = ?;', (int)$this->id);

		// Suppression du contenu s'il n'est pas utilisé par un autre fichier
		if (!$db->simpleQuerySingle('SELECT 1 FROM fichiers WHERE id_contenu = ? AND id != ? LIMIT 1;', 
			false, (int)$this->id_contenu, (int)$this->id))
		{
			$db->simpleExec('DELETE FROM fichiers_contenu WHERE id = ?;', (int)$this->id_contenu);
		}

		$cache_id = 'fichiers.' . $this->id_contenu;
		
		Static_Cache::remove($cache_id);

		foreach (self::$allowed_thumb_sizes as $size)
................................................................................
	 * Vérifie si le hash fourni n'est pas déjà stocké
	 * Utile pour par exemple reconnaître un ficher dont le contenu est déjà stocké, et éviter un nouvel upload
	 * @param  string $hash Hash SHA1
	 * @return boolean      TRUE si le hash est déjà présent dans fichiers_contenu, FALSE sinon
	 */
	static public function checkHash($hash)
	{
		return (boolean) DB::getInstance()->simpleQuerySingle(
			'SELECT 1 FROM fichiers_contenu WHERE hash = ?;', 
			false, 
			trim(strtolower($hash))
		);
	}

	/**
	 * Retourne un tableau de hash trouvés dans la DB parmi une liste de hash fournis
	 * @param  array  $list Liste de hash à vérifier
	 * @return array        Liste des hash trouvés
	 */
	static public function checkHashList($list)
	{
		$hash_list = '';
		$db = DB::getInstance();


		foreach ($list as $hash)
		{
			$hash_list .= '\'' . $db->escapeString($hash) . '\',';
		}

		$hash_list = substr($hash_list, 0, -1);

		return $db->queryFetchAssoc('SELECT hash, 1
			FROM fichiers_contenu WHERE hash IN (' . $hash_list . ');');


	}

	/**
	 * Récupération du message d'erreur
	 * @param  integer $error Code erreur du $_FILE
	 * @return string Message d'erreur
	 */
................................................................................
		$size = filesize($file['tmp_name']);

		$db = DB::getInstance();

		$db->begin();

		// Il peut arriver que l'on renvoie ici un fichier déjà stocké, auquel cas, ne pas le re-stocker
		if (!($id_contenu = $db->simpleQuerySingle('SELECT id FROM fichiers_contenu WHERE hash = ?;', false, $hash)))
		{
			$db->simpleInsert('fichiers_contenu', [
				'hash'		=>	$hash,
				'taille'	=>	(int)$size,
				'contenu'	=>	[\SQLITE3_BLOB, file_get_contents($file['tmp_name'])],
			]);
			
			$id_contenu = $db->lastInsertRowID();
		}

		$db->simpleInsert('fichiers', [
			'id_contenu'	=>	(int)$id_contenu,
			'nom'			=>	$name,
			'type'			=>	$type,
			'image'			=>	(int)$is_image,
		]);

		$db->commit();
................................................................................
	 * @return object       Un objet Fichiers en cas de succès
	 */
	static public function uploadExistingHash($name, $hash)
	{
		$db = DB::getInstance();
		$name = preg_replace('/[^\d\w._-]/ui', '', $name);

		$file = $db->simpleQuerySingle('SELECT * FROM fichiers 
			INNER JOIN fichiers_contenu AS fc ON fc.id = fichiers.id_contenu AND fc.hash = ?;', true, trim($hash));

		if (!$file)
		{
			throw new UserException('Le fichier à copier n\'existe pas (aucun hash ne correspond à '.$hash.').');
		}

		$db->simpleInsert('fichiers', [
			'id_contenu'	=>	(int)$file['id_contenu'],
			'nom'			=>	$name,
			'type'			=>	$file['type'],
			'image'			=>	(int)$file['image'],
		]);

		return new Fichiers($db->lastInsertRowID());
	}

    /**
     * Récupère la liste des fichiers liés à une ressource
................................................................................
		if (!in_array($type, $check))
		{
			throw new \LogicException('Type de lien de fichier inconnu.');
		}

    	$images = is_null($images) ? '' : ' AND image = ' . (int)$images;

        $files = DB::getInstance()->simpleStatementFetch('SELECT fichiers.*, c.hash, c.taille
        	FROM fichiers 
            INNER JOIN fichiers_'.$type.' AS fwp ON fwp.fichier = fichiers.id
            INNER JOIN fichiers_contenu AS c ON c.id = fichiers.id_contenu
            WHERE fwp.id = ? '.$images.'
            ORDER BY fichiers.nom COLLATE NOCASE;', \SQLITE3_ASSOC, (int)$id);

        foreach ($files as &$file)
        {
        	$file['url'] = self::_getURL($file['id'], $file['nom']);
        	$file['thumb'] = $file['image'] ? self::_getURL($file['id'], $file['nom'], 200) : false;
        }

        return $files;
    }

    /**
     * Enlève d'une liste de fichiers ceux qui sont mentionnés dans un texte wiki
................................................................................
     * @return array        Un tableau qui ne contient pas les fichiers mentionnés dans $text
     */
    static public function filterFilesUsedInText($files, $text)
    {
    	$used = self::listFilesUsedInText($text);

    	return array_filter($files, function ($row) use ($used) {
    		return !in_array($row['id'], $used);
    	});
    }

    /**
     * Renvoie une liste d'ID de fichiers mentionnées dans un texte wiki
     * @param  string $text Texte wiki
     * @return array       Liste des IDs de fichiers mentionnés







|


|







 







>

|
>
>
>
|

<
>
>
|
>
>
>
>
|
|
|
<
|










>
>

|

|
>








|







|






|




|
|

|






|
|




|





|







 







|
|
|

|


|
|

|







 







|

<











<


>
|
<
<
|

<
<
<
|
>
>







 







|

|








|







 







|
|






|
|
|
|
|







 







|




|



|
|







 







|







69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
...
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126

127
128
129
130
131
132
133
134
135
136

137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
...
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
...
362
363
364
365
366
367
368
369
370

371
372
373
374
375
376
377
378
379
380
381

382
383
384
385


386
387



388
389
390
391
392
393
394
395
396
397
...
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
...
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
...
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
...
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
	 * Constructeur de l'objet pour un fichier
	 * @param integer $id Numéro unique du fichier
	 */
	public function __construct($id, $data = null)
	{
		if (is_null($data))
		{
			$data = DB::getInstance()->first('SELECT fichiers.*, fc.hash, fc.taille,
				strftime(\'%s\', datetime) AS datetime
				FROM fichiers INNER JOIN fichiers_contenu AS fc ON fc.id = fichiers.id_contenu
				WHERE fichiers.id = ?;', (int)$id);
		}

		if (!$data)
		{
			throw new \InvalidArgumentException('Ce fichier n\'existe pas.');
		}

................................................................................
		$check = [self::LIEN_MEMBRES, self::LIEN_WIKI, self::LIEN_COMPTA];

		if (!in_array($type, $check))
		{
			throw new \LogicException('Type de lien de fichier inconnu.');
		}

		// Ne pas chercher dans le type qu'on veut lier
		unset($check[array_search($type, $check)]);

		// Vérifier que le fichier n'est pas déjà lié à un autre type
		$query = [];

		foreach ($check as $type)
		{

			$query[] = sprintf('SELECT 1 FROM fichiers_%s WHERE fichier = %d', $type, $this->id);
		}

		$query = implode(' UNION ', $query) . ';';

		if ($db->firstColumn($query))
		{
			throw new \LogicException('Ce fichier est déjà lié à un autre contenu : ' . $check_type);
		}


		return $db->query('INSERT OR IGNORE INTO fichiers_' . $type . ' (fichier, id) VALUES (?, ?);',
			(int)$this->id, (int)$foreign_id);
	}

	/**
	 * Vérifie que l'utilisateur a bien le droit d'accéder à ce fichier
	 * @param  mixed   $user Tableau contenant les infos sur l'utilisateur connecté, provenant de Membres::getLoggedUser, ou false
	 * @return boolean       TRUE si l'utilisateur a le droit d'accéder au fichier, sinon FALSE
	 */
	public function checkAccess($user = false)
	{
		$db = DB::getInstance();

		// On regarde déjà si le fichier n'est pas lié au wiki
		$query = sprintf('SELECT wp.droit_lecture FROM fichiers_%s AS link
			INNER JOIN wiki_pages AS wp ON wp.id = link.id
			WHERE link.fichier = ? LIMIT 1;', self::LIEN_WIKI);
		$wiki = $db->firstColumn($query, (int)$this->id);

		// Page wiki publique, aucune vérification à faire, seul cas d'accès à un fichier en dehors de l'espace admin
		if ($wiki !== false && $wiki == Wiki::LECTURE_PUBLIC)
		{
			return true;
		}
			
		// Pas d'utilisateur connecté, pas d'accès aux fichiers de l'espace admin
		if (empty($user->droits))
		{
			return false;
		}

		if ($wiki !== false)
		{
			// S'il n'a même pas droit à accéder au wiki c'est mort
			if ($user->droits->wiki < Membres::DROIT_ACCES)
			{
				return false;
			}

			// On renvoie à l'objet Wiki pour savoir si l'utilisateur a le droit de lire ce fichier
			$_w = new Wiki;
			$_w->setRestrictionCategorie($user->id_categorie, $user->droits->wiki);
			return $_w->canReadPage($wiki);
		}

		// On regarde maintenant si le fichier est lié à la compta
		$query = sprintf('SELECT 1 FROM fichiers_%s WHERE fichier = ? LIMIT 1;', self::LIEN_COMPTA);
		$compta = $db->firstColumn($query, (int)$this->id);

		if ($compta && $user->droits->compta >= Membres::DROIT_ACCES)
		{
			// OK si accès à la compta
			return true;
		}

		// Enfin, si le fichier est lié à un membre
		$query = sprintf('SELECT id FROM fichiers_%s WHERE fichier = ? LIMIT 1;', self::LIEN_MEMBRES);
		$membre = $db->firstColumn($query, (int)$this->id);

		if ($membre !== false)
		{
			// De manière évidente, l'utilisateur a le droit d'accéder aux fichiers liés à son profil
			if ((int)$membre == $user->id)
			{
				return true;
			}

			// Pour voir les fichiers des membres il faut pouvoir les gérer
			if ($user->droits->membres >= Membres::DROIT_ECRITURE)
			{
				return true;
			}
		}

		return false;
	}
................................................................................
	 * Supprime le fichier
	 * @return boolean TRUE en cas de succès
	 */
	public function remove()
	{
		$db = DB::getInstance();
		$db->begin();
		$db->delete('fichiers_compta_journal', 'fichier = ?', (int)$this->id);
		$db->delete('fichiers_wiki_pages', 'fichier = ?', (int)$this->id);
		$db->delete('fichiers_membres', 'fichier = ?', (int)$this->id);

		$db->delete('fichiers', 'id = ?', (int)$this->id);

		// Suppression du contenu s'il n'est pas utilisé par un autre fichier
		if (!$db->firstColumn('SELECT 1 FROM fichiers WHERE id_contenu = ? AND id != ? LIMIT 1;', 
			(int)$this->id_contenu, (int)$this->id))
		{
			$db->delete('fichiers_contenu', 'id = ?', (int)$this->id_contenu);
		}

		$cache_id = 'fichiers.' . $this->id_contenu;
		
		Static_Cache::remove($cache_id);

		foreach (self::$allowed_thumb_sizes as $size)
................................................................................
	 * Vérifie si le hash fourni n'est pas déjà stocké
	 * Utile pour par exemple reconnaître un ficher dont le contenu est déjà stocké, et éviter un nouvel upload
	 * @param  string $hash Hash SHA1
	 * @return boolean      TRUE si le hash est déjà présent dans fichiers_contenu, FALSE sinon
	 */
	static public function checkHash($hash)
	{
		return (boolean) DB::getInstance()->firstColumn(
			'SELECT 1 FROM fichiers_contenu WHERE hash = ?;', 

			trim(strtolower($hash))
		);
	}

	/**
	 * Retourne un tableau de hash trouvés dans la DB parmi une liste de hash fournis
	 * @param  array  $list Liste de hash à vérifier
	 * @return array        Liste des hash trouvés
	 */
	static public function checkHashList($list)
	{

		$db = DB::getInstance();

		array_walk($list, function ($a) use ($db) {
			return $db->quote($a);


		});




		$query = sprintf('SELECT hash, 1 FROM fichiers_contenu WHERE hash IN (%s);', 
			$list);
		return $db->getAssoc($query);
	}

	/**
	 * Récupération du message d'erreur
	 * @param  integer $error Code erreur du $_FILE
	 * @return string Message d'erreur
	 */
................................................................................
		$size = filesize($file['tmp_name']);

		$db = DB::getInstance();

		$db->begin();

		// Il peut arriver que l'on renvoie ici un fichier déjà stocké, auquel cas, ne pas le re-stocker
		if (!($id_contenu = $db->firstColumn('SELECT id FROM fichiers_contenu WHERE hash = ?;', $hash)))
		{
			$db->insert('fichiers_contenu', [
				'hash'		=>	$hash,
				'taille'	=>	(int)$size,
				'contenu'	=>	[\SQLITE3_BLOB, file_get_contents($file['tmp_name'])],
			]);
			
			$id_contenu = $db->lastInsertRowID();
		}

		$db->insert('fichiers', [
			'id_contenu'	=>	(int)$id_contenu,
			'nom'			=>	$name,
			'type'			=>	$type,
			'image'			=>	(int)$is_image,
		]);

		$db->commit();
................................................................................
	 * @return object       Un objet Fichiers en cas de succès
	 */
	static public function uploadExistingHash($name, $hash)
	{
		$db = DB::getInstance();
		$name = preg_replace('/[^\d\w._-]/ui', '', $name);

		$file = $db->first('SELECT * FROM fichiers 
			INNER JOIN fichiers_contenu AS fc ON fc.id = fichiers.id_contenu AND fc.hash = ?;', trim($hash));

		if (!$file)
		{
			throw new UserException('Le fichier à copier n\'existe pas (aucun hash ne correspond à '.$hash.').');
		}

		$db->insert('fichiers', [
			'id_contenu' =>	(int)$file->id_contenu,
			'nom'        =>	$name,
			'type'       =>	$file->type,
			'image'      =>	(int)$file->image,
		]);

		return new Fichiers($db->lastInsertRowID());
	}

    /**
     * Récupère la liste des fichiers liés à une ressource
................................................................................
		if (!in_array($type, $check))
		{
			throw new \LogicException('Type de lien de fichier inconnu.');
		}

    	$images = is_null($images) ? '' : ' AND image = ' . (int)$images;

        $files = DB::getInstance()->get('SELECT fichiers.*, c.hash, c.taille
        	FROM fichiers 
            INNER JOIN fichiers_'.$type.' AS fwp ON fwp.fichier = fichiers.id
            INNER JOIN fichiers_contenu AS c ON c.id = fichiers.id_contenu
            WHERE fwp.id = ? '.$images.'
            ORDER BY fichiers.nom COLLATE NOCASE;', (int)$id);

        foreach ($files as &$file)
        {
        	$file->url = self::_getURL($file->id, $file->nom);
        	$file->thumb = $file->image ? self::_getURL($file->id, $file->nom, 200) : false;
        }

        return $files;
    }

    /**
     * Enlève d'une liste de fichiers ceux qui sont mentionnés dans un texte wiki
................................................................................
     * @return array        Un tableau qui ne contient pas les fichiers mentionnés dans $text
     */
    static public function filterFilesUsedInText($files, $text)
    {
    	$used = self::listFilesUsedInText($text);

    	return array_filter($files, function ($row) use ($used) {
    		return !in_array($row->id, $used);
    	});
    }

    /**
     * Renvoie une liste d'ID de fichiers mentionnées dans un texte wiki
     * @param  string $text Texte wiki
     * @return array       Liste des IDs de fichiers mentionnés