PageRenderTime 48ms CodeModel.GetById 15ms RepoModel.GetById 1ms app.codeStats 0ms

/inc/attachment.php

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