Overview
Comment:Débuts gestion de fichier, à partir d'un patch de @Got
Downloads: Tarball | ZIP archive | SQL archive
Timelines: family | ancestors | descendants | both | trunk
Files: files | file ages | folders
SHA1: f387548bd8599442d93fd0ab431ecb1569499478
User & Date: bohwaz on 2015-01-23 04:28:36
Other Links: manifest | tags
Context
2015-01-26
00:16
Libellés des comptes sont inversés [cc08c62119] check-in: 0e915476b1 user: bohwaz tags: trunk
2015-01-23
04:28
Débuts gestion de fichier, à partir d'un patch de @Got check-in: f387548bd8 user: bohwaz tags: trunk
04:28
Exclure les éléments des squelettes check-in: 7f3f931fc1 user: bohwaz tags: trunk
Changes

Modified src/config.dist.php from [d696b6ae5b] to [152c78f796].

57
58
59
60
61
62
63










// Utilisation de cron pour les tâches automatiques
// Si "true" on s'attend à ce qu'une tâche automatisée appelle
// le script cron.php à la racine toutes les 24 heures. Sinon Garradin
// effectuera les actions automatiques quand quelqu'un se connecte à 
// l'administration ou visite le site.
// Défaut : false
const USE_CRON = false;

















>
>
>
>
>
>
>
>
>
>
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
// Utilisation de cron pour les tâches automatiques
// Si "true" on s'attend à ce qu'une tâche automatisée appelle
// le script cron.php à la racine toutes les 24 heures. Sinon Garradin
// effectuera les actions automatiques quand quelqu'un se connecte à 
// l'administration ou visite le site.
// Défaut : false
const USE_CRON = false;

// Activation de l'envoi de fichier directement par le serveur web.
// Permet d'améliorer la rapidité d'envoi des fichiers.
// Supporte les serveurs web suivants :
// - Apache avec mod_xsendfile (paquet libapache2-mod-xsendfile)
// - Lighttpd
// N'activer que si vous êtes sûr que le module est installé et activé.
// Nginx n'est PAS supporté, car X-Accel-Redirect ne peut gérer que des fichiers
// qui sont *dans* le document root du vhost, ce qui n'est pas le cas ici.
const ENABLE_XSENDFILE = false;

Modified src/include/data/0.7.0.sql from [8c45d58f37] to [6994f75ee3].

1
2
3
4
5
6
7
8












































CREATE TABLE plugins_signaux
-- Association entre plugins et signaux (hooks)
(
    signal TEXT NOT NULL,
    plugin TEXT NOT NULL REFERENCES plugins (id),
    callback TEXT NOT NULL,
    PRIMARY KEY (signal, plugin)
);




















































>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
CREATE TABLE plugins_signaux
-- Association entre plugins et signaux (hooks)
(
    signal TEXT NOT NULL,
    plugin TEXT NOT NULL REFERENCES plugins (id),
    callback TEXT NOT NULL,
    PRIMARY KEY (signal, plugin)
);

CREATE TABLE fichiers
-- Données sur les fichiers
(
	id INTEGER NOT NULL PRIMARY KEY,
	nom TEXT NOT NULL, -- nom de fichier (par exemple image1234.jpeg)
	type TEXT NOT NULL, -- Type MIME
	titre TEXT NOT NULL, -- Titre/description
	date TEXT NOT NULL DEFAULT CURRENT_DATE, -- Date d'ajout ou mise à jour du fichier
	hash TEXT NOT NULL, -- Hash SHA1 du contenu du fichier
	taille INTEGER NOT NULL -- Taille en octets
);

CREATE UNIQUE INDEX fichiers_hash ON fichiers (hash);
CREATE INDEX fichiers_titre ON fichiers (titre);
CREATE INDEX fichiers_date ON fichiers (date);

CREATE TABLE fichiers_contenu
-- Contenu des fichiers
(
	id INTEGER NOT NULL PRIMARY KEY REFERENCES fichiers (id),
	contenu BLOB
);

CREATE TABLE fichiers_membres
-- Associations entre fichiers et membres (photo de profil par exemple)
(
	fichier INTEGER NOT NULL REFERENCES fichiers (id),
	id INTEGER NOT NULL REFERENCES membres (id)
);

