PageRenderTime 61ms CodeModel.GetById 18ms RepoModel.GetById 0ms app.codeStats 0ms

/library/Adapto/Attribute/File.php

http://github.com/egeniq/adapto
PHP | 819 lines | 539 code | 66 blank | 214 comment | 114 complexity | 7776bb157803e7a2fb6f7ebf076e00fd MD5 | raw file
  1. <?php
  2. /**
  3. * This file is part of the Adapto Toolkit.
  4. * Detailed copyright and licensing information can be found
  5. * in the doc/COPYRIGHT and doc/LICENSE files which should be
  6. * included in the distribution.
  7. *
  8. * @package adapto
  9. * @subpackage attributes
  10. *
  11. * @copyright (c)2000-2004 Ibuildings.nl BV
  12. * @license http://www.achievo.org/atk/licensing ATK Open Source License
  13. *
  14. */
  15. /** flag(s) specific for the Adapto_Attribute_File */
  16. /**
  17. * Disable uploading of files
  18. */
  19. define("AF_FILE_NO_UPLOAD", AF_SPECIFIC_1);
  20. /**
  21. * Disable selecting of files
  22. */
  23. define("AF_FILE_NO_SELECT", AF_SPECIFIC_2);
  24. /**
  25. * Disable deleting of files
  26. */
  27. define("AF_FILE_NO_DELETE", AF_SPECIFIC_3);
  28. /**
  29. * Don't try to detect the file type (shows only filename)
  30. */
  31. define("AF_FILE_NO_AUTOPREVIEW", AF_SPECIFIC_4);
  32. /**
  33. * Removed the files physically
  34. */
  35. define("AF_FILE_PHYSICAL_DELETE", AF_SPECIFIC_5);
  36. /**
  37. * Show preview in popup instead of inline
  38. */
  39. define("AF_FILE_POPUP", AF_POPUP);
  40. /**
  41. * With this you can upload, select and remove files in a given directory.
  42. *
  43. * @todo - Code clean up (del variable is dirty)
  44. * - Support for storing the file itself in the db instead of on disk.
  45. *
  46. * @author Martin Roest <martin@ibuildings.nl>
  47. * @package adapto
  48. * @subpackage attributes
  49. *
  50. */
  51. class Adapto_Attribute_File extends Adapto_Attribute
  52. {
  53. /**
  54. * Directory with images
  55. */
  56. public $m_dir = ""; // defaulted to public
  57. public $m_url = ""; // defaulted to public
  58. /**
  59. * Name mangle feature. If you set filename tpl, then uploaded files
  60. * are renamed to what you set in the template. You can use
  61. * fieldnames between brackets to have the filename determined by
  62. * the record.
  63. *
  64. * This is useful in the following case:
  65. * Say, you have a class for managing users. Each user has a photo associated
  66. * with them. Now, if two users would upload 'gandalf.gif', then you would
  67. * have a naming conflicht and the picture of one user is overwritten with the
  68. * one from the other user.
  69. * If you set m_filenameTpl to "picture_[name]", then the file is renamed before
  70. * it is stored. If the user's name is 'Ivo Jansch', and he uploads 'gandalf.gif',
  71. * then the file that is stored is picture_Ivo_Jansch.gif. This way, you have a
  72. * unique filename per user.
  73. */
  74. public $m_filenameTpl = ""; // defaulted to public
  75. /**
  76. * When set to true, a file is auto-renumbered if another record exists with the
  77. * same filename.
  78. *
  79. * @var boolean
  80. */
  81. public $m_autonumbering = false; // defaulted to public
  82. /**
  83. * List of mime types which a uses is allowed to upload
  84. * Example: array('image/jpeg');
  85. *
  86. * @var array
  87. */
  88. public $m_allowedFileTypes = array(); // defaulted to public
  89. /**
  90. * Constructor
  91. * @param string $name Name of the attribute
  92. * @param array $dir Can be a string with the Directory with images/files or an array with a Directory and a Display Url
  93. * @param int $flags Flags for this attribute
  94. * @param int $size Filename size
  95. */
  96. public function __construct($name, $dir, $flags = 0, $size = 0)
  97. {
  98. // We use 255 as default filename size if no size specified.
  99. if ($size == 0)
  100. $size = 255;
  101. // Call base class constructor.
  102. parent::__construct($name, $flags | AF_CASCADE_DELETE, $size);
  103. $this->setDir($dir);
  104. }
  105. /**
  106. * Sets the directory into which uploaded files are saved. (See setAutonumbering() and setFilenameTemplate()
  107. * for some other ways of manipulating the names of uploaded files.)
  108. *
  109. * @param mixed $dir string with directory path or array with directory path and display url (see constructor)
  110. */
  111. public function setDir($dir)
  112. {
  113. if (is_array($dir)) {
  114. $this->m_dir = $this->AddSlash($dir[0]);
  115. $this->m_url = $this->AddSlash($dir[1]);
  116. } else {
  117. $this->m_dir = $this->AddSlash($dir);
  118. $this->m_url = $this->AddSlash($dir);
  119. }
  120. return $this;
  121. }
  122. /**
  123. * Turn auto-numbering of filenames on/off.
  124. *
  125. * When autonumbering is turned on, uploading a file with the same name as
  126. * the file of another record, will result in the file getting a unique
  127. * sequence number.
  128. *
  129. * @param bool $autonumbering
  130. */
  131. public function setAutonumbering($autonumbering = true)
  132. {
  133. $this->m_autonumbering = $autonumbering;
  134. return $this;
  135. }
  136. /**
  137. * returns a string with a / on the end
  138. * @param string $dir_url String with the url/dir
  139. * @return string with a / on the end
  140. */
  141. public function AddSlash($dir_url)
  142. {
  143. if (substr($dir_url, -1) !== '/') {
  144. $dir_url .= '/';
  145. }
  146. return $dir_url;
  147. }
  148. /**
  149. * Returns an array containing files in specified directory
  150. * optionally filtered by settings from setAllowedFileTypes method.
  151. *
  152. * @param string $dir Directory to read files from
  153. * @return array Array with files in specified dir
  154. */
  155. function getFiles($dir)
  156. {
  157. $dirHandle = dir($this->m_dir);
  158. $file_arr = array();
  159. if (!$dirHandle) {
  160. throw new Adapto_Exception("Unable to open directory {$this->m_dir}");
  161. return array();
  162. }
  163. while ($item = $dirHandle->read()) {
  164. if (count($this->m_allowedFileTypes) == 0) {
  165. if (is_file($this->m_dir . $item))
  166. $file_arr[] = $item;
  167. } else {
  168. $extension = $this->getFileExtension($item);
  169. if (in_array($extension, $this->m_allowedFileTypes)) {
  170. if (is_file($this->m_dir . $item))
  171. $file_arr[] = $item;
  172. }
  173. }
  174. }
  175. $dirHandle->close();
  176. return $file_arr;
  177. }
  178. /**
  179. * Returns a piece of html code that can be used in a form to edit this
  180. * attribute's value.
  181. * @param array $record Record
  182. * @param string $fieldprefix Field prefix
  183. * @param String $mode The mode we're in ('add' or 'edit')
  184. * @return string piece of html code with a browsebox
  185. */
  186. function edit($record = "", $fieldprefix = "", $mode = "")
  187. {
  188. // When in add mode or we have errors, don't show the filename above the input.
  189. if ($mode != 'add' && $record[$this->fieldName()]['error'] == 0) {
  190. if (method_exists($this->getOwnerInstance(), $this->fieldName() . '_display')) {
  191. $method = $this->fieldName() . '_display';
  192. $result = $this->m_ownerInstance->$method($record, 'view');
  193. } else {
  194. $result = $this->display($record, 'view');
  195. }
  196. }
  197. if (!is_dir($this->m_dir) || !is_writable($this->m_dir)) {
  198. atkwarning('Adapto_Attribute_File: ' . $this->m_dir . ' does not exist or is not writeable');
  199. return atktext("no_valid_directory", "atk") . ': ' . $this->m_dir;
  200. }
  201. $id = $fieldprefix . $this->fieldName();
  202. if ($result != "") {
  203. $result .= "<br>";
  204. $result .= '<input type="hidden" name="' . $id . '_orgfilename" value="' . $record[$this->fieldName()]['orgfilename'] . '">';
  205. }
  206. $result .= '<input type="hidden" name="' . $id . '_postfileskey" value="' . $id . '">';
  207. $onchange = '';
  208. if (count($this->m_onchangecode)) {
  209. $onchange = ' onchange="' . $id . '_onChange(this);"';
  210. $this->_renderChangeHandler($fieldprefix);
  211. }
  212. if (!$this->hasFlag(AF_FILE_NO_UPLOAD)) {
  213. $this->registerKeyListener($id, KB_CTRLCURSOR | KB_UPDOWN);
  214. $result .= '<input type="file" id="' . $id . '" name="' . $id . '" ' . $onchange . '>';
  215. }
  216. if (!$this->hasFlag(AF_FILE_NO_SELECT)) {
  217. $file_arr = $this->getFiles($this->m_dir);
  218. if (count($file_arr) > 0) {
  219. natcasesort($file_arr);
  220. $this->registerKeyListener($id . '_select', KB_CTRLCURSOR | KB_LEFTRIGHT);
  221. $result .= '<select id="' . $id . '_select" name="' . $id . '[select]" ' . $onchange . '>';
  222. // Add default option with value NULL
  223. $result .= "<option value=\"\" selected>" . atktext('selection', 'atk');
  224. while (list($key, $val) = each($file_arr)) {
  225. (isset($record[$this->fieldName()]['filename']) && $record[$this->fieldName()]['filename'] == $val) ? $selected = "selected"
  226. : $selected = '';
  227. if (is_file($this->m_dir . $val))
  228. $result .= "<option value=\"" . $val . "\" $selected>" . $val;
  229. }
  230. $result .= "</select>";
  231. }
  232. } else if (isset($record[$this->fieldName()]['filename']) && !empty($record[$this->fieldName()]['filename'])) {
  233. $result .= '<input type="hidden" name="' . $id . '[select]" value="' . $record[$this->fieldName()]['filename'] . '">';
  234. }
  235. if (!$this->hasFlag(AF_FILE_NO_DELETE) && isset($record[$this->fieldname()]['orgfilename']) && $record[$this->fieldname()]['orgfilename'] != '') {
  236. $this->registerKeyListener($id . '_del', KB_CTRLCURSOR | KB_CURSOR);
  237. $result .= '<br><input id="' . $id . '_del" type="checkbox" name="' . $id . '[del]" ' . $this->getCSSClassAttribute("atkcheckbox") . '>&nbsp;'
  238. . atktext("remove_current_file", "atk");
  239. }
  240. return $result;
  241. }
  242. /**
  243. * Convert value to record for database
  244. * @param array $rec Array with Fields
  245. * @return string Nothing or Fieldname or Original filename
  246. */
  247. function value2db($rec)
  248. {
  249. $del = (isset($rec[$this->fieldName()]['postdel'])) ? $rec[$this->fieldName()]['postdel'] : null;
  250. if ($rec[$this->fieldName()]["tmpfile"] == "" && $rec[$this->fieldName()]["filename"] != ""
  251. && (!isset($del) || $del != $rec[$this->fieldName()]['filename'])) {
  252. return $this->escapeSQL($rec[$this->fieldName()]["filename"]);
  253. }
  254. if ($this->hasFlag(AF_FILE_NO_DELETE))
  255. unset($del);
  256. // Make sure if flag is set $del unset!
  257. if (isset($del)) {
  258. if ($this->hasFlag(AF_FILE_PHYSICAL_DELETE)) {
  259. $file = "";
  260. if (isset($rec[$this->fieldName()]["postdel"]) && $rec[$this->fieldName()]["postdel"] != "") {
  261. Adapto_Util_Debugger::debug("postdel set");
  262. $file = $rec[$this->fieldName()]["postdel"];
  263. } else if (isset($rec[$this->fieldName()]["orgfilename"])) {
  264. Adapto_Util_Debugger::debug("postdel not set");
  265. $file = $rec[$this->fieldName()]["orgfilename"];
  266. }
  267. Adapto_Util_Debugger::debug("file is now " . $file);
  268. if ($file != "" && file_exists($this->m_dir . $file)) {
  269. unlink($this->m_dir . $file);
  270. } else
  271. Adapto_Util_Debugger::debug("File doesn't exist anymore.");
  272. }
  273. // echo ':::::return leeg::::';
  274. return '';
  275. } else {
  276. $filename = $rec[$this->fieldName()]["filename"];
  277. // why copy if the files are the same?
  278. if ($this->m_dir . $filename != $rec[$this->fieldName()]["tmpfile"]) {
  279. if ($filename != "") {
  280. $dirname = dirname($this->m_dir . $filename);
  281. if (!$this->mkdir($dirname)) {
  282. throw new Adapto_Exception("File could not be saved, unable to make directory '{$dirname}'");
  283. return "";
  284. }
  285. if (@copy($rec[$this->fieldName()]["tmpfile"], $this->m_dir . $filename)) {
  286. $this->processFile($this->m_dir, $filename);
  287. return $this->escapeSQL($filename);
  288. } else {
  289. throw new Adapto_Exception(
  290. "File could not be saved, unable to copy file '{$rec[$this->fieldName()]["tmpfile"]}' to destination '{$this->m_dir}{$filename}'");
  291. return "";
  292. }
  293. }
  294. }
  295. return $this->escapeSQL($rec[$this->fieldName()]["orgfilename"]);
  296. }
  297. }
  298. /**
  299. * Recursive rmdir.
  300. *
  301. * @see http://nl3.php.net/rmdir
  302. *
  303. * @param string $dir path to remove
  304. * @return succes/failure
  305. *
  306. * @static
  307. */
  308. function rmdir($dir)
  309. {
  310. if (!($handle = @opendir($dir)))
  311. return false;
  312. while (false !== ($item = readdir($handle))) {
  313. if ($item != "." && $item != "..") {
  314. if (is_dir("$dir/$item")) {
  315. if (!Adapto_Attribute_File::rmdir("$dir/$item"))
  316. return false;
  317. } else {
  318. if (!@unlink("$dir/$item"))
  319. return false;
  320. }
  321. }
  322. }
  323. closedir($handle);
  324. return @rmdir($dir);
  325. }
  326. /**
  327. * Perform processing on an image right after it is uploaded.
  328. *
  329. * If you need any resizing or other postprocessing to be done on a file
  330. * after it is uploaded, you can create a derived attribute that
  331. * implements the processFile($filepath) method.
  332. * The default implementation does not do any processing.
  333. * @param String $filepath The path of the uploaded file.
  334. * @param String $filename The name of the uploaded file.
  335. */
  336. function processFile($filepath, $filename)
  337. {
  338. }
  339. /**
  340. * Set the allowed file types. This can either be mime types (detected by the / in the middle
  341. * or file extensions (without the leading dot!).
  342. *
  343. * @param array $types
  344. * @return boolean
  345. */
  346. function setAllowedFileTypes($types)
  347. {
  348. if (!is_array($types)) {
  349. throw new Adapto_Exception('Adapto_Attribute_File::setAllowedFileTypes() Invalid types (types is not an array!');
  350. return false;
  351. }
  352. $this->m_allowedFileTypes = $types;
  353. return true;
  354. }
  355. /**
  356. * Check whether the filetype is is one of the allowed
  357. * file formats. If the FileType array is empty this assumes that
  358. * all formats are allowed!
  359. *
  360. * @todo It turns out that handling mimetypes is not that easy
  361. * the mime_content_type has been deprecated and there is no
  362. * Os independend alternative! For now we only support a few
  363. * image mime types.
  364. *
  365. * @param array $rec
  366. * @return boolean
  367. */
  368. function isAllowedFileType(&$rec)
  369. {
  370. if (count($this->m_allowedFileTypes) == 0)
  371. return true;
  372. // detect whether the file is uploaded or is an existing file.
  373. $filename = (!empty($rec[$this->fieldName()]['tmpfile'])) ? $rec[$this->fieldName()]['tmpfile'] : $this->m_dir . $rec[$this->fieldName()]['filename'];
  374. if (@empty($rec[$this->fieldName()]['postdel']) && $filename != $this->m_dir) {
  375. $valid = false;
  376. if (function_exists('getimagesize')) {
  377. $size = @getimagesize($filename);
  378. if (in_array($size['mime'], $this->m_allowedFileTypes)) {
  379. $valid = true;
  380. }
  381. }
  382. $orgFilename = @$rec[$this->fieldName()]['orgfilename'];
  383. if ($orgFilename != null) {
  384. $extension = $this->getFileExtension($orgFilename);
  385. if (in_array($extension, $this->m_allowedFileTypes)) {
  386. $valid = true;
  387. }
  388. }
  389. if (!$valid) {
  390. $rec[$this->fieldName()]['error'] = UPLOAD_ERR_EXTENSION;
  391. return false;
  392. }
  393. }
  394. return true;
  395. }
  396. /**
  397. * Convert value to string
  398. * @param array $rec Array with fields
  399. * @return array Array with tmpfile, orgfilename,filesize
  400. */
  401. function db2value($rec)
  402. {
  403. $retData = array('tmpfile' => null, 'orgfilename' => null, 'filename' => null, 'filesize' => null);
  404. if (isset($rec[$this->fieldName()])) {
  405. $retData = array('tmpfile' => $this->m_dir . $rec[$this->fieldName()], 'orgfilename' => $rec[$this->fieldName()],
  406. 'filename' => $rec[$this->fieldName()], 'filesize' => '?');
  407. }
  408. return $retData;
  409. }
  410. /**
  411. * Checks if the file has a valid filetype.
  412. *
  413. * Note that obligatory and unique fields are checked by the
  414. * atkEntityValidator, and not by the validate() method itself.
  415. *
  416. * @param array $record The record that holds the value for this
  417. * attribute. If an error occurs, the error will
  418. * be stored in the 'atkerror' field of the record.
  419. * @param String $mode The mode for which should be validated ("add" or
  420. * "update")
  421. */
  422. function validate(&$record, $mode)
  423. {
  424. parent::validate($record, $mode);
  425. $this->isAllowedFileType($record);
  426. $error = $record[$this->fieldName()]['error'];
  427. if ($error > 0) {
  428. $error_text = $this->fetchFileErrorType($error);
  429. atkTriggerError($record, $this, $error_text, atktext($error_text, "atk"));
  430. }
  431. }
  432. /**
  433. * Tests the $_FILE error code and returns the corresponding atk error text token.
  434. *
  435. * @param int $error
  436. * @return string error text token
  437. */
  438. static function fetchFileErrorType($error)
  439. {
  440. switch ($error) {
  441. case UPLOAD_ERR_INI_SIZE:
  442. case UPLOAD_ERR_FORM_SIZE:
  443. $error = 'error_file_size';
  444. break;
  445. case UPLOAD_ERR_EXTENSION:
  446. $error = 'error_file_mime_type';
  447. break;
  448. case UPLOAD_ERR_NO_TMP_DIR:
  449. case UPLOAD_ERR_PARTIAL:
  450. default:
  451. $error = 'error_file_unknown';
  452. }
  453. return $error;
  454. }
  455. /**
  456. * Get filename out of Array
  457. * @param array $rec Record
  458. * @return array Array with tmpfile,filename,filesize,orgfilename
  459. */
  460. function fetchValue($rec)
  461. {
  462. $del = (isset($rec[$this->fieldName()]['del'])) ? $rec[$this->fieldName()]['del'] : null;
  463. $postfiles_basename = $rec[$this->fieldName() . "_postfileskey"];
  464. $basename = $this->fieldName();
  465. if (is_array($_FILES) || ($rec[$this->fieldName()]["select"] != "") || ($rec[$this->fieldName()]["filename"] != "")) // php4
  466. {
  467. // if an error occured during the upload process
  468. // and the error is not 'no file' while the field isn't obligatory or a file was already selected
  469. $fileselected = isset($rec[$this->fieldName()]["select"]) && $rec[$this->fieldName()]["select"] != "";
  470. if ($_FILES[$postfiles_basename]['error'] > 0
  471. && !((!$this->hasFlag(AF_OBLIGATORY) || $fileselected) && $_FILES[$postfiles_basename]['error'] == UPLOAD_ERR_NO_FILE)) {
  472. return array('filename' => $_FILES[$this->fieldName()]['name'], 'error' => $_FILES[$this->fieldName()]['error']);
  473. }
  474. // if no new file has been uploaded..
  475. elseif (count($_FILES) == 0 || $_FILES[$postfiles_basename]["tmp_name"] == "none" || $_FILES[$postfiles_basename]["tmp_name"] == "") {
  476. // No file to upload, then check if the select box is filled
  477. if ($fileselected) {
  478. Adapto_Util_Debugger::debug("file selected!");
  479. $filename = $rec[$this->fieldName()]["select"];
  480. $orgfilename = $filename;
  481. $postdel = '';
  482. if (isset($del) && $del == "on") {
  483. $filename = '';
  484. $orgfilename = '';
  485. $postdel = $rec[$this->fieldName()]["select"];
  486. }
  487. $result = array("tmpfile" => "", "filename" => $filename, "filesize" => 0, "orgfilename" => $orgfilename, "postdel" => $postdel);
  488. } // maybe we atk restored data from session
  489. elseif (isset($rec[$this->fieldName()]["filename"]) && $rec[$this->fieldName()]["filename"] != "") {
  490. $result = $rec[$this->fieldName()];
  491. } else {
  492. $filename = (isset($rec[$basename . "_orgfilename"])) ? $rec[$basename . "_orgfilename"] : "";
  493. if (isset($del) && $del == "on") {
  494. $filename = '';
  495. }
  496. // Note: without file_exists() check, calling filesize() generates an error message:
  497. $result = array("tmpfile" => $filename == '' ? '' : $this->m_dir . $filename, "filename" => $filename,
  498. "filesize" => (is_file($this->m_dir . $filename) ? filesize($this->m_dir . $filename) : 0), "orgfilename" => $filename);
  499. }
  500. } else {
  501. $realname = $this->_filenameMangle($rec, $_FILES[$postfiles_basename]["name"]);
  502. if ($this->m_autonumbering) {
  503. $realname = $this->_filenameUnique($rec, $realname);
  504. }
  505. $result = array("tmpfile" => $_FILES[$postfiles_basename]["tmp_name"], "filename" => $realname,
  506. "filesize" => $_FILES[$postfiles_basename]["size"], "orgfilename" => $realname);
  507. }
  508. return $result;
  509. }
  510. }
  511. /**
  512. * Check if the attribute is empty..
  513. *
  514. * @param array $record the record
  515. *
  516. * @return boolean true if empty
  517. */
  518. function isEmpty($record)
  519. {
  520. return @empty($record[$this->fieldName()]['filename']);
  521. }
  522. /**
  523. * Deletes file from HD
  524. * @param array $record Array with fields
  525. * @return boolean False if the delete went wrong
  526. */
  527. function delete($record)
  528. {
  529. if ($this->hasFlag(AF_FILE_PHYSICAL_DELETE) && ($record[$this->fieldname()]["orgfilename"] != "")) {
  530. if (file_exists($this->m_dir . $record[$this->fieldName()]["orgfilename"]) && !@unlink($this->m_dir . $record[$this->fieldName()]["orgfilename"])) {
  531. return false;
  532. }
  533. }
  534. return true;
  535. }
  536. /**
  537. * Display values
  538. * @param array $record Array with fields
  539. * @return string Filename or Nothing
  540. */
  541. function display($record, $mode = "")
  542. {
  543. // Get random number to use as param when displaying images
  544. // while updating images was not allways visible due to caching
  545. $randval = mt_rand();
  546. $filename = isset($record[$this->fieldName()]["filename"]) ? $record[$this->fieldName()]["filename"] : null;
  547. Adapto_Util_Debugger::debug($this->fieldname() . " - File: $filename");
  548. $prev_type = Array("jpg", "jpeg", "gif", "tif", "png", "bmp", "htm", "html", "txt"); // file types for preview
  549. $imgtype_prev = Array("jpg", "jpeg", "gif", "png"); // types which are supported by GetImageSize
  550. if ($filename != "") {
  551. if (is_file($this->m_dir . $filename)) {
  552. $ext = $this->getFileExtension($filename);
  553. if ((in_array($ext, $prev_type) && $this->hasFlag(AF_FILE_NO_AUTOPREVIEW)) || (!in_array($ext, $prev_type))) {
  554. return "<a href=\"" . $this->m_url . "$filename\" target=\"_blank\">$filename</a>";
  555. } elseif (in_array($ext, $prev_type) && $this->hasFlag(AF_FILE_POPUP)) {
  556. if (in_array($ext, $imgtype_prev)) {
  557. $imagehw = GetImageSize($this->m_dir . $filename);
  558. } else {
  559. $imagehw = Array("0" => "640", "1" => "480");
  560. }
  561. $page = &atkPage::getInstance();
  562. $page->register_script(Adapto_Config::getGlobal("atkroot") . "atk/javascript/newwindow.js");
  563. return '<a href="' . $this->m_url . $filename . '" alt="' . $filename . '" onclick="NewWindow(this.href,\'name\',\'' . ($imagehw[0] + 50)
  564. . '\',\'' . ($imagehw[1] + 50) . '\',\'yes\');return false;">' . $filename . '</a>';
  565. }
  566. return '<img src="' . $this->m_url . $filename . '?b=' . $randval . '" alt="' . $filename . '">';
  567. } else {
  568. return $filename . "(<font color=\"#ff0000\">" . atktext("file_not_exist", "atk") . "</font>)";
  569. }
  570. }
  571. }
  572. /**
  573. * Get the file extension
  574. *
  575. * @param string $filename Filename
  576. * @return string The file extension
  577. */
  578. function getFileExtension($filename)
  579. {
  580. if ($dotPos = strrpos($filename, '.')) {
  581. return strtolower(substr($filename, $dotPos + 1, strlen($filename)));
  582. }
  583. return '';
  584. }
  585. /**
  586. * Retrieve the list of searchmodes which are supported.
  587. *
  588. * @return array List of supported searchmodes
  589. */
  590. function getSearchModes()
  591. {
  592. // exact match and substring search should be supported by any database.
  593. // (the LIKE function is ANSI standard SQL, and both substring and wildcard
  594. // searches can be implemented using LIKE)
  595. // Possible values
  596. //"regexp","exact","substring", "wildcard","greaterthan","greaterthanequal","lessthan","lessthanequal"
  597. return array("substring", "exact", "wildcard", "regexp");
  598. }
  599. /**
  600. * Set filename template.
  601. *
  602. * @param string $template
  603. */
  604. function setFilenameTemplate($template)
  605. {
  606. $this->m_filenameTpl = $template;
  607. }
  608. /**
  609. * Determine the real filename of a file.
  610. *
  611. * If a method <fieldname>_filename exists in the owner instance this method
  612. * is called with the record and default filename to determine the filename. Else
  613. * if a file template is set this is used instead and otherwise the default
  614. * filename is returned.
  615. *
  616. * @access private
  617. * @param array $rec The record
  618. * @param string $default The default filename
  619. * @return The real filename
  620. */
  621. function _filenameMangle($rec, $default)
  622. {
  623. $method = $this->fieldName() . '_filename';
  624. if (method_exists($this->m_ownerInstance, $method))
  625. return $this->m_ownerInstance->$method($rec, $default);
  626. else
  627. return $this->filenameMangle($rec, $default);
  628. }
  629. /**
  630. * Determine the real filename of a file (based on m_filenameTpl).
  631. * @access public
  632. * @param array $rec The record
  633. * @param string $default The default filename
  634. * @return The real filename based on the filename template
  635. */
  636. function filenameMangle($rec, $default)
  637. {
  638. if ($this->m_filenameTpl == "") {
  639. $filename = $default;
  640. } else {
  641. $parser = new Adapto_StringParser($this->m_filenameTpl);
  642. $includes = $parser->getAttributes();
  643. $record = $this->m_ownerInstance->updateRecord($rec, $includes, array($this->fieldname()));
  644. $record[$this->fieldName()] = substr($default, 0, strrpos($default, '.'));
  645. $ext = $this->getFileExtension($default);
  646. $filename = $parser->parse($record) . ($ext != '' ? "." . $ext : '');
  647. }
  648. return str_replace(' ', '_', $filename);
  649. }
  650. /**
  651. * Give the file a uniquely numbered filename.
  652. *
  653. * @access private
  654. * @param array $rec The record for thish the file was uploaded
  655. * @param String $filename The name of the uploaded file
  656. * @return String The name of the uploaded file, renumbered if necessary
  657. */
  658. function _filenameUnique($rec, $filename)
  659. {
  660. // check if there's another record using this same name. If so, (re)number the filename.
  661. Adapto_Util_Debugger::debug("Adapto_Attribute_File::_filenameUnique() -> unique check");
  662. if ($dotPos = strrpos($filename, '.')) {
  663. $name = substr($filename, 0, strrpos($filename, '.'));
  664. $ext = substr($filename, strrpos($filename, '.'));
  665. } else {
  666. $name = $filename;
  667. $ext = "";
  668. }
  669. $selector = "(" . $this->fieldName() . "='$filename' OR " . $this->fieldName() . " LIKE '$name-%$ext')";
  670. if ($rec[$this->m_ownerInstance->primaryKeyField()] != "") {
  671. $selector .= " AND NOT (" . $this->m_ownerInstance->primaryKey($rec) . ")";
  672. }
  673. $records = $this->m_ownerInstance->selectDb("($selector)", "", array($this->fieldName()));
  674. if (count($records) > 0) {
  675. // Check for the highest number
  676. $max_count = 0;
  677. foreach ($records as $record) {
  678. $dotPos = strrpos($record[$this->fieldName()]["filename"], '.');
  679. $dashPos = strrpos($record[$this->fieldName()]["filename"], '-');
  680. if ($dotPos !== false && $dashPos !== false) {
  681. $number = substr($record[$this->fieldName()]["filename"], ($dashPos + 1), ($dotPos - $dashPos) - 1);
  682. } elseif ($dotPos === false && $ext == "" && $dashPos !== false) {
  683. $number = substr($record[$this->fieldName()]["filename"], ($dashPos + 1));
  684. } else
  685. continue;
  686. if (intval($number) > $max_count)
  687. $max_count = $number;
  688. }
  689. // file name exists, so mangle it with a number.
  690. $filename = $name . "-" . ($max_count + 1) . $ext;
  691. }
  692. Adapto_Util_Debugger::debug("Adapto_Attribute_File::_filenameUnique() -> New filename = " . $filename);
  693. return $filename;
  694. }
  695. /**
  696. * Returns a piece of html code that can be used in a form to display
  697. * hidden values for this attribute.
  698. * @param array $record Array with values
  699. * @param String $fieldprefix The fieldprefix to put in front of the name
  700. * of any html form element for this attribute.
  701. * @return string Piece of htmlcode
  702. */
  703. function hide($record = "", $fieldprefix = "")
  704. {
  705. $field = $record[$this->fieldName()];
  706. if (is_array($field))
  707. foreach ($field as $key => $value)
  708. $result .= '<input type="hidden" name="' . $fieldprefix . $this->formName() . '[' . $key . ']" ' . 'value="' . $value . '">';
  709. else
  710. $result = '<input type="hidden" name="' . $fieldprefix . $this->formName() . '" value="' . $field . '">';
  711. return $result;
  712. }
  713. /**
  714. * Return the database field type of the attribute.
  715. * @return "string" which is the 'generic' type of the database field for
  716. * this attribute.
  717. */
  718. function dbFieldType()
  719. {
  720. return "string";
  721. }
  722. }
  723. ?>