Browse code

Extensive changes due to code review

Benjamin Roth authored on21/10/2025 16:56:28
Showing12 changed files
... ...
@@ -1,54 +1,73 @@
1
-# Contao 4 skeleton bundle
1
+# Contao Memberfiles Bundle
2 2
 
3
-Contao is an Open Source PHP Content Management System for people who want a
4
-professional website that is easy to maintain. Visit the [project website][1]
5
-for more information.
3
+A Contao bundle for managing secure member-specific file downloads with automatic file assignment and email notifications.
6 4
 
7
-You can use the skeleton bundle as basis for your own Contao bundle.
5
+## Features
8 6
 
9
-## Install
7
+- **Automatic File Import**: Files are automatically imported from source folders and assigned to members based on filename patterns
8
+- **Secure Downloads**: Files are stored in member-specific directories and accessible only to the respective member
9
+- **Email Notifications**: Members receive email notifications when new files are added
10
+- **Flexible Configuration**: Multiple configurations with different source folders and filename patterns
11
+- **Backend Integration**: Full Contao backend integration for managing member files
12
+- **Frontend Module**: Content element for displaying member files in the frontend
10 13
 
11
-Download the skeleton bundle:
14
+## Requirements
15
+
16
+- PHP 7.4 or 8.0+
17
+- Contao 4.13+
18
+- Symfony 5.4+
19
+- Notification Center 1.7+
20
+
21
+## Installation
22
+
23
+Install the bundle via Composer:
12 24
 
13 25
 ```bash
14
-wget https://github.com/contao/skeleton-bundle/archive/main.zip
15
-unzip main.zip
16
-mv skeleton-bundle-main [package name]
17
-cd [package name]
26
+composer require vonrotenberg/contao-memberfiles-bundle
18 27
 ```
19 28
 
20
-## Customize
29
+## Configuration
30
+
31
+1. Create a configuration in the backend (Memberfiles Configuration)
32
+2. Define:
33
+   - Source folder (where files are uploaded)
34
+   - Target folder (where files are stored)
35
+   - Regular expression to extract member identifier from filename
36
+   - Member fields to match against
37
+   - Optional: Email notification settings
38
+
39
+## File Naming Convention
21 40
 
22
-First adjust the following files:
41
+Files must follow a naming pattern that includes member identifiers. For example:
42
+- `invoice_12345_document.pdf` (where 12345 is the member ID)
43
+- `report_smith_john.pdf` (where smith/john match member fields)
23 44
 
24
- * `composer.json`
25
- * `ecs.php`
26
- * `LICENSE`
27
- * `phpunit.xml.dist`
28
- * `README.md`
45
+The regular expression in the configuration extracts these identifiers.
29 46
 
30
-Then rename the following files and/or the references to `SkeletonBundle` in
31
-the following files:
47
+## Cron Jobs
32 48
 
33
- * `src/ContaoManager/Plugin.php`
34
- * `src/DependencyInjection/ContaoSkeletonExtension.php`
35
- * `src/ContaoSkeletonBundle.php`
36
- * `tests/ContaoSkeletonBundleTest.php`
49
+The bundle includes two cron jobs:
50
+- **Import Job** (runs every minute): Imports new files from source folders
51
+- **Notification Job** (runs every 10 minutes): Sends email notifications for new files
37 52
 
38
-Finally, add your custom classes and resources. Make sure to register your services
39
-within `src/Resources/config/services.yml`. Also make sure to
40
-[adjust the Contao Manager Plugin][2] (and the dependencies within the `composer.json`)
41
-accordingly, if your bundle makes adjustments to other bundles (e.g. adjustments
42
-to a DCA of other bundles).
53
+## Testing
43 54
 
44
-## Release
55
+Run code quality checks before releasing:
45 56
 
46
-Run the PHP-CS-Fixer and the unit test before you release your bundle:
57
+```bash
58
+composer ecs
59
+composer phpstan
60
+composer unit-tests
61
+```
62
+
63
+Or run all checks at once:
47 64
 
