v1.php 8.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193
  1. <?php
  2. namespace zin;
  3. class fileSelector extends wg
  4. {
  5. /**
  6. * Define widget properties.
  7. *
  8. * @var array
  9. * @access protected
  10. */
  11. protected static $defineProps = array
  12. (
  13. 'name?: string="files[]"', // 作为表单字段的名称。
  14. 'accept?: string', // 限制文件类型。
  15. 'disabled?: bool', // 是否禁用。
  16. 'mode?: string="box"', // 界面模式,包括 'button'、'box'、'grid'
  17. 'tip?: string', // 提示信息。
  18. 'thumbnail?: bool=true', // 是否显示缩略图。
  19. 'gridWidth?: string|int', // 网格模式的宽度。
  20. 'gridHeight?: string|int', // 网格模式的高度。
  21. 'gridGap?: string|int', // 网格模式的间距。
  22. 'defaultFiles?: array[]', // 默认显示的文件列表。
  23. 'deleteName: string="deleteFiles"', // 默认显示文件中被删除的文件。
  24. 'renameName: string="renameFiles"', // 默认显示文件中被重命名的文件。
  25. 'extra?:string=""', // 根据extra筛选默认显示的文件列表
  26. 'multiple?: bool=true', // 是否允许在文件选择对话框中一次性选择多个文件(需要操作系统支持)。
  27. 'itemProps?: array|callback', // 文件项的属性。
  28. 'draggable?: bool=true', // 是否允许拖拽。
  29. 'fileIcons?: string|array= "paper-clip"', // 文件图标。
  30. 'uploadBtn?: string|array', // 上传按钮。
  31. 'renameBtn?: bool|string|array|callback=true', // 重命名按钮。
  32. 'removeBtn?: bool|string|array|callback', // 删除按钮。
  33. 'removeConfirm?: string|array', // 删除确认提示。
  34. 'maxFileSize?: int|string', // 限制文件大小。
  35. 'maxFileCount?: int=0', // 限制文件数目,如果设置为非大于 `0` 的数则不限制。
  36. 'totalFileSize?: int|string', // 限制总文件大小,如果设置为非大于 `0` 的数则不限制。
  37. 'allowSameName?: bool;', // 是否允许同名文件。
  38. 'duplicatedTip?: string|array', // 重名提示。
  39. 'exceededSizeTip?: string|array', // 超出大小提示。
  40. 'exceededTotalSizeTip?: string|array', // 超出总大小提示。
  41. 'exceededCountTip?: string|array', // 超出数量提示。
  42. 'onSelect?: callback', // 选择文件时的回调。
  43. 'onAdd?: callback', // 添加文件时的回调。
  44. 'onRemove?: callback', // 删除文件时的回调。
  45. 'onRename?: callback', // 重命名文件时的回调,返回 `false` 取消重命名。
  46. 'onDuplicated?: callback', // 重名时的回调,返回 `true` 保留重复文件。
  47. 'onExceededSize?: callback', // 超出大小时的回调,返回 `true` 保留超出大小文件。
  48. 'onExceededTotalSize?: callback', // 超出总大小时的回调,返回 `true` 保留超出总大小文件。
  49. 'onExceededCount?: callback' // 超出数量时的回调,返回 `true` 保留超出数量文件。
  50. );
  51. protected function created()
  52. {
  53. if(!$this->hasProp('removeBtn')) $this->setProp('removeBtn', common::hasPriv('file', 'delete'));
  54. if(!$this->hasProp('totalFileSize'))
  55. {
  56. $maxFileSize = ini_get('post_max_size');
  57. $lastChar = substr($maxFileSize, -1);
  58. $fileSizeUnit = array('K', 'M', 'G', 'T');
  59. if(in_array($lastChar, $fileSizeUnit)) $maxFileSize .= 'B';
  60. $this->setProp('totalFileSize', $maxFileSize);
  61. }
  62. if(!$this->hasProp('maxFileSize'))
  63. {
  64. $maxFileSize = ini_get('upload_max_filesize');
  65. $lastChar = substr($maxFileSize, -1);
  66. $fileSizeUnit = array('K', 'M', 'G', 'T');
  67. if(in_array($lastChar, $fileSizeUnit)) $maxFileSize .= 'B';
  68. $this->setProp('maxFileSize', $maxFileSize);
  69. }
  70. if(!$this->hasProp('tip'))
  71. {
  72. global $lang;
  73. $this->setProp('tip', sprintf($lang->noticeDrag, '{maxFileSize}'));
  74. }
  75. if(!$this->hasProp('exceededCountTip'))
  76. {
  77. global $app, $lang;
  78. $app->loadLang('file');
  79. $this->setProp('exceededCountHint', sprintf($lang->file->errorFileCount, '{maxCount}'));
  80. }
  81. if(!$this->hasProp('exceededTotalSizeTip'))
  82. {
  83. global $app, $lang;
  84. $app->loadLang('file');
  85. $maxUploadSize = strtoupper(ini_get('post_max_size'));
  86. $this->setProp('exceededTotalSizeTip', sprintf($lang->file->errorFileSize, $maxUploadSize));
  87. }
  88. /* Auto prepend suffix "[]" to multiple mode. */
  89. $name = $this->prop('name');
  90. if(!str_ends_with($name, ']') && ($this->prop('multiple') !== false || $this->prop('maxFileCount') !== 1))
  91. {
  92. $this->setProp('name', $name . '[]');
  93. }
  94. if($this->prop('defaultFiles') && $this->prop('extra'))
  95. {
  96. $defaultFiles = array();
  97. foreach($this->prop('defaultFiles') as $file)
  98. {
  99. if($file->extra !== $this->prop('extra')) continue;
  100. $defaultFiles[] = $file;
  101. }
  102. $this->setProp('defaultFiles', $defaultFiles);
  103. }
  104. if($this->hasProp('accept'))
  105. {
  106. $accept = explode(',', $this->prop('accept'));
  107. $dangers = explode(',', $app->config->file->dangers);
  108. $filteredAccept = array_filter($accept, function($item) use ($dangers)
  109. {
  110. $item = strtolower($item);
  111. $ext = ltrim($item, '.');
  112. return !in_array($ext, $dangers);
  113. });
  114. $newAccept = implode(',', $filteredAccept);
  115. $this->setProp('accept', $newAccept);
  116. }
  117. /* Check file type. */
  118. $acceptFileTypes = $this->prop('accept') ? ',' . str_replace('.', '', $this->prop('accept')) . ',' : '';
  119. $checkFiles = jsCallback('file')
  120. ->const('dangerFileTypes', ",{$app->config->file->dangers},")
  121. ->const('dangerFile', $lang->file->dangerFile)
  122. ->const('acceptFileTypes', $acceptFileTypes)
  123. ->do(<<<'JS'
  124. const typeIndex = file.name.lastIndexOf(".");
  125. const fileType = "," + file.name.slice(typeIndex + 1) + ",";
  126. if(acceptFileTypes)
  127. {
  128. if(acceptFileTypes.indexOf(fileType) == -1)
  129. {
  130. zui.Modal.alert(dangerFile);
  131. return false;
  132. }
  133. }
  134. else if(dangerFileTypes.indexOf(fileType) > -1)
  135. {
  136. zui.Modal.alert(dangerFile);
  137. return false;
  138. }
  139. JS
  140. );
  141. /* Get onAdd function.*/
  142. $onAdd = $this->prop('onAdd');
  143. if($onAdd)
  144. {
  145. if(is_object($onAdd))
  146. {
  147. /*
  148. * 获取在 ui 界面上通过 jsCallback 和 js 定义的 onAdd 函数。
  149. * eg: 1. $onAdd = jsCallbakc()..;
  150. * fileSelector(set::onAdd($onAdd));
  151. * 2. $onAdd = js()..;
  152. * fileSelector(set::onAdd($onAdd));
  153. */
  154. $objectClass = get_class($onAdd);
  155. if($objectClass == 'zin\js') $onAdd = $onAdd->toJS();
  156. if($objectClass == 'zin\jsCallback') $onAdd = $onAdd->buildBody();
  157. if(!is_object($onAdd)) $checkFiles = $checkFiles->do($onAdd);
  158. }
  159. else
  160. {
  161. /* 获取在 ui 界面上通过 jsRaw 定义的 onAdd 函数。 eg: fileSelector(set::onAdd(jsRaw('window.onAdd'))); */
  162. $onAdd = js::value($onAdd);
  163. $checkFiles = $checkFiles->call($onAdd, jsRaw('file'));
  164. }
  165. }
  166. $checkFiles = $checkFiles->do('return file');
  167. $this->setProp('onAdd', $checkFiles);
  168. }
  169. /**
  170. * Build the widget.
  171. *
  172. * @access protected
  173. * @return mixed
  174. */
  175. protected function build()
  176. {
  177. global $app, $lang;
  178. $app->control->loadModel('file');
  179. $savePath = $app->control->file->savePath;
  180. if(!is_writable($savePath)) return div(setClass('file-selector-box p-4'), html(sprintf($lang->file->errorCanNotWrite, $savePath, dirname($savePath, 2))));
  181. return zui::fileSelector(inherit($this));
  182. }
  183. }