Browse code

Update

Benjamin Roth authored on26/07/2023 17:00:15
Showing7 changed files
... ...
@@ -11,6 +11,11 @@
11 11
 use vonRotenberg\MemberfilesBundle\Model\SecureDownloadsModel;
12 12
 
13 13
 /**
14
+ * Backend modules
15
+ */
16
+$GLOBALS['BE_MOD']['accounts']['member']['tables'][] = 'tl_member_secureDownloads';
17
+
18
+/**
14 19
  * Notification types
15 20
  */
16 21
 $GLOBALS['NOTIFICATION_CENTER']['NOTIFICATION_TYPE']['contao']['secDownloads_submitted'] = array
17 22
deleted file mode 100644
... ...
@@ -1,48 +0,0 @@
1
-<?php
2
-
3
-declare(strict_types=1);
4
-
5
-/*
6
- * This file is part of eSales Media SinglereisenBundle
7
- *
8
- * (c) Benjamin Roth
9
- *
10
- * @license proprietary
11
- */
12
-
13
-namespace vonRotenberg\MemberfilesBundle\Command;
14
-
15
-use Contao\CoreBundle\Framework\ContaoFramework;
16
-use Contao\System;
17
-use Symfony\Component\Console\Command\Command;
18
-use Symfony\Component\Console\Input\InputInterface;
19
-use Symfony\Component\Console\Output\OutputInterface;
20
-
21
-class SecureDownloadsCommand extends Command
22
-{
23
-    public function __construct(ContaoFramework $framework)
24
-    {
25
-        $this->framework = $framework;
26
-
27
-        parent::__construct();
28
-    }
29
-
30
-    protected function configure(): void
31
-    {
32
-        $this
33
-            ->setName('secureDownloads:sendNotifications')
34
-            ->setDescription('Sends notifications about newly added files.')
35
-        ;
36
-    }
37
-
38
-    protected function execute(InputInterface $input, OutputInterface $output): int
39
-    {
40
-        $this->framework->initialize();
41
-
42
-        $secdl_service = System::getContainer()->get("vonrotenberg.cron.secure_downloads_service");
43
-
44
-        $secdl_service->sendFileNotifications('cli');
45
-
46
-        return 0;
47
-    }
48
-}
49 0
new file mode 100644
... ...
@@ -0,0 +1,48 @@
1
+<?php
2
+
3
+declare(strict_types=1);
4
+
5
+/*
6
+ * This file is part of eSales Media SinglereisenBundle
7
+ *
8
+ * (c) Benjamin Roth
9
+ *
10
+ * @license proprietary
11
+ */
12
+
13
+namespace vonRotenberg\MemberfilesBundle\Command;
14
+
15
+use Contao\CoreBundle\Framework\ContaoFramework;
16
+use Contao\System;
17
+use Symfony\Component\Console\Command\Command;
18
+use Symfony\Component\Console\Input\InputInterface;
19
+use Symfony\Component\Console\Output\OutputInterface;
20
+
21
+class SecureDownloadsImportCommand extends Command
22
+{
23
+    public function __construct(ContaoFramework $framework)
24
+    {
25
+        $this->framework = $framework;
26
+
27
+        parent::__construct();
28
+    }
29
+
30
+    protected function configure(): void
31
+    {
32
+        $this
33
+            ->setName('secureDownloads:import')
34
+            ->setDescription('Check source folder and import files if applicable.')
35
+        ;
36
+    }
37
+
38
+    protected function execute(InputInterface $input, OutputInterface $output): int
39
+    {
40
+        $this->framework->initialize();
41
+
42
+        $secdl_service = System::getContainer()->get("vonrotenberg.cron.secure_downloads_service");
43
+
44
+        $secdl_service->importFiles('cli');
45
+
46
+        return 0;
47
+    }
48
+}
0 49
new file mode 100644
... ...
@@ -0,0 +1,48 @@
1
+<?php
2
+
3
+declare(strict_types=1);
4
+
5
+/*
6
+ * This file is part of eSales Media SinglereisenBundle
7
+ *
8
+ * (c) Benjamin Roth
9
+ *
10
+ * @license proprietary
11
+ */
12
+
13
+namespace vonRotenberg\MemberfilesBundle\Command;
14
+
15
+use Contao\CoreBundle\Framework\ContaoFramework;
16
+use Contao\System;
17
+use Symfony\Component\Console\Command\Command;
18
+use Symfony\Component\Console\Input\InputInterface;
19
+use Symfony\Component\Console\Output\OutputInterface;
20
+
21
+class SecureDownloadsNotificationsCommand extends Command
22
+{
23
+    public function __construct(ContaoFramework $framework)
24
+    {
25
+        $this->framework = $framework;
26
+
27
+        parent::__construct();
28
+    }
29
+
30
+    protected function configure(): void
31
+    {
32
+        $this
33
+            ->setName('secureDownloads:sendNotifications')
34
+            ->setDescription('Sends notifications about newly added files.')
35
+        ;
36
+    }
37
+
38
+    protected function execute(InputInterface $input, OutputInterface $output): int
39
+    {
40
+        $this->framework->initialize();
41
+
42
+        $secdl_service = System::getContainer()->get("vonrotenberg.cron.secure_downloads_service");
43
+
44
+        $secdl_service->sendFileNotifications('cli');
45
+
46
+        return 0;
47
+    }
48
+}
... ...
@@ -14,11 +14,22 @@ namespace vonRotenberg\MemberfilesBundle\Cron;
14 14
 