CREATE TABLE fichiers_wiki_pages
-- Associations entre fichiers et pages du wiki
(
	fichier INTEGER NOT NULL REFERENCES fichiers (id),
	id INTEGER NOT NULL REFERENCES wiki_pages (id)
);

CREATE TABLE fichiers_compta_journal
-- Associations entre fichiers et journal de compta (pièce comptable par exemple)
(
	fichier INTEGER NOT NULL REFERENCES fichiers (id),
	id INTEGER NOT NULL REFERENCES compta_journal (id)
);

Modified src/include/data/schema.sql from [aeb1567568] to [290e3776d4].

319
320
321
322
323
324
325













































-- Association entre plugins et signaux (hooks)
(
    signal TEXT NOT NULL,
    plugin TEXT NOT NULL REFERENCES plugins (id),
    callback TEXT NOT NULL,
    PRIMARY KEY (signal, plugin)
);




















































>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
-- Association entre plugins et signaux (hooks)
(
    signal TEXT NOT NULL,
    plugin TEXT NOT NULL REFERENCES plugins (id),
    callback TEXT NOT NULL,
    PRIMARY KEY (signal, plugin)
);


CREATE TABLE fichiers
-- Données sur les fichiers
(
    id INTEGER NOT NULL PRIMARY KEY,
    nom TEXT NOT NULL, -- nom de fichier (par exemple image1234.jpeg)
    type TEXT NOT NULL, -- Type MIME
    titre TEXT NOT NULL, -- Titre/description
    date TEXT NOT NULL DEFAULT CURRENT_DATE, -- Date d'ajout ou mise à jour du fichier
    hash TEXT NOT NULL, -- Hash SHA1 du contenu du fichier
    taille INTEGER NOT NULL -- Taille en octets
);

CREATE UNIQUE INDEX fichiers_hash ON fichiers (hash);
CREATE INDEX fichiers_titre ON fichiers (titre);
CREATE INDEX fichiers_date ON fichiers (date);

CREATE TABLE fichiers_contenu
-- Contenu des fichiers
(
    id INTEGER NOT NULL PRIMARY KEY REFERENCES fichiers (id),
    contenu BLOB
);

CREATE TABLE fichiers_membres
-- Associations entre fichiers et membres (photo de profil par exemple)
(
    fichier INTEGER NOT NULL REFERENCES fichiers (id),
    id INTEGER NOT NULL REFERENCES membres (id)
);

CREATE TABLE fichiers_wiki_pages
-- Associations entre fichiers et pages du wiki
(
    fichier INTEGER NOT NULL REFERENCES fichiers (id),
    id INTEGER NOT NULL REFERENCES wiki_pages (id)
);

CREATE TABLE fichiers_compta_journal
-- Associations entre fichiers et journal de compta (pièce comptable par exemple)
(
    fichier INTEGER NOT NULL REFERENCES fichiers (id),
    id INTEGER NOT NULL REFERENCES compta_journal (id)
);

Modified src/include/init.php from [b9b3bbdd71] to [de2c349764].

117
118
119
120
121
122
123






124
125
126
127
128
129
130
}

// Utilisation de cron pour les tâches automatiques
if (!defined('Garradin\USE_CRON'))
{
    define('Garradin\USE_CRON', false);
}







define('Garradin\WEBSITE', 'http://garradin.eu/');
define('Garradin\PLUGINS_URL', 'https://garradin.eu/plugins/list.json');

// PHP devrait être assez intelligent pour chopper la TZ système mais nan
// il sait pas faire (sauf sur Debian qui a le bon patch pour ça), donc pour 
// éviter le message d'erreur à la con on définit une timezone par défaut







>
>
>
>
>
>







117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
}

// Utilisation de cron pour les tâches automatiques
if (!defined('Garradin\USE_CRON'))
{
    define('Garradin\USE_CRON', false);
}

// Activation de X-SendFile
if (!defined('Garradin\ENABLE_XSENDFILE'))
{
    define('Garradin\ENABLE_XSENDFILE', false);
}

