CWIS Developer Documentation
File.php
Go to the documentation of this file.
1 <?PHP
2 #
3 # FILE: File.php
4 #
5 # Part of the Collection Workflow Integration System (CWIS)
6 # Copyright 2010-2013 Edward Almasy and Internet Scout Research Group
7 # http://scout.wisc.edu/cwis/
8 #
9 
13 class File
14 {
15 
16  # ---- PUBLIC INTERFACE --------------------------------------------------
17 
18  # status codes (set by constructor and returned by File::Status())
19  const FILESTAT_OK = 0;
20  const FILESTAT_COPYERROR = 1;
25 
39  public function __construct($IdOrFileName, $ResourceId = NULL, $FieldId = NULL,
40  $DesiredFileName = NULL, $CheckFileLength = TRUE)
41  {
42  # assume constructor will succeed
43  $this->Status = self::FILESTAT_OK;
44 
45  # get our own database handle
46  $DB = new Database();
47  $this->DB = $DB;
48 
49  # if ID supplied
50  if (is_numeric($IdOrFileName))
51  {
52  # set file ID from supplied value
53  $this->Id = intval($IdOrFileName);
54 
55  # load file info from database
56  $DB->Query("SELECT * FROM Files WHERE FileId = ".$this->Id);
57  $this->DBFields = $DB->FetchRow();
58 
59  # if the image wasn't found in the database
60  if (!$DB->NumRowsSelected())
61  {
62  $this->Status = self::FILESTAT_DOESNOTEXIST;
63  }
64  }
65  # else if file name and resource ID and field ID supplied
66  elseif (strlen($IdOrFileName) && ($ResourceId != NULL) && ($FieldId != NULL))
67  {
68  # if file does not exist
69  $TempFileName = $IdOrFileName;
70  if (!file_exists($TempFileName) || !is_readable($TempFileName))
71  {
72  # set status indicating appropriate error
73  $this->Status = file_exists($TempFileName)
74  ? self::FILESTAT_DOESNOTEXIST : self::FILESTAT_UNREADABLE;
75  }
76  else
77  {
78  # if we were asked to check file length and file was zero length
79  $FileLength = filesize($TempFileName);
80  if ($CheckFileLength && !$FileLength)
81  {
82  # set status indicating zero length file
83  $this->Status = self::FILESTAT_ZEROLENGTH;
84  }
85  else
86  {
87  # generate secret string (used to protect from unauthorized download)
88  srand((double)microtime() * 1000000);
89  $SecretString = sprintf("%04X", rand(1, 30000));
90 
91  # attempt to get file type
92  $FileType = "";
93  if (function_exists("finfo_open"))
94  {
95  $FInfoHandle = finfo_open(FILEINFO_MIME);
96 
97  if ($FInfoHandle)
98  {
99  $FInfoMime = finfo_file($FInfoHandle, $TempFileName);
100  finfo_close($FInfoHandle);
101 
102  if ($FInfoMime)
103  {
104  $FileType = $FInfoMime;
105  }
106  }
107  }
108  else if (function_exists("mime_content_type"))
109  {
110  # mime_content_type has been deprecated, but it may be
111  # the only way to get the mimetype for PHP < 5.3
112  $MimeType = mime_content_type($TempFileName);
113 
114  if ($MimeType)
115  {
116  $FileType = $MimeType;
117  }
118  }
119 
120  # handle Office XML formats
121  # These are recognized by PHP as zip files (because they are), but
122  # IE (and maybe other things?) need a special-snowflake MIME type to
123  # handle them properly.
124  # For a list of the required types, see
125  # https://technet.microsoft.com/en-us/library/ee309278(office.12).aspx
126  if ($FileType == "application/zip; charset=binary")
127  {
128  $MstfPrefix = "application/vnd.openxmlformats-officedocument";
129 
130  $FileNameForExt = is_null($DesiredFileName) ?
131  $TempFileName : $DesiredFileName ;
132 
133  $FileExt = strtolower(pathinfo(
134  $FileNameForExt, PATHINFO_EXTENSION));
135 
136  switch ($FileExt)
137  {
138  case "docx":
139  $FileType = $MstfPrefix.".wordprocessingml.document";
140  break;
141 
142  case "xlsx":
143  $FileType = $MsftPrefix.".spreadsheetml.sheet";
144  break;
145 
146  case "pptx":
147  $FileType = $MsftPrefix.".presentationml.slideshow";
148  break;
149 
150  default:
151  # do nothing
152  }
153  }
154 
155  # add file info to database
156  $BaseFileName = $DesiredFileName
157  ? basename($DesiredFileName) : basename($TempFileName);
158  $DB->Query("INSERT INTO Files"
159  ." (ResourceId, FieldId, FileName, FileLength, FileType,"
160  ." SecretString)"
161  ." VALUES ("
162  .intval($ResourceId).", "
163  .intval($FieldId).", "
164  ."'".addslashes($BaseFileName)."', "
165  .$FileLength.", "
166  ."'".addslashes($FileType)."', "
167  ."'".$SecretString."')");
168 
169  # retrieve ID of new file
170  $this->Id = $DB->LastInsertId();
171 
172  # load file info back in from database
173  $DB->Query("SELECT * FROM Files WHERE FileId = ".$this->Id);
174  $this->DBFields = $DB->FetchRow();
175 
176  # copy file to storage
177  $CopySucceeded = copy($IdOrFileName, $this->GetNameOfStoredFile());
178 
179  # if copy failed
180  if (!$CopySucceeded)
181  {
182  # remove file info from database
183  $DB->Query("DELETE FROM Files WHERE FileId = ".$this->Id);
184 
185  # set status indicating constructor failed
186  $this->Status = self::FILESTAT_COPYERROR;
187  }
188  }
189  }
190  }
191  else
192  {
193  # set status indicating constructor failed
194  $this->Status = self::FILESTAT_PARAMERROR;
195  }
196  }
197 
202  public function Status()
203  {
204  return $this->Status;
205  }
206 
211  public function Id()
212  {
213  return $this->Id;
214  }
215 
220  public function Name()
221  {
222  return $this->DBFields["FileName"];
223  }
224 
229  public function GetLength()
230  {
231  return $this->DBFields["FileLength"];
232  }
233 
238  public function GetType()
239  {
240  return $this->DBFields["FileType"];
241  }
242 
248  public function Comment($NewValue = DB_NOVALUE)
249  {
250  return $this->UpdateValue("FileComment", $NewValue);
251  }
252 
258  public function FieldId($NewValue = DB_NOVALUE)
259  {
260  return $this->UpdateValue("FieldId", $NewValue);
261  }
262 
268  public function ResourceId($NewValue = DB_NOVALUE)
269  {
270  return $this->UpdateValue("ResourceId", $NewValue);
271  }
272 
277  public function GetMimeType()
278  {
279  return strlen($this->GetType())
280  ? $this->GetType() : "application/octet-stream";
281  }
282 
288  public function GetLink()
289  {
290  # if CleanURLs are enabled, use the redirect that includes
291  # the file name so that browsers don't use index.php as the name
292  # for the downloaded file
293  if ($GLOBALS["G_PluginManager"]->PluginEnabled("CleanURLs"))
294  {
295  return "downloads/".$this->Id."/".rawurlencode($this->Name());
296  }
297 
298  # otherwise use the download portal
299  else
300  {
301  return "index.php?P=DownloadFile&Id=".$this->Id;
302  }
303  }
304 
309  public function Delete()
310  {
311  # remove file entry from DB
312  $this->DB->Query("DELETE FROM Files WHERE FileId = ".$this->Id);
313 
314  # delete file
315  $FileName = $this->GetNameOfStoredFile();
316  if (file_exists($FileName))
317  {
318  unlink($FileName);
319  }
320  }
321 
326  public function GetNameOfStoredFile()
327  {
328  # for each possible storage location
329  foreach (self::$StorageLocations as $Dir)
330  {
331  # build file name for that location
332  $FileName = sprintf($Dir."/%06d-%s-%s",
333  $this->Id, $this->DBFields["SecretString"], $this->Name());
334 
335  # if file can be found in that location
336  if (file_exists($FileName))
337  {
338  # return file name to caller
339  return $FileName;
340  }
341  }
342 
343  # build file name for default (most preferred) location
344  $FileName = sprintf(self::GetStorageDirectory()."/%06d-%s-%s",
345  $this->Id, $this->DBFields["SecretString"], $this->Name());
346 
347  # return file name to caller
348  return $FileName;
349  }
350 
355  public static function GetStorageDirectory()
356  {
357  # for each possible storage location
358  foreach (self::$StorageLocations as $Dir)
359  {
360  # if location exists and is writeable
361  if (is_dir($Dir) && is_writeable($Dir))
362  {
363  # return location to caller
364  return $Dir;
365  }
366  }
367 
368  # return default (most preferred) location to caller
369  return self::$StorageLocations[0];
370  }
371 
372 
373  # ---- PRIVATE INTERFACE -------------------------------------------------
374 
375  private $DB;
376  private $Status;
377  private $Id;
378  private $DBFields;
379 
381  static private $StorageLocations = array(
382  "local/data/files",
383  "FileStorage",
384  );
385 
392  private function UpdateValue($FieldName, $NewValue)
393  {
394  return $this->DB->UpdateValue("Files", $FieldName, $NewValue,
395  "FileId = ".intval($this->Id),
396  $this->DBFields, TRUE);
397  }
398 }
const FILESTAT_ZEROLENGTH
Definition: File.php:22
GetNameOfStoredFile()
Returns the relative link to the stored file.
Definition: File.php:326
const FILESTAT_UNREADABLE
Definition: File.php:24
const FILESTAT_COPYERROR
Definition: File.php:20
GetType()
Gets the file&#39;s type.
Definition: File.php:238
const FILESTAT_DOESNOTEXIST
Definition: File.php:23
SQL database abstraction object with smart query caching.
Definition: Database.php:22
FieldId($NewValue=DB_NOVALUE)
Gets or sets the field ID of the File.
Definition: File.php:258
Id()
Gets the object&#39;s ID.
Definition: File.php:211
GetLink()
Returns the relative download link to download the file.
Definition: File.php:288
const FILESTAT_PARAMERROR
Definition: File.php:21
Delete()
Deletes the file and removes its entry from the database.
Definition: File.php:309
Status()
Gets the object&#39;s status.
Definition: File.php:202
__construct($IdOrFileName, $ResourceId=NULL, $FieldId=NULL, $DesiredFileName=NULL, $CheckFileLength=TRUE)
Constructs a File object using either an existing file or a new file.
Definition: File.php:39
GetMimeType()
Gets the MIME type of the file.
Definition: File.php:277
const DB_NOVALUE
Definition: Database.php:1541
GetLength()
Gets the length of the file.
Definition: File.php:229
static GetStorageDirectory()
Get file storage directory.
Definition: File.php:355
ResourceId($NewValue=DB_NOVALUE)
Gets or sets the resource ID of the File.
Definition: File.php:268
const FILESTAT_OK
Definition: File.php:19
Name()
Gets the name of the object.
Definition: File.php:220
Class representing a stored (usually uploaded) file.
Definition: File.php:13
Comment($NewValue=DB_NOVALUE)
Gets or sets the comment on the file.
Definition: File.php:248