48 65
 ```bash
49
-vendor/bin/ecs check src/ tests/ --fix
50
-vendor/bin/phpunit
66
+composer all
51 67
 ```
52 68
 
53
-[1]: https://contao.org
54
-[2]: https://docs.contao.org/dev/framework/manager-plugin/#the-bundleplugininterface
69
+## License
70
+
71
+This bundle is proprietary software. All rights reserved.
72
+
73
+(c) vonRotenberg
... ...
@@ -3,11 +3,11 @@
3 3
 declare(strict_types=1);
4 4
 
5 5
 /*
6
- * This file is part of eSales Media SinglereisenBundle
6
+ * This file is part of memberfiles bundle.
7 7
  *
8
- * (c) Benjamin Roth
8
+ * (c) vonRotenberg
9 9
  *
10
- * @license proprietary
10
+ * @license commercial
11 11
  */
12 12
 
13 13
 namespace vonRotenberg\MemberfilesBundle\Command;
... ...
@@ -18,8 +18,13 @@ use Symfony\Component\Console\Command\Command;
18 18
 use Symfony\Component\Console\Input\InputInterface;
19 19
 use Symfony\Component\Console\Output\OutputInterface;
20 20
 
21
+/**
22
+ * Console command to manually trigger file import for secure downloads
23
+ */
21 24
 class SecureDownloadsImportCommand extends Command