define('Garradin\WEBSITE', 'http://garradin.eu/');
define('Garradin\PLUGINS_URL', 'https://garradin.eu/plugins/list.json');

// PHP devrait être assez intelligent pour chopper la TZ système mais nan
// il sait pas faire (sauf sur Debian qui a le bon patch pour ça), donc pour 
// éviter le message d'erreur à la con on définit une timezone par défaut

Added src/include/lib/Garradin/Fichiers.php version [c00d51886f].

















































































































































































































































































































































>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
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
<?php

namespace Garradin;

class Fichiers
{
	protected $allowed_files_extensions = [
		'jpeg', 'jpg', 'jpe', 'gif', 'png', 'svg', 'svgz', 'psd', 'bmp', 'ico', // Images
		'pdf', 'txt', 'rtf', 'tex', 'lyx', 'html', 'epub', 'mobi', 'ps', 'xml', // Textes
		'sxw', 'sxc', 'sxd', 'sxi', 'sxf', 'odt', 'odg', 'odp', 'ods', 'odc', 'odf', // Libre Office
		'docx', 'xlsx', 'doc', 'xls', 'ppsx', 'pps', 'pptx', 'ppt', 'pub', // Microsoft
		'webm', 'mp4', 'flv', 'mkv', 'avi', 'mov', // Vidéos
		'mp3', 'm4a', 'aac', 'ogg', 'mid', // Audio
		'zip', 'rar', '7z', 'gz', 'xz', 'bz2', 'bz', 'tar', // Archives
		'sqlite', 'swf', // Divers
	];

	public $type;
	public $titre;
	public $nom;
	public $date;
	public $hash;
	public $taille;
	public $id;

	public function __construct($id)
	{
		$data = DB::getInstance()->simpleQuerySingle('SELECT *, strftime(\'%s\', date) AS date
			FROM fichiers WHERE id = ?;', true, (int)$id);

		foreach ($data as $key=>$value)
		{
			$this->$key = $value;
		}
	}	

	/**
	 * Envoie une miniature à la taille indiquée au client HTTP
	 * @param  integer $width  Largeur
	 * @param  integer $height Hauteur
	 * @param  boolean $crop   TRUE si on doit cropper aux dimensions indiquées
	 * @return void
	 */
	public function getThumbnail($width, $height, $crop = false)
	{
	}

	/**
	 * Supprime l'image
	 * @return boolean TRUE en cas de succès
	 */
	public function remove()
	{
		$db = DB::getInstance();
		$db->exec('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_contenu WHERE id = ?;', (int)$this->id);
		$db->simpleExec('DELETE FROM fichiers WHERE id = ?;', (int)$this->id);
		return $db->exec('END;');
	}

	/**
	 * Modifie les informations du fichier
	 * @param  string $titre Le titre du fichier
	 * @param  string $nom   Le nom du fichier (avec extension)
	 * @return boolean TRUE en cas de succès
	 */
	public function edit($titre, $nom)
	{

	}

	/**
	 * Envoie le fichier au client HTTP
	 * @return void
	 */
	public function serve()
	{
		$cache_id = 'fichiers.' . $this->id;

		// Le fichier n'existe pas dans le cache statique, on l'enregistre
		if (!Static_Cache::exists($cache_id))
		{
			$blob = DB::getInstance()->openBlob('fichiers_contenu', 'contenu', (int)$this->id);
			Static_Cache::storeFromPointer($cache_id, $blob);
			fclose($blob);
		}

		$path = Static_Cache::getPath($cache_id);

		// Désactiver le cache
		header('Pragma: public');
		header('Expires: -1');
		header('Cache-Control: public, must-revalidate, post-check=0, pre-check=0');

		header('Content-Disposition: attachment; filename="' . $this->nom . '"');
		
		// Utilisation de XSendFile si disponible
		if (ENABLE_XSENDFILE && isset($_SERVER['SERVER_SOFTWARE']))
		{
			if (stristr($_SERVER['SERVER_SOFTWARE'], 'apache') 
				&& function_exists('apache_get_modules') 
				&& in_array('mod_xsendfile', apache_get_modules()))
			{
				header('X-Sendfile: ' . $path);
				return true;
			}
			else if (stristr($_SERVER['SERVER_SOFTWARE'], 'lighttpd'))
			{
				header('X-Sendfile: ' . $path);
				return true;
			}
		}

		// Désactiver gzip
		if (function_exists('apache_setenv'))
		{
			@apache_setenv('no-gzip', 1);
		}

		@ini_set('zlib.output_compression', 'Off');

		header('Content-Length: '. (int)$this->taille);

		ob_clean();
		flush();

		// Sinon on envoie le fichier à la mano
		readfile($path);
	}

	/**
	 * Upload du fichier par POST
	 * @param  array  $file  Caractéristiques du fichier envoyé
	 * @param  string $titre Titre descriptif du fichier
	 * @return boolean TRUE en cas de succès
	 */
	static public function upload($file, $titre)
	{
		// FIXME traiter les images envoyées redimensionnées par javascript (base64)

		$name = '...';
		// FIXME name sanitization

		if (preg_match('/\.(?:php\d*|cgi|pl|perl|jsp|asp|py|exe|com|bat|vb[se]?|chm|pif|reg|ws[cfh]|scr|asp)$/i', $name))
		{
			throw new UserException('Extension de fichier interdite.');
		}

		$ext = substr($name, strrpos($name, '.')+1);
		$ext = strtolower($ext);

		if (!array_key_exists($ext, $this->allowed_files))
		{
			throw new UserException('Ce type de fichier n\'est pas autorisé.');
		}

		$bytes = file_get_contents($path, false, null, -1, 1024);
		$type = \KD2\FileInfo::guessMimeType($bytes);

		if (!$type)
		{
			throw new UserException('Type de fichier inconnu.');
		}
	}
}

Modified src/include/lib/Garradin/Static_Cache.php from [f40cd20c85] to [5fabb1fda6].

8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25

















26
27
28
29
30
31
32
	const CLEAN_EXPIRE = 86400; // 1 day