15 15
 use Contao\BackendUser;
16 16
 use Contao\Controller;
17
+use Contao\CoreBundle\Monolog\ContaoContext;
17 18
 use Contao\CoreBundle\ServiceAnnotation\Callback;
19
+use Contao\CoreBundle\ServiceAnnotation\CronJob;
18 20
 use Contao\DataContainer;
21
+use Contao\Dbafs;
22
+use Contao\File;
23
+use Contao\Files;
24
+use Contao\FilesModel;
25
+use Contao\MemberModel;
26
+use Contao\PageModel;
27
+use Contao\StringUtil;
19 28
 use Contao\System;
20 29
 use Doctrine\DBAL\Connection;
30
+use NotificationCenter\Model\Notification;
21 31
 use Psr\Log\LoggerInterface;
32
+use Psr\Log\LogLevel;
22 33
 
23 34
 class SecureDownloadsJob
24 35
 {
... ...
@@ -36,10 +47,200 @@ class SecureDownloadsJob
36 47
     }
37 48
 
38 49
     /**
39
-     * @CronJob("* * * * *")
50
+     * @CronJob("*\/10 * * * *")
40 51
      */
41 52
     public function sendFileNotifications(string $scope)
42 53
     {
54
+        // Get archives with notifications enabled
55
+        $Root = PageModel::findBy(array("sd_nc_enable = ?","type = 'root'"),'1');
56
+
57
+        if ($Root !== null)
58
+        {
59
+            while ($Root->next())
60
+            {
61
+                // Do we have new items
62
+                $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 ORDER BY s.ctime DESC, f.name");
63
+
64
+                // Load groups and notification models if we have news to share
65
+                if ($Members->rowCount() && ($Notification = Notification::findByPk($Root->sd_nc_notification)) !== null)
66
+                {
67
+                    foreach ($Members as $member)
68
+                    {
69
+                        if (($Member = MemberModel::findOneBy(array("disable != '1'","login = '1'","email LIKE '%@%'","id = ?"),array($member['pid']))) !== null)
70
+                        {
71
+                            $Files = $this->db->executeQuery("SELECT s.id, f.name, 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]);
72
+
73
+                            if ($Files->rowCount())
74
+                            {
75
+                                $arrDownloads = array();
76
+                                foreach ($Files as $file)
77
+                                {
78
+                                    $arrDownloads[] = date('d.m.Y', $file['ctime']) . " - " . $file['name'];
79
+                                }
80
+
81
+                                $Notification->send(array
82
+                                (
83
+                                    'member_email' => $Member->email,
84
+                                    'member_firstname' => $Member->firstname,
85
+                                    'member_lastname' => $Member->lastname,
86
+                                    'downloads' => implode("\n",$arrDownloads),
87
+                                    'downloads_html' => "<ul>\n<li>".implode("</li>\n<li>",$arrDownloads)."</li>\n</ul>"
88
+                                ),$GLOBALS['TL_LANGUAGE']);
89
+                            }
90
+
91
+                            // Flag news as sent
92
+                            $arrFileIds = $Files->fetchFirstColumn();
93
+                            $this->db->executeStatement("UPDATE tl_member_secureDownloads SET nc_sent = '1' WHERE id IN (" . implode(',', $arrFileIds) . ")");
94
+                        }
95
+                    }
96
+                    $this->logger->log(LogLevel::INFO, 'Secure downloads notifications has been sent (' . $Members->rowCount() . ')', array('contao' => new ContaoContext(__METHOD__, 'CRON')));
97
+                }
98
+            }
99
+        }
100
+    }
101
+
102
+    /**
103
+     * @CronJob("* * * * *")
104
+     */
105
+    public function importFiles(string $scope)
106
+    {
107
+        $objRoots = PageModel::findPublishedRootPages();
108
+
109
+        $intFileCount = 0;
110
+
111
+        // No root pages available
112
+        if ($objRoots === null)
113
+        {
114
+            return;
115
+        }
116
+
117
+        // Iterate page roots
118
+        while ($objRoots->next())
119
+        {
120
+            if (System::getContainer()->getParameter('kernel.debug'))
121
+            {
122
+                $this->logger->log(LogLevel::DEBUG, sprintf('Starting secure downloads import for %s', $objRoots->title), array('contao' => new ContaoContext(__METHOD__, 'CRON')));
123
+            }
124
+
125
+            // Continue if secure downloads is disabled
126
+            if (!$objRoots->secureDownloadsEnabled)
127
+            {
128
+                continue;
129
+            }
130
+
131
+            // Get folder models
132
+            $objFolder = FilesModel::findByUuid($objRoots->secureDownloadsSRC);
133
+            $objTarget = FilesModel::findByUuid($objRoots->secureDownloadsTarget);
134
+
135
+            // Continue if folder doesn't exist
136
+            if ($objFolder === null || !file_exists(TL_ROOT . "/".$objFolder->path) || $objTarget === null || !file_exists(TL_ROOT . "/".$objTarget->path))
137
+            {
138
+                $this->logger->log(LogLevel::WARNING, sprintf('Source or target folder is missing for page root %s. %s %s', $objRoots->title,$objFolder->path,$objTarget->path), array('contao' => new ContaoContext(__METHOD__, 'ERROR')));
139
+                continue;
140
+            }
141
+
142
+            // Read files inside folder
143
+            $objFiles = FilesModel::findByPid($objFolder->uuid);
144
+
145
+            // Continue if folder is empty
146
+            if ($objFiles === null)
147
+            {
148
+                continue;
149
+            }
150
+
151
+            // Escape single quotes in fragment regexp
152
+            $objRoots->secureDownloadsRegExp = addcslashes($objRoots->secureDownloadsRegExp,'\'');
153
+
154
+            // Iterate files
155
+            while ($objFiles->next())
156
+            {
157
+                // Skip subfolders and special files
158
+                if ($objFiles->type == 'folder' || strpos($objFiles->name,'.') === 0)
159
+                {
160
+                    continue;
161
+                }
162
+
163
+                // Continue if file doesn't exist
164
+                if (!file_exists(TL_ROOT . "/".$objFiles->path))
165
+                {
166
+                    continue;
167
+                }
168
+
169
+                $objFile = new File($objFiles->path);
170
+
171
+                // Check for fragments
172
+                if (!preg_match('/'.$objRoots->secureDownloadsRegExp.'/U',$objFile->filename,$fragments))
173
+                {
174
+                    $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')));
175
+                    continue;
176
+                }
177
+
178
+                // Find member
179
+                $arrColumns = array();
180
+                $arrValues = array();
181
+                foreach (StringUtil::deserialize($objRoots->secureDownloadsFields, true) as $i => $field)
182
+                {
183
+                    $arrColumns[] = "$field = ?";
184
+                    $arrValues[] = $fragments[$i+1];
185
+                }
186
+                $objMember = MemberModel::findOneBy($arrColumns,$arrValues);
187
+
188
+                // Continue if no member found
189
+                if ($objMember === null) {
190
+                    $this->logger->log(LogLevel::WARNING, sprintf('Could not find member for file "%s". (%s) [%s]', $objFile->name, implode(' AND ',$arrColumns),implode(', ',$arrValues)), array('contao' => new ContaoContext(__METHOD__, 'ERROR')));
191
+                    continue;
192
+                }
193
+
194
+                // Remove fragments from file name
195
+                $sanitizedFilename = preg_replace('/'.$objRoots->secureDownloadsRegExp.'/U','',$objFile->filename).'.'.$objFile->extension;
196
+
197
+                // Check or create folder structure
198
+                $strSubFolder = sprintf("%03d",ceil(($objMember->id+1)/100)-1).'/'.$objMember->id;
199
+                $strTargetFolder = $objTarget->path.'/'.$strSubFolder;
200
+
201
+                $arrFolders = explode('/',$strTargetFolder);
202
+                $strStartPath = '';
203
+
204
+                foreach ($arrFolders as $folder)
205
+                {
206
+                    if (!is_dir(TL_ROOT . '/' . $strStartPath . $folder))
207
+                    {
208
+                        Files::getInstance()->mkdir($strStartPath . $folder);
209
+                    }
210
+                    $strStartPath .= $folder . '/';
211
+                }
212
+
213
+                // Try to copy file
214
+                if (!Files::getInstance()->copy($objFile->path,$strTargetFolder.'/'.$sanitizedFilename))
215
+                {
216
+                    $this->logger->log(LogLevel::WARNING, sprintf('Could not import file "%s" for member ID "%s".', $objFile->name, $objMember->id), array('contao' => new ContaoContext(__METHOD__, 'ERROR')));
217
+                    continue;
218
+                }
219
+
220
+                $File = Dbafs::addResource($strTargetFolder.'/'.$sanitizedFilename,false);
221
+
222
+                // Add file to DB and delete source
223
+                $arrData = array(
224
+                    'pid'     => $objMember->id,
225
+                    'uuid'    => ($File !== null ? $File->uuid : ''),
226
+                    'tstamp'  => time(),
227
+                    'ctime'   => time(),
228
+//          'name'    => $sanitizedFilename,
229
+//          'path'    => $strTargetFolder.'/'.$sanitizedFilename
230
+                );
231
+                $this->db->insert('tl_member_secureDownloads',$arrData);
232
+
233
+                if (!$objFile->delete()) {
234
+                    $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')));
235
+                }
236
+
237
+                $intFileCount++;
238
+            }
239
+        }
43 240
 
241
+        if ($intFileCount)
242
+        {
243
+            $this->logger->log(LogLevel::INFO, sprintf("Imported %s files for secure member downloads",$intFileCount), array('contao' => new ContaoContext(__METHOD__, 'CRON')));
244
+        }
44 245
     }
45 246
 }