22 25
 {
26
+    private ContaoFramework $framework;
27
+
23 28
     public function __construct(ContaoFramework $framework)
24 29
     {
25 30
         $this->framework = $framework;
... ...
@@ -3,11 +3,11 @@
3 3
 declare(strict_types=1);
4 4
 
5 5
 /*
6
- * This file is part of eSales Media SinglereisenBundle
6
+ * This file is part of memberfiles bundle.
7 7
  *
8
- * (c) Benjamin Roth
8
+ * (c) vonRotenberg
9 9
  *
10
- * @license proprietary
10
+ * @license commercial
11 11
  */
12 12
 
13 13
 namespace vonRotenberg\MemberfilesBundle\Command;
... ...
@@ -18,8 +18,13 @@ use Symfony\Component\Console\Command\Command;
18 18
 use Symfony\Component\Console\Input\InputInterface;
19 19
 use Symfony\Component\Console\Output\OutputInterface;
20 20
 
21
+/**
22
+ * Console command to manually trigger sending notifications for new files
23
+ */
21 24
 class SecureDownloadsNotificationsCommand extends Command
22 25
 {
26
+    private ContaoFramework $framework;
27
+
23 28
     public function __construct(ContaoFramework $framework)
24 29
     {
25 30
         $this->framework = $framework;
... ...
@@ -3,11 +3,11 @@
3 3
 declare(strict_types=1);
4 4
 
5 5
 /*
6
- * This file is part of eSales Media SinglereisenBundle
6
+ * This file is part of memberfiles bundle.
7 7
  *
8
- * (c) Benjamin Roth
8
+ * (c) vonRotenberg
9 9
  *
10
- * @license proprietary
10
+ * @license commercial
11 11
  */
12 12
 
13 13
 namespace vonRotenberg\MemberfilesBundle\Controller\Frontend\ContentElement;
... ...
@@ -34,16 +34,16 @@ use Symfony\Contracts\Translation\TranslatorInterface;
34 34
 use vonRotenberg\MemberfilesBundle\Model\SecureDownloadsModel;
35 35
 
36 36
 /**
37
+ * Content element controller for displaying member-specific secure downloads
38
+ *
37 39
  * @ContentElement(SecureDownloadsController::TYPE, category="files")
38 40
  */
39 41
 class SecureDownloadsController extends AbstractContentElementController
40 42
 {
41 43
     public const TYPE = 'secure_downloads';
42 44
 
43
-    /** @var Connection */
44
-    protected $db;
45
-
46
-    protected $User;
45
+    protected Connection $db;
46
+    protected FrontendUser $User;
47 47
 
48 48
     public function __construct(Connection $db)
49 49
     {
... ...
@@ -68,18 +68,21 @@ class SecureDownloadsController extends AbstractContentElementController
68 68
             return new Response('',403);
69 69
         }
70 70
 
71
-        // Handle file requests
71
+        // Handle file download requests
72 72
         if (
73 73
             ($path = Input::get('file', true)) !== null
74 74
             && ($File = FilesModel::findByPath($path)) !== null
75
-            && ($SecFile = $SecFile = SecureDownloadsModel::findBy(["uuid = ?"],[$File->uuid])) !== null
75
+            && ($SecFile = SecureDownloadsModel::findBy(["uuid = ?"],[$File->uuid])) !== null
76 76
         )
77 77
         {
78
+            // Verify the file belongs to the current user
78 79
             if ($SecFile->pid == $this->User->id)
79 80
             {
81
+                // Track when the file was opened
80 82
                 $this->db->update('tl_member_secureDownloads', ['opened' => time()], ['id' => $SecFile->id]);
81 83
                 Controller::sendFileToBrowser($path);
82 84
             } else {
85
+                // Unauthorized access attempt
83 86
                 return new Response('',403);
84 87
             }
85 88
         }
... ...
@@ -102,10 +105,12 @@ class SecureDownloadsController extends AbstractContentElementController
102 105
             return new Response();
103 106
         }
104 107
 
108
+        $projectDir = System::getContainer()->getParameter('kernel.project_dir');
109
+
105 110
         while ($objFiles->next())
106 111
         {
107
-            // Continue if the files has been processed or does not exist
108
-            if (isset($files[$objFiles->path]) || !file_exists(TL_ROOT . '/' . $objFiles->path))
112
+            // Continue if the file has been processed or does not exist
113
+            if (isset($files[$objFiles->path]) || !file_exists($projectDir . '/' . $objFiles->path))
109 114
             {
110 115
                 continue;
111 116
             }
... ...
@@ -32,14 +32,13 @@ use Psr\Log\LoggerInterface;
32 32
 use Psr\Log\LogLevel;
33 33
 use vonRotenberg\MemberfilesBundle\Model\MemberfilesConfigModel;
34 34
 
35
+/**
36
+ * Cron job for importing member files and sending notifications
37
+ */
35 38
 class SecureDownloadsJob
36 39
 {
37
-
38
-    /** @var LoggerInterface */
39
-    private $logger;
40
-
41
-    /** @var Connection */
42
-    protected $db;
40
+    private LoggerInterface $logger;
41
+    private Connection $db;
43 42
 
44 43
     public function __construct(Connection $db, LoggerInterface $logger)
45 44
     {
... ...
@@ -48,33 +47,39 @@ class SecureDownloadsJob
48 47
     }
49 48
 
50 49
     /**
50
+     * Send email notifications about new files to members
51
+     * Runs every 10 minutes
52
+     *
51 53
      * @CronJob("*\/10 * * * *")
52 54
      */
53
-    public function sendFileNotifications(string $scope)
55
+    public function sendFileNotifications(string $scope): void
54 56
     {
55
-        // Get archives with notifications enabled
57
+        // Get configurations with notifications enabled
56 58
         $Configurations = MemberfilesConfigModel::findBy(["hasNotification = ?", "enabled = ?"], ['1','1']);
57 59
 
58 60
         if ($Configurations !== null)
59 61
         {
60 62
             while ($Configurations->next())
61 63
             {
62
-                // Do we have new items
64
+                // Check for members with unsent file notifications
63 65
                 $Members = $this->db->executeQuery("SELECT s.pid FROM tl_member_secureDownloads s INNER JOIN tl_files f ON f.uuid = s.uuid WHERE s.nc_sent != '1' GROUP BY s.pid");
64 66
 
65
-                // Load groups and notification models if we have news to share
67
+                // Send notifications if there are new files and notification is configured
66 68
                 if ($Members->rowCount() && ($Notification = Notification::findByPk($Configurations->notification)) !== null)
67 69
                 {
68 70
                     foreach ($Members->iterateAssociative() as $member)
69 71
                     {
72
+                        // Only process active members with valid email addresses
70 73
                         if (($Member = MemberModel::findOneBy(array("disable != '1'","login = '1'","email LIKE '%@%'","id = ?"),array($member['pid']))) !== null)
71 74
                         {
75
+                            // Get all unsent files for this member
72 76
                             $Files = $this->db->executeQuery("SELECT s.id, f.uuid, s.ctime FROM tl_member_secureDownloads s INNER JOIN tl_files f ON f.uuid = s.uuid WHERE s.pid = ? AND s.nc_sent != '1' ORDER BY s.ctime DESC, f.name",[$Member->id]);
73 77
                             $arrFileIds = [];
74 78
 
75 79
                             if ($Files->rowCount())
76 80
                             {
77 81
                                 $arrDownloads = array();
82
+                                // Build list of files for notification email
78 83
                                 foreach ($Files->iterateAssociative() as $file)
79 84
                                 {
80 85
                                     if (($File = FilesModel::findByUuid($file['uuid'])) !== null)
... ...
@@ -94,8 +99,14 @@ class SecureDownloadsJob
94 99
                                 ),$GLOBALS['TL_LANGUAGE']);
95 100
                             }
96 101
 
97
-                            // Flag news as sent
98
-                            $this->db->executeStatement("UPDATE tl_member_secureDownloads SET nc_sent = '1' WHERE id IN (" . implode(',', $arrFileIds) . ")");
102
+                            // Flag notifications as sent
103
+                            if (!empty($arrFileIds)) {
104
+                                $placeholders = implode(',', array_fill(0, count($arrFileIds), '?'));
105
+                                $this->db->executeStatement(
106
+                                    "UPDATE tl_member_secureDownloads SET nc_sent = '1' WHERE id IN ($placeholders)",
107
+                                    $arrFileIds
108
+                                );
109
+                            }
99 110
                         }
100 111
                     }
101 112
                     $this->logger->log(LogLevel::INFO, 'Secure downloads notifications has been sent (' . $Members->rowCount() . ')', array('contao' => new ContaoContext(__METHOD__, 'CRON')));
... ...
@@ -105,22 +116,25 @@ class SecureDownloadsJob
105 116
     }
106 117
 
107 118
     /**
119
+     * Import files from source folders and assign them to members
120
+     * Runs every minute
121
+     *
108 122
      * @CronJob("* * * * *")
109 123
      */
110
-    public function importFiles(string $scope)
124
+    public function importFiles(string $scope): void
111 125
     {
112 126
         $projectDir = System::getContainer()->getParameter('kernel.project_dir');
113 127
         $Configurations = MemberfilesConfigModel::findAll();
114 128
 
115 129
         $intFileCount = 0;
116 130
 
117
-        // No root pages available
131
+        // Exit if no configurations available
118 132
         if ($Configurations === null)
119 133
         {
120 134
             return;
121 135
         }
122 136
 
123
-        // Iterate page roots
137
+        // Process each configuration
124 138
         while ($Configurations->next())
125 139
         {
126 140
             if (System::getContainer()->getParameter('kernel.debug'))
... ...
@@ -138,14 +152,14 @@ class SecureDownloadsJob
138 152
             $Source = FilesModel::findByUuid($Configurations->source);
139 153
             $Target = FilesModel::findByUuid($Configurations->target);
140 154
 
141
-            // Continue if folder doesn't exist
155
+            // Continue if source or target folder doesn't exist
142 156
             if ($Source === null || !file_exists($projectDir . "/".$Source->path) || $Target === null || !file_exists($projectDir . "/".$Target->path))
143 157
             {
144
-                $this->logger->log(LogLevel::WARNING, sprintf('Source or target folder is missing for page root %s. %s %s', $Configurations->title,$Source->path,$Target->path), array('contao' => new ContaoContext(__METHOD__, 'ERROR')));
158
+                $this->logger->log(LogLevel::WARNING, sprintf('Source or target folder is missing for configuration %s. %s %s', $Configurations->title,$Source->path,$Target->path), array('contao' => new ContaoContext(__METHOD__, 'ERROR')));
145 159
                 continue;
146 160
             }
147 161
 
148
-            // Read files inside folder
162
+            // Read files from source folder
149 163
             $Files = FilesModel::findByPid($Source->uuid);
150 164
 
151 165
             // Continue if folder is empty
... ...
@@ -174,14 +188,14 @@ class SecureDownloadsJob
174 188
 
175 189
                 $objFile = new File($Files->path);
176 190
 
177
-                // Check for fragments
191
+                // Extract member identifier from filename using regex
178 192
                 if (!preg_match('/'.$Configurations->regexp.'/U',$objFile->filename,$fragments))
179 193
                 {
180 194
                     $this->logger->log(LogLevel::WARNING, sprintf('File "%s" is missing a member fragment or has the wrong syntax.', $objFile->name), array('contao' => new ContaoContext(__METHOD__, 'ERROR')));
181 195
                     continue;
182 196
                 }
183 197
 
184
-                // Find member
198
+                // Find member based on extracted fragments
185 199
                 $arrColumns = array();
186 200
                 $arrValues = array();
187 201
                 foreach (StringUtil::deserialize($Configurations->fields, true) as $i => $field)
... ...
@@ -197,11 +211,11 @@ class SecureDownloadsJob
197 211
                     continue;
198 212
                 }
199 213
 
200
-                // Remove fragments from file name
214
+                // Remove member identifier fragments from filename
201 215
                 $baseFilename = preg_replace('/'.$Configurations->regexp.'/U','',$objFile->filename);
202 216
                 $sanitizedFilename = $baseFilename.'.'.$objFile->extension;
203 217
 
204
-                // Check or create folder structure
218
+                // Create organized folder structure (groups of 100 members per folder)
205 219
                 $strSubFolder = sprintf("%03d",ceil(($objMember->id+1)/100)-1).'/'.$objMember->id;
206 220
                 $strTargetFolder = $Target->path.'/'.$strSubFolder;
207 221
 
... ...
@@ -233,17 +247,16 @@ class SecureDownloadsJob
233 247
 
234 248
                 $File = Dbafs::addResource($strTargetFolder.'/'.$sanitizedFilename,false);
235 249
 
236
-                // Add file to DB and delete source
250
+                // Register file in database for member
237 251
                 $arrData = array(
238 252
                     'pid'     => $objMember->id,
239 253
                     'uuid'    => ($File !== null ? $File->uuid : ''),
240 254
                     'tstamp'  => time(),
241 255
                     'ctime'   => time(),
242
-//          'name'    => $sanitizedFilename,
243
-//          'path'    => $strTargetFolder.'/'.$sanitizedFilename
244 256
                 );
245 257
                 $this->db->insert('tl_member_secureDownloads',$arrData);
246 258
 
259
+                // Delete source file after successful import
247 260
                 if (!$objFile->delete()) {
248 261
                     $this->logger->log(LogLevel::WARNING, sprintf('Could not delete source file "%s" for member ID "%s".', $objFile->name, $objMember->id), array('contao' => new ContaoContext(__METHOD__, 'ERROR')));
249 262
                 }
... ...
@@ -25,12 +25,12 @@ use Contao\Date;
25 25
 use Contao\Config;
26 26
 use vonRotenberg\MemberfilesBundle\Model\SecureDownloadsModel;
27 27
 
28
+/**
29
+ * Listener for member data container operations
30
+ */
28 31
 class MemberListener {
29 32
 
30
-    /**
31
-     * @var BackendUser
32
-     */
33
-    protected $User;
33
+    protected BackendUser $User;
34 34
 
35 35
     public function __construct()
36 36
     {
... ...
@@ -24,13 +24,14 @@ use Contao\System;
24 24
 use Contao\Date;
25 25
 use Contao\Config;
26 26
 use vonRotenberg\MemberfilesBundle\Model\SecureDownloadsModel;
27
+use Exception;
27 28
 
29
+/**
30
+ * Listener for secure downloads data container operations
31
+ */
28 32
 class MemberSecureDownloadsListener {
29 33
 
30
-    /**
31
-     * @var BackendUser
32
-     */
33
-    protected $User;
34
+    protected BackendUser $User;
34 35
 
35 36
     public function __construct()
36 37
     {
... ...
@@ -196,13 +197,13 @@ class MemberSecureDownloadsListener {
196 197
 
197 198
         if (!$File->exists())
198 199
         {
199
-            throw new \Exception(sprintf($GLOBALS['TL_LANG']['ERR']['invalidFile'],'1'.$varValue));
200
+            throw new Exception(sprintf($GLOBALS['TL_LANG']['ERR']['invalidFile'],'1'.$varValue));
200 201
         }
201 202
 
202 203
         $strDirectory = dirname($objFile->path);
203 204
         if (!$File->renameTo($strDirectory.'/'.$varValue))
204 205
         {
205
-            throw new \Exception(sprintf($GLOBALS['TL_LANG']['ERR']['invalidFile'],'2'.$varValue));
206
+            throw new Exception(sprintf($GLOBALS['TL_LANG']['ERR']['invalidFile'],'2'.$varValue));
206 207
         }
207 208
 
208 209
         if (($objFile = $File->getModel()) !== null) {
... ...
@@ -26,12 +26,12 @@ use Contao\Date;
26 26
 use Contao\Config;
27 27
 use vonRotenberg\MemberfilesBundle\Model\SecureDownloadsModel;
28 28
 
29
+/**
30
+ * Listener for memberfiles configuration data container operations
31
+ */
29 32
 class MemberfilesConfigListener {
30 33
 
31
-    /**
32
-     * @var BackendUser
33
-     */
34
-    protected $User;
34
+    protected BackendUser $User;
35 35
 
36 36
     public function __construct()
37 37
     {
... ...
@@ -18,12 +18,12 @@ use Contao\CoreBundle\ServiceAnnotation\Callback;
18 18
 use Contao\DataContainer;
19 19
 use Contao\System;
20 20
 
21
+/**
22
+ * Listener for page data container operations (legacy support)
23
+ */
21 24
 class PageListener {
22 25
 
23
-    /**
24
-     * @var BackendUser
25
-     */
26
-    protected $User;
26
+    protected BackendUser $User;
27 27
 
28 28
     public function __construct()
29 29
     {
... ...
@@ -9,6 +9,9 @@ use Contao\CoreBundle\Migration\MigrationResult;
9 9
 use Doctrine\DBAL\Connection;
10 10
 use Doctrine\DBAL\Schema\Schema;
11 11
 
12
+/**
13
+ * Migration to move secure downloads configuration from tl_page to tl_memberfiles_config
14
+ */
12 15
 class MemberfilesConfigMigration extends AbstractMigration
13 16
 {
14 17
     private Connection $connection;
... ...
@@ -22,18 +25,18 @@ class MemberfilesConfigMigration extends AbstractMigration
22 25
     {
23 26
         $schemaManager = $this->connection->createSchemaManager();
24 27
 
25
-        // Prüfen ob die Quelltabelle existiert
28
+        // Check if source table exists
26 29
         if (!$schemaManager->tablesExist(['tl_page'])) {
27 30
             return false;
28 31
         }
29 32
 
30
-        // Prüfen ob die notwendige Spalte in tl_page existiert
33
+        // Check if required column exists in tl_page
31 34
         $columns = $schemaManager->listTableColumns('tl_page');
32 35
         if (!isset($columns['securedownloadsenabled'])) {
33 36
             return false;
34 37
         }
35 38
 
36
-        // Prüfen ob es Einträge gibt, die migriert werden müssen
39
+        // Check if there are entries that need to be migrated
37 40
         $result = $this->connection->fetchOne(
38 41
             "SELECT COUNT(*) FROM tl_page WHERE secureDownloadsEnabled='1'"
39 42
         );
... ...
@@ -45,7 +48,7 @@ class MemberfilesConfigMigration extends AbstractMigration
45 48
     {
46 49
         $schemaManager = $this->connection->createSchemaManager();
47 50
 
48
-        // Erstelle die Zieltabelle, falls sie nicht existiert
51
+        // Create target table if it doesn't exist
49 52
         if (!$schemaManager->tablesExist(['tl_memberfiles_config'])) {
50 53
             $this->createMemberfilesConfigTable();
51 54
         }
... ...
@@ -76,16 +79,19 @@ class MemberfilesConfigMigration extends AbstractMigration
76 79
 
77 80
         return new MigrationResult(
78 81
             true,
79
-            sprintf('Erfolgreich %d Secure Downloads Konfigurationen von tl_page nach tl_memberfiles_config migriert.', count($pages))
82
+            sprintf('Successfully migrated %d secure downloads configurations from tl_page to tl_memberfiles_config.', count($pages))
80 83
         );
81 84
     }
82 85
 
86
+    /**
87
+     * Create the tl_memberfiles_config table
88
+     */
83 89
     private function createMemberfilesConfigTable(): void
84 90
     {
85 91
         $schema = new Schema();
86 92
         $table = $schema->createTable('tl_memberfiles_config');
87 93
 
88
-        // Pflichtfelder
94
+        // Define table columns
89 95
         $table->addColumn('id', 'integer', ['unsigned' => true, 'autoincrement' => true]);
90 96
         $table->addColumn('tstamp', 'integer', ['unsigned' => true, 'default' => 0]);
91 97
         $table->addColumn('title', 'string', ['length' => 255, 'default' => '']);
... ...
@@ -97,10 +103,10 @@ class MemberfilesConfigMigration extends AbstractMigration
97 103
         $table->addColumn('notification', 'integer', ['unsigned' => true, 'default' => 0]);
98 104
         $table->addColumn('enabled', 'string', ['length' => 1, 'fixed' => true, 'default' => '']);
99 105
 
100
-        // Primary Key
106
+        // Set primary key
101 107
         $table->setPrimaryKey(['id']);
102 108
 
103
-        // Schema anwenden
109
+        // Apply schema changes
104 110
         $queries = $schema->toSql($this->connection->getDatabasePlatform());
105 111
         foreach ($queries as $query) {
106 112
             $this->connection->executeStatement($query);
... ...
@@ -2,6 +2,14 @@
2 2
 
3 3
 declare(strict_types=1);
4 4
 
5
+/*
6
+ * This file is part of memberfiles bundle.
7
+ *
8
+ * (c) vonRotenberg
9
+ *
10
+ * @license commercial
11
+ */
12
+
5 13
 namespace vonRotenberg\MemberfilesBundle\Model;
6 14
 
7 15
 use Contao\Model;
... ...
@@ -1,11 +1,11 @@
1 1
 <?php
2 2
 
3 3
 /*
4
- * This file is part of Contao.
4
+ * This file is part of memberfiles bundle.
5 5
  *
6
- * (c) Leo Feyer
6
+ * (c) vonRotenberg
7 7
  *
8
- * @license LGPL-3.0-or-later
8
+ * @license commercial
9 9
  */
10 10
 
11 11
 namespace vonRotenberg\MemberfilesBundle\Model;
... ...
@@ -14,7 +14,7 @@ use Contao\Model;
14 14
 use Contao\Model\Collection;
15 15
 
16 16
 /**
17
- * Reads and writes image sizes
17
+ * Reads and writes secure downloads for members
18 18
  *
19 19
  * @property string|integer      $id
20 20
  * @property string|integer      $tstamp