<?php declare(strict_types=1); /* * This file is part of memberfiles bundle. * * (c) vonRotenberg * * @license commercial */ namespace vonRotenberg\MemberfilesBundle\Cron; use Contao\BackendUser; use Contao\Controller; use Contao\CoreBundle\Monolog\ContaoContext; use Contao\CoreBundle\ServiceAnnotation\Callback; use Contao\CoreBundle\ServiceAnnotation\CronJob; use Contao\DataContainer; use Contao\Dbafs; use Contao\File; use Contao\Files; use Contao\FilesModel; use Contao\MemberModel; use Contao\PageModel; use Contao\StringUtil; use Contao\System; use Doctrine\DBAL\Connection; use NotificationCenter\Model\Notification; use Psr\Log\LoggerInterface; use Psr\Log\LogLevel; class SecureDownloadsJob { /** @var LoggerInterface */ private $logger; /** @var Connection */ protected $db; public function __construct(Connection $db, LoggerInterface $logger) { $this->logger = $logger; $this->db = $db; } /** * @CronJob("*\/10 * * * *") */ public function sendFileNotifications(string $scope) { // Get archives with notifications enabled $Root = PageModel::findBy(array("sd_nc_enable = ?","type = 'root'"),'1'); if ($Root !== null) { while ($Root->next()) { // Do we have new items $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"); // Load groups and notification models if we have news to share if ($Members->rowCount() && ($Notification = Notification::findByPk($Root->sd_nc_notification)) !== null) { foreach ($Members->iterateAssociative() as $member) { if (($Member = MemberModel::findOneBy(array("disable != '1'","login = '1'","email LIKE '%@%'","id = ?"),array($member['pid']))) !== null) { $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]); $arrFileIds = []; if ($Files->rowCount()) { $arrDownloads = array(); foreach ($Files->iterateAssociative() as $file) { if (($File = FilesModel::findByUuid($file['uuid'])) !== null) { $arrDownloads[] = date('d.m.Y', $file['ctime']) . " - " . $File->name; $arrFileIds[] = $file['id']; } } $Notification->send(array ( 'member_email' => $Member->email, 'member_firstname' => $Member->firstname, 'member_lastname' => $Member->lastname, 'downloads' => implode("\n",$arrDownloads), 'downloads_html' => "<ul>\n<li>".implode("</li>\n<li>",$arrDownloads)."</li>\n</ul>" ),$GLOBALS['TL_LANGUAGE']); } // Flag news as sent $this->db->executeStatement("UPDATE tl_member_secureDownloads SET nc_sent = '1' WHERE id IN (" . implode(',', $arrFileIds) . ")"); } } $this->logger->log(LogLevel::INFO, 'Secure downloads notifications has been sent (' . $Members->rowCount() . ')', array('contao' => new ContaoContext(__METHOD__, 'CRON'))); } } } } /** * @CronJob("* * * * *") */ public function importFiles(string $scope) { $objRoots = PageModel::findPublishedRootPages(); $intFileCount = 0; // No root pages available if ($objRoots === null) { return; } // Iterate page roots while ($objRoots->next()) { if (System::getContainer()->getParameter('kernel.debug')) { $this->logger->log(LogLevel::DEBUG, sprintf('Starting secure downloads import for %s', $objRoots->title), array('contao' => new ContaoContext(__METHOD__, 'CRON'))); } // Continue if secure downloads is disabled if (!$objRoots->secureDownloadsEnabled) { continue; } // Get folder models $objFolder = FilesModel::findByUuid($objRoots->secureDownloadsSRC); $objTarget = FilesModel::findByUuid($objRoots->secureDownloadsTarget); // Continue if folder doesn't exist if ($objFolder === null || !file_exists(TL_ROOT . "/".$objFolder->path) || $objTarget === null || !file_exists(TL_ROOT . "/".$objTarget->path)) { $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'))); continue; } // Read files inside folder $objFiles = FilesModel::findByPid($objFolder->uuid); // Continue if folder is empty if ($objFiles === null) { continue; } // Escape single quotes in fragment regexp $objRoots->secureDownloadsRegExp = addcslashes($objRoots->secureDownloadsRegExp,'\''); // Iterate files while ($objFiles->next()) { // Skip subfolders and special files if ($objFiles->type == 'folder' || strpos($objFiles->name,'.') === 0) { continue; } // Continue if file doesn't exist if (!file_exists(TL_ROOT . "/".$objFiles->path)) { continue; } $objFile = new File($objFiles->path); // Check for fragments if (!preg_match('/'.$objRoots->secureDownloadsRegExp.'/U',$objFile->filename,$fragments)) { $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'))); continue; } // Find member $arrColumns = array(); $arrValues = array(); foreach (StringUtil::deserialize($objRoots->secureDownloadsFields, true) as $i => $field) { $arrColumns[] = "$field = ?"; $arrValues[] = $fragments[$i+1]; } $objMember = MemberModel::findOneBy($arrColumns,$arrValues); // Continue if no member found if ($objMember === null) { $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'))); continue; } // Remove fragments from file name $sanitizedFilename = preg_replace('/'.$objRoots->secureDownloadsRegExp.'/U','',$objFile->filename).'.'.$objFile->extension; // Check or create folder structure $strSubFolder = sprintf("%03d",ceil(($objMember->id+1)/100)-1).'/'.$objMember->id; $strTargetFolder = $objTarget->path.'/'.$strSubFolder; $arrFolders = explode('/',$strTargetFolder); $strStartPath = ''; foreach ($arrFolders as $folder) { if (!is_dir(TL_ROOT . '/' . $strStartPath . $folder)) { Files::getInstance()->mkdir($strStartPath . $folder); } $strStartPath .= $folder . '/'; } // Try to copy file if (!Files::getInstance()->copy($objFile->path,$strTargetFolder.'/'.$sanitizedFilename)) { $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'))); continue; } $File = Dbafs::addResource($strTargetFolder.'/'.$sanitizedFilename,false); // Add file to DB and delete source $arrData = array( 'pid' => $objMember->id, 'uuid' => ($File !== null ? $File->uuid : ''), 'tstamp' => time(), 'ctime' => time(), // 'name' => $sanitizedFilename, // 'path' => $strTargetFolder.'/'.$sanitizedFilename ); $this->db->insert('tl_member_secureDownloads',$arrData); if (!$objFile->delete()) { $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'))); } $intFileCount++; } } if ($intFileCount) { $this->logger->log(LogLevel::INFO, sprintf("Imported %s files for secure member downloads",$intFileCount), array('contao' => new ContaoContext(__METHOD__, 'CRON'))); } } }