php8
[web.mtrack] / MTrack / Attachment.php
1 <?php # vim:ts=2:sw=2:et:
2 /* For licensing and copyright terms, see the file named LICENSE */
3
4 //require_once 'MTrack/Changeset.php';
5 require_once 'MTrack/DB.php';
6 require_once 'MTrack/Config.php';
7
8
9 class MTrackAttachment {
10
11   static function add($object, $local_filename, $filename, MTrackChangeset $CS)
12   {
13     $size = filesize($local_filename);
14     if (!$size) {
15       return false;
16     }
17     $hash = self::import_file($local_filename);
18     $fp = fopen($local_filename, 'rb');
19     $q = MTrackDB::get()->prepare(
20       'insert into attachments (object, hash, filename, size, cid, payload)
21       values (?, ?, ?, ?, ?, ?)');
22     $q->bindValue(1, $object);
23     $q->bindValue(2, $hash);
24     $q->bindValue(3, $filename);
25     $q->bindValue(4, $size);
26     $q->bindValue(5, $CS->cid);
27     $q->bindValue(6, $fp, PDO::PARAM_LOB);
28     $q->execute();
29     $CS->add("$object:@attachment:", '', $filename);
30   }
31
32   static function process_delete($relobj, MTrackChangeset $CS) {
33     if (!isset($_POST['delete_attachment'])) return;
34     if (!is_array($_POST['delete_attachment'])) return;
35     foreach ($_POST['delete_attachment'] as $name) {
36       $vars = explode('/', $name);
37       $filename = array_pop($vars);
38       $cid = array_pop($vars);
39       $object = join('/', $vars);
40
41       if ($object != $relobj) return;
42       MTrackDB::q('delete from attachments where object = ? and
43           cid = ? and filename = ?', $object, $cid, $filename);
44       $CS->add("$object:@attachment:", $filename, '');
45     }
46   }
47
48   /* this function is registered into sqlite and invoked from
49    * a trigger whenever an attachment row is deleted */
50   static function attachment_row_deleted($hash, $count)
51   {
52     if ($count == 0) {
53       // unlink the underlying file here
54       unlink(self::local_path($hash, false));
55     }
56     return $count;
57   }
58
59   static function hash_file($filename)
60   {
61     return sha1_file($filename);
62   }
63
64   static function local_path($hash, $fetch = true)
65   {
66     $adir = MTrackConfig::get('core', 'vardir') . '/attach';
67
68     /* 40 hex digits: split into 16, 16, 4, 4 */
69     $a = substr($hash, 0, 16);
70     $b = substr($hash, 16, 16);
71     $c = substr($hash, 32, 4);
72     $d = substr($hash, 36, 4);
73
74     $dir = "$adir/$a/$b/$c";
75     if (!is_dir($dir)) {
76       $mask = umask(0);
77       mkdir($dir, 02777, true);
78       umask($mask);
79     }
80     $filename = $dir . "/$d";
81
82     if ($fetch) {
83       // Tricky locking bit
84       $fp = @fopen($filename, 'c+');
85       flock($fp, LOCK_EX);
86       $st = fstat($fp);
87       if ($st['size'] == 0) {
88         /* we get to fill it out */
89
90         $db = MTrackDB::get();
91         $q = $db->prepare(
92             'select payload from attachments where hash = ?');
93         $q->execute(array($hash));
94         $q->bindColumn(1, $blob, PDO::PARAM_LOB);
95         $q->fetch();
96         if (is_string($blob)) {
97           fwrite($fp, $blob);
98         } else {
99           stream_copy_to_stream($blob, $fp);
100         }
101         rewind($fp);
102       }
103     }
104
105     return $filename;
106   }
107
108   /* calculates the hash of the filename.  If another file with
109    * the same hash does not already exist in the attachment area,
110    * the file is copied in.
111    * Returns the hash */
112   static function import_file($filename)
113   {
114     $h = self::hash_file($filename);
115     $dest = self::local_path($h, false);
116     if (!file_exists($dest)) {
117       if (is_uploaded_file($filename)) {
118         move_uploaded_file($filename, $dest);
119       } else if (!is_file($filename)) {
120         throw new Exception("$filename does not exist");
121       } else {
122         copy($filename, $dest);
123       }
124     }
125     return $h;
126   }
127
128   static function renderDeleteList($object)
129   {
130     global $ABSWEB;
131
132     $atts = MTrackDB::q('
133       select * from attachments
134       left join changes on (attachments.cid = changes.cid)
135       where attachments.object = ? order by changedate, filename',
136         $object)->fetchAll(PDO::FETCH_ASSOC);
137
138     if (count($atts) == 0) return '';
139
140     $max_dim = 150;
141
142     $html = <<<HTML
143 <em>Select the checkbox to delete an attachment</em>
144 <table>
145   <tr>
146     <td>&nbsp;</td>
147     <td>Attachment</td>
148     <td>Size</td>
149     <td>Added</td>
150   </tr>
151 HTML;
152
153     foreach ($atts as $row) {
154       $url = "{$ABSWEB}attachment.php/$object/$row[cid]/$row[filename]";
155       $html .= <<<HTML
156 <tr>
157   <td><input type='checkbox' name='delete_attachment[]'
158       value='$object/$row[cid]/$row[filename]'></td>
159   <td><a class='attachment' href='$url'>$row[filename]</a></td>
160   <td>$row[size]</td>
161   <td>
162 HTML;
163       $html .= mtrack_username($row['who'], array(
164           'no_image' => true
165         )) .
166         " " . mtrack_date($row['changedate']) . "</td></tr>\n";
167     }
168     $html .= "</table><br>";
169     return $html;
170   }
171
172   /* renders the attachment list for a given object */
173   static function renderList($object)
174   {
175     global $ABSWEB;
176
177     $atts = MTrackDB::q('
178       select * from attachments
179       left join changes on (attachments.cid = changes.cid)
180       where attachments.object = ? order by changedate, filename',
181         $object)->fetchAll(PDO::FETCH_ASSOC);
182
183     if (count($atts) == 0) return '';
184
185     $max_dim = 150;
186
187     $html = "<div class='attachment-list'><b>Attachments</b><ul>";
188     foreach ($atts as $row) {
189       $url = "{$ABSWEB}attachment.php/$object/$row[cid]/$row[filename]";
190       $html .= "<li><a class='attachment'" .
191         " href='$url'>".
192         "$row[filename]</a> ($row[size]) added by " .
193         mtrack_username($row['who'], array(
194           'no_image' => true
195         )) .
196         " " . mtrack_date($row['changedate']);
197
198       list($width, $height) = getimagesize(self::local_path($row['hash']));
199       if ($width + $height) {
200         /* limit maximum size */
201         if ($width > $max_dim) {
202           $height *= $max_dim / $width;
203           $width = $max_dim;
204         }
205         if ($height > $max_dim) {
206           $width *= $max_dim / $height;
207           $height = $max_dim;
208         }
209         $html .= "<br><a href='$url'><img src='$url' width='$width' border='0' height='$height'></a>";
210       }
211
212       $html .= "</li>\n";
213     }
214     $html .= "</ul></div>";
215     return $html;
216   }
217 }