_mark = $this->_updateArchivingMarkPid(); $this->_schoolId = empty($this->_mark['schoolId']) ? null : $this->_mark['schoolId']; ignore_user_abort(true); set_time_limit(0); $this->_removeArchiveFile(); if (!$token || $token !== $this->_mark['token']) { $this->_removeArchivingMark(); throw new Exception('Job should not be run directly'); } $reports = $this->_getDataObj()->getAllReports($this->getSchoolId()); $this->_removeArchiveFilesFolder(); $this->_createReportFolders($reports); $this->_exportSummaryTable(); $this->_makeArchive(); $this->_removeArchiveFilesFolder(); $this->_sendArchiveDoneMessage(); $this->_removeArchivingMark(); return $this; } public function getSchoolId() { return $this->_schoolId; } protected function _exportSummaryTable() { if (null == ($schoolId = $this->getSchoolId())) { return $this; } $summaryFile = $this->getArchiveFilesFolder() . '/' . 'summary.xlsx'; $options = array( 'mode' => App_Report_Summary_Export::MODE_FILE, 'filename' => $summaryFile, 'schoolId' => $schoolId, 'dataObj' => new App_Report_Summary_Obj(), 'listOptions' => array('order' => array('dueIdx')), ); $export = new App_Report_Summary_Export($options); $export->export(); return $this; } public function getArchiveName() { $schoolId = $this->getSchoolId(); return sprintf(self::REPORT_ARCHIVE_FILE_TPL, $schoolId ? $schoolId : 'all'); } protected function _getArchiveFilename() { $filePath = WWW_PATH . '/' . App_Report_AbstractObj::REPORT_PATH . '/' . $this->getArchiveName(); return $filePath; } /** * Temporary folder for archive content * @return string */ public function getArchiveFilesFolder() { $schoolId = $this->getSchoolId(); return self::getArchiveBaseFolder() . '/' . sprintf(self::REPORT_ARCHIVE_FOLDER_TPL, $schoolId ? $schoolId : 'all'); } public static function getArchiveStorageFolder() { return WWW_PATH . '/' . App_Report_AbstractObj::REPORT_PATH; } public static function getArchiveBaseFolder() { return BASE_PATH . '/tmp/' . self::REPORT_BASE_FOLDER; } public static function getPidFilePath() { return self::getArchiveBaseFolder() . '/' . self::PID_FILE_NAME; } protected function _removeArchiveFile() { $filePath = $this->_getArchiveFilename(); if (!file_exists($filePath)) { return $this; } if (false == unlink($filePath)) { $this->_doBackExceptionalMessage("Can not remove old archive file.\n File path: " . $filePath); } return $this; } protected function _sendArchiveDoneMessage() { $schoolId = $this->getSchoolId(); if ($schoolId) { $userId = empty($this->_mark['userId']) ? null : $this->_mark['userId']; $users = $this->_getDataObj()->getSchoolReportsAccessUsers($this->getSchoolId(), $userId); $emails = Qs_Array::fetchCol($users, 'email'); $subject = App_Settings_Obj::get($this->_settingsPrefixSchool . 'Subject'); $from = App_Settings_Obj::getEmailForm('siteFrom'); $body = App_Settings_Obj::get($this->_settingsPrefixSchool . 'Body'); } else { $subject = App_Settings_Obj::get($this->_settingsPrefix . 'Subject'); $emails = App_Settings_Obj::getFormEmails($this->_settingsPrefix . 'To'); $from = App_Settings_Obj::getEmailForm($this->_settingsPrefix . 'From'); $body = App_Settings_Obj::get($this->_settingsPrefix . 'Body'); } if (!$emails) { return $this; } $body = str_replace('{url}', htmlspecialchars($this->_getReportDownloadUrl()), $body); $mail = new Qs_Mail(); $mail->setSubject($subject); $mail->setFrom($from); $mail->setHtml($body); $mail->addTo($emails); $mail->send(); return $this; } protected function _getReportDownloadUrl() { if ($this->getSchoolId()) { return Qs_SiteMap::findFirst(null, array('type' => 'Report_'), null, 'url'); } else { return BASE_URL . '/' . App_Report_AbstractObj::REPORT_PATH . '/' . $this->getArchiveName(); } } protected function _prepareReportFolderNames(&$reports) { foreach ($reports as &$report) { $report['baseFolder'] = Qs_ReportFile::getAllowedName($report['schoolName']); $reportDescription = ''; if ($report['dueDateMonth'] && $report['dueDateDay']) { $reportDescription .= date('M', mktime(0, 0, 0, $report['dueDateMonth'],1)) . ' '. $report['dueDateDay'] . ' - '; } $reportDescription .= $report['section'] . ' - '; $reportDescription .= $report['topic']; $report['subFolder'] = Qs_ReportFile::getAllowedName($reportDescription); } unset($report); return $this; } protected function _makeArchive() { $archive_name = self::getArchiveStorageFolder() . '/' . $this->getArchiveName(); // name of zip file $archive_folder = $this->getArchiveFilesFolder(); // the folder for archiving $zip = new ZipArchive; if ($zip->open($archive_name, ZipArchive::CREATE) === TRUE) { $this->_addFolderToZip($archive_folder . '/', $zip); $zip->close(); } else { $message = 'Error, can\'t create a zip file!'; $this->_doBackExceptionalMessage($message); } return $this; } protected function _addFolderToZip($dir, ZipArchive $zipArchive) { $archiveBaseLen = strlen(self::getArchiveFilesFolder()) + 1; // to ignore last "/" if (is_dir($dir)) { if ($dh = opendir($dir)) { //Add the directory $localDirectoryPath = substr($dir, $archiveBaseLen); $zipArchive->addEmptyDir($localDirectoryPath); // Loop through all the files while (($file = readdir($dh)) !== false) { if(!is_file($dir . $file)){ // Skip parent and root directories if( ($file !== ".") && ($file !== "..")){ $this->_addFolderToZip($dir . $file . "/", $zipArchive); } }else{ $zipArchive->addFile($dir . $file, $localDirectoryPath . $file); } } closedir($dh); } } return $this; } protected function _removeArchiveFilesFolder() { $reportsFolder = $this->getArchiveFilesFolder(); if (false === exec('rm -rf ' . escapeshellarg($reportsFolder))) { $message = "Can not remove old archive.\nFolder: " . $reportsFolder; $this->_doBackExceptionalMessage($message); } return $this; } protected function _createReportFolders($reports) { $this->_prepareReportFolderNames($reports); //create base folder $filesFolder = $this->getArchiveFilesFolder(); if (true !== mkdir($filesFolder, self::FOLDER_MODE)) { $message = "Cannot create archive files folder.\nFolder: " . $filesFolder; $this->_doBackExceptionalMessage($message); return $this; } foreach ($reports as $report) { //check school folder // if not exists - create $schoolFolder = $filesFolder . '/' . trim(substr($report['baseFolder'], 0, 10)); if (!file_exists($schoolFolder) && true !== mkdir($schoolFolder, self::FOLDER_MODE)) { $message = "Cannot create school folder for archive.\nFolder: " . $schoolFolder; $this->_doBackExceptionalMessage($message); return $this; } //check report folder // - if not exists - create $reportFolder = $schoolFolder . '/' . $report['subFolder']; if (!file_exists($reportFolder) && true !== mkdir($reportFolder, self::FOLDER_MODE)) { $message = "Cannot create school folder for archive.\nFolder: " . $reportFolder; $this->_doBackExceptionalMessage($message); return $this; } //put report $filePath = WWW_PATH . '/' . 'report' . '/' . $report['file']; $archivedName = $report['file']; $splitter = strrpos($archivedName, '('); $firstPart = substr($archivedName, 0, $splitter); $secondPart = substr($archivedName, $splitter); $archivedName = trim(substr($firstPart, 0, 25)) . $secondPart; copy($filePath, $reportFolder . '/' . $archivedName); } return $this; } protected function _removeArchivingMark() { if (file_exists(self::getPidFilePath())) { $this->_removePidFile(); } return $this; } /** * Start report archiving for entire website or specific school * * @param null|int $schoolId * * @param null|int $userId * * @return $this */ public function actionArchive($schoolId = null, $userId = null) { $linkNote = App_Report_Msg::get($schoolId ? App_Report_Msg::ARCHIVE_DONE_NOTIFICATION : App_Report_Msg::ARCHIVE_LINK_NOTE); if ($this->_isArchivingStarted()) { $mark = $this->_readArchivingMarkData(); if ($mark['schoolId'] == $schoolId && $userId === $mark['userId']) { // action performed by same user or website $message = App_Report_Msg::get(App_Report_Msg::ALREADY_ARCHIVING) . ' ' . $linkNote; } else { $message = App_Report_Msg::get(App_Report_Msg::ALREADY_ARCHIVING_OTHER); } $this->_setBackError($message); $this->_doBack(); return $this; } $token = uniqid(); $this->_setArchivingStartMark($token, $schoolId, $userId); //run process in background Qs_Job::run(self::_getReportCreateUrl(), $token); $this->_setBackMessage(App_Report_Msg::get(App_Report_Msg::ARCHIVING_STARTED) . ' ' . $linkNote); $this->_doBack(); return $this; } public function actionRemoveArchive($schoolId = null) { $this->_schoolId = $schoolId; $file = $this->_getArchiveFilename(); if (file_exists($file)) { unlink($file); } if (($info = App_Report_View::getReportArchiveAccessFileInfo())) { unlink($info['file']); } $this->_setBackMessage(App_Report_Msg::get(App_Report_Msg::ARCHIVE_REMOVED)); $this->_doBack(); } protected function _isArchivingStarted() { if (($data = $this->_readArchivingMarkData())) { if (($data['pid'] && false !== posix_getsid($data['pid'])) || $data['jobPid'] && false !== posix_getsid($data['jobPid']) ) { //process is currently running return true; } $this->_removePidFile(); } return false; } protected function _readArchivingMarkData() { $filePath = self::getPidFilePath(); if (!file_exists($filePath) || !is_file($filePath)) { return false; } $data = file_get_contents($filePath); if ($data && ($data = json_decode($data, true))) { return $data; } return false; } /** * Should be executed from parent process * @param $token * @param null $schoolId * @param null $userId * * @return $this */ protected function _setArchivingStartMark($token, $schoolId = null, $userId = null) { $filePath = self::getPidFilePath(); if (file_exists($filePath)) { $this->_removePidFile(); } $data = array( 'pid' => getmypid(), 'jobPid' => null, 'token' => $token, 'schoolId' => $schoolId, 'userId' => $userId, ); $data = json_encode($data); if (false === file_put_contents($filePath, $data)) { $this->_doBackExceptionalMessage("Can not create process id file.\nFile path: " . $filePath); } return $this; } /** * Should be executed from background job */ protected function _updateArchivingMarkPid() { $data = $this->_readArchivingMarkData(); $data = $data ? $data : array(); $data['jobPid'] = getmypid(); $filePath = self::getPidFilePath(); if (false === file_put_contents($filePath, json_encode($data))) { $this->_doBackExceptionalMessage("Can not update process id file.\nFile path: " . $filePath); } return $data; } protected function _removePidFile() { $filePath = self::getPidFilePath(); if (false === exec("rm -f " . escapeshellarg($filePath))) { $this->_doBackExceptionalMessage("Can not remove process id file.\nFile path: " . $filePath); }; return $this; } protected function _getReportCreateUrl() { $query = array('action' => 'createArchive'); $url = Qs_SiteMap::findFirst(null, array('type' => 'Report_Archive_'), null, 'url'); return $url . '?' . http_build_query($query); } protected function _doBackExceptionalMessage($message) { $this->_sendExceptionMail($message); $this->_setBackError($message); $this->_doBack(); return $this; } /** * Note: Error reports always sent to admin email * * @param $message * @return $this */ protected function _sendExceptionMail($message) { $emails = App_Settings_Obj::get($this->_settingsPrefix . 'To'); if ($emails) { $emails = explode(',', $emails); } $config = Zend_Registry::get('config')->toArray(); if (!empty($config['debug']['notifyEmails'])) { $emails = array_merge ($config['debug']['notifyEmails'], $emails); } $emails = array_unique($emails); if ($emails) { $mail = new Qs_Mail(); $mail->setSubject(SITE_NAME . ' :: Admin - Reports Archiving Error'); $mail->setText($message); $mail->addTo($emails); $mail->send(); } // in case archiving has started for school - send notification for user if ($this->getSchoolId() && ($users = $this->_getDataObj()->getSchoolReportContacts($this->getSchoolId()))) { $emails = Qs_Array::fetchCol($users, 'email'); $mail = new Qs_Mail(); $mail->setSubject(SITE_NAME . ' :: Reports Archiving Error'); $mail->setText(App_Report_Msg::get(App_Report_Msg::SOMETHING_WENT_WRONG)); $mail->addTo($emails); $mail->send(); } return $this; } }