Окно мультизагрузки файлов

18 Октября 2021 13:25

Окно, с помощью которого, можно будет загружать файлы на сервер из админки.

Архитектура и необходимые файлы

  • assets
    • components
      • doodles
        • js
          • mgr
            • widgets
              • items.grid.js
  • core
    • components
      • doodles
        • processors
          • mgr
            • upload.class.php

Добавление кнопки

Как правило, в архитектуре административной панели предусматривается функция добавления верхней части с кнопками, поиском и т.д. функцией getTopBar, поэтому если работа ведется над модулем стандартной сборки modExtra то выглядет это будет следующим образом:

getTopBar: function () {
    return [{
		xtype: 'button',
		cls: 'primary-button',
		text: _('upload'),
		handler: this.uploadFiles,
		scope: this
	}];
},

Сама функция просто возвращает массив объектов с параметрами, определяющими как будут выглядеть добавляемые элементы.

Параметр handler объявляет обработчик для поля и это будет специально подготовленная функция uploadFiles.

Добавление функции-обработчика

Та самая функция uploadFiles, которая будет вызывать окно загрузки.

uploadFiles: function(btn) {
	if (!this.uploader) {
		aVer = MODx.config.version.split('.');
		uploaddialog = ((aVer[0] == 2) && aVer[1] >= 3)? MODx.util.MultiUploadDialog.Dialog : Ext.ux.UploadDialog.Dialog;

		this.uploader = new uploaddialog({
			title: _('upload'),
			url: this.config.url,
			base_params: {
				action: 'mgr/upload',
				docid: Doodles.config.docid
			},
			cls: 'ext-ux-uploaddialog-dialog modx-upload-window'
		});
		this.uploader.on('hide', this.refresh,this);
		this.uploader.on('close', this.refresh,this);
	}

	// Automatically open picker
	this.uploader.show(btn);
	this.uploader.buttons[0].input_file.dom.click();
},

Основной алгоритм:

  1. Проверяем версию модыкса. Если версия выше 2.3 то используем Мультизагружочное окно, иначе обычное (которое было в старых версиях);
  2. В base_params указываем параметр action - это процессор-обработчик, который будет раскидывать файлы;
  3. По желанию, можем указать автоматическое вызывание проводника через браузер для выбора фйлов (последние 2 строчки функции).

Добавление процессора-обработчика

<?php
/**
 * Upload files to a directory
 *
 * @param string $docid resource ID
 */
class modFileUploadProcessor extends modProcessor {
	/** @var modMediaSource $source */
	private $source;
	private $privatemode;
	private $filename;
	public $path;
	private $localpath;

	private $calc_hash;
	private $user_folders;
	private $doc_folders;

	public function checkPermissions() {
		return $this->modx->hasPermission('file_upload');
	}

	public function getLanguageTopics() {
		return array('file');
	}

	public function initialize() {
		$this->calc_hash = $this->modx->getOption('fileupload.calchash');
		$this->user_folders = $this->modx->getOption('fileupload.user_folders');
		$this->doc_folders = $this->modx->getOption('fileupload.put_docid');
		$this->path = $this->modx->getOption('fileupload.files_path');
		$this->privatemode = $this->modx->getOption('fileupload.private');
		$this->translit = $this->modx->getOption('fileupload.translit');

		$this->setDefaultProperties(array('docid' => 0));

		$this->localpath = '';

		if ($this->user_folders)
			$this->localpath .= (int) $this->modx->user->get('id') . '/';

		if ($this->doc_folders)
			$this->localpath .=  (int) $this->getProperty('docid') . '/';

		$this->setProperty('path', $this->path . $this->localpath);
		$this->setProperty('source', $this->modx->getOption('fileupload.mediasource'));

		if (!$this->getProperty('path')) return $this->modx->lexicon('file_folder_err_ns');

		return true;
	}

	public function process() {
		if (!$this->getSource())
			return $this->failure($this->modx->lexicon('permission_denied'));

		$this->source->setRequestProperties($this->getProperties());
		$this->source->initialize();

		if (!$this->source->checkPolicy('create'))
			return $this->failure($this->modx->lexicon('permission_denied'));

		// Create subfolder
		if ($this->user_folders || $this->doc_folders) {
			$path = $this->source->getBasePath() . $this->getProperty('path');
			$d = $this->source->fileHandler->make($path, array(), 'modDirectory');

			if (!$d->exists()) {
				if (!$this->source->createContainer($this->getProperty('path'), ''))
					return $this->failure($this->modx->lexicon('permission_denied'));
			}
		}

		$path = $this->source->getBasePath() . $this->getProperty('path');
		$list = array();

		$this->modx->loadClass('FileItem');

		// Create serie of FileItem objects
		foreach ($_FILES as $file) {
			$filename = $file['name'];
			$ext = pathinfo($filename, PATHINFO_EXTENSION);
			$ext = strtolower($ext);

			if ($this->translit)
				$filename = modResource::filterPathSegment($this->modx, $filename);

			// Generate name and check for existence
			if ($this->privatemode)
				$this->filename = FileItem::generateName() . ".$ext";
			else
				$this->filename = $filename;

			$fullpath = '';

			while(1) {
				$fullpath = $path . '/' . $this->filename;
				$f = $this->source->fileHandler->make($fullpath, array(), 'modFile');
				if (!$f->exists()) break;

				// Generate new name again
				if ($this->privatemode)
					$this->filename = FileItem::generateName() . ".$ext";
				else
					$this->filename = '_' . $this->filename;
			}

			$success = $this->source->uploadObjectsToContainer(
				$this->getProperty('path'),
				array(array( // emulate a $_FILES object
					"name" => $this->filename,
					"tmp_name" => $file['tmp_name'],
					"error" => "0")
				));

			if (empty($success)) {
				$msg = '';
				$errors = $this->source->getErrors();
				foreach ($errors as $k => $msg) {
					$this->modx->error->addField($k,$msg);
				}

				return $this->failure($msg);
			} else {
				$fid = FileItem::generateName();
        $date = date('Y-m-d H:i:s');
				$fileitem = $this->modx->newObject('FileItem', array(
					'fid' => $fid,
					'docid' => $this->getProperty('docid'),
					'name' => $filename,
					'date' => $date,
					'internal_name' => $this->filename,
					'path' => $this->localpath,
					'private' => $this->privatemode,
					'uid' => $this->modx->user->get('id'),
          'date' => date('Y-m-d H:i:s', time()),
					'hash' => ($this->calc_hash)? sha1_file($fullpath) : NULL
					));

				if (!$fileitem->save())
					return $this->failure($this->modx->lexicon('fileupload.item_err_save'));

				$list[] = array(
					'id' => $fileitem->get('id'),
					'fid' => $fid,
					'name' => $filename);
			}
		}

		return $this->outputArray($list, count($list));
	}

	/**
	 * Get the active Source
	 * @return modMediaSource|boolean
	 */
	public function getSource() {
		if (empty($this->source)) {
			$this->modx->loadClass('sources.modMediaSource');
			$this->source = modMediaSource::getDefaultSource($this->modx,$this->getProperty('source'));
		}

		if (empty($this->source) || !$this->source->getWorkingContext())
			return false;

		return $this->source;
	}
}

return 'modFileUploadProcessor';

Примечание. За основу взята реализация из модуля FileAttach.