Open Journal Systems  3.3.0
FileLoader.inc.php
1 <?php
2 
15 import('lib.pkp.classes.scheduledTask.ScheduledTask');
16 
17 define('FILE_LOADER_RETURN_TO_STAGING', 0x01);
18 define('FILE_LOADER_ERROR_MESSAGE_TYPE', 'common.error');
19 define('FILE_LOADER_WARNING_MESSAGE_TYPE', 'common.warning');
20 
21 define('FILE_LOADER_PATH_STAGING', 'stage');
22 define('FILE_LOADER_PATH_PROCESSING', 'processing');
23 define('FILE_LOADER_PATH_REJECT', 'reject');
24 define('FILE_LOADER_PATH_ARCHIVE', 'archive');
25 
26 abstract class FileLoader extends ScheduledTask {
27 
29  private $_claimedFilename;
30 
32  private $_basePath;
33 
35  private $_stagePath;
36 
38  private $_processingPath;
39 
41  private $_archivePath;
42 
44  private $_rejectPath;
45 
47  private $_adminEmail;
48 
50  private $_adminName;
51 
53  private $_stagedBackFiles = array();
54 
56  private $_compressArchives = false;
57 
62  function __construct($args) {
63  parent::__construct($args);
64 
65  // Set an initial process id and load translations (required
66  // for email notifications).
67  AppLocale::requireComponents(LOCALE_COMPONENT_PKP_ADMIN);
68 
69  // Canonicalize the base path.
70  $basePath = rtrim($args[0], DIRECTORY_SEPARATOR);
71  $basePathFolder = basename($basePath);
72  // We assume that the parent folder of the base path
73  // does already exist and can be canonicalized.
74  $basePathParent = realpath(dirname($basePath));
75  if ($basePathParent === false) {
76  $basePath = null;
77  } else {
78  $basePath = $basePathParent . DIRECTORY_SEPARATOR . $basePathFolder;
79  }
80  $this->_basePath = $basePath;
81 
82  // Configure paths.
83  if (!is_null($basePath)) {
84  $this->_stagePath = $basePath . DIRECTORY_SEPARATOR . FILE_LOADER_PATH_STAGING;
85  $this->_archivePath = $basePath . DIRECTORY_SEPARATOR . FILE_LOADER_PATH_ARCHIVE;
86  $this->_rejectPath = $basePath . DIRECTORY_SEPARATOR . FILE_LOADER_PATH_REJECT;
87  $this->_processingPath = $basePath . DIRECTORY_SEPARATOR . FILE_LOADER_PATH_PROCESSING;
88  }
89 
90  // Set admin email and name.
91  $siteDao = DAORegistry::getDAO('SiteDAO'); /* @var $siteDao SiteDAO */
92  $site = $siteDao->getSite(); /* @var $site Site */
93  $this->_adminEmail = $site->getLocalizedContactEmail();
94  $this->_adminName = $site->getLocalizedContactName();
95  }
96 
97 
98  //
99  // Getters and setters.
100  //
105  public function getStagePath() {
106  return $this->_stagePath;
107  }
108 
113  public function getProcessingPath() {
114  return $this->_processingPath;
115  }
116 
121  public function getRejectPath() {
122  return $this->_rejectPath;
123  }
124 
129  public function getArchivePath() {
130  return $this->_archivePath;
131  }
132 
137  function getCompressArchives() {
138  return $this->_compressArchives;
139  }
140 
145  function setCompressArchives($compressArchives) {
146  $this->_compressArchives = $compressArchives;
147  }
148 
149 
150  //
151  // Public methods
152  //
156  protected function executeActions() {
157  if (!$this->checkFolderStructure()) return false;
158 
159  $foundErrors = false;
160  while(!is_null($filePath = $this->_claimNextFile())) {
161  if ($filePath === false) {
162  // Problem claiming the file.
163  $foundErrors = true;
164  break;
165  }
166  try {
167  $result = $this->processFile($filePath);
168  } catch(Exception $e) {
169  $foundErrors = true;
170  $this->_rejectFile();
171  $this->addExecutionLogEntry($e->getMessage(), SCHEDULED_TASK_MESSAGE_TYPE_ERROR);
172  continue;
173  }
174 
175  if ($result === FILE_LOADER_RETURN_TO_STAGING) {
176  $foundErrors = true;
177  $this->_stageFile();
178  // Let the script know what files were sent back to staging,
179  // so it doesn't claim them again thereby entering an infinite loop.
180  $this->_stagedBackFiles[] = $this->_claimedFilename;
181  } else {
182  $this->_archiveFile();
183  }
184 
185  if ($result) {
186  $this->addExecutionLogEntry(__('admin.fileLoader.fileProcessed',
187  array('filename' => $filePath)), SCHEDULED_TASK_MESSAGE_TYPE_NOTICE);
188  }
189  }
190  return !$foundErrors;
191  }
192 
203  public function checkFolderStructure($install = false) {
204  // Make sure that the base path is inside the private files dir.
205  // The files dir has appropriate write permissions and is assumed
206  // to be protected against information leak and symlink attacks.
207  $filesDir = realpath(Config::getVar('files', 'files_dir'));
208  if (is_null($this->_basePath) || strpos($this->_basePath, $filesDir) !== 0) {
209  $this->addExecutionLogEntry(__('admin.fileLoader.wrongBasePathLocation', array('path' => $this->_basePath)),
210  SCHEDULED_TASK_MESSAGE_TYPE_ERROR);
211  return false;
212  }
213 
214  // Check folder presence and readability.
215  $pathsToCheck = array(
216  $this->_stagePath,
217  $this->_archivePath,
218  $this->_rejectPath,
219  $this->_processingPath
220  );
221  $fileManager = null;
222  foreach($pathsToCheck as $path) {
223  if (!(is_dir($path) && is_readable($path))) {
224  if ($install) {
225  // Try installing the folder if it is missing.
226  if (is_null($fileManager)) {
227  import('lib.pkp.classes.file.FileManager');
228  $fileManager = new FileManager();
229  }
230  $fileManager->mkdirtree($path);
231  }
232 
233  // Try again.
234  if (!(is_dir($path) && is_readable($path))) {
235  // Give up...
236  $this->addExecutionLogEntry(__('admin.fileLoader.pathNotAccessible', array('path' => $path)),
237  SCHEDULED_TASK_MESSAGE_TYPE_ERROR);
238  return false;
239  }
240  }
241  }
242  return true;
243  }
244 
245 
246  //
247  // Protected methods.
248  //
256  abstract protected function processFile($filePath);
257 
265  protected function moveFile($sourceDir, $destDir, $filename) {
266  $currentFilePath = $sourceDir . DIRECTORY_SEPARATOR . $filename;
267  $destinationPath = $destDir . DIRECTORY_SEPARATOR . $filename;
268 
269  if (!rename($currentFilePath, $destinationPath)) {
270  $message = __('admin.fileLoader.moveFileFailed', array('filename' => $filename,
271  'currentFilePath' => $currentFilePath, 'destinationPath' => $destinationPath));
272  $this->addExecutionLogEntry($message, SCHEDULED_TASK_MESSAGE_TYPE_ERROR);
273 
274  // Script shoudl always stop if it can't manipulate files inside
275  // its own directory system.
276  fatalError($message);
277  }
278 
279  return $destinationPath;
280  }
281 
282  //
283  // Private helper methods.
284  //
290  private function _claimNextFile() {
291  $stageDir = opendir($this->_stagePath);
292  $processingFilePath = false;
293 
294  while($filename = readdir($stageDir)) {
295  if ($filename == '..' || $filename == '.' ||
296  in_array($filename, $this->_stagedBackFiles)) continue;
297 
298  $processingFilePath = $this->moveFile($this->_stagePath, $this->_processingPath, $filename);
299  break;
300  }
301 
302  if (pathinfo($processingFilePath, PATHINFO_EXTENSION) == 'gz') {
303  $fileMgr = new FileManager();
304  try {
305  $processingFilePath = $fileMgr->decompressFile($processingFilePath);
306  $filename = pathinfo($processingFilePath, PATHINFO_BASENAME);
307  } catch (Exception $e) {
308  $this->moveFile($this->_processingPath, $this->_stagePath, $filename);
309  $this->addExecutionLogEntry($e->getMessage(), SCHEDULED_TASK_MESSAGE_TYPE_ERROR);
310  return false;
311  }
312  }
313 
314  if ($processingFilePath) {
315  $this->_claimedFilename = $filename;
316  return $processingFilePath;
317  } else {
318  return null;
319  }
320  }
321 
325  private function _rejectFile() {
326  $this->moveFile($this->_processingPath, $this->_rejectPath, $this->_claimedFilename);
327  }
328 
332  private function _archiveFile() {
333  $this->moveFile($this->_processingPath, $this->_archivePath, $this->_claimedFilename);
334  if ($this->getCompressArchives()) {
335  try {
336  $fileMgr = new FileManager();
337  $filePath = $this->_archivePath . DIRECTORY_SEPARATOR . $this->_claimedFilename;
338  $fileMgr->compressFile($filePath);
339  } catch (Exception $e) {
340  $this->addExecutionLogEntry($e->getMessage(), SCHEDULED_TASK_MESSAGE_TYPE_ERROR);
341  }
342  }
343  }
344 
348  private function _stageFile() {
349  $this->moveFile($this->_processingPath, $this->_stagePath, $this->_claimedFilename);
350  }
351 
356  private function _notify($message, $messageType) {
357  // Instantiate the email to the admin.
358  import('lib.pkp.classes.mail.Mail');
359  $mail = new Mail();
360 
361  // Recipient
362  $mail->addRecipient($this->_adminEmail, $this->_adminName);
363 
364  // The message
365  $mail->setSubject(__('admin.fileLoader.emailSubject', array('processId' => $this->getProcessId())) .
366  ' - ' . __($messageType));
367  $mail->setBody($message);
368 
369  $mail->send();
370  }
371 }
372 
373 
AppLocale\requireComponents
static requireComponents()
Definition: env1/MockAppLocale.inc.php:56
DAORegistry\getDAO
static & getDAO($name, $dbconn=null)
Definition: DAORegistry.inc.php:57
FileLoader\processFile
processFile($filePath)
FileLoader\getArchivePath
getArchivePath()
Definition: FileLoader.inc.php:159
ScheduledTask\getProcessId
getProcessId()
Definition: ScheduledTask.inc.php:83
FileLoader\moveFile
moveFile($sourceDir, $destDir, $filename)
Definition: FileLoader.inc.php:295
Mail
Class defining basic operations for handling and sending emails.
Definition: Mail.inc.php:23
FileLoader\getCompressArchives
getCompressArchives()
Definition: FileLoader.inc.php:167
Config\getVar
static getVar($section, $key, $default=null)
Definition: Config.inc.php:35
ScheduledTask
Base class for executing scheduled tasks. All scheduled task classes must extend this class and imple...
Definition: ScheduledTask.inc.php:20
FileLoader
Base scheduled task class to reliably handle files processing.
Definition: FileLoader.inc.php:26
FileLoader\setCompressArchives
setCompressArchives($compressArchives)
Definition: FileLoader.inc.php:175
FileLoader\getStagePath
getStagePath()
Definition: FileLoader.inc.php:135
ScheduledTask\addExecutionLogEntry
addExecutionLogEntry($message, $type=null)
Definition: ScheduledTask.inc.php:111
FileLoader\executeActions
executeActions()
Definition: FileLoader.inc.php:186
FileLoader\checkFolderStructure
checkFolderStructure($install=false)
Definition: FileLoader.inc.php:233
FileLoader\__construct
__construct($args)
Definition: FileLoader.inc.php:92
FileLoader\getRejectPath
getRejectPath()
Definition: FileLoader.inc.php:151
fatalError
if(!function_exists('import')) fatalError($reason)
Definition: functions.inc.php:32
FileManager
Class defining basic operations for file management.
Definition: FileManager.inc.php:35
FileLoader\getProcessingPath
getProcessingPath()
Definition: FileLoader.inc.php:143