... ...
@@ -42,23 +42,14 @@ class MemberListener {
42 42
      */
43 43
     public function onListSecureDownloadsOperationCallback(array $row, ?string $href, string $label, string $title, ?string $icon, string $attributes, string $table, array $rootRecordIds, ?array $childRecordIds, bool $circularReference, ?string $previous, ?string $next, DataContainer $dc)
44 44
     {
45
-        if (Input::get('popup'))
45
+        if (!$this->User->isAdmin && !$this->User->hasAccess(1,'sec_dl_access'))
46 46
         {
47 47
             return '';
48
-        } else
49
-        {
50
-            $objSecFile = SecureDownloadsModel::findByPk($row['id']);
51
-            $objFile = $objSecFile->getRelated('uuid');
52
-            $title = sprintf($GLOBALS['TL_LANG']['tl_member_secureDownloads']['show'][1], $objFile->name);
53
-
54
-            if ($objFile !== null)
55
-            {
56
-                return '<a href="contao/popup.php?src=' . base64_encode($objFile->path) . '" title="' . StringUtil::specialchars($title, false, true) . '"' . $attributes . ' onclick="Backend.openModalIframe({\'width\':600,\'title\':\'' . str_replace("'", "\\'", StringUtil::specialchars($objFile->name, false, true)) . '\',\'url\':this.href,\'height\':300});return false">' . Image::getHtml($icon, $label) . '</a> ';
57
-            } else
58
-            {
59
-                return '';
60
-            }
61 48
         }
49
+
50
+        $href .= '&amp;id='.$row['id'];
51
+
52
+        return '<a href="'.Backend::addToUrl($href).'" title="'.StringUtil::specialchars($title).'">'.Image::getHtml($icon, $label).'</a> ';
62 53
     }
63 54
 
64 55
 }
... ...
@@ -126,7 +126,7 @@ class MemberSecureDownloadsListener {
126 126
     }
127 127
 
128 128
     /**
129
-     * @Callbacktable="tl_member_secureDownloads", target="fields.name.load")
129
+     * @Callback(table="tl_member_secureDownloads", target="fields.name.load")
130 130
      */
131 131
     public function onNameLoadCallback($varValue, DataContainer $dc)
132 132
     {
... ...
@@ -141,7 +141,7 @@ class MemberSecureDownloadsListener {
141 141
     }
142 142
 
143 143
     /**
144
-     * @Callbacktable="tl_member_secureDownloads", target="fields.name.save")
144
+     * @Callback(table="tl_member_secureDownloads", target="fields.name.save")
145 145
      */
146 146
     public function onNameSaveCallback($varValue, DataContainer $dc)
147 147
     {