├── tests
├── codeception
│ ├── _data
│ │ ├── test.txt
│ │ ├── version
│ │ │ └── test.txt
│ │ └── test.zip
│ ├── _output
│ │ └── .gitignore
│ ├── _support
│ │ ├── _generated
│ │ │ └── .gitignore
│ │ ├── UnitTester.php
│ │ ├── FunctionalTester.php
│ │ └── AcceptanceTester.php
│ ├── config
│ │ ├── api.php
│ │ ├── unit.php
│ │ └── functional.php
│ ├── unit
│ │ ├── _bootstrap.php
│ │ ├── FolderTest.php
│ │ └── MigrateFromOldTest.php
│ ├── api
│ │ ├── _bootstrap.php
│ │ ├── FileCest.php
│ │ ├── ManageCest.php
│ │ └── FolderCest.php
│ ├── acceptance
│ │ ├── _bootstrap.php
│ │ ├── GuestAccessCest.php
│ │ ├── ImportZipCest.php
│ │ ├── VersionCest.php
│ │ ├── FileContextCest.php
│ │ ├── UploadAndMoveCest.php
│ │ ├── ItemSelectionCest.php
│ │ ├── FolderContextCest.php
│ │ └── VisibilityCest.php
│ ├── functional
│ │ └── _bootstrap.php
│ ├── unit.suite.yml
│ ├── api.suite.yml
│ ├── fixtures
│ │ ├── FolderFixtrue.php
│ │ ├── FilesFixture.php
│ │ └── data
│ │ │ ├── file.php
│ │ │ └── folder.php
│ ├── functional.suite.yml
│ ├── acceptance.suite.yml
│ └── _bootstrap.php
├── config
│ ├── api.php
│ ├── unit.php
│ ├── common.php
│ ├── functional.php
│ └── test.php
└── codeception.yml
├── resources
├── module_image.png
├── screenshot1.png
├── screenshot2.png
├── screenshot3.png
├── screenshot4.png
├── screenshot5.png
├── screenshot6.png
├── screenshot7.png
├── screenshot8.png
├── directorylist
│ ├── drive.png
│ ├── folder.png
│ ├── directory.png
│ ├── page_white.png
│ ├── directory_join.png
│ ├── page_white_php.png
│ ├── directory_exand.png
│ ├── directory_hassub.png
│ ├── page_white_code.png
│ ├── page_white_text.png
│ ├── page_white_picture.png
│ ├── directory_join_hassub.png
│ ├── directory_last-child.png
│ ├── page_white_compressed.png
│ ├── directory_join_hassub_expand.png
│ ├── directory_last-child_hassub.png
│ └── directory_last-child_hassub_expand.png
└── css
│ ├── directorylist.css
│ └── cfiles.css
├── docs
├── swagger
│ └── build.sh
└── README.md
├── .github
└── workflows
│ ├── php-cs-fixer.yml
│ ├── rector-auto-pr.yaml.yml
│ ├── marketplace-upload.yml
│ ├── codeception-develop.yml
│ ├── codeception-master.yml
│ ├── codeception-next.yml
│ └── codeception-min-version.yml
├── .gitignore
├── widgets
├── views
│ ├── fileListContextMenu.php
│ ├── wallEntryFolder.php
│ ├── folderView.php
│ ├── versionsView.php
│ ├── breadcrumbBar.php
│ ├── wallEntryFile.php
│ ├── versionItem.php
│ ├── fileSelectionMenu.php
│ ├── fileListMenu.php
│ └── fileList.php
├── DirectoryTree.php
├── BreadcrumbBar.php
├── DropdownButton.php
├── FileListMenu.php
├── FileSelectionMenu.php
├── FolderView.php
├── WallEntryFolder.php
├── FileSystemItem.php
├── VersionItem.php
├── WallEntryFile.php
└── VersionsView.php
├── migrations
├── uninstall.php
├── m160817_180831_add_type.php
├── m201030_071320_add_download_counter.php
├── m160824_120822_add_has_wall_entry.php
├── m170211_110702_show_in_stream.php
├── m160921_1102234_remove_has_wall_entry.php
├── m160810_174011_add_description.php
├── m150720_174011_initial.php
├── m170830_122433_set_root_partent_null.php
├── m170210_154141_folderNoStream.php
└── m170830_122439_foreignkeys.php
├── controllers
├── rest
│ ├── CfilesController.php
│ ├── FileController.php
│ └── FolderController.php
├── ConfigController.php
├── ConfigContainerController.php
├── DownloadController.php
├── DeleteController.php
├── ZipController.php
├── BrowseController.php
├── UploadController.php
└── VersionController.php
├── views
├── move
│ ├── errors.php
│ ├── directory_tree_item.php
│ ├── directory_tree.php
│ └── modal_move.php
├── version
│ └── index.php
├── config-container
│ └── index.php
├── config
│ └── index.php
├── edit
│ ├── modal_edit_folder.php
│ └── modal_edit_file.php
└── browse
│ └── index.php
├── composer.json
├── extensions
└── custom_pages
│ └── elements
│ ├── FilesElementVariable.php
│ ├── FoldersElementVariable.php
│ ├── FilesElement.php
│ ├── FoldersElement.php
│ ├── FolderElement.php
│ ├── FolderElementVariable.php
│ ├── FileElementVariable.php
│ └── FileElement.php
├── assets
└── Assets.php
├── module.json
├── models
├── ZipImportHandler.php
├── ItemInterface.php
├── forms
│ ├── SelectionForm.php
│ ├── VersionForm.php
│ └── MoveForm.php
├── rows
│ ├── FolderRow.php
│ ├── SpecialFolderRow.php
│ └── FileRow.php
├── ConfigureContainerForm.php
└── ConfigureForm.php
├── permissions
├── ManageFiles.php
└── WriteAccess.php
├── actions
├── UploadZipAction.php
└── UploadAction.php
├── config.php
├── helpers
└── RestDefinitions.php
├── libs
├── ZipUtil.php
├── ZipExtractor.php
└── ZIPCreator.php
├── components
└── UrlRule.php
└── messages
└── en-GB
└── base.php
/tests/codeception/_data/test.txt:
--------------------------------------------------------------------------------
1 | First version
--------------------------------------------------------------------------------
/tests/codeception/_data/version/test.txt:
--------------------------------------------------------------------------------
1 | Second version
--------------------------------------------------------------------------------
/resources/module_image.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/humhub/cfiles/HEAD/resources/module_image.png
--------------------------------------------------------------------------------
/resources/screenshot1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/humhub/cfiles/HEAD/resources/screenshot1.png
--------------------------------------------------------------------------------
/resources/screenshot2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/humhub/cfiles/HEAD/resources/screenshot2.png
--------------------------------------------------------------------------------
/resources/screenshot3.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/humhub/cfiles/HEAD/resources/screenshot3.png
--------------------------------------------------------------------------------
/resources/screenshot4.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/humhub/cfiles/HEAD/resources/screenshot4.png
--------------------------------------------------------------------------------
/resources/screenshot5.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/humhub/cfiles/HEAD/resources/screenshot5.png
--------------------------------------------------------------------------------
/resources/screenshot6.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/humhub/cfiles/HEAD/resources/screenshot6.png
--------------------------------------------------------------------------------
/resources/screenshot7.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/humhub/cfiles/HEAD/resources/screenshot7.png
--------------------------------------------------------------------------------
/resources/screenshot8.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/humhub/cfiles/HEAD/resources/screenshot8.png
--------------------------------------------------------------------------------
/tests/codeception/_data/test.zip:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/humhub/cfiles/HEAD/tests/codeception/_data/test.zip
--------------------------------------------------------------------------------
/resources/directorylist/drive.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/humhub/cfiles/HEAD/resources/directorylist/drive.png
--------------------------------------------------------------------------------
/resources/directorylist/folder.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/humhub/cfiles/HEAD/resources/directorylist/folder.png
--------------------------------------------------------------------------------
/tests/codeception/_output/.gitignore:
--------------------------------------------------------------------------------
1 | # Ignore everything in this directory
2 | *
3 | # Except this file
4 | !.gitignore
--------------------------------------------------------------------------------
/resources/directorylist/directory.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/humhub/cfiles/HEAD/resources/directorylist/directory.png
--------------------------------------------------------------------------------
/resources/directorylist/page_white.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/humhub/cfiles/HEAD/resources/directorylist/page_white.png
--------------------------------------------------------------------------------
/resources/directorylist/directory_join.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/humhub/cfiles/HEAD/resources/directorylist/directory_join.png
--------------------------------------------------------------------------------
/resources/directorylist/page_white_php.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/humhub/cfiles/HEAD/resources/directorylist/page_white_php.png
--------------------------------------------------------------------------------
/tests/codeception/_support/_generated/.gitignore:
--------------------------------------------------------------------------------
1 | # Ignore everything in this directory
2 | *
3 | # Except this file
4 | !.gitignore
--------------------------------------------------------------------------------
/resources/directorylist/directory_exand.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/humhub/cfiles/HEAD/resources/directorylist/directory_exand.png
--------------------------------------------------------------------------------
/resources/directorylist/directory_hassub.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/humhub/cfiles/HEAD/resources/directorylist/directory_hassub.png
--------------------------------------------------------------------------------
/resources/directorylist/page_white_code.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/humhub/cfiles/HEAD/resources/directorylist/page_white_code.png
--------------------------------------------------------------------------------
/resources/directorylist/page_white_text.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/humhub/cfiles/HEAD/resources/directorylist/page_white_text.png
--------------------------------------------------------------------------------
/tests/codeception/config/api.php:
--------------------------------------------------------------------------------
1 |
7 |
8 |
9 |
14 |
--------------------------------------------------------------------------------
/migrations/uninstall.php:
--------------------------------------------------------------------------------
1 | dropTable('cfiles_file');
10 | $this->dropTable('cfiles_folder');
11 | }
12 |
13 | public function down()
14 | {
15 | echo "uninstall does not support migration down.\n";
16 | return false;
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/tests/config/test.php:
--------------------------------------------------------------------------------
1 | ['cfiles'],
12 | 'fixtures' => [
13 | 'default',
14 | 'files' => \humhub\modules\cfiles\tests\codeception\fixtures\FilesFixture::class,
15 | ],
16 | ];
17 |
--------------------------------------------------------------------------------
/tests/codeception/unit.suite.yml:
--------------------------------------------------------------------------------
1 | # Codeception Test Suite Configuration
2 |
3 | # suite for unit (internal) tests.
4 | # RUN `build` COMMAND AFTER ADDING/REMOVING MODULES.
5 |
6 | actor: UnitTester
7 | modules:
8 | enabled:
9 | - tests\codeception\_support\CodeHelper
10 | - Yii2
11 | config:
12 | Yii2:
13 | configFile: 'codeception/config/unit.php'
14 | transaction: false
15 |
--------------------------------------------------------------------------------
/controllers/rest/CfilesController.php:
--------------------------------------------------------------------------------
1 |
9 |
10 | = Html::a(Html::encode($folder->title), $folderUrl); ?>
11 |
12 |
13 | = Html::a(Yii::t('CfilesModule.base', 'Open file folder'), $folderUrl, ['class' => 'btn btn-sm btn-light', 'data-ui-loader' => '']); ?>
14 |
15 |
16 |
--------------------------------------------------------------------------------
/tests/codeception/api.suite.yml:
--------------------------------------------------------------------------------
1 | # Codeception Test Suite Configuration
2 |
3 | # suite for REST API tests.
4 | # RUN `build` COMMAND AFTER ADDING/REMOVING MODULES.
5 |
6 | actor: ApiTester
7 | modules:
8 | enabled:
9 | - REST
10 | - Yii2
11 | - tests\codeception\_support\DynamicFixtureHelper
12 | config:
13 | REST:
14 | url: 'http://localhost:8080/api/v1/'
15 | depends: Yii2
16 | part: Json
17 | Yii2:
18 | configFile: 'codeception/config/api.php'
19 |
--------------------------------------------------------------------------------
/tests/codeception/fixtures/FolderFixtrue.php:
--------------------------------------------------------------------------------
1 |
13 | hasErrors()) : ?>
14 |
15 |
16 | getErrors() as $errors) : ?>
17 | = "$errors[0] " ?>
18 |
19 |
20 |
21 |
22 |
--------------------------------------------------------------------------------
/migrations/m160817_180831_add_type.php:
--------------------------------------------------------------------------------
1 | addColumn('cfiles_folder', 'type', $this->string(32));
11 | }
12 |
13 | public function down()
14 | {
15 | echo "m160817_180831_add_type cannot be reverted.\n";
16 |
17 | return false;
18 | }
19 |
20 | /*
21 | * // Use safeUp/safeDown to run migration code within a transaction public function safeUp() { } public function safeDown() { }
22 | */
23 | }
24 |
--------------------------------------------------------------------------------
/tests/codeception/fixtures/FilesFixture.php:
--------------------------------------------------------------------------------
1 | addColumn('cfiles_file', 'download_count', $this->integer()->unsigned()->defaultValue(0)->notNull());
16 | }
17 |
18 | /**
19 | * {@inheritdoc}
20 | */
21 | public function safeDown()
22 | {
23 | $this->dropColumn('cfiles_file', 'download_count');
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/tests/codeception/functional.suite.yml:
--------------------------------------------------------------------------------
1 | # Codeception Test Suite Configuration
2 |
3 | # suite for functional (integration) tests.
4 | # emulate web requests and make application process them.
5 | # (tip: better to use with frameworks).
6 |
7 | # RUN `build` COMMAND AFTER ADDING/REMOVING MODULES.
8 | actor: FunctionalTester
9 | modules:
10 | enabled:
11 | - Filesystem
12 | - Yii2
13 | - tests\codeception\_support\TestHelper
14 | - tests\codeception\_support\DynamicFixtureHelper
15 | - tests\codeception\_support\HumHubHelper
16 | config:
17 | Yii2:
18 | configFile: 'codeception/config/functional.php'
19 |
--------------------------------------------------------------------------------
/migrations/m160824_120822_add_has_wall_entry.php:
--------------------------------------------------------------------------------
1 | addColumn('cfiles_folder', 'has_wall_entry', $this->boolean()->defaultValue(false));
11 | }
12 |
13 | public function down()
14 | {
15 | echo "m160824_120822_add_has_wall_entry cannot be reverted.\n";
16 |
17 | return false;
18 | }
19 |
20 | /*
21 | * // Use safeUp/safeDown to run migration code within a transaction public function safeUp() { } public function safeDown() { }
22 | */
23 | }
24 |
--------------------------------------------------------------------------------
/widgets/DirectoryTree.php:
--------------------------------------------------------------------------------
1 | update('file', ['show_in_stream' => false], ['object_model' => \humhub\modules\cfiles\models\File::class]);
10 | }
11 |
12 | public function down()
13 | {
14 | echo "m170211_110702_show_in_stream cannot be reverted.\n";
15 |
16 | return false;
17 | }
18 |
19 | /*
20 | // Use safeUp/safeDown to run migration code within a transaction
21 | public function safeUp()
22 | {
23 | }
24 |
25 | public function safeDown()
26 | {
27 | }
28 | */
29 | }
30 |
--------------------------------------------------------------------------------
/views/version/index.php:
--------------------------------------------------------------------------------
1 |
15 |
16 | Yii::t('CfilesModule.base', 'File versions'),
18 | 'footer' => ModalButton::cancel(Yii::t('CfilesModule.base', 'Close')),
19 | ]) ?>
20 |
21 | = VersionsView::widget(['file' => $model->file]) ?>
22 |
23 |
24 |
--------------------------------------------------------------------------------
/views/move/directory_tree_item.php:
--------------------------------------------------------------------------------
1 |
14 |
15 |
16 | = $folder['folder']->title ?>
17 |
18 |
19 |
20 | = $this->render('directory_tree_item', ['folder' => $subfolder])?>
21 |
22 |
23 |
24 |
25 |
--------------------------------------------------------------------------------
/extensions/custom_pages/elements/FilesElementVariable.php:
--------------------------------------------------------------------------------
1 | getItems() as $file) {
20 | $this->items[] = FileElementVariable::instance($elementContent)->setRecord($file);
21 | }
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/migrations/m160921_1102234_remove_has_wall_entry.php:
--------------------------------------------------------------------------------
1 | dropColumn('cfiles_folder', 'has_wall_entry');
12 | } catch (Exception) {
13 | Yii::error("Could not drop haswall entry column", 'cfiles');
14 | }
15 | }
16 |
17 | public function down()
18 | {
19 | echo "m160921_1102234_remove_has_wall_entry cannot be reverted.\n";
20 |
21 | return false;
22 | }
23 |
24 | /*
25 | * // Use safeUp/safeDown to run migration code within a transaction public function safeUp() { } public function safeDown() { }
26 | */
27 | }
28 |
--------------------------------------------------------------------------------
/widgets/BreadcrumbBar.php:
--------------------------------------------------------------------------------
1 | render('breadcrumbBar', [
27 | 'folder' => $this->folder,
28 | 'contentContainer' => $this->contentContainer,
29 | ]);
30 | }
31 |
32 | }
33 |
--------------------------------------------------------------------------------
/extensions/custom_pages/elements/FoldersElementVariable.php:
--------------------------------------------------------------------------------
1 | getItems() as $folder) {
20 | $this->items[] = FolderElementVariable::instance($elementContent)->setRecord($folder);
21 | }
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/tests/codeception/acceptance.suite.yml:
--------------------------------------------------------------------------------
1 | # Codeception Test Suite Configuration
2 |
3 | # suite for acceptance tests.
4 | # perform tests in browser using the Selenium-like tools.
5 | # powered by Mink (http://mink.behat.org).
6 | # (tip: that's what your customer will see).
7 | # (tip: test your ajax and javascript by one of Mink drivers).
8 |
9 | # RUN `build` COMMAND AFTER ADDING/REMOVING MODULES.
10 |
11 | actor: AcceptanceTester
12 | modules:
13 | enabled:
14 | - WebDriver
15 | - tests\codeception\_support\WebHelper
16 | - tests\codeception\_support\DynamicFixtureHelper
17 | config:
18 | WebDriver:
19 | url: 'http://localhost:8080/'
20 | browser: chrome
21 | restart: true
22 | port: 4444
23 | capabilities:
24 | chromeOptions:
25 | args: ["--lang=en-US"]
26 |
--------------------------------------------------------------------------------
/tests/codeception/fixtures/data/file.php:
--------------------------------------------------------------------------------
1 | addColumn('cfiles_folder', 'description', $this->string(1000));
11 | $this->update('cfiles_folder', ['description' => '']);
12 | $this->addColumn('cfiles_file', 'description', $this->string(1000));
13 | $this->update('cfiles_file', ['description' => '']);
14 | }
15 |
16 | public function down()
17 | {
18 | echo "m160810_174011_add_description cannot be reverted.\n";
19 |
20 | return false;
21 | }
22 |
23 | /*
24 | * // Use safeUp/safeDown to run migration code within a transaction public function safeUp() { } public function safeDown() { }
25 | */
26 | }
27 |
--------------------------------------------------------------------------------
/views/move/directory_tree.php:
--------------------------------------------------------------------------------
1 |
17 |
18 |
19 |
= Yii::t('CfilesModule.base', '/ (root)'); ?>
20 |
21 |
22 | = $this->render('directory_tree_item', ['folder' => $folder]); ?>
23 |
24 |
25 |
26 |
--------------------------------------------------------------------------------
/views/config-container/index.php:
--------------------------------------------------------------------------------
1 |
10 |
11 |
12 |
13 |
= Yii::t('CfilesModule.base', 'Files module configuration'); ?>
14 |
15 |
16 | 'configure-form']); ?>
17 |
18 | = $form->field($model, 'contentHiddenDefault')->widget(ContentHiddenCheckbox::class, [
19 | 'type' => ContentHiddenCheckbox::TYPE_CONTENTCONTAINER,
20 | ]); ?>
21 |
22 | = Button::save()->submit() ?>
23 |
24 |
25 |
26 |
--------------------------------------------------------------------------------
/tests/codeception.yml:
--------------------------------------------------------------------------------
1 | actor: Tester
2 | namespace: cfiles
3 | bootstrap: _bootstrap.php
4 | settings:
5 | suite_class: \PHPUnit_Framework_TestSuite
6 | colors: true
7 | shuffle: false
8 | memory_limit: 1024M
9 | log: true
10 |
11 | # This value controls whether PHPUnit attempts to backup global variables
12 | # See https://phpunit.de/manual/current/en/appendixes.annotations.html#appendixes.annotations.backupGlobals
13 | backup_globals: true
14 | paths:
15 | tests: codeception
16 | output: codeception/_output
17 | data: codeception/_data
18 | helpers: codeception/_support
19 | envs: ../../../humhub/tests/config/env
20 | config:
21 | # the entry script URL (with host info) for functional and acceptance tests
22 | # PLEASE ADJUST IT TO THE ACTUAL ENTRY SCRIPT URL
23 | test_entry_url: http://localhost:8080/index-test.php
24 |
--------------------------------------------------------------------------------
/docs/README.md:
--------------------------------------------------------------------------------
1 | # Files
2 |
3 | Enhance your network's efficiency with the Files Module, a complete solution for easy file management. Seamlessly integrated with HumHub, this module enables you to effortlessly manage and share important files throughout your network and team.
4 |
5 | ## Key Features
6 |
7 | - **File Overview:** Get instant access to all files from your stream and profile.
8 | - **Interact:** Comment on and like files for better collaboration and feedback.
9 | - **Folder Structure:** Organize your files into unlimited folders and subfolders.
10 | - **Migration:** Effortlessly move files and folders within your network.
11 | - **File Info:** Always visible information about the creator, editor, and creation date.
12 | - **Import Files:** Import files and folder structures directly from a .zip file.
13 | - **Export Files:** Download folders and files in bulk as a .zip file.
14 |
--------------------------------------------------------------------------------
/migrations/m150720_174011_initial.php:
--------------------------------------------------------------------------------
1 | createTable('cfiles_file', [
11 | 'id' => 'pk',
12 | 'parent_folder_id' => 'int(11) NULL',
13 | ], '');
14 |
15 | $this->createTable('cfiles_folder', [
16 | 'id' => 'pk',
17 | 'parent_folder_id' => 'int(11) NULL',
18 | 'title' => 'varchar(255) NOT NULL',
19 | ], '');
20 | }
21 |
22 | public function down()
23 | {
24 | echo "m150720_174011_initial cannot be reverted.\n";
25 |
26 | return false;
27 | }
28 |
29 | /*
30 | * // Use safeUp/safeDown to run migration code within a transaction public function safeUp() { } public function safeDown() { }
31 | */
32 | }
33 |
--------------------------------------------------------------------------------
/tests/codeception/_support/UnitTester.php:
--------------------------------------------------------------------------------
1 |
12 |
13 | Yii::t('CfilesModule.base', 'Move files'),
15 | 'footer' => ModalButton::cancel() . ' ' . ModalButton::save()->submit($model->getMoveUrl()),
16 | ]) ?>
17 |
18 | = $this->render('directory_tree', ['root' => $model->root]) ?>
19 |
20 | = $form->field($model, 'destId')->hiddenInput(['id' => 'input-hidden-selectedFolder'])->label(false) ?>
21 |
22 | selection as $item) : ?>
23 | = Html::hiddenInput('selection[]', $item, ['class' => 'input-hidden-selectedItem']) ?>
24 |
25 |
26 |
27 |
--------------------------------------------------------------------------------
/tests/codeception/unit/FolderTest.php:
--------------------------------------------------------------------------------
1 | becomeUser('Admin');
28 | $space = Space::findOne(1);
29 | $rootFolder = Folder::initRoot($space);
30 |
31 | $this->assertTrue($rootFolder instanceof Folder);
32 | // Prevent double root initialization
33 | $this->assertFalse(Folder::initRoot($space));
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/views/config/index.php:
--------------------------------------------------------------------------------
1 |
10 |
11 |
12 |
13 |
= Yii::t('CfilesModule.base', 'Files module configuration'); ?>
14 |
15 |
16 | 'configure-form']); ?>
17 |
18 | = $form->field($model, 'disableZipSupport')->checkbox(); ?>
19 |
20 | = $form->field($model, 'displayDownloadCount')->checkbox(); ?>
21 |
22 | = $form->field($model, 'contentHiddenDefault')->widget(ContentHiddenCheckbox::class, [
23 | 'type' => ContentHiddenCheckbox::TYPE_GLOBAL,
24 | ]); ?>
25 |
26 | = Button::save()->submit() ?>
27 |
28 |
29 |
30 |
--------------------------------------------------------------------------------
/controllers/ConfigController.php:
--------------------------------------------------------------------------------
1 | load(Yii::$app->request->post()) && $form->save()) {
32 | $this->view->saved();
33 | }
34 |
35 | return $this->render('index', ['model' => $form]);
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/widgets/views/folderView.php:
--------------------------------------------------------------------------------
1 |
14 |
15 | = Html::beginTag('div', $options) ?>
16 |
17 | = BreadcrumbBar::widget(['folder' => $folder, 'contentContainer' => $contentContainer]) ?>
18 |
19 | = UploadProgress::widget(['id' => 'cfiles_progress']) ?>
20 |
21 | = FileListMenu::widget([
22 | 'folder' => $folder,
23 | 'contentContainer' => $contentContainer,
24 | ]) ?>
25 |
26 |
27 | = FileList::widget([
28 | 'folder' => $folder,
29 | 'contentContainer' => $contentContainer,
30 | ])?>
31 |
32 | = Html::endTag('div') ?>
--------------------------------------------------------------------------------
/assets/Assets.php:
--------------------------------------------------------------------------------
1 | =1.5
19 | *
20 | * @var bool
21 | */
22 | public $defer = true;
23 |
24 | public $publishOptions = [
25 | 'forceCopy' => false,
26 | ];
27 |
28 | public $css = [
29 | 'css/cfiles.css',
30 | 'css/directorylist.css',
31 | ];
32 |
33 | public $jsOptions = [
34 | 'position' => \yii\web\View::POS_BEGIN,
35 | ];
36 |
37 | public $js = [
38 | 'js/humhub.cfiles.js',
39 | ];
40 |
41 | public function init()
42 | {
43 | $this->sourcePath = dirname(__FILE__, 2) . '/resources';
44 | parent::init();
45 | }
46 |
47 | }
48 |
--------------------------------------------------------------------------------
/module.json:
--------------------------------------------------------------------------------
1 | {
2 | "id": "cfiles",
3 | "name": "Files",
4 | "description": "Streamline file management across your network and enhance efficiency.",
5 | "keywords": [
6 | "files",
7 | "docs",
8 | "structure",
9 | "organisation",
10 | "sharing"
11 | ],
12 | "version": "0.17.2",
13 | "humhub": {
14 | "minVersion": "1.18"
15 | },
16 | "homepage": "https://github.com/humhub/cfiles",
17 | "authors": [
18 | {
19 | "name": "Sebastian Stumpf"
20 | },
21 | {
22 | "name": "Lucas Bartholemy"
23 | },
24 | {
25 | "name": "Julian Harrer"
26 | }
27 |
28 | ],
29 | "screenshots": [
30 | "resources/screenshot1.png",
31 | "resources/screenshot2.png",
32 | "resources/screenshot3.png",
33 | "resources/screenshot4.png",
34 | "resources/screenshot5.png",
35 | "resources/screenshot6.png",
36 | "resources/screenshot7.png",
37 | "resources/screenshot8.png"
38 | ]
39 | }
40 |
--------------------------------------------------------------------------------
/controllers/ConfigContainerController.php:
--------------------------------------------------------------------------------
1 | [Space::USERGROUP_ADMIN, User::USERGROUP_SELF]]];
20 | }
21 |
22 | public function actionIndex()
23 | {
24 | $form = new ConfigureContainerForm(['contentContainer' => $this->contentContainer]);
25 |
26 | if ($form->load(Yii::$app->request->post()) && $form->save()) {
27 | $this->view->saved();
28 | }
29 |
30 | return $this->render('index', ['model' => $form]);
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/extensions/custom_pages/elements/FilesElement.php:
--------------------------------------------------------------------------------
1 | update('cfiles_file', ['parent_folder_id' => new \yii\db\Expression('NULL')], ['parent_folder_id' => 0]);
17 | $this->update('cfiles_folder', ['parent_folder_id' => new \yii\db\Expression('NULL')], ['parent_folder_id' => 0]);
18 | }
19 |
20 | public function safeDown()
21 | {
22 | echo "m170830_122433_set_root_partent_null.\n";
23 |
24 | return false;
25 | }
26 |
27 | /*
28 | // Use up()/down() to run migration code without a transaction.
29 | public function up()
30 | {
31 |
32 | }
33 |
34 | public function down()
35 | {
36 | echo "m170830_122432_foreignkeys cannot be reverted.\n";
37 |
38 | return false;
39 | }
40 | */
41 | }
42 |
--------------------------------------------------------------------------------
/models/ZipImportHandler.php:
--------------------------------------------------------------------------------
1 | Icon::get($this->icon) . Yii::t('CfilesModule.base', 'Import Zip'),
31 | 'data-action-click' => 'file.upload',
32 | 'data-action-target' => '#cfilesUploadZipFile',
33 | ];
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/views/edit/modal_edit_folder.php:
--------------------------------------------------------------------------------
1 | isNewRecord)
12 | ? Yii::t('CfilesModule.base', 'Create folder')
13 | : Yii::t('CfilesModule.base', 'Edit folder');
14 | ?>
15 |
16 | $header,
18 | 'footer' => ModalButton::cancel() . ' ' . ModalButton::save()->submit($submitUrl),
19 | ]) ?>
20 |
21 | = $form->field($folder, 'title')->textInput(['autofocus' => '']) ?>
22 | = $form->field($folder, 'description') ?>
23 | = $form->field($folder, 'visibility')->widget(ContentVisibilitySelect::class, ['readonly' => !$folder->isRoot() && $folder->parentFolder->content->isPrivate()]) ?>
24 | = $form->field($folder, 'hidden')->widget(ContentHiddenCheckbox::class) ?>
25 |
26 |
27 |
--------------------------------------------------------------------------------
/migrations/m170210_154141_folderNoStream.php:
--------------------------------------------------------------------------------
1 | dropColumn('cfiles_folder', 'has_wall_entry');
11 | } catch (Exception) {
12 | Yii::error("Could not drop haswall entry column", 'cfiles');
13 | }
14 |
15 | $this->db->createCommand('UPDATE content c '
16 | . 'LEFT JOIN cfiles_folder f ON f.id=c.object_id AND c.object_model=:folderClass '
17 | . 'SET c.stream_channel = NULL '
18 | . 'WHERE f.id IS NOT NULL', [':folderClass' => humhub\modules\cfiles\models\Folder::class])->execute();
19 | }
20 |
21 | public function down()
22 | {
23 | echo "m170210_154141_folderNoStream cannot be reverted.\n";
24 |
25 | return false;
26 | }
27 |
28 | /*
29 | // Use safeUp/safeDown to run migration code within a transaction
30 | public function safeUp()
31 | {
32 | }
33 |
34 | public function safeDown()
35 | {
36 | }
37 | */
38 | }
39 |
--------------------------------------------------------------------------------
/views/edit/modal_edit_file.php:
--------------------------------------------------------------------------------
1 |
14 |
15 | Yii::t('CfilesModule.base', 'Edit file'),
17 | 'footer' => ModalButton::cancel() . ' ' . ModalButton::save()->submit($submitUrl),
18 | ]) ?>
19 |
20 | = $form->field($file->baseFile, 'file_name')->textInput(['autofocus' => '']) ?>
21 | = $form->field($file, 'description')->widget(RichTextField::class) ?>
22 | = $form->field($file, 'visibility')->widget(ContentVisibilitySelect::class, ['readonly' => $file->parentFolder->content->isPrivate()]) ?>
23 | = $form->field($file, 'hidden')->widget(ContentHiddenCheckbox::class, []) ?>
24 | = $form->field($file, 'download_count')->staticControl(['style' => 'display:inline']) ?>
25 |
26 |
27 |
--------------------------------------------------------------------------------
/widgets/views/versionsView.php:
--------------------------------------------------------------------------------
1 |
13 |
14 |
15 |
16 | = Yii::t('CfilesModule.base', 'Time'); ?>
17 | = Yii::t('CfilesModule.base', 'Author'); ?>
18 | = Yii::t('CfilesModule.base', 'Size'); ?>
19 | = Yii::t('CfilesModule.base', 'Actions'); ?>
20 |
21 |
22 |
23 | = $versionsRowsHtml ?>
24 |
25 |
26 |
27 |
28 |
29 | = Button::light(Yii::t('CfilesModule.base', 'Show older versions'))
30 | ->icon('chevron-down')
31 | ->action('cfiles.loadNextPageVersions', $nextPageVersionsUrl)
32 | ->sm() ?>
33 |
34 |
35 |
--------------------------------------------------------------------------------
/widgets/views/breadcrumbBar.php:
--------------------------------------------------------------------------------
1 |
9 |
10 | isRoot()): ?>
11 |
12 |
13 |
15 | = Icon::get($folder->content->isPublic() ? 'unlock' : 'lock')->size(Icon::SIZE_LG) ?>
16 |
17 |
18 |
19 | getCrumb() as $parentFolder): ?>
20 |
21 |
22 | = $parentFolder->isRoot() ?
23 | Icon::get('home')->size(Icon::SIZE_LG) :
24 | Html::encode($parentFolder->title) ?>
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
--------------------------------------------------------------------------------
/migrations/m170830_122439_foreignkeys.php:
--------------------------------------------------------------------------------
1 | addForeignKey('fk_cfiles_file_parent_folder', 'cfiles_file', 'parent_folder_id', 'cfiles_folder', 'id', 'SET NULL');
18 | $this->addForeignKey('fk_cfiles_folder_parent_folder', 'cfiles_folder', 'parent_folder_id', 'cfiles_folder', 'id', 'SET NULL');
19 | } catch (Exception $e) {
20 | Yii::error($e);
21 | }
22 | }
23 |
24 | public function safeDown()
25 | {
26 | echo "m170830_122437_foreignkeys.\n";
27 |
28 | return false;
29 | }
30 |
31 | /*
32 | // Use up()/down() to run migration code without a transaction.
33 | public function up()
34 | {
35 |
36 | }
37 |
38 | public function down()
39 | {
40 | echo "m170830_122432_foreignkeys cannot be reverted.\n";
41 |
42 | return false;
43 | }
44 | */
45 | }
46 |
--------------------------------------------------------------------------------
/models/ItemInterface.php:
--------------------------------------------------------------------------------
1 | request->post('selection');
36 |
37 | if ($selection === null) {
38 | // Try to get param from GET because REST API method $I->sendDelete()
39 | // sends params as GET params instead of expected BODY params
40 | $selection = Yii::$app->request->get('selection');
41 | }
42 |
43 | if (is_array($selection)) {
44 | $this->selection = $selection;
45 | }
46 | }
47 |
48 | }
49 |
--------------------------------------------------------------------------------
/widgets/views/wallEntryFile.php:
--------------------------------------------------------------------------------
1 |
15 |
16 | applyFile($file)): ?>
17 |
18 | = $previewImage->renderGalleryLink(['style' => 'padding-right:12px']); ?>
19 |
20 |
21 |
22 | = FileHelper::createLink($file, null, ['style' => 'text-decoration: underline']); ?>
23 | = Yii::t('CfilesModule.base', 'Size: {size}', ['size' => Yii::$app->formatter->asShortSize($fileSize, 1)]); ?>
24 |
25 | description)): ?>
26 |
27 |
28 | = RichText::convert($cFile->description, RichText::FORMAT_HTML) ?>
29 |
30 |
31 |
32 |
33 |
34 |
35 | = Html::a(Yii::t('CfilesModule.base', 'Open file folder'), $folderUrl, ['class' => 'btn btn-sm btn-light', 'data-ui-loader' => '']); ?>
36 |
37 |
38 |
39 |
--------------------------------------------------------------------------------
/controllers/DownloadController.php:
--------------------------------------------------------------------------------
1 | $guid]);
30 |
31 | if (!$file) {
32 | throw new NotFoundHttpException();
33 | }
34 |
35 | return $this->redirect($file->getUrl([
36 | 'download' => 1, // Force downloading even if file can be viewable by browser
37 | 'file_name' => $file->file_name, // used to avoid browser cache when file was renamed
38 | ]));
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/controllers/DeleteController.php:
--------------------------------------------------------------------------------
1 | request->post('selection');
30 |
31 | if (is_array($selectedItems)) {
32 | foreach ($selectedItems as $itemId) {
33 | $item = FileSystemItem::getItemById($itemId);
34 |
35 | if (!$item->content->canEdit()) {
36 | throw new HttpException(403);
37 | }
38 |
39 | if ($item && $item->isDeletable() && $item->content->container->id === $this->contentContainer->id) {
40 | $item->delete();
41 | }
42 | }
43 | }
44 |
45 | return $this->renderFileList();
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/tests/codeception/_bootstrap.php:
--------------------------------------------------------------------------------
1 | $testRoot]);
16 | codecept_debug('Module root: ' . $testRoot);
17 |
18 | $humhubPath = getenv('HUMHUB_PATH');
19 | if ($humhubPath === false) {
20 | // If no environment path was set, we assume residing in default the modules directory
21 | $moduleConfig = require $testRoot . '/config/test.php';
22 | if (isset($moduleConfig['humhub_root'])) {
23 | $humhubPath = $moduleConfig['humhub_root'];
24 | } else {
25 | $humhubPath = dirname(__DIR__, 5);
26 | }
27 | }
28 |
29 | \Codeception\Configuration::append(['humhub_root' => $humhubPath]);
30 | codecept_debug('HumHub Root: ' . $humhubPath);
31 |
32 | // Load test configuration (/config/test.php or /config/env//test.php
33 | $globalConfig = require $humhubPath . '/protected/humhub/tests/codeception/_loadConfig.php';
34 |
35 | // Load default test bootstrap (initialize Yii...)
36 | require $globalConfig['humhub_root'] . '/protected/humhub/tests/codeception/_bootstrap.php';
37 |
--------------------------------------------------------------------------------
/tests/codeception/acceptance/GuestAccessCest.php:
--------------------------------------------------------------------------------
1 | amAdmin();
19 | $I->allowGuestAccess();
20 |
21 | $I->amUser1(true);
22 | $I->wantToTest('the visibility of folders and files for guests');
23 | $I->amGoingTo('install the cfiles module for space 2');
24 | $I->enableCfilesOnSpace(2);
25 |
26 | $I->amGoingTo('create a public folder and file');
27 | $I->createFolder('guest', 'guest test', true);
28 | $I->uploadFile('test.txt');
29 |
30 | $I->amInRoot();
31 |
32 | $I->createFolder('private', 'private test', false);
33 | $I->uploadFile('test.txt');
34 |
35 | $I->logout();
36 |
37 | $I->amOnSpace2();
38 | $I->waitForText('Files', 10, '.layout-nav-container');
39 | $I->click('Files', '.layout-nav-container');
40 |
41 | $I->seeInFileList('guest');
42 | $I->dontSeeInFileList('private');
43 |
44 | $I->openFolder('guest');
45 | $I->seeInFileList('test.txt');
46 | }
47 |
48 | }
49 |
--------------------------------------------------------------------------------
/widgets/DropdownButton.php:
--------------------------------------------------------------------------------
1 | buttons) > 1) {
19 | foreach ($this->buttons as $button) {
20 | $items[] = '' . str_replace($this->icon, '', $button) . ' ';
21 | }
22 | if ($this->split) {
23 | array_shift($items);
24 | }
25 | return ButtonDropdown::widget([
26 | 'dropdown' => [
27 | 'items' => $items,
28 | ],
29 | 'splitButton' => $this->split ? [
30 | 'visible' => true,
31 | 'options' => $this->options,
32 | ] : null,
33 | 'button' => $this->split ? $this->buttons[0] : [
34 | 'encodeLabel' => false,
35 | 'label' => $this->icon . $this->label,
36 | 'options' => $this->options,
37 | ],
38 | ]);
39 | } elseif (count($this->buttons) > 0) {
40 | return $this->buttons[0];
41 | }
42 | }
43 |
44 | }
45 |
--------------------------------------------------------------------------------
/permissions/ManageFiles.php:
--------------------------------------------------------------------------------
1 | SORT_ASC];
25 |
26 | public const ORDER_MAPPING = [
27 | self::ORDER_TYPE_NAME => 'title',
28 | self::ORDER_TYPE_UPDATED_AT => 'content.updated_at',
29 | self::ORDER_TYPE_SIZE => null,
30 | ];
31 |
32 | /**
33 | * @var \humhub\modules\cfiles\models\Folder
34 | */
35 | public $item;
36 |
37 | /**
38 | * @inheritdoc
39 | */
40 | public function getBaseFile()
41 | {
42 | return null;
43 | }
44 |
45 | /**
46 | * @return bool
47 | */
48 | public function canEdit()
49 | {
50 | return $this->item->content->canEdit();
51 | }
52 |
53 | /**
54 | * @inheritdoc
55 | */
56 | public function getContext(): WallStreamModuleEntryWidget
57 | {
58 | return new WallEntryFolder(['model' => $this->item]);
59 | }
60 | }
61 |
--------------------------------------------------------------------------------
/permissions/WriteAccess.php:
--------------------------------------------------------------------------------
1 | Yii::t('CfilesModule.base', 'Folder content ID'),
41 | ];
42 | }
43 |
44 | public function __toString(): string
45 | {
46 | return (string) Html::encode($this->record?->title);
47 | }
48 |
49 | /**
50 | * @inheritdoc
51 | */
52 | public function getTemplateVariable(): BaseElementVariable
53 | {
54 | return FolderElementVariable::instance($this)->setRecord($this->getRecord());
55 | }
56 | }
57 |
--------------------------------------------------------------------------------
/actions/UploadZipAction.php:
--------------------------------------------------------------------------------
1 | extract($this->controller->getCurrentFolder(), $uploadedFile);
30 |
31 | if ($file->hasErrors()) {
32 | return $this->getValidationErrorResponse($file);
33 | }
34 |
35 | return ['error' => false];
36 | }
37 |
38 | protected function getValidationErrorResponse(FileSystemItem $file)
39 | {
40 | $errorMessage = Yii::t('FileModule.actions_UploadAction', 'File {fileName} could not be uploaded!', ['fileName' => $file->baseFile->name ?? '']);
41 |
42 | if (!empty($file->hasErrors())) {
43 | $errorMessage = array_values($file->getErrors())[0];
44 | }
45 |
46 | return [
47 | 'error' => true,
48 | 'errors' => $errorMessage,
49 | 'name' => $file->baseFile->name ?? '',
50 | 'size' => $file->baseFile->size ?? '',
51 | ];
52 | }
53 | }
54 |
--------------------------------------------------------------------------------
/tests/codeception/acceptance/ImportZipCest.php:
--------------------------------------------------------------------------------
1 | amAdmin();
17 | $I->wantToTest('the import a zip file');
18 | $I->amGoingTo('install the cfiles module for space 1');
19 | $I->enableCfilesOnSpace();
20 |
21 | $I->createFolder('test');
22 | $I->wait(1);
23 |
24 | $I->attachFile('#cfilesUploadFiles', 'test.txt');
25 |
26 | $I->click('.fa-home', '#cfiles-crumb');
27 | $I->wait(2);
28 |
29 | $I->attachFile('#cfilesUploadZipFile', 'test.zip');
30 |
31 | $I->wait(5);
32 |
33 | $I->click('test', '#fileList');
34 | $I->waitForText('test.txt', 10, '#fileList');
35 | $I->waitForText('test(1).txt', 10, '#fileList');
36 | $I->waitForText('test.jpg', 10, '#fileList');
37 | $I->waitForText('test2', 10, '#fileList');
38 |
39 | // Disable tooltip from 'test description' of the parent "test" folder which is misplaced during tests
40 | $I->executeJS("
41 | var tooltips = document.querySelectorAll('.tooltip');
42 | tooltips.forEach(function(tooltip) {
43 | tooltip.remove();
44 | });
45 | ");
46 | $I->click('test2', '#fileList');
47 | $I->waitForText('test2.txt', 10, '#fileList');
48 |
49 | }
50 | }
51 |
--------------------------------------------------------------------------------
/widgets/FileListMenu.php:
--------------------------------------------------------------------------------
1 | contentContainer->can(WriteAccess::class);
43 |
44 | return $this->render('fileListMenu', [
45 | 'folder' => $this->folder,
46 | 'contentContainer' => $this->contentContainer,
47 | 'canUpload' => $canUpload,
48 | 'fileHandlers' => array_merge($fileHandlerCreate, $fileHandlerImport),
49 | ]);
50 | }
51 |
52 | }
53 |
--------------------------------------------------------------------------------
/models/rows/SpecialFolderRow.php:
--------------------------------------------------------------------------------
1 | null,
25 | self::ORDER_TYPE_UPDATED_AT => null,
26 | self::ORDER_TYPE_SIZE => null,
27 | ];
28 |
29 | /**
30 | * @inheritdoc
31 | */
32 | public function isSocialActionsAvailable()
33 | {
34 | return false;
35 | }
36 |
37 | /**
38 | * @inheritdoc
39 | */
40 | public function isSelectable()
41 | {
42 | return false;
43 | }
44 |
45 | /**
46 | * @inheritdoc
47 | */
48 | public function getCreator()
49 | {
50 | // do not display creator of automatically generated folders
51 | return false;
52 | }
53 |
54 | /**
55 | * @inheritdoc
56 | */
57 | public function getEditor()
58 | {
59 | // do not display editor of automatically generated folders
60 | return false;
61 | }
62 |
63 | /**
64 | * @inheritdoc
65 | */
66 | public function getUpdatedAt()
67 | {
68 | return ($this->item->isAllPostedFiles()) ? '' : parent::getUpdatedAt();
69 | }
70 |
71 | public function canEdit()
72 | {
73 | return false;
74 | }
75 | }
76 |
--------------------------------------------------------------------------------
/models/ConfigureContainerForm.php:
--------------------------------------------------------------------------------
1 | loadBySettings();
22 | }
23 |
24 | /**
25 | * @return Module
26 | */
27 | public function getModule()
28 | {
29 | return Yii::$app->getModule('cfiles');
30 | }
31 |
32 | /**
33 | * @inheritdoc
34 | */
35 | public function rules()
36 | {
37 | return [
38 | [['contentHiddenDefault'], 'boolean'],
39 | ];
40 | }
41 |
42 | public function loadBySettings()
43 | {
44 | $this->contentHiddenDefault = $this->getSettings()->get(
45 | 'contentHiddenDefault',
46 | $this->getModule()->getContentHiddenGlobalDefault(),
47 | );
48 | }
49 |
50 | public function save(): bool
51 | {
52 | if (!$this->validate()) {
53 | return false;
54 | }
55 |
56 | $this->getSettings()->set('contentHiddenDefault', $this->contentHiddenDefault);
57 |
58 | return true;
59 | }
60 |
61 | private function getSettings(): ContentContainerSettingsManager
62 | {
63 | return $this->getModule()->settings->contentContainer($this->contentContainer);
64 | }
65 |
66 | }
67 |
--------------------------------------------------------------------------------
/views/browse/index.php:
--------------------------------------------------------------------------------
1 | registerJsConfig('cfiles', [
13 | 'text' => [
14 | 'confirm.delete' => Yii::t('CfilesModule.base', 'Do you really want to delete this {number} item(s) with all subcontent?'),
15 | 'confirm.delete.header' => Yii::t('CfilesModule.base', 'Confirm delete file'),
16 | 'confirm.delete.confirmText' => Yii::t('CfilesModule.base', 'Delete')
17 | ],
18 | 'showUrlModal' => [
19 | 'head' => Yii::t('CfilesModule.base', 'File url'),
20 | 'headFile' => Yii::t('CfilesModule.base', 'File download url'),
21 | 'headFolder' => Yii::t('CfilesModule.base', 'Folder url'),
22 | 'info' => Yii::t('base', 'Copy to clipboard'),
23 | 'buttonClose' => Yii::t('base', 'Close'),
24 | ],
25 | 'reloadEntryUrl' => $contentContainer->createUrl('/cfiles/browse/load-entry'),
26 | ]);
27 | ?>
28 |
29 | = Html::beginForm(null, 'post', ['data-bs-target' => '#globalModal', 'id' => 'cfiles-form']); ?>
30 |
31 |
32 |
33 |
34 | = FolderView::widget([
35 | 'contentContainer' => $contentContainer,
36 | 'folder' => $folder
37 | ])?>
38 |
39 |
40 |
41 | = Html::endForm(); ?>
42 |
--------------------------------------------------------------------------------
/widgets/views/versionItem.php:
--------------------------------------------------------------------------------
1 |
19 | = Html::beginTag('tr', $options) ?>
20 | = Yii::$app->formatter->asDatetime($date, 'short') ?>
21 | = Html::encode($user->displayName) ?>
22 | = Yii::$app->formatter->asShortSize($size, 1) ?>
23 |
24 |
25 | = Html::a(' ', $revertUrl, [
26 | 'title' => Yii::t('CfilesModule.base', 'Revert to this version'),
27 | 'data-method' => 'POST',
28 | ]) ?>
29 |
30 | = Html::a(' ', $downloadUrl, [
31 | 'title' => Yii::t('CfilesModule.base', 'Download'),
32 | 'target' => '_blank',
33 | ]) ?>
34 |
35 | = Html::a(' ', $deleteUrl, [
36 | 'title' => Yii::t('CfilesModule.base', 'Delete this version!'),
37 | 'data-action-confirm' => Yii::t('CfilesModule.base', 'Are you really sure to delete this version?'),
38 | 'data-action-click' => 'cfiles.deleteVersion',
39 | ]) ?>
40 |
41 |
42 | = Html::endTag('tr') ?>
43 |
--------------------------------------------------------------------------------
/extensions/custom_pages/elements/FolderElementVariable.php:
--------------------------------------------------------------------------------
1 | title = $record->title;
37 | $this->description = $record->description;
38 | $this->type = $record->type;
39 | $this->icon = $record->getIcon();
40 |
41 | foreach ($record->subFolders as $subFolder) {
42 | $this->subFolders[] = self::instance($this->elementContent)->setRecord($subFolder);
43 | }
44 |
45 | foreach ($record->subFiles as $file) {
46 | $this->subFiles[] = FileElementVariable::instance($this->elementContent)->setRecord($file);
47 | }
48 | }
49 |
50 | return parent::setRecord($record);
51 | }
52 | }
53 |
--------------------------------------------------------------------------------
/extensions/custom_pages/elements/FileElementVariable.php:
--------------------------------------------------------------------------------
1 | description = $record->description;
35 | $this->downloadCount = (int) $record->download_count;
36 | $this->icon = $record->getIcon();
37 |
38 | if ($record->baseFile->store->has()) {
39 | $this->fileUrl = $record->baseFile->getUrl();
40 | $this->file = BaseFileElementVariable::instance($this->elementContent)
41 | ->setRecord($record->baseFile);
42 | }
43 | }
44 |
45 | return parent::setRecord($record);
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/widgets/FileSelectionMenu.php:
--------------------------------------------------------------------------------
1 | folder->createUrl('/cfiles/delete');
40 | $moveSelectionUrl = $this->folder->createUrl('/cfiles/move', ['init' => 1]);
41 |
42 | $zipSelectionUrl = $this->folder->createUrl('/cfiles/zip/download');
43 | $makePrivateUrl = $this->folder->createUrl('/cfiles/edit/make-private');
44 | $makePublicUrl = $this->folder->createUrl('/cfiles/edit/make-public');
45 |
46 | $canWrite = $this->contentContainer->can(ManageFiles::class);
47 |
48 | return $this->render('fileSelectionMenu', [
49 | 'deleteSelectionUrl' => $deleteSelectionUrl,
50 | 'folder' => $this->folder,
51 | 'moveSelectionUrl' => $moveSelectionUrl,
52 | 'zipSelectionUrl' => $zipSelectionUrl,
53 | 'canWrite' => $canWrite,
54 | 'zipEnabled' => Yii::$app->getModule('cfiles')->isZipSupportEnabled(),
55 | 'makePrivateUrl' => $makePrivateUrl,
56 | 'makePublicUrl' => $makePublicUrl,
57 | ]);
58 | }
59 | }
60 |
--------------------------------------------------------------------------------
/config.php:
--------------------------------------------------------------------------------
1 | 'cfiles',
14 | 'class' => 'humhub\modules\cfiles\Module',
15 | 'namespace' => 'humhub\modules\cfiles',
16 | 'urlManagerRules' => [
17 | ['class' => UrlRule::class],
18 | ],
19 | 'events' => [
20 | [Menu::class, Menu::EVENT_INIT, ['humhub\modules\cfiles\Events', 'onSpaceMenuInit']],
21 | [ProfileMenu::class, ProfileMenu::EVENT_INIT, ['humhub\modules\cfiles\Events', 'onProfileMenuInit']],
22 | [IntegrityController::class, IntegrityController::EVENT_ON_RUN, ['humhub\modules\cfiles\Events', 'onIntegrityCheck']],
23 | [FileController::class, FileController::EVENT_AFTER_ACTION, ['humhub\modules\cfiles\Events', 'onAfterFileAction']],
24 | [File::class, File::EVENT_AFTER_NEW_STORED_FILE, ['humhub\modules\cfiles\Events', 'onAfterNewStoredFile']],
25 | [ContentContainerActiveRecord::class, ContentContainerActiveRecord::EVENT_AFTER_INSERT, ['humhub\modules\cfiles\Events', 'onContentContainerActiveRecordInsert']],
26 | [ContentContainerModuleState::class, ContentContainerModuleState::EVENT_AFTER_INSERT, ['humhub\modules\cfiles\Events', 'onContentContainerModuleStateInsert']],
27 | ['humhub\modules\rest\Module', 'restApiAddRules', ['humhub\modules\cfiles\Events', 'onRestApiAddRules']],
28 | ['humhub\modules\custom_pages\modules\template\services\ElementTypeService', 'init', ['humhub\modules\cfiles\Events', 'onCustomPagesTemplateElementTypeServiceInit']],
29 | ],
30 | ];
31 |
--------------------------------------------------------------------------------
/helpers/RestDefinitions.php:
--------------------------------------------------------------------------------
1 | $folder->id,
27 | 'title' => $folder->title,
28 | 'description' => $folder->description,
29 | ];
30 | }
31 |
32 | public static function getFolder(Folder $folder)
33 | {
34 | return [
35 | 'id' => $folder->id,
36 | 'title' => $folder->title,
37 | 'description' => $folder->description,
38 | 'parent_folder_id' => $folder->parent_folder_id,
39 | 'type' => $folder->type,
40 | 'created_at' => $folder->content->created_at,
41 | 'created_by' => UserDefinitions::getUserShort($folder->getOwner()),
42 | 'content' => ContentDefinitions::getContent($folder->content),
43 | ];
44 | }
45 |
46 | public static function getFile(File $file)
47 | {
48 | return [
49 | 'id' => $file->id,
50 | 'description' => $file->description,
51 | 'parent_folder' => static::getFolderShort($file->parentFolder),
52 | 'created_at' => $file->content->created_at,
53 | 'created_by' => UserDefinitions::getUserShort($file->getOwner()),
54 | 'content' => ContentDefinitions::getContent($file->content),
55 | ];
56 | }
57 |
58 | }
59 |
--------------------------------------------------------------------------------
/models/rows/FileRow.php:
--------------------------------------------------------------------------------
1 | SORT_ASC];
25 |
26 | public const ORDER_MAPPING = [
27 | self::ORDER_TYPE_NAME => 'file.file_name',
28 | self::ORDER_TYPE_UPDATED_AT => 'file.updated_at',
29 | self::ORDER_TYPE_DOWNLOAD_COUNT => 'cfiles_file.download_count',
30 | self::ORDER_TYPE_SIZE => 'cast(file.size as unsigned)',
31 | ];
32 |
33 | /**
34 | * @var \humhub\modules\cfiles\models\File
35 | */
36 | public $item;
37 |
38 | /**
39 | * @inheritdoc
40 | */
41 | public function getUrl()
42 | {
43 | return $this->item->content->container->createUrl('/cfiles/download', ['guid' => $this->item->baseFile->guid], true);
44 | }
45 |
46 | /**
47 | * @inheritdoc
48 | */
49 | public function getDisplayUrl()
50 | {
51 | return $this->getUrl();
52 | }
53 |
54 | /**
55 | * @inheritdoc
56 | */
57 | public function getBaseFile()
58 | {
59 | return $this->item->baseFile;
60 | }
61 |
62 | /**
63 | * @return bool
64 | */
65 | public function canEdit()
66 | {
67 | return $this->item->content->canEdit();
68 | }
69 |
70 | /**
71 | * @inheritdoc
72 | */
73 | public function getContext(): WallStreamModuleEntryWidget
74 | {
75 | return new WallEntryFile(['model' => $this->item]);
76 | }
77 | }
78 |
--------------------------------------------------------------------------------
/widgets/FolderView.php:
--------------------------------------------------------------------------------
1 | $this->folder->id,
46 | 'upload-url' => $this->folder->createUrl('/cfiles/upload'),
47 | 'reload-file-list-url' => $this->folder->createUrl('/cfiles/browse/file-list'),
48 | 'delete-url' => $this->folder->createUrl('/cfiles/delete'),
49 | 'zip-upload-url' => $this->folder->createUrl('/cfiles/zip/upload'),
50 | 'download-archive-url' => $this->folder->createUrl('/cfiles/zip/download'),
51 | 'move-url' => $this->folder->createUrl('/cfiles/move'),
52 | 'drop-url' => $this->folder->createUrl('/cfiles/move/drop'),
53 | 'import-url' => $this->folder->createUrl('/cfiles/upload/import'),
54 | ];
55 | }
56 |
57 | /**
58 | * @inheritdoc
59 | */
60 | public function run()
61 | {
62 | return $this->render('folderView', [
63 | 'folder' => $this->folder,
64 | 'options' => $this->getOptions(),
65 | 'contentContainer' => $this->contentContainer,
66 | ]);
67 | }
68 |
69 | }
70 |
--------------------------------------------------------------------------------
/libs/ZipUtil.php:
--------------------------------------------------------------------------------
1 | getTempPath());
30 | }
31 |
32 | /**
33 | * Get the output path of the user specified temporary folder used for packing and unpacking zip data for this user.
34 | *
35 | * @return string @runtime/temp/[guid]
36 | */
37 | protected function getZipOutputPath()
38 | {
39 | // init output directory
40 | $outputPath = $this->getTempPath();
41 | $outputPath .= DIRECTORY_SEPARATOR . \Yii::$app->user->guid;
42 | if (!is_dir($outputPath)) {
43 | mkdir($outputPath);
44 | }
45 |
46 | return $outputPath;
47 | }
48 |
49 | /**
50 | * Get the output path of the base temporary folder used for packing and unpacking zip data for all users.
51 | *
52 | * @return string @runtime/temp/[guid]
53 | */
54 | protected function getTempPath()
55 | {
56 | // init output directory
57 | $outputPath = Yii::getAlias('@runtime/cfiles-temp');
58 | if (!is_dir($outputPath)) {
59 | mkdir($outputPath);
60 | }
61 | return $outputPath;
62 | }
63 |
64 | /**
65 | * Fixes ZIP location path, removes trailling slash
66 | *
67 | * @param string $path
68 | * @return string the fixed path
69 | */
70 | protected function fixPath($path)
71 | {
72 | return ltrim($path, DIRECTORY_SEPARATOR);
73 | }
74 | }
75 |
--------------------------------------------------------------------------------
/tests/codeception/acceptance/VersionCest.php:
--------------------------------------------------------------------------------
1 | amAdmin();
20 | $I->wantToTest('the file versioning');
21 | $I->amGoingTo('install the cfiles module for space 1');
22 | $I->enableCfilesOnSpace();
23 |
24 | $I->attachFile('#cfilesUploadFiles', 'test.txt');
25 | $I->waitForText('test.txt', 10, '#fileList');
26 | $I->wait(1);
27 |
28 | $firstVersionFile = $this->getFile(1);
29 | $I->seeFileSizeOnSpaceStream($firstVersionFile);
30 | $I->amOnFilesBrowser();
31 |
32 | $I->wantToTest('the keep old version');
33 | $I->attachFile('#cfilesUploadFiles', 'version/test.txt');
34 | $I->wait(1);
35 |
36 | $secondVersionFile = $this->getFile(1);
37 | $I->seeFileSizeOnSpaceStream($secondVersionFile);
38 | $I->amOnFilesBrowser();
39 |
40 | $I->amGoingTo('view file versions');
41 | $I->seeElement('[data-cfiles-item="file_1"]');
42 | $I->wait(1);
43 | $I->clickFileContext(1, 'Versions');
44 | $I->waitForText('File versions', 10, '#globalModal');
45 |
46 | $I->jsClick('#version_file_' . $firstVersionFile->id . ' [title="Revert to this version"]');
47 | $I->seeSuccess('File ' . $firstVersionFile->file_name . ' has been reverted to version from ' . Yii::$app->formatter->asDatetime($firstVersionFile->created_at, 'short'));
48 | $I->seeFileSizeOnSpaceStream($firstVersionFile);
49 | }
50 |
51 | private function getFile(int $id): BaseFile
52 | {
53 | return BaseFile::findOne(['id' => $id]);
54 | }
55 | }
56 |
--------------------------------------------------------------------------------
/tests/codeception/api/FileCest.php:
--------------------------------------------------------------------------------
1 | isRestModuleEnabled()) {
13 | return;
14 | }
15 |
16 | $I->wantTo('find by container');
17 | $I->amAdmin();
18 | $I->seePaginationFilesResponse('cfiles/files/container/1', []);
19 |
20 | $I->createSampleFile();
21 | $I->seePaginationFilesResponse('cfiles/files/container/1', [1]);
22 | }
23 |
24 | public function testUploadFiles(ApiTester $I)
25 | {
26 | if (!$this->isRestModuleEnabled()) {
27 | return;
28 | }
29 |
30 | $I->wantTo('upload files');
31 | $I->amAdmin();
32 |
33 | $I->createFolder('Root');
34 | $I->seeLastCreatedFolderDefinition();
35 |
36 | $I->uploadFiles(['test.txt', 'test.zip']);
37 | $I->seeSuccessMessage('Files successfully uploaded!');
38 | }
39 |
40 | public function testGetFileById(ApiTester $I)
41 | {
42 | if (!$this->isRestModuleEnabled()) {
43 | return;
44 | }
45 |
46 | $I->wantTo('get file by id');
47 | $I->amAdmin();
48 |
49 | $I->createSampleFile();
50 | $I->sendGet('cfiles/file/1');
51 | $I->seeFileDefinitionById(1);
52 | }
53 |
54 | public function testDeleteFileById(ApiTester $I)
55 | {
56 | if (!$this->isRestModuleEnabled()) {
57 | return;
58 | }
59 |
60 | $I->wantTo('delete file by id');
61 | $I->amAdmin();
62 |
63 | $I->sendDelete('cfiles/file/1');
64 | $I->seeNotFoundMessage('Content record not found!');
65 |
66 | $I->createSampleFile();
67 | $I->sendDelete('cfiles/file/1');
68 | $I->seeSuccessMessage('Successfully deleted!');
69 | }
70 |
71 | }
72 |
--------------------------------------------------------------------------------
/widgets/WallEntryFolder.php:
--------------------------------------------------------------------------------
1 | render('wallEntryFolder', [
42 | 'folder' => $this->model,
43 | 'folderUrl' => $this->model->getUrl(),
44 | ]);
45 | }
46 |
47 | /**
48 | * Returns the edit url to edit the content (if supported)
49 | *
50 | * @return string url
51 | */
52 | public function getEditUrl()
53 | {
54 | if (empty(parent::getEditUrl())) {
55 | return '';
56 | }
57 |
58 | if ($this->model instanceof Folder) {
59 | return $this->model->content->container->createUrl($this->editRoute, ['id' => $this->model->getItemId(), 'fromWall' => true]);
60 | }
61 |
62 | return '';
63 | }
64 |
65 | /**
66 | * @return string
67 | */
68 | protected function getIcon()
69 | {
70 | return $this->model->getIcon();
71 | }
72 |
73 | /**
74 | * @return string a non encoded plain text title (no html allowed) used in the header of the widget
75 | */
76 | protected function getTitle()
77 | {
78 | return $this->model->getTitle();
79 | }
80 |
81 | }
82 |
--------------------------------------------------------------------------------
/widgets/FileSystemItem.php:
--------------------------------------------------------------------------------
1 | row->showSelect = $this->itemsSelectable;
46 |
47 | return $this->render('fileSystemItem', [
48 | 'folder' => $this->folder,
49 | 'row' => $this->row,
50 | 'options' => $this->getOptions(),
51 | ]);
52 | }
53 |
54 | public function getData()
55 | {
56 | return [
57 | 'cfiles-item' => $this->row->getItemId(),
58 | 'cfiles-content' => $this->row->getContentId(),
59 | 'cfiles-type' => $this->row->getType(),
60 | 'cfiles-url' => $this->row->getUrl(),
61 | 'cfiles-editable' => $this->row->canEdit(),
62 | 'cfiles-url-full' => $this->row->getDisplayUrl(),
63 | 'cfiles-wall-url' => $this->row->getWallUrl(),
64 | 'cfiles-edit-url' => ($this->row->canEdit()) ? $this->row->getEditUrl() : '',
65 | 'cfiles-move-url' => ($this->row->canEdit()) ? $this->row->getMoveUrl() : '',
66 | 'cfiles-versions-url' => ($this->row->canEdit()) ? $this->row->getVersionsUrl() : '',
67 | ];
68 | }
69 |
70 | }
71 |
--------------------------------------------------------------------------------
/models/ConfigureForm.php:
--------------------------------------------------------------------------------
1 | getModule();
26 | $this->disableZipSupport = !$module->isZipSupportEnabled();
27 | $this->displayDownloadCount = $module->getDisplayDownloadCount();
28 | $this->contentHiddenDefault = $module->getContentHiddenGlobalDefault();
29 | }
30 |
31 | /**
32 | * @return Module
33 | */
34 | public function getModule()
35 | {
36 | return Yii::$app->getModule('cfiles');
37 | }
38 |
39 | /**
40 | * @inheritdoc
41 | */
42 | public function rules()
43 | {
44 | return [
45 | [['disableZipSupport', 'displayDownloadCount', 'contentHiddenDefault'], 'boolean'],
46 | ];
47 | }
48 |
49 | /**
50 | * @inheritdoc
51 | */
52 | public function attributeLabels()
53 | {
54 | return [
55 | 'disableZipSupport' => Yii::t('CfilesModule.base', 'Disable archive (ZIP) support'),
56 | 'displayDownloadCount' => Yii::t('CfilesModule.base', 'Display a download count column'),
57 | ];
58 | }
59 |
60 | public function save(): bool
61 | {
62 | if (!$this->validate()) {
63 | return false;
64 | }
65 |
66 | $module = $this->getModule();
67 | $module->settings->set('disableZipSupport', $this->disableZipSupport);
68 | $module->settings->set('displayDownloadCount', $this->displayDownloadCount);
69 | $module->settings->set('contentHiddenGlobalDefault', $this->contentHiddenDefault);
70 |
71 | return true;
72 | }
73 | }
74 |
--------------------------------------------------------------------------------
/tests/codeception/acceptance/FileContextCest.php:
--------------------------------------------------------------------------------
1 | amAdmin();
19 | $I->wantToTest('the folder context menu');
20 | $I->amGoingTo('install the cfiles module for space 1');
21 | $I->enableCfilesOnSpace();
22 |
23 | $I->uploadFile();
24 | $I->wait(1);
25 |
26 | $I->amGoingTo('edit my file per context menu');
27 | $I->clickFileContext(1, 'Edit');
28 | $I->waitForText('Edit file', 10, '#globalModal');
29 | $I->fillField('File[file_name]', 'newFile.txt');
30 | $I->click('Save', '#globalModal');
31 |
32 | $I->seeInFileList('newFile.txt');
33 |
34 | $I->amGoingTo('move my file per context menu');
35 | $I->createFolder('move');
36 | $I->amInRoot();
37 | $I->clickFileContext(1, 'Move');
38 | $I->waitForText('Move files', 10, '#globalModal');
39 | $I->click('[data-id="3"]');
40 | $I->click('Save', '#globalModal');
41 | $I->seeInCrumb('move');
42 | $I->seeInFileList('newFile.txt');
43 |
44 | $I->amGoingTo('delete my file per context menu');
45 | $I->seeElement('[data-cfiles-item="file_1"]');
46 | $I->clickFileContext(1, 'Delete');
47 | $I->waitForElementVisible('#globalModalConfirm', 5);
48 | $I->see('Confirm delete file');
49 | $I->wait(1);
50 | $I->click('Delete', '#globalModalConfirm');
51 | $I->waitForElementNotVisible('[data-cfiles-item="file_1"]');
52 |
53 | $I->amGoingTo('show post of my file');
54 | $I->uploadFile();
55 | $I->clickFileContext(2, 'Show Post');
56 | $I->waitForElementVisible('#wallStream');
57 | $I->waitForText('test.txt', 10, '[data-stream-entry]');
58 | }
59 | }
60 |
--------------------------------------------------------------------------------
/tests/codeception/acceptance/UploadAndMoveCest.php:
--------------------------------------------------------------------------------
1 | amAdmin();
19 | $I->wantToTest('the upload entry');
20 | $I->amGoingTo('install the cfiles module for space 1');
21 | $I->enableCfilesOnSpace();
22 |
23 | $I->attachFile('#cfilesUploadFiles', 'test.txt');
24 | $I->waitForText('test.txt', 10, '#fileList');
25 |
26 | $I->wantToTest('the duplicate names entry');
27 | $I->attachFile('#cfilesUploadFiles', 'test.txt');
28 | $I->waitForText('test.txt', 10, '#fileList');
29 |
30 | $I->wantToTest('the creation of a folder');
31 | $I->click('Add directory', '.files-action-menu');
32 |
33 | $I->waitForText('Create folder', 10, '#globalModal');
34 | $I->fillField('Folder[title]', 'NewFolder');
35 | $I->click('Save', '#globalModal');
36 |
37 | $I->waitForText('This folder is empty.');
38 | $I->click('.fa-home', '#cfiles-crumb');
39 |
40 | $I->waitForText('NewFolder', 10, '#fileList');
41 |
42 | $I->wantToTest('to move a file into my new folder');
43 |
44 | $I->jsClick('.allselect');
45 | $I->click('.chkCnt', '.files-action-menu');
46 | $I->click('.filemove-button', '.files-action-menu');
47 |
48 | $I->waitForText('Move files', 10, '#globalModal');
49 | $I->click('[data-id="3"]');
50 | $I->click('Save', '#globalModal');
51 | $I->seeError('Some files could not be moved: Folder NewFolder can\'t be moved to itself!');
52 |
53 | $I->see('NewFolder', '#fileList');
54 | $I->dontSee('test.txt', '#fileList');
55 |
56 | $I->click('NewFolder', '#fileList');
57 | $I->waitForText('test.txt', 10, '#fileList');
58 | $I->see('test.txt', '#fileList');
59 | }
60 | }
61 |
--------------------------------------------------------------------------------
/widgets/VersionItem.php:
--------------------------------------------------------------------------------
1 | isCurrent()) {
47 | $rowOptions = ['class' => 'bg-warning'];
48 | } else {
49 | $rowOptions = ['id' => 'version_file_' . $this->version->id];
50 | }
51 |
52 | return $this->render('versionItem', [
53 | 'options' => $rowOptions,
54 | 'user' => $this->getUser(),
55 | 'date' => $this->getDate(),
56 | 'size' => $this->getSize(),
57 | 'revertUrl' => $this->revertUrl,
58 | 'downloadUrl' => $this->downloadUrl,
59 | 'deleteUrl' => $this->deleteUrl,
60 | ]);
61 | }
62 |
63 | private function isCurrent(): bool
64 | {
65 | return $this->version instanceof BaseFile;
66 | }
67 |
68 | private function getUser(): User
69 | {
70 | return $this->isCurrent() ? $this->version->updatedBy : $this->version->createdBy;
71 | }
72 |
73 | private function getDate(): string
74 | {
75 | return $this->isCurrent() ? $this->version->updated_at : $this->version->created_at;
76 | }
77 |
78 | private function getSize(): string
79 | {
80 | return $this->version->size;
81 | }
82 |
83 | }
84 |
--------------------------------------------------------------------------------
/actions/UploadAction.php:
--------------------------------------------------------------------------------
1 | controller->renderFileList();
39 | return $result;
40 | }
41 |
42 | protected function handleFileUpload(UploadedFile $uploadedFile, $hideInStream = false)
43 | {
44 | $folder = $this->controller->getCurrentFolder();
45 |
46 | $file = $folder->addUploadedFile($uploadedFile);
47 |
48 | if ($file->hasErrors()) {
49 | return $this->getValidationErrorResponse($file);
50 | }
51 |
52 | if ($file->baseFile->hasErrors()) {
53 | return $this->getErrorResponse($file->baseFile);
54 | }
55 |
56 | return array_merge(['error' => false], FileHelper::getFileInfos($file->baseFile));
57 | }
58 |
59 | protected function getValidationErrorResponse(FileSystemItem $file)
60 | {
61 | $errorMessage = Yii::t('FileModule.actions_UploadAction', 'File {fileName} could not be uploaded!', ['fileName' => $file->baseFile->name ?? '']);
62 |
63 | if (!empty($file->hasErrors())) {
64 | $errorMessage = $file->getErrorSummary(false);
65 | }
66 |
67 | return [
68 | 'error' => true,
69 | 'errors' => $errorMessage,
70 | 'name' => $file->baseFile->name ?? '',
71 | 'size' => $file->baseFile->size ?? '',
72 | ];
73 | }
74 | }
75 |
--------------------------------------------------------------------------------
/components/UrlRule.php:
--------------------------------------------------------------------------------
1 | amAdmin();
19 | $I->wantToTest('the visibility of folders');
20 | $I->amGoingTo('install the cfiles module for space 1');
21 | $I->enableCfilesOnSpace();
22 |
23 | $I->uploadFile();
24 | $I->createFolder('test');
25 |
26 | $I->amInRoot();
27 | $I->jsClick('.allselect');
28 | $I->click('.chkCnt', '.files-action-menu');
29 | $I->click('Delete', '.files-action-menu');
30 | $I->waitForText('Confirm delete file', 10, '#globalModalConfirm');
31 | $I->wait(1);
32 | $I->click('Delete', '#globalModalConfirm');
33 |
34 | $I->waitForElementNotVisible('[data-cfiles-item="file_1"]');
35 | $I->waitForElementNotVisible('[data-cfiles-item="folder_3"]');
36 |
37 | $I->uploadFile();
38 | $I->createFolder('test');
39 |
40 | $I->amInRoot();
41 | $I->jsClick('.allselect');
42 | $I->click('.chkCnt', '.files-action-menu');
43 | $I->click('Move', '.files-action-menu');
44 | $I->waitForText('Move files', 10, '#globalModal');
45 | $I->click('[data-id="4"]');
46 | $I->click('Save', '#globalModal');
47 | $I->seeError('Some files could not be moved: Folder test can\'t be moved to itself!');
48 | $I->seeInFileList('test');
49 | $I->dontSeeInFileList('test.txt');
50 | $I->openFolder('test');
51 |
52 | // Move back to root
53 | $I->jsClick('.allselect');
54 | $I->click('.chkCnt', '.files-action-menu');
55 | $I->click('Move', '.files-action-menu');
56 | $I->waitForText('Move files', 10, '#globalModal');
57 | $I->click('[data-id="1"]');
58 | $I->click('Save', '#globalModal');
59 | $I->wait(2);
60 | $I->seeInFileList('test.txt');
61 | }
62 | }
63 |
--------------------------------------------------------------------------------
/tests/codeception/acceptance/FolderContextCest.php:
--------------------------------------------------------------------------------
1 | amAdmin();
19 | $I->wantToTest('the visibility of folders');
20 | $I->amGoingTo('install the cfiles module for space 1');
21 | $I->enableCfilesOnSpace();
22 |
23 | $I->createFolder('context', 'context test');
24 | $I->uploadFile();
25 |
26 | $I->amInRoot();
27 |
28 | $I->amGoingTo('test the folder open context item');
29 | $I->clickFolderContext(3, 'Open');
30 | $I->seeInCrumb('context');
31 |
32 | $I->amInRoot();
33 |
34 | $I->amGoingTo('test the folder edit context item');
35 | $I->clickFolderContext(3, 'Edit');
36 | $I->waitForText('Edit folder', 10, '#globalModal');
37 | $I->fillField('Folder[title]', 'context2');
38 | $I->click('Save', '#globalModal');
39 |
40 | $I->uploadFile();
41 |
42 | $I->amInRoot();
43 | $I->seeInFileList('context2');
44 |
45 | $I->amGoingTo('test the folder move context item');
46 |
47 | $I->createFolder('context1', 'context test');
48 | $I->amInRoot();
49 | $I->clickFolderContext(3, 'Move');
50 |
51 | $I->waitForText('Move files', 10, '#globalModal');
52 | $I->click('[data-id="4"]');
53 | $I->click('Save', '#globalModal');
54 |
55 | $I->seeInCrumb('context1');
56 | $I->seeInFileList('context2');
57 | $I->openFolder('context2');
58 | $I->seeInFileList('test.txt');
59 |
60 | $I->amGoingTo('test the folder delete context item');
61 | $I->amInRoot();
62 | $I->seeElement('[data-cfiles-item="folder_4"]');
63 | $I->clickFolderContext(4, 'Delete');
64 | $I->waitForElementVisible('#globalModalConfirm', 5);
65 | $I->see('Confirm delete file');
66 | $I->click('Delete', '#globalModalConfirm');
67 | $I->waitForElementNotVisible('[data-cfiles-item="folder_4"]');
68 |
69 |
70 | }
71 | }
72 |
--------------------------------------------------------------------------------
/tests/codeception/api/ManageCest.php:
--------------------------------------------------------------------------------
1 | isRestModuleEnabled()) {
13 | return;
14 | }
15 |
16 | $I->wantTo('make items as public');
17 | $I->amAdmin();
18 | $I->createSampleFile();
19 |
20 | $I->sendPatch('cfiles/items/container/1/make-public', [
21 | 'selection' => ['folder_1', 'file_1'],
22 | ]);
23 | $I->seeSuccessMessage('Items successfully marked public!');
24 | }
25 |
26 | public function testMakePrivate(ApiTester $I)
27 | {
28 | if (!$this->isRestModuleEnabled()) {
29 | return;
30 | }
31 |
32 | $I->wantTo('make items as private');
33 | $I->amAdmin();
34 | $I->createSampleFile();
35 |
36 | $I->sendPatch('cfiles/items/container/1/make-private', [
37 | 'selection' => ['folder_1', 'file_1'],
38 | ]);
39 | $I->seeSuccessMessage('Items successfully marked private!');
40 | }
41 |
42 | public function testMoveDelete(ApiTester $I)
43 | {
44 | if (!$this->isRestModuleEnabled()) {
45 | return;
46 | }
47 |
48 | $I->wantTo('move and delete items');
49 | $I->amAdmin();
50 |
51 | $I->createFolder('First folder'); // folder id = 2, (root folder id = 1)
52 | $I->createFolder('Sub folder 1', ['target_id' => 2]); // folder id = 3
53 | $I->uploadFiles(['test.txt', 'test.zip'], ['folder_id' => 3]); // file ids = 1, 2
54 | $I->createFolder('Sub folder 2', ['target_id' => 2]); // folder id = 4
55 | $I->createFolder('Sub-sub folder 1', ['target_id' => 3]); // folder id = 5
56 |
57 | $I->sendPost('cfiles/items/container/1/move', [
58 | 'source_id' => 5,
59 | 'MoveForm' => ['destId' => 2],
60 | 'selection' => ['folder_4', 'file_1', 'file_2'],
61 | ]);
62 | $I->seeSuccessMessage('Items successfully moved.');
63 |
64 | $I->sendDelete('cfiles/items/container/1/delete', [
65 | 'selection' => ['file_1', 'file_2', 'folder_5', 'folder_4', 'folder_3', 'folder_2'],
66 | ]);
67 | $I->seeSuccessMessage('Selected items are successfully deleted!');
68 | }
69 |
70 | }
71 |
--------------------------------------------------------------------------------
/messages/en-GB/base.php:
--------------------------------------------------------------------------------
1 | '',
4 | '%filename% has invalid extension and was skipped.' => '',
5 | '%title% was replaced by a newer version.' => '',
6 | '/ (root)' => '',
7 | 'Confirm delete file' => '',
8 | 'Create folder' => '',
9 | 'Edit folder' => '',
10 | 'Files module configuration' => '',
11 | 'Move files' => '',
12 | 'A folder with this name already exists.' => '',
13 | 'Add directory' => '',
14 | 'Add file(s)' => '',
15 | 'Adds files module to this space.' => '',
16 | 'Adds files module to your profile.' => '',
17 | 'Archive %filename% could not be extracted.' => '',
18 | 'Archive (zip) support is not enabled.' => '',
19 | 'Close' => '',
20 | 'Could not save file %title%. ' => '',
21 | 'Creator' => '',
22 | 'Delete' => '',
23 | 'Disable archive (ZIP) support' => '',
24 | 'Do you really want to delete this %number% item(s) with all subcontent?' => '',
25 | 'Download' => '',
26 | 'Download ZIP' => '',
27 | 'Edit' => '',
28 | 'Edit directory' => '',
29 | 'File' => '',
30 | 'Files' => '',
31 | 'Files from the stream' => '',
32 | 'Folder' => '',
33 | 'Folder options' => '',
34 | 'Folder should not start or end with blank space.' => '',
35 | 'Insufficient rights to execute this action.' => '',
36 | 'Invalid parameter.' => '',
37 | 'Move' => '',
38 | 'Moving to the same folder is not valid. Choose a valid parent folder for %title%.' => '',
39 | 'Name' => '',
40 | 'No valid items were selected to move.' => '',
41 | 'Open' => '',
42 | 'Opening archive failed with error code %code%.' => '',
43 | 'Please select a valid destination folder for %title%.' => '',
44 | 'Save' => '',
45 | 'Selected items...' => '',
46 | 'Show' => '',
47 | 'Show Post' => '',
48 | 'Size' => '',
49 | 'The archive could not be created.' => '',
50 | 'The folder %filename% already exists. Contents have been overwritten.' => '',
51 | 'The folder with the id %id% does not exist.' => '',
52 | 'This folder is empty.' => '',
53 | 'Unfortunately you have no permission to upload/edit files.' => '',
54 | 'Updated' => '',
55 | 'Upload' => '',
56 | 'Upload ZIP' => '',
57 | 'Upload files or create a subfolder with the buttons on the top.' => '',
58 | 'Upload files to the stream to fill this folder.' => '',
59 | 'ZIP all' => '',
60 | 'ZIP selected' => '',
61 | 'changed:' => '',
62 | 'created:' => '',
63 | 'root' => '',
64 | ];
65 |
--------------------------------------------------------------------------------
/controllers/ZipController.php:
--------------------------------------------------------------------------------
1 | [WriteAccess::class], 'actions' => ['upload']],
33 | ];
34 | }
35 |
36 | public function actions()
37 | {
38 | return [
39 | 'upload' => [
40 | 'class' => UploadZipAction::class,
41 | ],
42 | ];
43 | }
44 |
45 | public function checkZipSupport($rule, $access)
46 | {
47 | if (!$this->module->isZipSupportEnabled()) {
48 | $access->code = 404;
49 | $access->reason = Yii::t('CfilesModule.base', 'ZIP support is not enabled.');
50 | return false;
51 | }
52 |
53 | return true;
54 | }
55 |
56 | /**
57 | * Action to download a zip of the selected items.
58 | */
59 | public function actionDownload()
60 | {
61 | $selectedItems = Yii::$app->request->post('selection');
62 |
63 | $items = [];
64 | // Download only the selected items if at least one is selected
65 | if (is_array($selectedItems)) {
66 | foreach ($selectedItems as $itemId) {
67 | $item = FileSystemItem::getItemById($itemId);
68 | if ($item !== null) {
69 | $items[] = $item;
70 | }
71 | }
72 | }
73 | // Otherwise fallback to current folder when no items are selected
74 | if ($items === []) {
75 | $items[] = $this->getCurrentFolder();
76 | }
77 |
78 | $zip = new ZIPCreator();
79 | foreach ($items as $item) {
80 | $zip->add($item);
81 | }
82 | $zip->close();
83 |
84 | return Yii::$app->response->sendFile($zip->getZipFile(), (count($items) == 1) ? $items[0]->title . '.zip' : 'files.zip');
85 | }
86 | }
87 |
--------------------------------------------------------------------------------
/models/forms/VersionForm.php:
--------------------------------------------------------------------------------
1 | getFileVersion()) {
51 | $this->addError($attribute, 'The selected version doesn\'t exist for the File!');
52 | }
53 | }
54 |
55 | public function attributeHints(): array
56 | {
57 | return [
58 | 'version' => Yii::t('CfilesModule.base', 'Select what file version you want to switch.'),
59 | ];
60 | }
61 |
62 | public function getFileVersion(): ?FileHistory
63 | {
64 | return $this->file->baseFile->getFileHistoryById($this->version);
65 | }
66 |
67 | /**
68 | * @inheritdoc
69 | */
70 | public function load($data = null, $formName = null)
71 | {
72 | if ((int)Yii::$app->request->get('version') > 0) {
73 | $data = Yii::$app->request->get();
74 | $formName = '';
75 | } else {
76 | $data = Yii::$app->request->post();
77 | }
78 |
79 | return parent::load($data, $formName);
80 | }
81 |
82 | /**
83 | * Switch the File to a selected version
84 | *
85 | * @return bool
86 | */
87 | public function save(): bool
88 | {
89 | if (!$this->validate()) {
90 | return false;
91 | }
92 |
93 | $this->file->baseFile->setStoredFile($this->getFileVersion()->getFileStorePath());
94 |
95 | return $this->file->baseFile->save() && $this->file->refresh();
96 | }
97 |
98 | }
99 |
--------------------------------------------------------------------------------
/tests/codeception/api/FolderCest.php:
--------------------------------------------------------------------------------
1 | isRestModuleEnabled()) {
13 | return;
14 | }
15 |
16 | $I->wantTo('find by container');
17 | $I->amAdmin();
18 | $I->seePaginationFoldersResponse('cfiles/folders/container/1', []);
19 |
20 | $I->createSampleFolder();
21 | $I->seePaginationFoldersResponse('cfiles/folders/container/1', [1, 2]);
22 | }
23 |
24 | public function testCreateFolder(ApiTester $I)
25 | {
26 | if (!$this->isRestModuleEnabled()) {
27 | return;
28 | }
29 |
30 | $I->wantTo('create a folder');
31 | $I->amAdmin();
32 |
33 | $I->createFolder('New folder');
34 | $I->seeLastCreatedFolderDefinition();
35 | }
36 |
37 | public function testGetFolderById(ApiTester $I)
38 | {
39 | if (!$this->isRestModuleEnabled()) {
40 | return;
41 | }
42 |
43 | $I->wantTo('get a folder by id');
44 | $I->amAdmin();
45 |
46 | $I->createSampleFolder();
47 | $I->sendGet('cfiles/folder/2');
48 | $I->seeFolderDefinitionById(2);
49 | }
50 |
51 | public function testUpdateFolder(ApiTester $I)
52 | {
53 | if (!$this->isRestModuleEnabled()) {
54 | return;
55 | }
56 |
57 | $I->wantTo('update a folder');
58 | $I->amAdmin();
59 |
60 | $I->sendPut('cfiles/folder/2');
61 | $I->seeNotFoundMessage('cFiles folder not found!');
62 |
63 | $I->createSampleFolder();
64 | $I->sendPut('cfiles/folder/2', [
65 | 'Folder' => [
66 | 'title' => 'Updated title',
67 | 'description' => 'Updated description',
68 | 'visibility' => 1,
69 | ],
70 | ]);
71 | $I->seeFolderDefinitionById(2);
72 | }
73 |
74 | public function testDeleteFolder(ApiTester $I)
75 | {
76 | if (!$this->isRestModuleEnabled()) {
77 | return;
78 | }
79 |
80 | $I->wantTo('delete a folder');
81 | $I->amAdmin();
82 |
83 | $I->sendDelete('cfiles/folder/2');
84 | $I->seeNotFoundMessage('Content record not found!');
85 |
86 | $I->createSampleFolder();
87 | $I->sendDelete('cfiles/folder/2');
88 | $I->seeSuccessMessage('Successfully deleted!');
89 | }
90 |
91 | }
92 |
--------------------------------------------------------------------------------
/extensions/custom_pages/elements/FileElement.php:
--------------------------------------------------------------------------------
1 | Yii::t('CfilesModule.base', 'File content ID'),
43 | ];
44 | }
45 |
46 | public function __toString(): string
47 | {
48 | if ($this->hasFile()) {
49 | return $this->getFile()->getUrl();
50 | }
51 |
52 | return (string) Html::encode($this->record?->description);
53 | }
54 |
55 | /**
56 | * @inheritdoc
57 | */
58 | public function getTemplateVariable(): BaseElementVariable
59 | {
60 | return FileElementVariable::instance($this)->setRecord($this->getRecord());
61 | }
62 |
63 | /**
64 | * Get File
65 | *
66 | * @return BaseFile|null
67 | */
68 | public function getFile(): ?BaseFile
69 | {
70 | return empty($this->contentId) ? null
71 | : BaseFile::find()
72 | ->innerJoin(Content::tableName(), Content::tableName() . '.object_model = ' . BaseFile::tableName() . '.object_model' . ' AND ' . Content::tableName() . '.object_id = ' . BaseFile::tableName() . '.object_id')
73 | ->where([BaseFile::tableName() . '.object_model' => File::class])
74 | ->andWhere([Content::tableName() . '.id' => $this->contentId])
75 | ->one();
76 | }
77 |
78 | /**
79 | * Check if a File is found for this Element
80 | *
81 | * @return bool
82 | */
83 | public function hasFile(): bool
84 | {
85 | return $this->getFile() !== null;
86 | }
87 | }
88 |
--------------------------------------------------------------------------------
/widgets/WallEntryFile.php:
--------------------------------------------------------------------------------
1 | model;
43 |
44 | return $this->render('wallEntryFile', [
45 | 'cFile' => $cFile,
46 | 'fileSize' => $cFile->getSize(),
47 | 'file' => $cFile->baseFile,
48 | 'previewImage' => new PreviewImage(),
49 | 'folderUrl' => $this->getFolderUrl(),
50 | ]);
51 | }
52 |
53 | /**
54 | * Returns the edit url to edit the content (if supported)
55 | *
56 | * @return string url
57 | */
58 | public function getEditUrl()
59 | {
60 | if (empty(parent::getEditUrl())) {
61 | return '';
62 | }
63 |
64 | if ($this->model instanceof File) {
65 | return $this->model->content->container->createUrl($this->editRoute, ['id' => $this->model->getItemId(), 'fromWall' => true]);
66 | }
67 |
68 | return '';
69 | }
70 |
71 | /**
72 | * @return string
73 | */
74 | protected function getIcon()
75 | {
76 | return $this->model->getIcon();
77 | }
78 |
79 | /**
80 | * @return string a non encoded plain text title (no html allowed) used in the header of the widget
81 | */
82 | protected function getTitle()
83 | {
84 | return $this->model->getTitle();
85 | }
86 |
87 | protected function getFolderUrl(): ?string
88 | {
89 | if (!$this->model->parentFolder instanceof Folder) {
90 | return null;
91 | }
92 |
93 | if ($this->model->parentFolder->content->getStateService()->isDeleted()) {
94 | return null;
95 | }
96 |
97 | return $this->model->parentFolder->getUrl();
98 | }
99 |
100 | }
101 |
--------------------------------------------------------------------------------
/controllers/rest/FileController.php:
--------------------------------------------------------------------------------
1 | $containerId]);
44 | if ($containerRecord === null) {
45 | return $this->returnError(404, 'Content container not found!');
46 | }
47 | /** @var ContentContainerActiveRecord $container */
48 | $container = $containerRecord->getPolymorphicRelation();
49 |
50 | $folderId = Yii::$app->request->post('folder_id', null);
51 | if (is_null($folderId)) {
52 | return $this->returnError(400, 'Target folder id is required!');
53 | }
54 |
55 | $targetDir = Folder::find()->contentContainer($container)->andWhere(['cfiles_folder.id' => $folderId])->one();
56 | if ($targetDir === null) {
57 | return $this->returnError(404, 'cFiles folder not found!');
58 | }
59 | if (!$container->can(ManageFiles::class)) {
60 | return $this->returnError(403, 'You cannot upload files into this folder!');
61 | }
62 |
63 | $files = UploadedFile::getInstancesByName('files');
64 |
65 | if (empty($files)) {
66 | return $this->returnError(400, 'No files to upload.');
67 | }
68 |
69 | foreach ($files as $file) {
70 | $file = $targetDir->addUploadedFile($file);
71 |
72 | if ($file->hasErrors() || $file->baseFile->hasErrors()) {
73 | return $this->returnError(422, "File {$file->baseFile->name} could not be uploaded!", [
74 | 'errors' => array_merge($file->getErrors(), $file->baseFile->getErrors()),
75 | ]);
76 | }
77 | }
78 |
79 | return $this->returnSuccess('Files successfully uploaded!');
80 | }
81 | }
82 |
--------------------------------------------------------------------------------
/controllers/BrowseController.php:
--------------------------------------------------------------------------------
1 | getCurrentFolder();
29 | if (!$currentFolder->content->canView()) {
30 | throw new HttpException(403);
31 | }
32 |
33 | return $this->render('index', [
34 | 'contentContainer' => $this->contentContainer,
35 | 'folder' => $currentFolder,
36 | 'canWrite' => $this->canWrite(),
37 | ]);
38 | }
39 |
40 | public function actionFileList()
41 | {
42 | return $this->asJson(['output' => $this->renderFileList()]);
43 | }
44 |
45 | /**
46 | * Returns rendered file list.
47 | *
48 | * @return string
49 | */
50 | public function renderFileList(): string
51 | {
52 | return FileList::widget([
53 | 'folder' => $this->getCurrentFolder(),
54 | 'contentContainer' => $this->contentContainer,
55 | ]);
56 | }
57 |
58 | public function actionLoadEntry()
59 | {
60 | if ($file = $this->getFileById()) {
61 | return $this->asJson([
62 | 'output' => $this->renderFileRow($file),
63 | // Additional scripts may be generated here in order to display some messages in info footer bar
64 | 'scripts' => $this->renderAjaxContent(''),
65 | ]);
66 | }
67 |
68 | return $this->asJson([
69 | 'success' => false,
70 | 'error' => Yii::t('CfilesModule.base', 'No file found!'),
71 | ]);
72 | }
73 |
74 | private function getFileById(): ?File
75 | {
76 | $fileId = Yii::$app->request->get('id');
77 | $fileId = str_starts_with((string) $fileId, 'file_') ? substr((string) $fileId, 5) : 0;
78 |
79 | if (empty($fileId)) {
80 | return null;
81 | }
82 |
83 | return File::find()->readable()->where(['cfiles_file.id' => $fileId])->one();
84 | }
85 |
86 | private function renderFileRow(File $file)
87 | {
88 | if ($file->parent_folder_id) {
89 | $folder = Folder::findOne(['id' => $file->parent_folder_id]);
90 | } else {
91 | $folder = $this->getRootFolder();
92 | }
93 |
94 | return FileSystemItem::widget([
95 | 'folder' => $folder,
96 | 'row' => new FileRow(['item' => $file]),
97 | ]);
98 | }
99 |
100 | }
101 |
--------------------------------------------------------------------------------
/controllers/UploadController.php:
--------------------------------------------------------------------------------
1 | [WriteAccess::class]],
35 | ];
36 | }
37 |
38 | public function actions()
39 | {
40 | return [
41 | 'index' => ['class' => UploadAction::class],
42 | ];
43 | }
44 |
45 | public function actionImport($fid)
46 | {
47 | $guids = Yii::$app->request->post('guids');
48 | $guids = is_string($guids) ? array_map(trim(...), explode(',', $guids)) : $guids;
49 |
50 | if (!is_array($guids)) {
51 | throw new HttpException(400);
52 | }
53 |
54 | $folder = Folder::findOne($fid);
55 |
56 | $errors = [];
57 |
58 | foreach ($guids as $guid) {
59 | $cFile = ModelFile::findOne(['guid' => $guid]);
60 |
61 | if (!$cFile) {
62 | $error = Yii::t('CfilesModule.base', 'Could not import file with guid {guid}. File not found', ['guid' => $guid]);
63 | $errors[] = $error;
64 | Yii::error($error);
65 | continue;
66 | }
67 |
68 | $cFile->show_in_stream = false;
69 |
70 | $file = new File($this->contentContainer);
71 | $file->setFileContent($cFile);
72 | $folder->moveItem($file);
73 |
74 | $file->visibility = Content::VISIBILITY_PRIVATE;
75 | $file->save();
76 |
77 | if ($file->hasErrors()) {
78 | $errors[] = $this->actionResponseError($file);
79 | }
80 |
81 | if ($file->baseFile->hasErrors()) {
82 | $errors[] = $this->actionResponseError($file->baseFile);
83 | }
84 | }
85 |
86 | if ($errors) {
87 | array_unshift($errors, Yii::t('CfilesModule.base', 'Some files could not be imported: '));
88 | }
89 |
90 | return $this->asJson(['success' => empty($errors), 'errors' => $errors]);
91 | }
92 |
93 | public function actionResponseError(ActiveRecord $record)
94 | {
95 | $errorMsg = Yii::t('CfilesModule.base', 'Some files could not be imported: ');
96 | foreach ($record->getErrors() as $errors) {
97 | foreach ($errors as $error) {
98 | $errorMsg .= $error . ' ';
99 | }
100 | }
101 | }
102 | }
103 |
--------------------------------------------------------------------------------
/widgets/VersionsView.php:
--------------------------------------------------------------------------------
1 | initVersions();
53 | }
54 |
55 | /**
56 | * Initialise versions
57 | */
58 | private function initVersions()
59 | {
60 | $pagination = new Pagination([
61 | 'page' => $this->page - 1,
62 | 'pageSize' => $this->pageSize,
63 | 'totalCount' => $this->file->baseFile->getHistoryFiles()->count(),
64 | ]);
65 |
66 | $this->isLastPage = ($pagination->page >= $pagination->pageCount - 1);
67 |
68 | $this->versions = $this->file->baseFile
69 | ->getHistoryFiles()
70 | ->offset($pagination->offset)
71 | ->limit($pagination->limit)
72 | ->all();
73 | }
74 |
75 | /**
76 | * @inheritdoc
77 | */
78 | public function run()
79 | {
80 | return $this->render('versionsView', [
81 | 'versionsRowsHtml' => $this->renderVersions(),
82 | 'nextPageVersionsUrl' => $this->getNextPageVersionsUrl(),
83 | ]);
84 | }
85 |
86 | public function renderVersions(): string
87 | {
88 | $html = '';
89 |
90 | if ($this->page == 1) {
91 | $html .= VersionItem::widget([
92 | 'version' => $this->file->baseFile,
93 | 'revertUrl' => false,
94 | 'downloadUrl' => $this->file->baseFile->getUrl(),
95 | 'deleteUrl' => false,
96 | ]);
97 | }
98 |
99 | foreach ($this->versions as $version) {
100 | $html .= VersionItem::widget([
101 | 'version' => $version,
102 | 'revertUrl' => $this->file->getVersionsUrl($version->id),
103 | 'downloadUrl' => $version->getFileUrl(),
104 | 'deleteUrl' => $this->file->getDeleteVersionUrl($version->id),
105 | ]);
106 | }
107 |
108 | return $html;
109 | }
110 |
111 | public function isLastPage(): bool
112 | {
113 | return $this->isLastPage;
114 | }
115 |
116 | private function getNextPageVersionsUrl(): string
117 | {
118 | return $this->isLastPage() ? ''
119 | : $this->file->content->container->createUrl('/cfiles/version/page', ['id' => $this->file->id]);
120 | }
121 |
122 | }
123 |
--------------------------------------------------------------------------------
/tests/codeception/unit/MigrateFromOldTest.php:
--------------------------------------------------------------------------------
1 | becomeUser('Admin');
28 | $space1 = Space::findOne(1);
29 |
30 | $this->assertTrue($this->createFile($space1, 'f1.txt'));
31 | $this->assertTrue($this->createFile($space1, 'f2.txt'));
32 | $this->assertTrue($this->createFile($space1, 'f3.txt'));
33 | $this->assertTrue($this->createFile($space1, 'f4.txt'));
34 | $this->assertTrue($this->createFile($space1, 'f5.txt'));
35 |
36 | // Add one folder manually with a file
37 | $folder = new Folder($space1);
38 | $folder->title = 'fo1';
39 | $this->assertTrue($folder->save(false));
40 |
41 | $fileA = new File($space1);
42 | $fileA->setUploadedFile(new UploadedFile([
43 | 'name' => 'fileA.txt',
44 | 'size' => 1024,
45 | 'type' => 'text/plain',
46 | ]));
47 |
48 | $folder->moveItem($fileA);
49 |
50 | $this->assertTrue($this->createFolder($space1, 'fo2'));
51 | $this->assertTrue($this->createFolder($space1, 'fo3'));
52 | $this->assertTrue($this->createFolder($space1, 'fo4'));
53 | $this->assertTrue($this->createFolder($space1, 'fo5'));
54 |
55 | $root = Folder::initRoot($space1);
56 | $root->migrateFromOldStructure();
57 |
58 | $children = $root->getChildren();
59 |
60 | $this->assertEquals(10, count($children));
61 | $this->assertNotNull($root->findFileByName('f1.txt'));
62 | $this->assertNotNull($root->findFileByName('f2.txt'));
63 | $this->assertNotNull($root->findFileByName('f3.txt'));
64 | $this->assertNotNull($root->findFileByName('f4.txt'));
65 | $this->assertNotNull($root->findFileByName('f5.txt'));
66 |
67 | $this->assertNotNull($root->findFolderByName('fo1'));
68 | $this->assertNotNull($root->findFolderByName('fo2'));
69 | $this->assertNotNull($root->findFolderByName('fo3'));
70 | $this->assertNotNull($root->findFolderByName('fo4'));
71 | $this->assertNotNull($root->findFolderByName('fo5'));
72 |
73 | $root->refresh();
74 |
75 | $this->assertNull($root->parent_folder_id);
76 |
77 | }
78 |
79 | public function createFile($space, $name)
80 | {
81 | $file = new File($space);
82 | $file->setUploadedFile(new UploadedFile([
83 | 'name' => $name,
84 | 'size' => 1024,
85 | 'type' => 'text/plain',
86 | ]));
87 |
88 | //Save without parent_id;
89 | return $file->save(false);
90 | }
91 |
92 | public function createFolder($space, $title)
93 | {
94 | $folder = new Folder($space);
95 | $folder->title = $title;
96 | return $folder->save(false);
97 | }
98 |
99 | }
100 |
--------------------------------------------------------------------------------
/tests/codeception/acceptance/VisibilityCest.php:
--------------------------------------------------------------------------------
1 | amAdmin();
19 | $I->wantToTest('the visibility of folders');
20 | $I->amGoingTo('install the cfiles module for space 1');
21 | $I->enableCfilesOnSpace();
22 |
23 | $I->createFolder('visibility', 'visibility test');
24 | $I->expect('The new folder to be private');
25 | $I->seeElement('.folder-visibility .fa-lock');
26 |
27 | $I->uploadFile();
28 | $I->expect('The new file to be private');
29 | $I->seeElement('[data-cfiles-item] .fa-lock');
30 |
31 | $I->click('Add directory', '.files-action-menu');
32 |
33 | // Create another folder
34 | $I->waitForText('Create folder', 10, '#globalModal');
35 | $I->fillField('Folder[title]', 'visibility2');
36 | $I->fillField('Folder[description]', 'visibility2');
37 |
38 | $I->expect('The folder visibility to be private');
39 | $I->waitForElement('input#folder-visibility:disabled');
40 |
41 | $I->click('Save', '#globalModal');
42 | $I->waitForText('This folder is empty.');
43 | $I->seeElement('.folder-visibility .fa-lock');
44 | $I->uploadFile();
45 | $I->expect('The new file to be private');
46 | $I->seeElement('[data-cfiles-item] .fa-lock');
47 |
48 | $I->amUser1(true);
49 | $I->amOnSpace(1, '/cfiles/browse');
50 | $I->expect('Not to see the files entry since there are no public files available');
51 | $I->see('Files from the stream', '#fileList');
52 |
53 | $I->amAdmin(true);
54 | $I->amOnSpace(1, '/cfiles/browse');
55 |
56 | $I->amGoingTo('set the folder visibility to public');
57 | $I->clickFolderContext(3, 'Edit');
58 | $I->waitForText('Edit folder', 10, '#globalModal');
59 | $I->jsClick('input#folder-visibility');
60 | $I->click('Save', '#globalModal');
61 | $I->waitForText('visibility2', 10, '#fileList');
62 |
63 | $I->expect('all subfiles and subfolders to be public too');
64 | $I->seeElement('.folder-visibility .fa-unlock');
65 | $I->seeElement('[data-cfiles-item="folder_4"] .fa-unlock');
66 | $I->seeElement('[data-cfiles-item="file_1"] .fa-unlock');
67 |
68 | $I->click('visibility2', '#fileList');
69 | $I->waitForText('visibility2', 10, '#cfiles-crumb');
70 | $I->seeElement('[data-cfiles-item="file_2"] .fa-unlock');
71 |
72 | $I->amGoingTo('Reset the file visibility of /visibility/visibility2/test.txt to private');
73 | $I->clickFileContext(2, 'Edit');
74 | $I->waitForText('Edit file', 10, '#globalModal');
75 | $I->jsClick('input[type="checkbox"][name="File[visibility]"]');
76 | $I->click('Save', '#globalModal');
77 |
78 | $I->seeSuccess();
79 | $I->waitForElementVisible('[data-cfiles-item="file_2"] .fa-lock');
80 |
81 | $I->amUser1(true);
82 | $I->amOnSpace(1, '/cfiles/browse');
83 | $I->seeElement('#fileList');
84 | $I->see('visibility');
85 | $I->click('visibility', '#fileList');
86 | $I->waitForText('visibility2', 10, '#fileList');
87 | $I->see('test.txt', '#fileList');
88 | $I->click('visibility2', '#fileList');
89 | $I->waitForText('visibility2', 10, '#cfiles-crumb');
90 | $I->dontSee('test.txt', '#fileList');
91 | }
92 | }
93 |
--------------------------------------------------------------------------------
/widgets/views/fileSelectionMenu.php:
--------------------------------------------------------------------------------
1 |
19 |
20 |
21 |
22 |
23 |
24 | ( ) = Yii::t('CfilesModule.base', 'Selected items...') ?>
25 |
26 |
85 |
86 |
87 |
--------------------------------------------------------------------------------
/widgets/views/fileListMenu.php:
--------------------------------------------------------------------------------
1 | createUrl('/cfiles/zip/download', ['fid' => $folder->id]);
20 | $zipUploadUrl = $contentContainer->createUrl('/cfiles/zip/upload', ['fid' => $folder->id]);
21 |
22 | $addFolderUrl = $contentContainer->createUrl('/cfiles/edit/folder', ['fid' => $folder->id]);
23 | $editFolderUrl = $contentContainer->createUrl('/cfiles/edit/folder', ['id' => $folder->getItemId()]);
24 |
25 | $uploadUrl = $contentContainer->createUrl('/cfiles/upload', ['fid' => $folder->id]);
26 | ?>
27 |
28 |
89 |
--------------------------------------------------------------------------------
/models/forms/MoveForm.php:
--------------------------------------------------------------------------------
1 | contentContainer = $this->root->content->container;
50 | parent::init();
51 | }
52 |
53 | /**
54 | * @inheritdoc
55 | */
56 | public function rules()
57 | {
58 | return [
59 | ['destId', 'required'],
60 | ['destId', 'integer'],
61 | ['destId', 'validateDestination'],
62 | ];
63 | }
64 |
65 | /**
66 | * @param $model MoveForm
67 | * @param $attribute
68 | */
69 | public function validateDestination($attribute)
70 | {
71 | $this->destination = Folder::findOne($this->destId);
72 |
73 | if (!$this->destination) {
74 | $this->addError($attribute, Yii::t('CfilesModule.base', 'Destination folder not found!'));
75 | return;
76 | }
77 |
78 | if ($this->sourceFolder->id == $this->destination->id) {
79 | $this->addError($attribute, Yii::t('CfilesModule.base', 'Moving to the same folder is not valid.'));
80 | return;
81 | }
82 |
83 | if ($this->destination->isAllPostedFiles() || $this->destination->content->container->id !== $this->contentContainer->id) {
84 | $this->addError($attribute, Yii::t('CfilesModule.base', 'Moving to this folder is invalid.'));
85 | return;
86 | }
87 | }
88 |
89 | /**
90 | * @return string move action url
91 | */
92 | public function getMoveUrl()
93 | {
94 | return $this->sourceFolder->createUrl('/cfiles/move');
95 | }
96 |
97 | /**
98 | * @return string URL to move Content to another Container
99 | */
100 | public function getMoveToContainerUrl(): ?string
101 | {
102 | if (count($this->selection) !== 1) {
103 | // Only single selected File/Folder can be moved to another Container
104 | return null;
105 | }
106 |
107 | $item = FileSystemItem::getItemById($this->selection[0]);
108 | if (!$item || !$item->content->container || !$item->content->checkMovePermission()) {
109 | return null;
110 | }
111 |
112 | return $item->content->container->createUrl('/content/move/move', ['id' => $item->content->id]);
113 | }
114 |
115 | /**
116 | * Executes the actual move of the selection files from source into target.
117 | * @return bool
118 | */
119 | public function save()
120 | {
121 | if (!$this->validate()) {
122 | return false;
123 | }
124 |
125 | $result = true;
126 |
127 | foreach ($this->selection as $selectedItemId) {
128 | $item = FileSystemItem::getItemById($selectedItemId);
129 |
130 | if (!$this->destination->moveItem($item)) {
131 | $this->addItemErrors($item);
132 | $result = false;
133 | }
134 | }
135 |
136 | return $result;
137 | }
138 |
139 | /**
140 | * @param FileSystemItem $item
141 | */
142 | public function addItemErrors(FileSystemItem $item)
143 | {
144 | foreach ($item->errors as $key => $error) {
145 | $this->addErrors([$key => $error]);
146 | }
147 | }
148 |
149 |
150 | }
151 |
--------------------------------------------------------------------------------
/resources/css/directorylist.css:
--------------------------------------------------------------------------------
1 | .directory-list * {
2 | font-family: sans-serif;
3 | font-size: 12px;
4 | line-height: 1.5em;
5 | }
6 |
7 | .directory-list {
8 | border: 1px solid #808080;
9 | color: #333333;
10 | padding: 4px;
11 | overflow: auto;
12 | width: auto;
13 | height: 320px;
14 | margin-bottom: 1.5em;
15 | }
16 |
17 | .directory-list ul,.directory-list li {
18 | padding: 0;
19 | margin: 0;
20 | display: block;
21 | background: none;
22 | list-style: none;
23 | }
24 |
25 | .directory-list ul {
26 | background: url(../directorylist/directory_last.png) no-repeat 0 -8px;
27 | }
28 |
29 | .directory-list ul ul {
30 | padding-left: 16px;
31 | background: url(../directorylist/directory.png) repeat-y 0 0;
32 | }
33 |
34 | .directory-list li:last-child>ul {
35 | background: none;
36 | }
37 |
38 | .directory-list li.last-child>ul {
39 | background: none;
40 | }
41 |
42 | .directory-list li {
43 | background: url(../directorylist/directory_join.png) no-repeat 0 -8px;
44 | }
45 |
46 | .directory-list li.last-child {
47 | background-image: url(../directorylist/directory_last-child.png);
48 | }
49 |
50 | .directory-list li:last-child {
51 | background-image: url(../directorylist/directory_last-child.png);
52 | }
53 |
54 | .directory-list li.hassub {
55 | background-image: url(../directorylist/directory_join_hassub.png);
56 | }
57 |
58 | .directory-list li.hassub.expand {
59 | background-image: url(../directorylist/directory_join_hassub_expand.png);
60 | }
61 |
62 | .directory-list li.last-child.hassub {
63 | background-image: url(../directorylist/directory_last-child_hassub.png);
64 | }
65 |
66 | .directory-list li:last-child.hassub {
67 | background-image: url(../directorylist/directory_last-child_hassub.png);
68 | }
69 |
70 | .directory-list li.last-child.hassub.expand {
71 | background-image:
72 | url(../directorylist/directory_last-child_hassub_expand.png);
73 | }
74 |
75 | .directory-list li:last-child.hassub.expand {
76 | background-image:
77 | url(../directorylist/directory_last-child_hassub_expand.png);
78 | }
79 |
80 | * html .directory-list ul {
81 | background-image: none !important;
82 | }
83 |
84 | * html .directory-list li {
85 | background-image: none !important;
86 | }
87 |
88 | * html .directory-list li.hassub {
89 | background-image: url(../directorylist/directory_hassub.png) !important;
90 | background-position: 3px 0;
91 | }
92 |
93 | * html .directory-list li.expand {
94 | background-image: url(../directorylist/directory_exand.png) !important;
95 | background-position: 3px 0;
96 | }
97 |
98 | .directory-list div,.directory-list span,.directory-list a {
99 | display: inline-block;
100 | height: 1.5em;
101 | padding: 0 4px 0 39px;
102 | margin: 0;
103 | background: url(../directorylist/folder.png) no-repeat 16px 0;
104 | text-decoration: none;
105 | font-weight: normal;
106 | white-space: nowrap;
107 | }
108 |
109 | .directory-list div {
110 | color: #000000;
111 | padding-left: 23px;
112 | background-position: 3px 0;
113 | background-image: url(../directorylist/drive.png);
114 | }
115 |
116 | .directory-list li>span,.directory-list>div {
117 | cursor: pointer;
118 | }
119 |
120 | .directory-list .selectable.selectedFolder {
121 | background-color: #EBEBEB !important;
122 | border-left: 3px solid #6FDBE8 !important;
123 | }
124 |
125 | .directory-list a {
126 | background-image: url(../directorylist/page_white.png);
127 | }
128 |
129 | .directory-list a {
130 | background-image: url(../directorylist/page_white.png);
131 | }
132 |
133 | .directory-list a[type^='image/'] {
134 | background-image: url(../directorylist/page_white_picture.png);
135 | }
136 |
137 | .directory-list a[type^='text/'] {
138 | background-image: url(../directorylist/page_white_text.png);
139 | }
140 |
141 | .directory-list a[type='text/html'] {
142 | background-image: url(../directorylist/page_white_code.png);
143 | }
144 |
145 | .directory-list a[type='application/zip'],.directory-list a[type='application/x-gzip']
146 | {
147 | background-image: url(../directorylist/page_white_compressed.png);
148 | }
149 |
150 | .directory-list a[type='application/x-httpd-php'] {
151 | background-image: url(../directorylist/page_white_php.png);
152 | }
--------------------------------------------------------------------------------
/widgets/views/fileList.php:
--------------------------------------------------------------------------------
1 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 | = Html::checkbox('allchk', false, ['class' => 'allselect']); ?>
29 |
30 |
31 |
32 | >
33 | = Yii::t('CfilesModule.base', 'Name'); ?>
34 |
35 |
36 |
37 |
38 | >= Yii::t('CfilesModule.base', 'Size'); ?>
39 | >= Yii::t('CfilesModule.base', 'Updated'); ?>
40 | getModule('cfiles')->settings->get('displayDownloadCount')): ?>
41 | >= Yii::t('CfilesModule.base', 'Downloads'); ?>
42 |
43 |
44 | isAllPostedFiles()): // Files currently have no content object but the Post they may be connected to. ?>
45 | = Yii::t('CfilesModule.base', 'Likes/Comments'); ?>
46 |
47 |
48 | = Yii::t('CfilesModule.base', 'Creator'); ?>
49 |
50 |
51 |
52 |
53 |
54 | = FileSystemItem::widget([
55 | 'folder' => $folder,
56 | 'row' => $row,
57 | 'itemsSelectable' => $itemsSelectable
58 | ]) ?>
59 |
60 |
61 |
62 |
63 |
64 | = LinkPager::widget(['pagination' => $pagination]); ?>
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 | = Yii::t('CfilesModule.base', 'This folder is empty.'); ?>
75 |
76 | isAllPostedFiles()): ?>
77 | = Yii::t('CfilesModule.base', 'Upload files to the stream to fill this folder.'); ?>
78 |
79 | = Yii::t('CfilesModule.base', 'Upload files or create a subfolder with the buttons on the top.'); ?>
80 |
81 | = Yii::t('CfilesModule.base', 'Unfortunately you have no permission to upload/edit files.'); ?>
82 |
83 |
84 |
85 |
86 |
87 |
--------------------------------------------------------------------------------
/controllers/rest/FolderController.php:
--------------------------------------------------------------------------------
1 | $containerId]);
43 | if ($containerRecord === null) {
44 | return $this->returnError(404, 'Content container not found!');
45 | }
46 | /** @var ContentContainerActiveRecord $container */
47 | $container = $containerRecord->getPolymorphicRelation();
48 | $params = Yii::$app->request->getBodyParams();
49 | $isPublicDirectory = isset($params['Folder']['visibility']) && $params['Folder']['visibility'] === Content::VISIBILITY_PUBLIC;
50 |
51 | if (empty($params['target_id'])) {
52 | if (!($targetDir = Folder::getRoot($container))
53 | && !($targetDir = Folder::initRoot($container))) {
54 | return $this->returnError(400, 'Target folder id is required!');
55 | }
56 | } else {
57 | $targetDir = Folder::findOne(['id' => $params['target_id']]);
58 | }
59 |
60 | if (empty($targetDir)) {
61 | return $this->returnError(404, 'cFiles folder not found!');
62 | }
63 | if (!$container->can(ManageFiles::class)) {
64 | return $this->returnError(403, 'You cannot create folder in this container!');
65 | }
66 |
67 | if ((! $targetDir->isRoot() && $targetDir->content->isPrivate()) && $isPublicDirectory) {
68 | return $this->returnError(403, 'Could not create public directory inside private directory!');
69 | }
70 |
71 | $folder = $targetDir->newFolder();
72 | $folder->content->container = $container;
73 |
74 | if ($folder->load($params) && $folder->save()) {
75 | return RestDefinitions::getFolder(Folder::findOne(['id' => $folder->id]));
76 | }
77 |
78 | if ($folder->hasErrors()) {
79 | return $this->returnError(422, 'Validation failed', [
80 | 'errors' => $folder->getErrors(),
81 | ]);
82 | } else {
83 | Yii::error('Could not create valid folder.', 'api');
84 | return $this->returnError(500, 'Internal error while create folder!');
85 | }
86 | }
87 |
88 | public function actionUpdate($id)
89 | {
90 | $folder = Folder::findOne(['id' => $id]);
91 |
92 | if ($folder === null) {
93 | return $this->returnError(404, 'cFiles folder not found!');
94 | }
95 | if (!$folder->content->canEdit()) {
96 | return $this->returnError(403, 'You cannot edit this folder!');
97 | }
98 |
99 | if ($folder->load(Yii::$app->request->getBodyParams()) && $folder->save()) {
100 | return RestDefinitions::getFolder(Folder::findOne(['id' => $folder->id]));
101 | }
102 |
103 | if ($folder->hasErrors()) {
104 | return $this->returnError(422, 'Validation failed', [
105 | 'errors' => $folder->getErrors(),
106 | ]);
107 | } else {
108 | Yii::error('Could not update valid folder.', 'api');
109 | return $this->returnError(500, 'Internal error while update cFiles folder!');
110 | }
111 | }
112 | }
113 |
--------------------------------------------------------------------------------
/controllers/VersionController.php:
--------------------------------------------------------------------------------
1 | getFile();
32 |
33 | if (!$file->content->canEdit()) {
34 | throw new HttpException(403);
35 | }
36 |
37 | $model = new VersionForm(['file' => $file]);
38 |
39 | if (!$model->load()) {
40 | return $this->renderAjax('index', [
41 | 'model' => $model,
42 | ]);
43 | }
44 |
45 | if ($model->save()) {
46 | $this->view->success(Yii::t('CfilesModule.base', 'File {fileName} has been reverted to version from {fileDateTime}', [
47 | 'fileName' => $model->file->baseFile->file_name,
48 | 'fileDateTime' => Yii::$app->formatter->asDatetime($model->getFileVersion()->created_at, 'short'),
49 | ]));
50 | } else {
51 | $errorMsg = '';
52 | foreach ($model->getErrors() as $errors) {
53 | foreach ($errors as $error) {
54 | $errorMsg .= $error . ' ';
55 | }
56 | }
57 | $this->view->error($errorMsg);
58 | }
59 |
60 | return $this->htmlRedirect($model->file->content->container->createUrl('/cfiles/browse', ['fid' => $model->file->parent_folder_id]));
61 | }
62 |
63 | /**
64 | * Load file versions for the single requested page
65 | */
66 | public function actionPage()
67 | {
68 | $file = $this->getFile();
69 |
70 | if (!$file->content->canEdit()) {
71 | throw new HttpException(403);
72 | }
73 |
74 | $versionsView = new VersionsView([
75 | 'file' => $file,
76 | 'page' => (int)Yii::$app->request->get('page', 2),
77 | ]);
78 |
79 | return $this->asJson([
80 | 'html' => $versionsView->renderVersions(),
81 | 'isLast' => $versionsView->isLastPage(),
82 | ]);
83 | }
84 |
85 | /**
86 | * Action to delete a version of the requested File
87 | * @throws HttpException
88 | */
89 | public function actionDelete()
90 | {
91 | $this->forcePostRequest();
92 |
93 | $file = $this->getFile();
94 |
95 | if (!$file->canEdit()) {
96 | throw new HttpException(403);
97 | }
98 |
99 | $fileVersionId = (int)Yii::$app->request->get('version');
100 | $deletedFileVersion = $file->baseFile->getFileHistoryById($fileVersionId);
101 |
102 | if (!$deletedFileVersion) {
103 | throw new HttpException(404, 'Version not found!');
104 | }
105 |
106 | $deletedVersionDate = Yii::$app->formatter->asDatetime($deletedFileVersion->created_at, 'short');
107 |
108 | if (!$deletedFileVersion->delete()) {
109 | return $this->asJson([
110 | 'error' => Yii::t('CfilesModule.base', 'The version "{versionDate}" could not be deleted!', ['versionDate' => $deletedVersionDate]),
111 | ]);
112 | }
113 |
114 | return $this->asJson([
115 | 'deleted' => $fileVersionId,
116 | 'message' => Yii::t('CfilesModule.base', 'The version "{versionDate}" has been deleted.', ['versionDate' => $deletedVersionDate]),
117 | ]);
118 | }
119 |
120 | private function getFile(): File
121 | {
122 | /* @var File $file */
123 | $file = File::find()
124 | ->readable()
125 | ->andWhere(['cfiles_file.id' => Yii::$app->request->get('id')])
126 | ->one();
127 |
128 | if (!$file) {
129 | throw new HttpException(404, 'File not found!');
130 | }
131 |
132 | return $file;
133 | }
134 |
135 | }
136 |
--------------------------------------------------------------------------------
/libs/ZipExtractor.php:
--------------------------------------------------------------------------------
1 | cleanup();
32 | if (strtolower($file->extension) === 'zip') {
33 | $sourcePath = $this->getZipOutputPath() . '/zipped.zip';
34 | $extractionPath = $this->getZipOutputPath() . '/extracted';
35 |
36 | if ($file->saveAs($sourcePath, false)) {
37 | $this->unpackArchive($sourcePath, $extractionPath);
38 | $this->generateModelsFromFilesystem($targetFolder, $extractionPath);
39 | } else {
40 | $targetFolder->addError('upload', Yii::t('CfilesModule.base', 'Archive %filename% could not be extracted.', ['%filename%' => $file->name]));
41 | }
42 | } else {
43 | $targetFolder->addError('upload', Yii::t('CfilesModule.base', '%filename% has invalid extension and was skipped.', ['%filename%' => $file->name]));
44 | }
45 |
46 | return $targetFolder;
47 | }
48 |
49 | /**
50 | * Unzips an archive from sourcePath to extractionPath.
51 | *
52 | * @param string $sourcePath
53 | * @param string $extractionPath
54 | */
55 | protected function unpackArchive($sourcePath, $extractionPath)
56 | {
57 | $zip = new \ZipArchive();
58 | $zip->open($sourcePath);
59 | $zip->extractTo($extractionPath);
60 | $zip->close();
61 | }
62 |
63 | /**
64 | * Generate the cfolder and cfile structure in the database of a given folder and all its subfolders recursively.
65 | *
66 | * @param Folder $targetFolder the folders parent folder.
67 | * @param string $folderPath the path of the folder.
68 | * @param Folder|null $root the $root folder which is used for adding errors if not given $targetFolder will be used as $root
69 | */
70 | protected function generateModelsFromFilesystem(Folder $targetFolder, $folderPath, ?Folder $root = null)
71 | {
72 |
73 | // remove unwanted parent folder references from the scanned files
74 | $files = array_diff(scandir($folderPath), ['..','.']);
75 |
76 | if (!$root) {
77 | $root = $targetFolder;
78 | }
79 |
80 | foreach ($files as $file) {
81 | $filePath = $folderPath . DIRECTORY_SEPARATOR . $file;
82 | if (is_dir($filePath)) {
83 | $folder = $targetFolder->findFolderByName($file);
84 |
85 | if (!$folder) {
86 | $folder = $targetFolder->newFolder($file);
87 | if (!$folder->save()) {
88 | $root->addError('upload', Yii::t('CfilesModule.base', 'An error occurred while creating folder {folder}.', ['folder' => $file]));
89 | continue;
90 | }
91 | }
92 |
93 | $this->generateModelsFromFilesystem($folder, $filePath, $root);
94 | } else {
95 | $result = $this->generateModelFromFile($targetFolder, $folderPath, $file);
96 | if ($result->hasErrors()) {
97 | $root->addError('upload', Yii::t('CfilesModule.base', 'An error occurred while unpacking {filename}.', ['filename' => $file]));
98 | }
99 | }
100 | }
101 | }
102 |
103 | /**
104 | * Create a cfile model and create and connect it with its basefile File model from a given data file, connect it with its parent folder.
105 | * TODO: This method has a lot in common with BrowseController/actionUpload, common logic needs to be extracted and reused
106 | *
107 | * @param Folder $targetFolder the files pid.
108 | * @param string $folderPath the path of the folder the file data lies in.
109 | * @param string $filename the files name.
110 | */
111 | protected function generateModelFromFile(Folder $targetFolder, $folderPath, $filename)
112 | {
113 | $filePath = $folderPath . DIRECTORY_SEPARATOR . $filename;
114 | return $targetFolder->addFileFromPath($filename, $filePath);
115 | }
116 | }
117 |
--------------------------------------------------------------------------------
/resources/css/cfiles.css:
--------------------------------------------------------------------------------
1 | #cfiles-folderView a:not(:hover) {
2 | color: var(--hh-text-color-highlight);
3 | }
4 |
5 | .overflow-ellipsis {
6 | position: relative;
7 | overflow: hidden;
8 | max-width: 100%;
9 | text-overflow: ellipsis;
10 | white-space: nowrap;
11 | }
12 | .cfiles-content .breadcrumb {
13 | border-radius: 0;
14 | margin: 0;
15 | }
16 |
17 | .cfiles-content .file-controls {
18 | font-size: 11px;
19 | color: var(--hh-text-color-soft2);
20 | margin-top: 10px;
21 | margin-bottom: 0;
22 | }
23 | .cfiles-content .file-controls a {
24 | font-size: 11px;
25 | color: var(--hh-text-color-soft2);
26 | margin-top: 10px;
27 | margin-bottom: 0;
28 | }
29 | .fileinput-button input {
30 | position: absolute;
31 | top: 0;
32 | right: 0;
33 | margin: 0;
34 | opacity: 0;
35 | -ms-filter: 'alpha(opacity=0)';
36 | font-size: 200px;
37 | direction: ltr;
38 | cursor: pointer;
39 | }
40 |
41 | .panel .panel-body .files-action-menu .btn {
42 | font-size: 1em !important;
43 | font-weight: normal !important;
44 | }
45 | .panel .panel-body .files-action-menu .btn-group .split-toggle {
46 | width: auto !important;
47 | float: right !important;
48 | border-radius: 0px 3px 3px 0px;
49 | }
50 | .panel .panel-body .files-action-menu .btn-group .split-toggle:not(:first-child):not(:last-child):not(.dropdown-toggle) {
51 | border-radius: 0px 3px 3px 0px;
52 | }
53 | .panel .panel-body .files-action-menu .btn-group > .split-button {
54 | width: auto !important;
55 | overflow: hidden;
56 | display: block;
57 | float: none !important;
58 | border-radius: 3px 0px 0px 3px;
59 | }
60 |
61 | .filedelete-button {
62 | cursor: pointer;
63 | }
64 | .filemove-button {
65 | cursor: pointer;
66 | }
67 | .downloadzipped-button {
68 | cursor: pointer;
69 | }
70 | .table-responsive td .title,
71 | .table-responsive td .timestamp,
72 | .table-responsive td .creator,
73 | .table-responsive td .size {
74 | overflow: hidden;
75 | -ms-text-overflow: ellipsis;
76 | -o-text-overflow: ellipsis;
77 | text-overflow: ellipsis;
78 | display: block;
79 | white-space: nowrap;
80 | }
81 | .table-responsive > table > tbody > tr > td {
82 | vertical-align: middle;
83 | }
84 | .table-responsive .table .file-controls {
85 | font-size: 11px;
86 | margin-top: 0;
87 | margin-bottom: 0;
88 | }
89 | .table-responsive .table .file-controls a {
90 | font-size: 11px;
91 | margin-top: 0;
92 | margin-bottom: 0;
93 | }
94 | .table-responsive .table .file-controls hr {
95 | margin-top: 2px;
96 | margin-bottom: 2px;
97 | }
98 |
99 | #fileList .table-responsive {
100 | overflow: initial;
101 | border: none;
102 | }
103 |
104 | #bs-table.table .file-actions {
105 | padding-left: 0;
106 | padding-right: 2px;
107 | width: 0;
108 | }
109 |
110 | @media (min-width: 418px) {
111 | #bs-table.table tr .context-icon {
112 | display: none;
113 | }
114 | #bs-table.table tr:hover .context-icon {
115 | display: inline-block;
116 | }
117 | }
118 |
119 | #bs-table.table>thead>tr>th,
120 | #bs-table.table>tbody>tr>th,
121 | #bs-table.table>tfoot>tr>th,
122 | #bs-table.table>thead>tr>td,
123 | #bs-table.table>tbody>tr>td,
124 | #bs-table.table>tfoot>tr>td {
125 | padding: 10px 5px 5px 5px;
126 | height: 45px;
127 | }
128 |
129 | #bs-table.table>thead>tr>th label,
130 | #bs-table.table>tbody>tr>th label,
131 | #bs-table.table>tfoot>tr>th label,
132 | #bs-table.table>thead>tr>td label,
133 | #bs-table.table>tbody>tr>td label,
134 | #bs-table.table>tfoot>tr>td label {
135 | margin-bottom: 0px;
136 | }
137 |
138 | #bs-table.table ul.contextMenu li a {
139 | font-size: 14px;
140 | font-weight: normal;
141 | }
142 |
143 | #bs-table.table tr.ui-draggable-dragging {
144 | z-index: 10000;
145 | }
146 |
147 | #fileList [data-ui-sort] {
148 | font-weight:bold;
149 | cursor: pointer;
150 | }
151 |
152 | #fileList [data-ui-order="ASC"]:after {
153 | content: "\25bc";
154 | }
155 |
156 | #fileList [data-ui-order="DESC"]:after {
157 | content: "\25b2";
158 | }
159 |
160 |
161 | .table-responsive td .title {
162 | position: absolute;
163 | width: 100%;
164 | top: -10px;
165 | }
166 | @media (min-width: 420px) {
167 | .table-responsive td:nth-child(2) {
168 | width: 25%;
169 | max-width: 25%;
170 | }
171 | }
172 | @media (min-width: 768px) {
173 | .table-responsive td:nth-child(2) {
174 | width: 30%;
175 | max-width: 30%;
176 | }
177 | }
178 | @media (min-width: 992px) {
179 | .table-responsive td:nth-child(2) {
180 | width: 35%;
181 | max-width: 35%;
182 | }
183 | }
184 | @media (min-width: 1200px) {
185 | .table-responsive td:nth-child(2) {
186 | width: 40%;
187 | max-width: 40%;
188 | }
189 | }
190 |
--------------------------------------------------------------------------------
/tests/codeception/_support/AcceptanceTester.php:
--------------------------------------------------------------------------------
1 | waitForText($text, 10, '#cfiles-crumb');
37 | }
38 |
39 | public function dontSeeInCrumb($text)
40 | {
41 | $this->dontSee($text, '#cfiles-crumb');
42 | }
43 |
44 | public function seeInFileList($text)
45 | {
46 | $this->waitForElement('#fileList');
47 | $this->waitForText($text);
48 | }
49 |
50 | public function dontSeeInFileList($text)
51 | {
52 | $this->dontSee($text, '#fileList');
53 | }
54 |
55 | public function openFolder($text)
56 | {
57 | $this->click($text, '#fileList');
58 | $this->seeInCrumb($text);
59 | }
60 |
61 | public function amInRoot()
62 | {
63 | $this->click('.fa-home', '#cfiles-crumb');
64 | $this->waitForText('Files from the stream', 10, '#fileList');
65 | }
66 |
67 | public function uploadFile($file = "test.txt")
68 | {
69 | $this->attachFile('#cfilesUploadFiles', $file);
70 | $this->wait(2);
71 | $this->waitForText($file, 10, '#fileList');
72 | }
73 |
74 | public function rightClickFolder($id)
75 | {
76 | $this->clickWithRightButton('[data-cfiles-item="folder_' . $id . '"]');
77 | }
78 |
79 | public function rightClickFile($id)
80 | {
81 | $this->clickWithRightButton('[data-cfiles-item="file_' . $id . '"]');
82 | }
83 |
84 | public function clickFileContext($id, $menuItem)
85 | {
86 | $this->rightClickFile($id);
87 | $this->waitForText($menuItem, 10, '[data-cfiles-item="file_' . $id . '"] .contextMenu');
88 | $this->click($menuItem, '[data-cfiles-item="file_' . $id . '"] .contextMenu');
89 | }
90 |
91 | public function clickFolderContext($id, $menuItem)
92 | {
93 | $this->rightClickFolder($id);
94 | $this->waitForText($menuItem, 10, '[data-cfiles-item="folder_' . $id . '"] .contextMenu');
95 | $this->click($menuItem, '[data-cfiles-item="folder_' . $id . '"] .contextMenu');
96 | }
97 |
98 | public function importZip($file = "test.zip")
99 | {
100 | $this->attachFile('#cfilesUploadZipFile', $file);
101 | $this->waitForText($file, 10, '#fileList');
102 | }
103 |
104 | public function enableCfilesOnSpace($guid = 1)
105 | {
106 | $this->enableModule($guid, 'cfiles');
107 |
108 | $this->amOnSpace($guid);
109 | $this->expectTo('see files in the space nav');
110 | $this->waitForText('Files', 30, '.layout-nav-container');
111 |
112 | $this->amOnFilesBrowser();
113 | }
114 |
115 | public function amOnFilesBrowser()
116 | {
117 | $this->amGoingTo('open files module');
118 | $this->click('Files', '.layout-nav-container');
119 | $this->waitForText('Files from the stream', 10, '#fileList');
120 | }
121 |
122 | /**
123 | * Define custom actions here
124 | */
125 | public function createFolder($title = 'test', $description = 'test description', $isPublic = false)
126 | {
127 | $this->click('Add directory', '.files-action-menu');
128 |
129 | $this->waitForText('Create folder', 10, '#globalModal');
130 | $this->fillField('Folder[title]', $title);
131 | $this->fillField('Folder[description]', $description);
132 |
133 | if ($isPublic) {
134 | $this->jsClick('input#folder-visibility');
135 | }
136 |
137 | $this->click('Save', '#globalModal');
138 | $this->waitForText('This folder is empty.', 10, '.folderEmptyMessage');
139 | }
140 |
141 | public function seeFileSizeOnSpaceStream(BaseFile $file, $guid = 1)
142 | {
143 | $this->amOnSpace($guid);
144 | $this->waitForText($file->file_name);
145 | $this->see('Size: ' . Yii::$app->formatter->asShortSize($file->size, 1));
146 | }
147 | }
148 |
--------------------------------------------------------------------------------
/libs/ZIPCreator.php:
--------------------------------------------------------------------------------
1 | cleanup();
44 |
45 | $this->archive = new ZipArchive();
46 | $this->tempFile = $this->getTempPath() . DIRECTORY_SEPARATOR . time();
47 |
48 | $code = $this->archive->open($this->tempFile, ZIPARCHIVE::CREATE | ZIPARCHIVE::OVERWRITE);
49 | if ($code !== true) {
50 | throw new Exception(Yii::t('CfilesModule.base', 'Opening archive failed with error code %code%.', ['%code%' => $code]));
51 | }
52 | }
53 |
54 | /**
55 | * Closes and writes the ZIP File
56 | */
57 | public function close()
58 | {
59 | Yii::info('Written ZIP: ' . $this->tempFile . '(Number of files: ' . $this->archive->numFiles . ' - Status: ' . $this->archive->status . ')', 'cfiles');
60 | $this->archive->close();
61 | }
62 |
63 | /**
64 | * Returns the temporary generated ZIP File name
65 | *
66 | * @return string the ZIP filename
67 | */
68 | public function getZipFile()
69 | {
70 | return $this->tempFile;
71 | }
72 |
73 | /**
74 | * Adds file or folder to the ZIP
75 | *
76 | * @param FileSystemItem $item the item
77 | * @param string $path path in the ZIP archive
78 | * @throws Exception
79 | */
80 | public function add(FileSystemItem $item, $path = '')
81 | {
82 | if ($item instanceof CFile) {
83 | $this->addFile($item, $path);
84 | } elseif ($item instanceof Folder) {
85 | $this->addFolder($item, $path);
86 | } else {
87 | throw new Exception("Invalid file system item given to add to ZIP!");
88 | }
89 | }
90 |
91 | /**
92 | * Add a file to zip file.
93 | *
94 | * @param File $file the parent folder id which content should be added to the zip file
95 | * @param string $path where we currently are in the zip file
96 | * @param string $fileName alternative fileName otherwise use db filename
97 | */
98 | public function addFile($file, $path = '', $fileName = null)
99 | {
100 | if ($file instanceof CFile) {
101 | $file = $file->baseFile;
102 | }
103 |
104 | if (!$file || !$file->canView()) {
105 | return;
106 | }
107 |
108 | if ($fileName === null) {
109 | $filePath = $path . DIRECTORY_SEPARATOR . $file->file_name;
110 | } else {
111 | $filePath = $path . DIRECTORY_SEPARATOR . $fileName;
112 | }
113 |
114 | $filePath = $this->fixPath($filePath);
115 |
116 | $realFilePath = $file->store->get();
117 | if (is_file($realFilePath)) {
118 | $this->archive->addFile($realFilePath, $filePath);
119 | }
120 | }
121 |
122 | /**
123 | * Add files and sub-directories in a folder to zip file.
124 | *
125 | * @param int $folderId the parent folder id which content should be added to the zip file
126 | * @param string $path where we currently are in the zip file
127 | */
128 | public function addFolder(Folder $folder, $path = '')
129 | {
130 | if (!$folder->content->canView()) {
131 | return;
132 | }
133 |
134 | $path = $this->fixPath($path . DIRECTORY_SEPARATOR . $folder->title);
135 |
136 | $this->archive->addEmptyDir($path);
137 |
138 | $subFiles = CFile::find()->readable()->where(['parent_folder_id' => $folder->id])->all();
139 | $subFolders = Folder::find()->readable()->where(['parent_folder_id' => $folder->id])->all();
140 |
141 | foreach ($subFiles as $file) {
142 | $this->addFile($file, $path);
143 | }
144 |
145 | foreach ($subFolders as $folder) {
146 | // checkout subfolders recursively with adapted local path
147 | $this->addFolder($folder, $path);
148 | }
149 | }
150 | }
151 |
--------------------------------------------------------------------------------