/** * Returns static instance * @return \Gems_Log */ public static function getLogger() { if (empty(self::$_instance)) { self::$_instance = new \Gems_Log(); } return self::$_instance; }
/** * Returns true if and only if $value meets the validation requirements * * If $value fails validation, then this method returns false, and * getMessages() will return an array of messages that explain why the * validation failed. * * @param mixed $value * @return boolean * @throws \Zend_Valid_Exception If validation of $value is impossible */ public function isValid($value) { if ($throttleSettings = $this->project->getAskThrottleSettings()) { // Prune the database for (very) old attempts $where = $this->db->quoteInto('gta_datetime < DATE_SUB(NOW(), INTERVAL ? second)', $throttleSettings['period'] * 20); $this->db->delete('gems__token_attempts', $where); // Retrieve the number of failed attempts that occurred within the specified window $select = $this->db->select(); $select->from('gems__token_attempts', array(new \Zend_Db_Expr('COUNT(*) AS attempts'), new \Zend_Db_Expr('UNIX_TIMESTAMP(MAX(gta_datetime)) - UNIX_TIMESTAMP() AS last')))->where('gta_datetime > DATE_SUB(NOW(), INTERVAL ? second)', $throttleSettings['period']); $attemptData = $this->db->fetchRow($select); $remainingDelay = $attemptData['last'] + $throttleSettings['delay']; // \MUtil_Echo::track($throttleSettings, $attemptData, $remainingDelay); if ($attemptData['attempts'] > $throttleSettings['threshold'] && $remainingDelay > 0) { $this->logger->log("Possible token brute force attack, throttling for {$remainingDelay} seconds", \Zend_Log::ERR); $this->_messages = $this->translate->_('The server is currently busy, please wait a while and try again.'); return false; } } // The pure token check if ($this->isValidToken($value)) { return true; } $max_length = $this->tracker->getTokenLibrary()->getLength(); $this->db->insert('gems__token_attempts', array('gta_id_token' => substr($value, 0, $max_length), 'gta_ip_address' => $this->getRequest()->getClientIp())); return false; }
/** * Action for displaying an error, CLI as well as HTTP */ public function errorAction() { $errors = $this->_getParam('error_handler'); $exception = $errors->exception; $info = null; $message = 'Application error'; $responseCode = 200; switch ($errors->type) { case \Zend_Controller_Plugin_ErrorHandler::EXCEPTION_NO_CONTROLLER: case \Zend_Controller_Plugin_ErrorHandler::EXCEPTION_NO_ACTION: // 404 error -- controller or action not found $responseCode = 404; $message = 'Page not found'; break; case \Zend_Controller_Plugin_ErrorHandler::EXCEPTION_OTHER: if ($exception instanceof \Gems_Exception) { $responseCode = $exception->getCode(); $message = $exception->getMessage(); $info = $exception->getInfo(); break; } // Intentional fall through // Intentional fall through default: $message = $exception->getMessage(); break; } \Gems_Log::getLogger()->logError($errors->exception, $errors->request); if (\MUtil_Console::isConsole()) { $this->_helper->viewRenderer->setNoRender(true); echo $message . "\n\n"; if ($info) { echo $info . "\n\n"; } $next = $exception->getPrevious(); while ($next) { echo ' ' . $next->getMessage() . "\n"; $next = $next->getPrevious(); } echo $exception->getTraceAsString(); } else { if ($responseCode) { $this->getResponse()->setHttpResponseCode($responseCode); } $this->view->exception = $exception; $this->view->message = $message; $this->view->request = $errors->request; if ($info) { $this->view->info = $info; } } }
/** * * @param array $job */ public function execute($jobId = null) { $sql = $this->db->select()->from('gems__comm_jobs')->join('gems__comm_templates', 'gcj_id_message = gct_id_template')->where('gcj_active = 1')->where('gcj_id_job = ?', $jobId); $job = $this->db->fetchRow($sql); if (empty($job)) { throw new Exception($this->_('Mail job not found!')); } $dbLookup = $this->loader->getUtil()->getDbLookup(); $mailLoader = $this->loader->getMailLoader(); $sendByMail = $this->getUserEmail($job['gcj_id_user_as']); $filter = $dbLookup->getFilterForMailJob($job); $tracker = $this->loader->getTracker(); $model = $tracker->getTokenModel(); // Fix for #680: token with the valid from the longest in the past should be the // used as first token and when multiple rounds start at the same date the // lowest round order should be used. $model->setSort(array('gto_valid_from' => SORT_ASC, 'gto_round_order' => SORT_ASC)); $multipleTokensData = $model->load($filter); $errors = 0; $mails = 0; $updates = 0; if (count($multipleTokensData)) { $sentMailAddresses = array(); foreach ($multipleTokensData as $tokenData) { $mailer = $mailLoader->getMailer('token', $tokenData); /* @var $mailer \Gems_Mail_TokenMailer */ $token = $mailer->getToken(); $email = $token->getEmail(); $respondentId = $token->getRespondent()->getId(); $mail = false; $update = false; if (!empty($email)) { // Set the from address to use in this job switch ($job['gcj_from_method']) { case 'O': // Send on behalf of organization $organization = $mailer->getOrganization(); $from = $organization->getEmail(); //$organization->getName() . ' <' . $organization->getEmail() . '>'; break; case 'U': // Send on behalf of fixed user $from = $sendByMail; break; case 'F': // Send on behalf of fixed email address $from = $job['gcj_from_fixed']; break; default: throw new \Gems_Exception(sprintf($this->_('Invalid option for `%s`'), $this->_('From address used'))); } $mailer->setFrom($from); $mailer->setBy($sendByMail); try { switch ($job['gcj_process_method']) { case 'M': // Each token sends an email $mail = true; $update = true; break; case 'A': // Only first token mailed and marked if (!isset($sentMailAddresses[$respondentId][$email])) { // When not mailed before $mail = true; $update = true; } break; case 'O': // Only first token mailed, all marked if (!isset($sentMailAddresses[$respondentId][$email])) { // When not mailed before $mail = true; } $update = true; break; default: throw new \Gems_Exception(sprintf($this->_('Invalid option for `%s`'), $this->_('Processing Method'))); } if ($mail == true) { $mailer->setTemplate($job['gcj_id_message']); $mailer->send(); $mails++; $sentMailAddresses[$respondentId][$email] = true; } if ($update == true) { $mailer->updateToken(); $updates++; } } catch (\Zend_Mail_Exception $exception) { $fields = $mailer->getMailFields(false); $info = sprintf("Error mailing to %s respondent %s with email address %s.", $fields['organization'], $fields['full_name'], $fields['email']); // Use a gems exception to pass extra information to the log $gemsException = new \Gems_Exception($info, 0, $exception); \Gems_Log::getLogger()->logError($gemsException); $errors++; } } } } $this->getBatch()->addMessage(sprintf($this->_('Sent %d e-mails with template %s, updated %d tokens.'), $mails, $job['gct_name'], $updates)); if ($errors) { $this->getBatch()->addMessage(sprintf($this->_('%d error(s) occurred while creating mails for template %s. Check error log for details.'), $errors, $job['gct_name'])); } }
/** * Handles receiving and storing the data from a form, files are stored on actual upload process * this only handles storing form data and can be used for resubmission too. * * @param type $xmlFile * @return string ResultID or false on failure */ private function processReceivedForm($answerXmlFile) { //Log what we received $log = \Gems_Log::getLogger(); //$log->log(print_r($xmlFile, true), \Zend_Log::ERR); $xml = simplexml_load_file($answerXmlFile); $formId = $xml->attributes()->id; $formVersion = $xml->attributes()->version; //Lookup what form belongs to this formId and then save $model = $this->getModel(); $filter = array('gof_form_id' => $formId, 'gof_form_version' => $formVersion); if ($formData = $model->loadFirst($filter)) { $this->openrosaFormID = $formData['gof_id']; // Safeguard for when the form definition no longer exists try { $form = new OpenRosa_Tracker_Source_OpenRosa_Form($this->formDir . $formData['gof_form_xml']); $answers = $form->saveAnswer($answerXmlFile); return $answers['orf_id']; } catch (\Exception $exc) { return false; } } else { return false; } }
/** * Reset de ACL en bouw opnieuw op */ private function _initAcl() { $this->_acl = new \MUtil_Acl(); if (get_class(self::$_instanceOfSelf) !== 'Gems_Roles') { throw new \Gems_Exception_Coding("Don't use project specific roles file anymore, you can now do so by using the gems_roles tabel and setup->roles from the interface."); } // Probeer eerst uit db in te lezen met fallback als dat niet lukt try { $this->loadDbAcl(); } catch (\Exception $e) { \Gems_Log::getLogger()->logError($e); // Reset all roles unset($this->_acl); $this->_acl = new \MUtil_Acl(); //Voeg standaard rollen en privileges in $this->loadDefaultRoles(); $this->loadDefaultPrivileges(); } // Now allow 'master' all access, except for the actions that have the // nologin privilege (->the login action) if (!$this->_acl->hasRole('master')) { //Add role if not already present $this->_acl->addRole('master'); } $this->_acl->allow('master'); $this->_acl->deny('master', null, 'pr.nologin'); }
/** * Handle sending responses to the response database (if used) * * Triggered by checkTokenCompletion * * @param int $userId The id of the gems user */ protected function toResponseDatabase($userId) { $db = $this->project->getResponseDatabase(); // WHY EXPLANATION!! // // For some reason mysql prepared parameters do nothing with a \Zend_Db_Expr // object and that causes an error when using CURRENT_TIMESTAMP $current = \MUtil_Date::now()->toString(\Gems_Tracker::DB_DATETIME_FORMAT); $rValues = array('gdr_id_token' => $this->_tokenId, 'gdr_changed' => $current, 'gdr_changed_by' => $userId, 'gdr_created' => $current, 'gdr_created_by' => $userId); $responses = $this->getRawAnswers(); unset($responses['token'], $responses['id'], $responses['lastpage'], $responses['startlanguage'], $responses['submitdate'], $responses['startdate'], $responses['datestamp']); // first read current responses to differentiate between insert and update $responseSelect = $db->select()->from('gemsdata__responses', array('gdr_answer_id', 'gdr_response'))->where('gdr_id_token = ?', $this->_tokenId); $currentResponses = $db->fetchPairs($responseSelect); if (!$currentResponses) { $currentResponses = array(); } // \MUtil_Echo::track($currentResponses, $responses); // Prepare sql $sql = "UPDATE gemsdata__responses\n SET `gdr_response` = ?, `gdr_changed` = ?, `gdr_changed_by` = ?\n WHERE gdr_id_token = ? AND gdr_answer_id = ? AND gdr_answer_row = 1"; $stmt = $db->prepare($sql); $inserts = array(); foreach ($responses as $fieldName => $response) { $rValues['gdr_answer_id'] = $fieldName; if (is_array($response)) { $response = join('|', $response); } $rValues['gdr_response'] = $response; if (array_key_exists($fieldName, $currentResponses)) { // Already exists, do update // But only if value changed if ($currentResponses[$fieldName] != $response) { try { // \MUtil_Echo::track($sql, $rValues['gdr_id_token'], $fieldName, $response); $stmt->execute(array($response, $rValues['gdr_changed'], $rValues['gdr_changed_by'], $rValues['gdr_id_token'], $fieldName)); } catch (\Zend_Db_Statement_Exception $e) { error_log($e->getMessage()); \Gems_Log::getLogger()->logError($e); } } } else { // We add the inserts together in the next prepared statement to improve speed $inserts[$fieldName] = $rValues; } } if (count($inserts) > 0) { // \MUtil_Echo::track($inserts); try { $fields = array_keys(reset($inserts)); $fields = array_map(array($db, 'quoteIdentifier'), $fields); $sql = 'INSERT INTO gemsdata__responses (' . implode(', ', $fields) . ') VALUES (' . implode(', ', array_fill(1, count($fields), '?')) . ')'; // \MUtil_Echo::track($sql); $stmt = $db->prepare($sql); foreach ($inserts as $insert) { // \MUtil_Echo::track($insert); $stmt->execute($insert); } } catch (\Zend_Db_Statement_Exception $e) { error_log($e->getMessage()); \Gems_Log::getLogger()->logError($e); } } }
/** * Stores the current log entry * * @param \Zend_Controller_Request_Abstract $request * @param array $row * @param boolean $force Should we force the logentry to be inserted or should we try to skip duplicates? * @return boolean True when a log entry was stored */ private function _storeLogEntry(\Zend_Controller_Request_Abstract $request, array $row, $force) { if (!$force) { if (isset($this->_sessionStore->last) && $row === $this->_sessionStore->last) { return false; } // Now save the variables to the session to prevent duplicates if needed // // We skip $force as they are always saved and this prevents double logging in case of // e.g. a show => edit => show cycle $this->_sessionStore->last = $row; } try { $this->_db->insert('gems__log_activity', $row); return true; } catch (\Exception $exc) { \Gems_Log::getLogger()->logError($exc, $request); $this->_warn(); return false; } }
/** * Initialize the logger * * @return \Gems_Log */ protected function _initLogger() { $this->bootstrap('project'); // Make sure the project object is available $logger = \Gems_Log::getLogger(); $logPath = GEMS_ROOT_DIR . '/var/logs'; try { $writer = new \Zend_Log_Writer_Stream($logPath . '/errors.log'); } catch (Exception $exc) { try { // Try to solve the problem, otherwise fail heroically \MUtil_File::ensureDir($logPath); $writer = new \Zend_Log_Writer_Stream($logPath . '/errors.log'); } catch (Exception $exc) { $this->bootstrap(array('locale', 'translate')); die(str_replace(GEMS_ROOT_DIR . '/', '', sprintf($this->translateAdapter->_('Path %s not writable') . "\n%s\n", $logPath, $exc->getMessage()))); } } $filter = new \Zend_Log_Filter_Priority($this->project->getLogLevel()); $writer->addFilter($filter); $logger->addWriter($writer); // OPTIONAL STARTY OF FIREBUG LOGGING. if ($this->_startFirebird) { $logger->addWriter(new \Zend_Log_Writer_Firebug()); //We do not add the logLevel here, as the firebug window is intended for use by //developers only and it is only written to the active users' own screen. } \Zend_Registry::set('logger', $logger); return $logger; }
public function saveAnswer($file, $remove = true) { if (!file_exists($file)) { throw new \Gems_Exception_Coding(sprintf($this->translate->_('File not found: %s'), $file)); } //We read the xml file $xml = simplexml_load_file($file); if ($xml === false) { throw new \Gems_Exception_Coding(sprintf($this->translate->_('Could not read form definition for form %s'), $file)); } $formId = (string) $xml->attributes()->id; if ($formId != $this->getFormID()) { //Can not save to this object as it is a different form! throw new \Gems_Exception_Coding(sprintf($this->translate->_('Response is for a different formId: %s <-> %s'), $formId, $this->getFormID())); } $model = $this->getModel(); $answers = $this->flattenAnswers($xml); //Now we should parse the response, extract the options given for a (multi)select $output = array(); foreach ($this->instance as $name => $element) { $bindName = $this->_getBindName($name); if (array_key_exists($bindName, $this->bind)) { $bindInfo = $this->bind[$bindName]; } else { $bindInfo['type'] = 'string'; } if (array_key_exists($bindName, $this->body) && array_key_exists('repeat', $this->body[$bindName])) { // CHECK NESTED // We found a field that should go into the nested record // Now process all answers $group = $this->body[$bindName]['repeat']; foreach ($answers[$group] as $idx => $element) { if (!array_key_exists($group, $output)) { $output[$group] = array(); } if (!array_key_exists($idx, $output[$group])) { $output[$group][$idx] = array(); } $output[$group][$idx] = $output[$group][$idx] + $this->_processAnswer($name, $element, $bindInfo['type']); } } else { $output = $output + $this->_processAnswer($name, $answers, $bindInfo['type']); } } $output['orf_id'] = null; $answers = $model->save($output); if ($model->getChanged() && $remove) { $log = \Gems_Log::getLogger(); $log->log($file . '-->' . substr($file, 0, -4) . '.bak', \Zend_Log::ERR); rename($file, substr($file, 0, -4) . '.bak'); } // @@TODO: make hook for respondentID lookup too if (isset($answers['token'])) { // We receveid a form linked to a token, signal the 'inSource' for this token. $loader = GemsEscort::getInstance()->getLoader(); $token = $loader->getTracker()->getToken($answers['token']); $token->getUrl($loader->getCurrentUser()->getLocale(), $loader->getCurrentUser()->getUserId()); } return $answers; }
/** * Perform automatic job mail */ public function commJob() { /* \Zend_Mail::setDefaultTransport(new \Zend_Mail_Transport_File(array( 'callback' => function ($transport) { // throw new \Zend_Mail_Transport_Exception('Invalid e-mail address'); return $transport->recipients . '_' . time() . '_' . mt_rand() . '.tmp'; }, 'path' => GEMS_ROOT_DIR . '/var/sentmails' ))); // */ $dbLookup = $this->util->getDbLookup(); $mailLoader = $this->loader->getMailLoader(); $tracker = $this->loader->getTracker(); $model = $tracker->getTokenModel(); // Fix for #680: token with the valid from the longest in the past should be the // used as first token and when multiple rounds start at the same date the // lowest round order should be used. $model->setSort(array('gto_valid_from' => SORT_ASC, 'gto_round_order' => SORT_ASC)); // Check for unprocessed tokens $tracker->processCompletedTokens(null, $this->currentUser->getUserId()); $sql = "SELECT *\r\n FROM gems__comm_jobs INNER JOIN\r\n gems__comm_templates ON gcj_id_message = gct_id_template\r\n WHERE gcj_active = 1\r\n ORDER BY CASE WHEN gcj_id_survey IS NULL THEN 1 ELSE 0 END,\r\n CASE WHEN gcj_round_description IS NULL THEN 1 ELSE 0 END,\r\n CASE WHEN gcj_id_track IS NULL THEN 1 ELSE 0 END,\r\n CASE WHEN gcj_id_organization IS NULL THEN 1 ELSE 0 END"; $jobs = $this->db->fetchAll($sql); $mailed = false; if ($jobs) { foreach ($jobs as $job) { $sendByMail = $this->getUserEmail($job['gcj_id_user_as']); $filter = $dbLookup->getFilterForMailJob($job); $multipleTokensData = $model->load($filter); if (count($multipleTokensData)) { $errors = 0; $mails = 0; $updates = 0; $sentMailAddresses = array(); foreach ($multipleTokensData as $tokenData) { $mailer = $mailLoader->getMailer('token', $tokenData); /* @var $mailer \Gems_Mail_TokenMailer */ $token = $mailer->getToken(); $email = $token->getEmail(); $respondentId = $token->getRespondent()->getId(); if (!empty($email)) { if ($job['gcj_from_method'] == 'O') { $organization = $mailer->getOrganization(); $from = $organization->getEmail(); //$organization->getName() . ' <' . $organization->getEmail() . '>'; $mailer->setFrom($from); } elseif ($job['gcj_from_method'] == 'U') { $from = $sendByMail; $mailer->setFrom($from); } elseif ($job['gcj_from_method'] == 'F') { $mailer->setFrom($job['gcj_from_fixed']); } $mailer->setBy($sendByMail); try { if ($job['gcj_process_method'] == 'M') { $mailer->setTemplate($job['gcj_id_message']); $mailer->send(); $mailed = true; $mails++; $updates++; } elseif (!isset($sentMailAddresses[$respondentId][$email])) { $mailer->setTemplate($job['gcj_id_message']); $mailer->send(); $mailed = true; $mails++; $updates++; $sentMailAddresses[$respondentId][$email] = true; } elseif ($job['gcj_process_method'] == 'O') { $mailer->updateToken(); $updates++; } } catch (\Zend_Mail_Exception $exception) { $fields = $mailer->getMailFields(false); $info = sprintf("Error mailing to %s respondent %s with email address %s.", $fields['organization'], $fields['full_name'], $fields['email']); // Use a gems exception to pass extra information to the log $gemsException = new \Gems_Exception($info, 0, $exception); \Gems_Log::getLogger()->logError($gemsException); $errors++; } } } $this->addMessage(sprintf($this->_('Sent %d e-mails with template %s, updated %d tokens.'), $mails, $job['gct_name'], $updates)); if ($errors) { $this->addMessage(sprintf($this->_('%d error(s) occurred while creating mails for template %s. Check error log for details.'), $errors, $job['gct_name'])); } } $tokensData = null; } } if (!$mailed) { $this->addMessage($this->_('No mails sent.')); } }