	protected static function _getCacheDir()
	{
		return CACHE_ROOT . '/static';
	}

	protected static function _getCachePath($id)
	{
		$id = 'cache_' . sha1($id);
		return self::_getCacheDir() . '/' . $id;
	}

	static public function store($id, $content)
	{
		$path = self::_getCachePath($id);
		return (bool) file_put_contents($path, $content);
	}


















	static public function expired($id, $expire = self::EXPIRE)
	{
		$path = self::_getCachePath($id);
		$time = @filemtime($path);

		if (!$time)







|










>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>







8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
	const CLEAN_EXPIRE = 86400; // 1 day

	protected static function _getCacheDir()
	{
		return CACHE_ROOT . '/static';
	}

	protected static function __getCachePath($id)
	{
		$id = 'cache_' . sha1($id);
		return self::_getCacheDir() . '/' . $id;
	}

	static public function store($id, $content)
	{
		$path = self::_getCachePath($id);
		return (bool) file_put_contents($path, $content);
	}

	static public function storeFromPointer($id, $pointer)
	{
		$path = self::_getCachePath($id);

		$fp = fopen($path, 'wb');
		$ok = stream_copy_to_stream($pointer, $fp);
		fclose($fp);

		return $ok;
	}

	static public function storeFromUpload($id, $uploaded_file)
	{
		$path = self::_getCachePath($id);
		return (bool) move_uploaded_file($uploaded_file, $path);
	}

	static public function expired($id, $expire = self::EXPIRE)
	{
		$path = self::_getCachePath($id);
		$time = @filemtime($path);

		if (!$time)
49
50
51
52
53
54
55





56
57
58
59
60
61
62
		return readfile($path);
	}

	static public function getPath($id)
	{
		return self::_getCachePath($id);
	}






	static public function remove($id)
	{
		$path = self::_getCachePath($id);
		return unlink($path);
	}








>
>
>
>
>







66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
		return readfile($path);
	}

	static public function getPath($id)
	{
		return self::_getCachePath($id);
	}

	static public function exists($id)
	{
		return file_exists(self::_getCachePath($id));
	}

	static public function remove($id)
	{
		$path = self::_getCachePath($id);
		return unlink($path);
	}