protected static function _initStaticProperties() { if (self::$_dbConn) { return; } \PFXUtils::validateSettings(self::$_SETTINGS, self::$_SETTING_TESTS); /* Since we use the OAuth back end for authentication, and it (optionally) uses the database, we can piggyback off its credentials if provided. This isn't enforced because some subclasses may be built to work correctly with no database connection. */ if (OAUTH_DB_DSN) { self::$_dbConn = \PFXUtils::getDBConn(OAUTH_DB_DSN, OAUTH_DB_USER, OAUTH_DB_PASSWORD); /* This table used to pertain only to the Google Analytics back end, but then it became obvious that it was more generally useful. I retained the old name simply because it created more hassle than necessary to change it. */ self::$_DB_STATEMENTS['google_analytics_api_fetch_log'] = array(); $q = <<<EOF INSERT INTO google_analytics_api_fetch_log (entity, etag, result_count, fetch_date) VALUES (:entity, :etag, :result_count, UNIX_TIMESTAMP()) EOF; self::$_DB_STATEMENTS['google_analytics_api_fetch_log']['insert'] = self::$_dbConn->prepare($q); $q = <<<EOF SELECT * FROM google_analytics_api_fetch_log WHERE entity = :entity ORDER BY id DESC LIMIT 1 EOF; self::$_DB_STATEMENTS['google_analytics_api_fetch_log']['select'] = self::$_dbConn->prepare($q); } }
/** * Performs some special handling if the operator requires a range or a * list. * * @param string, array $operand */ protected function _validateRightOperand($operand) { if ($this->_operator == self::OP_BETWEEN || $this->_operator == self::OP_IN) { /* This logic doesn't attempt to verify that the ends of a range are valid for range comparison, as that's sort of a pain. */ if (is_array($operand)) { if ($this->_operator == self::OP_BETWEEN) { if (count($operand) != 2) { throw new InvalidArgumentException('When passing an array as an operand to a range ' . 'operator, it must contain exactly two values.'); } $operand = implode('_', $operand); } else { foreach ($operand as &$opComponent) { $opComponent = \PFXUtils::escape($opComponent, '|'); } $operand = implode('|', $operand); } } elseif ($this->_operator == self::OP_BETWEEN) { $components = explode('_', $operand); if (count($components) != 2) { throw new InvalidArgumentException('Operands used with the <> operator must have ' . 'exactly two boundaries separated by an underscore.'); } } } return parent::_validateRightOperand($operand); }
private static function _initStaticProperties() { self::$_hasIntl = extension_loaded('intl'); PFXUtils::validateSettings(self::$_SETTINGS, self::$_SETTING_TESTS); self::$_stringComparisonCallback = function (URL $url, $compURL) { return (string) $url === (string) $compURL; }; self::$_hostComparisonCallback = function (URL $url, $compURL, $requireSubdomainMatch = false) { $schemeDelimiterPos = strpos($compURL, '://'); if ($schemeDelimiterPos === false) { return false; } $schemeDelimiterPos += 3; $slashPos = strpos($compURL, '/', $schemeDelimiterPos); if ($slashPos === false) { $compHost = substr($compURL, $schemeDelimiterPos); } else { $compHost = substr($compURL, $schemeDelimiterPos, $slashPos - $schemeDelimiterPos); } if ($requireSubdomainMatch) { return $url->getHost() === $compHost; } try { return $url->compareRootDomain(new URL($compHost)); } catch (URLException $e) { return false; } }; self::$_staticPropsReady = true; }
/** * Ensures required settings are set and valid. */ private static function _validateSettings() { try { \PFXUtils::validateSettings(self::$_SETTINGS, self::$_SETTING_TESTS); } catch (\UnexpectedValueException $e) { throw new UnexpectedValueException('Caught error while validating settings.', null, $e); } }
public static function setUpBeforeClass() { \PFXUtils::validateSettings(array('TEST_DB_DSN' => null, 'TEST_DB_USER' => null, 'TEST_DB_PASSWORD' => null, 'TEST_DB_DBNAME' => null, 'TEST_DB_SCHEMA_HISTORY' => null, 'TEST_DB_SCHEMA_DIRECTORY' => null, 'TEST_DB_BINARY' => null, 'TEST_IGNORE_DB' => false), array('TEST_DB_DSN' => 'str', 'TEST_DB_USER' => 'str', 'TEST_DB_PASSWORD' => 'str', 'TEST_DB_DBNAME' => 'str', 'TEST_DB_SCHEMA_HISTORY' => '?file', 'TEST_DB_SCHEMA_DIRECTORY' => '?string', 'TEST_DB_BINARY' => 'executable')); self::$_dbConn = \PFXUtils::getDBConn(TEST_DB_DSN, TEST_DB_USER, TEST_DB_PASSWORD); /* If we're ignoring the database, we can bail out now that we have satisfied PHPUnit's requirements about what this class has to set up. */ if (TEST_IGNORE_DB) { return; } // Make sure the database is empty first $stmt = self::$_dbConn->query('SHOW TABLES'); if ($stmt->fetch()) { throw new \RuntimeException('Cannot execute a test case against a non-empty database.'); } printf('Running scripts against test database %s...%s', TEST_DB_DBNAME, PHP_EOL); /* This opens a security hole if the test database user's password is sensitive, which it really shouldn't be. */ $cmd = sprintf('%s %s --user=%s --password=%s', TEST_DB_BINARY, TEST_DB_DBNAME, TEST_DB_USER, TEST_DB_PASSWORD); $scripts = array(); if (TEST_DB_SCHEMA_HISTORY) { $fh = fopen(TEST_DB_SCHEMA_HISTORY, 'r'); $baseDir = dirname(TEST_DB_SCHEMA_HISTORY); while ($line = fgets($fh)) { $scripts[] = $baseDir . DIRECTORY_SEPARATOR . trim($line); } } if (TEST_DB_SCHEMA_DIRECTORY) { // Treat this as a comma-delimited list of directories $directories = explode(',', TEST_DB_SCHEMA_DIRECTORY); foreach ($directories as $dir) { $dir = trim($dir); if ($dir[0] != '/') { $dir = realpath(__DIR__ . '/../../schemata') . '/' . $dir; } $dirH = opendir($dir); if ($dirH === false) { throw new RuntimeException('Failed to open the directory ' . $dir . ' for reading.'); } while (false !== ($entry = readdir($dirH))) { $fullPath = $dir . '/' . $entry; if (!is_dir($fullPath)) { $scripts[] = $fullPath; } } closedir($dirH); } } foreach ($scripts as $script) { system($cmd . ' < ' . $script, $returnVal); if ($returnVal) { throw new \RuntimeException('Execution of database script ' . $script . ' failed ' . 'with exit code ' . $returnVal . '.'); } } printf('Done.%s', PHP_EOL); }
/** * Opens a handle to the log file and instantiates a mutex that prevents * concurrent writes. * * @param string $logFile * @param string $emailRecipient = null * @param Mutex $mutex = null * @param boolean $useGZip = null */ public function __construct($logFile, $emailRecipient = null, Mutex $mutex = null, $useGZip = null) { if (!self::$_registeredSendFunction) { /* We want to ensure that emails get sent even if an error takes place that stops the destructor from getting called (e.g. a PHP fatal error). However, we can't register any instance methods as shutdown functions, because it prevents their refcounts from dropping to zero. My solution is to maintain the emails in a static property, and the shutdown function will iterate through any of them with pending content and send them. */ register_shutdown_function(array(__CLASS__, 'sendEmail')); } if ($emailRecipient) { try { // This default subject can be overridden later self::$_emails[] = $this->_email = new Email($emailRecipient, 'Automated log message report'); } catch (EmailException $e) { throw new LoggerException('Caught error while initializing email.', null, $e); } } if ($useGZip === null) { $useGZip = substr($logFile, -3) == '.gz'; } $this->_useGZip = $useGZip; if (!$mutex) { try { $mutex = new Mutex(__CLASS__); } catch (MutexException $e) { throw new LoggerException('Unable to instantiate mutex.', null, $e); } } $this->_mutex = $mutex; $this->_logFileName = $logFile; /* We'll defer the actual opening of the file handle until it's needed. This is mainly due to the fact that appending to a gzip stream appends a gzip header each time, so if we're not going to end up with anything to log, it's better not to even open the handle in the first place. It doesn't really make a difference for uncompressed files, but it's simplest to handle both stream types in the same way. */ if (!PFXUtils::testWritable($this->_logFileName)) { throw new LoggerException($this->_logFileName . ' does not appear to be writable.'); } if ($this->_useGZip) { $this->_openFunction = 'gzopen'; $this->_closeFunction = 'gzclose'; $this->_writeFunction = 'gzwrite'; } else { $this->_openFunction = 'fopen'; $this->_closeFunction = 'fclose'; $this->_writeFunction = 'fwrite'; } }
/** * Determine the nature of the environment and how we will perform the * mutex. * * @param mixed $lockParam */ public function __construct($lockParam) { if (self::$_useSysV === null) { PFXUtils::validateSettings(self::$_SETTINGS, self::$_SETTING_TESTS); if (MUTEX_DEBUG_LOG_FILE) { self::$_log = fopen(MUTEX_DEBUG_LOG_FILE, 'w'); } $osType = strtolower(substr(PHP_OS, 0, 3)); // Cygwin has the SysV functions but they don't actually work if (!function_exists('sem_acquire') || $osType == 'cyg') { self::$_useSysV = false; self::$_lockFileBase = sys_get_temp_dir() . DIRECTORY_SEPARATOR . 'mutex.{LOCK_ID}.tmp'; } else { self::$_useSysV = true; } self::$_activeResources = new SplQueue(); } if (self::$_log) { $this->_mutexID = uniqid(); $trace = debug_backtrace(); self::_log(sprintf('Mutex %s constructed in %s (%s)', $this->_mutexID, $trace[0]['file'], $trace[0]['line'])); } // If we were passed an object instance, get its class name if (is_object($lockParam)) { $lockParam = get_class($lockParam); } if (filter_var($lockParam, FILTER_VALIDATE_INT) !== false) { $lockMethod = self::LOCK_METHOD_MANUAL; $lockID = (int) $lockParam; } else { $lockMethod = self::LOCK_METHOD_AUTO; $lockID = crc32($lockParam); } if (isset(self::$_REGISTERED_KEY_LOCK_METHODS[$lockID]) && self::$_REGISTERED_KEY_LOCK_METHODS[$lockID] != $lockMethod) { throw new MutexException('The mutex lock parameter "' . $lockParam . '" conflicts ' . 'with an existing mutex.'); } self::$_REGISTERED_KEY_LOCK_METHODS[$lockID] = $lockMethod; $this->_lockID = $lockID; if (self::$_useSysV) { $this->_lockResource = sem_get($this->_lockID, 1, 0666, 1); } else { $lockFile = str_replace('{LOCK_ID}', $this->_lockID, self::$_lockFileBase); $this->_lockResource = fopen($lockFile, 'a+b'); // I'm not supporting the cleanup operation for real sempahores self::$_activeResources->enqueue($this->_lockResource); self::$_activeResources->enqueue($lockFile); } if (!$this->_lockResource) { throw new MutexException('Failed to obtain lock resource for mutex.'); } }
/** * Instantiates an object using a string formatted according to the Google * Analytics API's syntax. * * @param string $filters * @return Google\Analytics\GaDataFilterCollection */ public static function createFromString($filters) { $andFilters = \PFXUtils::explodeUnescaped(self::OP_AND, $filters); $andCollections = array(); $reflector = new \ReflectionClass(__CLASS__); foreach ($andFilters as $andFilter) { $orFilters = \PFXUtils::explodeUnescaped(self::OP_OR, $andFilter); $orFilterInstances = array(); foreach ($orFilters as $orFilter) { $orFilterInstances[] = new GaDataConditionalExpression($orFilter); } $andCollections[] = $reflector->newInstanceArgs(array_merge(array(self::OP_OR), $orFilterInstances)); } return $reflector->newInstanceArgs(array_merge(array(self::OP_AND), $andCollections)); }
/** * Validates the right operand and returns it if valid. * * @param string $operand * @return string */ protected function _validateRightOperand($operand) { if (!is_scalar($operand)) { throw new InvalidArgumentException('Operands must be passed as scalar values.'); } // This does nothing other than escaping ANDs and ORs return \PFXUtils::escape($operand, GaDataLogicalCollection::OP_AND . GaDataLogicalCollection::OP_OR); }
/** * This method provides a multibyte-safe fgets() equivalent with * transparent character set conversion. * * @param resource $fh * @param string &$buffer * @param string $sourceEncoding = null * @param string $destEncoding = 'UTF-8' * @param string $eol = PHP_EOL * @return string, boolean */ public static function fgetsMB($fh, &$buffer, $sourceEncoding = null, $destEncoding = 'UTF-8', $eol = PHP_EOL) { /* If character set conversion isn't required and the EOL character ends with "\n", it's (at least in principle) more efficient to fall back to the native fgets(). */ if ($sourceEncoding !== null && $sourceEncoding == $destEncoding && substr($eol, -1) == "\n") { return fgets($fh); } /* When we look for EOL characters in the data, we need to look for their encoded representations; this is a caching mechanism that prevents us from having to do the conversion on every call. */ if ($sourceEncoding !== self::$_lastUsedEncoding) { self::$_lastUsedEncoding = $sourceEncoding; /* This is an array because there could be multiple possible EOL sequences we try to look for on the same encoding. */ self::$_lastUsedEncodingEOL = array(); } if (!isset(self::$_lastUsedEncodingEOL[$eol])) { /* This assumes that the EOL is being passed in the same encoding to which we are being asked to convert. */ self::$_lastUsedEncodingEOL[$eol] = $sourceEncoding === null ? $eol : mb_convert_encoding($eol, $sourceEncoding, $destEncoding); } $eolLen = strlen(self::$_lastUsedEncodingEOL[$eol]); $buffer = (string) $buffer; $bufSize = strlen($buffer); $line = ''; $eofReached = feof($fh); // First deal with the contents of the buffer, if any if ($bufSize) { $eolPos = strpos($buffer, self::$_lastUsedEncodingEOL[$eol]); if ($eolPos !== false) { $eolPos += $eolLen; $line .= substr($buffer, 0, $eolPos); $buffer = substr($buffer, $eolPos); if ($sourceEncoding !== null && $sourceEncoding != $destEncoding) { $line = mb_convert_encoding($line, $destEncoding, $sourceEncoding); } return $line; } elseif ($eofReached) { /* We only want to return the remainder of the buffer if we've reached EOF. Otherwise we always want to append the next chunk that we read to the remainder of the buffer, because that's the only way we will be able to detect a line ending in a case where EOL is represented by a sequence of more than one character and the last line read split the EOL character. */ $line = $buffer; $buffer = ''; } } elseif ($eofReached) { return false; } $chunk = $buffer; $buffer = ''; $eolPos = false; while ($eolPos === false && !feof($fh)) { $chunklet = fread($fh, 4096); $chunk .= $chunklet; $eolPos = strpos($chunk, self::$_lastUsedEncodingEOL[$eol]); } if ($eolPos === false) { $line .= $chunk; } else { $eolPos += $eolLen; $line .= substr($chunk, 0, $eolPos); $buffer = substr($chunk, $eolPos); } if ($sourceEncoding !== null && $sourceEncoding != $destEncoding) { /* We are allowing this to fail if mbstring isn't available, because code that requires this method should fail in such an environment. */ $line = mb_convert_encoding($line, $destEncoding, $sourceEncoding); } return $line; }
/** * Tests PFXUtils::implodeSemantically(). */ public function testImplodeSemantically() { $this->assertEquals('foo and bar', PFXUtils::implodeSemantically(', ', array('foo', 'bar'))); $this->assertEquals('foo, bar, and baz', PFXUtils::implodeSemantically(', ', array('foo', 'bar', 'baz'))); $this->assertEquals('foo; bar; or baz', PFXUtils::implodeSemantically('; ', array('foo', 'bar', 'baz'), 'or')); $this->assertEquals('1, 2, 3, 4, 5, 6, 7, 8, aaaaaaaaand 9', PFXUtils::implodeSemantically(', ', array(1, 2, 3, 4, 5, 6, 7, 8, 9), 'aaaaaaaaand')); $this->assertEquals('1 aaaaaaaaand 9', PFXUtils::implodeSemantically('+', array(1, 9), 'aaaaaaaaand')); }
PFX_SHORT_USAGE_MESSAGE. */ require_once __DIR__ . DIRECTORY_SEPARATOR . 'PFXUtils.class.php'; try { $modifiers = new SplStack(); while ($argv) { $arg = array_pop($argv); if ($arg == '--') { break; } $modifiers->push($arg); } $args = unserialize($modifiers->pop()); if ($args === false) { throw new RuntimeException('Could not unserialize the first item in the argument vector.'); } try { define('PFX_USAGE_MESSAGE', $modifiers->pop()); try { define('PFX_SHORT_USAGE_MESSAGE', $modifiers->pop()); } catch (RuntimeException $e) { // Short message was not passed } } catch (RuntimeException $e) { // Message was not passed } $parsedArgs = call_user_func_array(array('PFXUtils', 'collapseArgs'), $args); echo serialize($parsedArgs); exit(0); } catch (Exception $e) { PFXUtils::printUsage(sprintf('%s: %s', get_class($e), $e->getMessage()), 1, true); }
/** * Performs a request, which may involve more than one call (e.g. if there * is a redirect involved or the request fails on the first attempt and is * subsequently retried). This method returns false if the HTTP status code * of the final call was anything other than 200, unless it was handled by * mapping an HTTP status to an action. * * @param GenericAPI\Request $request * @param boolean $parse = true * @return boolean */ protected function _getResponse(Request $request, $parse = true) { if (!$this->_instanceSetupComplete) { $this->_setupInstance(); } if ($this->_requestDelayInterval) { $this->_mutex->acquire(); $timeSinceLast = \PFXUtils::millitime() - $this->_shmSegment->getVar($this->_lastRequestTimeVarName, 0); if ($timeSinceLast < $this->_requestDelayInterval) { usleep(($this->_requestDelayInterval - $timeSinceLast) * 1000); } $this->_mutex->release(); } $this->_resetState(); $this->_request = $request; for ($i = 0; $i < $this->_responseTries; $i++) { $this->_attemptCount++; if (API_VERBOSE_REQUESTS) { echo 'Executing attempt ' . $this->_attemptCount . ' of ' . $this->_responseTries . '...' . PHP_EOL; } if (!$this->_requestBound) { $this->_bindRequestToCurlHandle(); } $response = $this->_executeCurlHandle(); if (API_VERBOSE_REQUESTS) { echo 'Request trace:' . PHP_EOL . curl_getinfo($this->_curlHandle, CURLINFO_HEADER_OUT) . PHP_EOL . PHP_EOL; } /* I didn't used to set the raw response property until this loop was finished, but we need it in order to make the call to $this->_determineResponseAction() work correctly. In order to preserve the legacy behavior, I'm going to leave the property null if we get a zero-byte response. */ if (strlen($response)) { $this->_responseRaw = $response; } if ($this->_requestDelayInterval) { // Store last request timestamp down to the millisecond $this->_shmSegment->putVar($this->_lastRequestTimeVarName, \PFXUtils::millitime()); } $this->_responseCode = $this->_getLastHTTPResponse(); if (API_VERBOSE_REQUESTS) { echo 'Response code was ' . $this->_responseCode . '; received ' . strlen($response) . ' bytes' . PHP_EOL; } $action = $this->_determineResponseAction(); if ($action === null) { throw new $this->_EXCEPTION_TYPE('Failed to determine response action (response code was ' . $this->_responseCode . ').'); } if ($action == self::ACTION_URL_MOVED) { /* This condition throws an exception so it's easier to know that URLs in library code need to be updated. Note that this only takes effect if the request is not set to redirect automatically or if the number of redirects emitted by the remote service exceeds 10. */ $headers = $this->getResponseHeaderAsAssociativeArray(); if (isset($headers['Location'])) { $message = 'The remote service reports that this resource ' . 'has moved to ' . $headers['Location'] . ' (response code was ' . $this->_responseCode . ').'; } else { $message = 'Got response code ' . $this->_responseCode . ' from remote service.'; } throw new $this->_EXCEPTION_TYPE($message); } if ($action != self::ACTION_REPEAT_REQUEST) { break; } sleep($this->_repeatPauseInterval); } /* In order for certain things to work properly (e.g. the storing of raw SERP source code from the SEMRush API), we need to parse the response before we store the raw response. However, if for some reason the parse code throws an exception, we don't want to die without at least attempting to store the raw data. Therefore, we'll catch any exceptions here, then re-throw them after storing the raw response. */ $rethrowException = null; if ($parse) { try { $this->_parseResponse(); } catch (\Exception $e) { $rethrowException = $e; } } if (strlen($this->_responseRaw)) { if ($this->_transferFile) { $this->_storeRawResponse(); } } $this->_finalizeRequest(); if ($rethrowException) { throw $rethrowException; } $this->_handleError(); $request->validateResponse($this->_responseRaw, $this->_responseParsed); }
private static function _initStaticProperties() { \PFXUtils::validateSettings(self::$_SETTINGS, self::$_SETTING_TESTS); $reflector = new \ReflectionClass(__CLASS__); self::$_constantsByName = $reflector->getConstants(); self::$_constantsByVal = array_flip(self::$_constantsByName); }
The --sampling-level argument provides a way to express a preference regarding the degree of sampling in the Google Analytics data. If the option "none" is used, the report will be treated as having failed if Google reports sampling as being present in the results; in this case the user must specify a shorter time interval in the --split-queries-by option in order to avoid data sampling. The --formatter argument may be used to specify the name of a PHP class that inherits from Google\\Analytics\\ReportFormatter; an instance of this class will be used to format the report's contents. The --name argument only makes sense when emailing a report; it allows for the specification of a meaningful name to describe the report's contents. The --group-name argument fulfills a similar function to the --name argument, but applies when including multiple reports in the same file, and will be used to describe the entire collection. This argument is ignored if specified when running only a single report. EOF ); require_once __DIR__ . DIRECTORY_SEPARATOR . 'bootstrap.php'; try { try { $args = PFXUtils::collapseArgs(array(), array('profile-name:', 'profile-id:', 'metric:', 'start-date:', 'end-date:', 'email:', 'file:', 'dimension:', 'sort:', 'limit:', 'filter:', 'segment:', 'split-queries-by:', 'date-format-string:', 'sampling-level:', 'name:', 'group-name:', 'formatter:', 'conf:', 'help')); $ga = new Google\Analytics\API(); Google\Analytics\QueryConfiguration::createFromCommandLineArgs($args)->run($ga); } catch (InvalidArgumentException $e) { PFXUtils::printUsage($e->getMessage(), 1, true); } } catch (Exception $e) { echo PFXUtils::buildExceptionTrace($e) . "\n"; }
/** * Parses a sequence condition into its component pieces. * * @param string $str */ protected function _setPropertiesFromString($str) { /* This isn't quite as slick as the approach that Google\Analytics\GaDataConditionalExpression uses to find the operator, but it does ensure that we only match the correct operators, and it's unlikely this will need much (if any) maintenance in the future. */ $operators = array(self::OP_FOLLOWED_BY, self::OP_FOLLOWED_BY_IMMEDIATE, self::OP_FIRST_HIT_MATCHES_FIRST_STEP); foreach ($operators as $operator) { $len = strlen($operator); if (substr($str, 0, $len) == $operator) { $this->_constraintAgainstPrevious = $operator; $str = substr($str, $len); break; } } // See if there were any additional conditions $conditions = \PFXUtils::explodeUnescaped(GaDataLogicalCollection::OP_AND, $str); $str = array_shift($conditions); foreach ($conditions as $condition) { $this->addCondition(new GaDataSegmentSimpleCondition($condition)); } parent::_setPropertiesFromString($str); }
/** * Sends the message and returns a boolean value indicating whether or not * the sending took place. The reasons why this method might return a false * value include the following: * * 1) This system does not support sending email via PHP's mail() function * 2) This email was already sent, without the subject, message or * attachment content having been changed * 3) PHP's mail() function returned a false value * * @return boolean */ public function mail() { $attachmentCount = count($this->_attachmentContent); if ($this->_sent) { return false; } elseif (PFX_UNIT_TEST) { $GLOBALS['__lastEmailSent'] = array('sent_time' => time(), 'to' => $this->getRecipient(), 'subject' => $this->getSubject(), 'from' => $this->getFromAddress(), 'from_name' => $this->getFromName(), 'reply_to' => $this->getReplyTo(), 'charset' => $this->getCharset(), 'message' => $this->getMessage(), 'attachment_filenames' => $this->_attachmentFilenames, 'attachment_content' => array()); foreach ($this->_attachmentContent as $attachment) { $GLOBALS['__lastEmailSent']['attachment_content'][] = base64_decode($attachment); } $result = true; } elseif (!PFXUtils::hasMail()) { echo 'Mailable content (' . $attachmentCount . ' attachments):' . PHP_EOL . PHP_EOL . $this->_message . PHP_EOL; if ($attachmentCount) { $cwd = getcwd(); echo 'Attempting to dump attachments in ' . $cwd . PHP_EOL; for ($i = 0; $i < $attachmentCount; $i++) { $file = $cwd . DIRECTORY_SEPARATOR . $this->_attachmentFilenames[$i]; file_put_contents($file, base64_decode($this->_attachmentContent[$i])); } } $result = false; } else { $uid = uniqid(); $content = sprintf("From: %s <%s>\r\nReply-To: %s\r\nMIME-Version: 1.0\r\n" . "Content-Type: multipart/mixed; boundary=\"%s\"\r\n" . "This is a multi-part message in MIME format.\r\n" . "--%s\r\nContent-type: text/plain; charset=%s\r\n" . "Content-Transfer-Encoding: 7bit\r\n\r\n%s\r\n", $this->getFromName(), $this->getFromAddress(), $this->getReplyTo(), $uid, $uid, $this->getCharset(), $this->getMessage()); for ($i = 0; $i < $attachmentCount; $i++) { $content .= sprintf("--%s\r\n" . "Content-Type: application/octet-stream; name=\"%s\"\r\n" . "Content-Transfer-Encoding: base64\r\n" . "Content-Disposition: attachment; filename=\"%s\"\r\n" . "\r\n%s\r\n", $uid, $this->_attachmentFilenames[$i], $this->_attachmentFilenames[$i], $this->_attachmentContent[$i]); } $content .= '--' . $uid . '--'; $result = mail($this->getRecipient(), $this->getSubject(), '', $content); } $this->_sent = true; return $result; }
/** * Tests PFXUtils::beginTransactionSafe(). */ public function testBeginTransactionSafe() { // Our database connection shouldn't be in a transaction yet $this->assertTrue(PFXUtils::beginTransactionSafe(self::$_dbConn)); // And now it is, so it shouldn't begin another one $this->assertFalse(PFXUtils::beginTransactionSafe(self::$_dbConn)); self::$_dbConn->commit(); $this->assertTrue(PFXUtils::beginTransactionSafe(self::$_dbConn)); self::$_dbConn->commit(); }
/** * Gets a message explaining how many iterations failed in the last query * and why. * * @return string */ public function getFailedIterationsMessage() { if ($this->_failedIterations) { return \PFXUtils::quantify(count($this->_failedIterations), 'iteration') . ' failed . Failed iterations were ' . \PFXUtils::implodeSemantically(', ', $this->_failedIterations) . '.'; } }
protected function _handleError() { if (!$this->_responseCode < 400 && !isset($this->_responseParsed['error'])) { return; } $responseID = null; if (\PFXUtils::nestedArrayKeyExists(array('error', 'message'), $this->_responseParsed)) { $message = $this->_responseParsed['error']['message']; // Why is this buried so deep? if (\PFXUtils::nestedArrayKeyExists(array('error', 'details', 0, 'errorDetails', 0, 'message'), $this->_responseParsed)) { $message .= ' (' . $this->_responseParsed['error']['details'][0]['errorDetails'][0]['message'] . ')'; } } else { $responseID = uniqid(); $message = 'Could not find error message in API response. ' . 'Response code was ' . $this->_responseCode . '; response ID is ' . $responseID . '.'; $this->_logger->log('Raw content of response ID ' . $responseID . ': ' . $this->_responseRaw, false); } switch ($this->_responseCode) { case 400: throw new BadRequestException($message); case 404: throw new NotFoundException($message); default: throw new RemoteException($message); } }