Example #1
0
 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;
 }
    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);
        }
    }
Example #3
0
 /**
  * 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);
 }
Example #5
0
 protected static function _initStaticProperties()
 {
     parent::_initStaticProperties();
     \PFXUtils::validateSettings(self::$_SETTINGS, self::$_SETTING_TESTS);
     if (PFX_CA_BUNDLE) {
         self::_registerSSLCertificate(PFX_CA_BUNDLE);
     }
     self::_prepareDBStatements();
     self::$_apiMutex = new \Mutex(__CLASS__);
     self::$_validator = new \Validator(__NAMESPACE__);
     self::$_staticPropsReady = true;
 }
Example #6
0
 /**
  * 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.');
     }
 }
 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);
 }
    /**
     * Tests PFXUtils::collapseArgs().
     */
    public function testCollapseArgs()
    {
        /* This is a pretty unorthodox test due to the fact that we can't
           maniuplate the argument vector from within this process. To solve that
           problem we have a helper script that will be responsible for making
           the actual calls to PFXUtils::collapseArgs for us. */
        PFXUtils::validateSettings(array('PHP_BINARY' => null), array('PHP_BINARY' => 'executable'));
        $this->assertTrue(file_exists(__DIR__ . DIRECTORY_SEPARATOR . 'test_collapse_args.php'));
        $expected = array('foo' => true);
        $this->assertEquals($expected, $this->runCollapseArgsCommand('--foo', array('f'), array('foo')));
        // Should get the same thing with the short form
        $this->assertEquals($expected, $this->runCollapseArgsCommand('-f', array('f'), array('foo')));
        // With an argument
        $expected = array('foo' => 'bar');
        $this->assertEquals($expected, $this->runCollapseArgsCommand('--foo=bar', array('f:'), array('foo:')));
        $this->assertEquals($expected, $this->runCollapseArgsCommand('-f=bar', array('f:'), array('foo:')));
        // Omitting a mandatory argument should spell death
        $this->assertThrows('InvalidArgumentException', array($this, 'runCollapseArgsCommand'), array('-f=bar', array('f:', '!b:'), array('foo:', 'bar:')));
        // Put the mandatory flag on the long form only
        $this->assertThrows('InvalidArgumentException', array($this, 'runCollapseArgsCommand'), array('-f=bar', array('f:', 'b:'), array('foo:', '!bar:')));
        // How about on both
        $this->assertThrows('InvalidArgumentException', array($this, 'runCollapseArgsCommand'), array('-f=bar', array('f:', '!b:'), array('foo:', '!bar:')));
        // But omitting a non-mandatory argument should be OK
        $expected = array('bar' => 'foo');
        $this->assertEquals($expected, $this->runCollapseArgsCommand('--bar=foo --baz', array('f:', '!b:'), array('foo:', 'bar:')));
        /* We should be able to mix arguments that come in both forms,
           arguments that only have a short form, and arguments that only have a
           long form, provided they line up in the arrays we pass. */
        $expected = array('a' => true, 'bee' => '2', 'cee' => 'three');
        $this->assertEquals($expected, $this->runCollapseArgsCommand('-a -b=2 --cee=three', array('a', 'b:'), array(null, 'bee:', 'cee:')));
        /* If an available boolean argument is not supplied, it should
           automatically be resolved as false. */
        $expected = array('foo' => true, 'bar' => false);
        $this->assertEquals($expected, $this->runCollapseArgsCommand('-f', array('f', 'b'), array('foo', 'bar')));
        $expected = array('a' => false, 'b' => true, 'c' => false);
        $this->assertEquals($expected, $this->runCollapseArgsCommand('-b', array('a', 'b', 'c')));
        // Multiple invocations should end up in an array
        $expected = array('f' => array('bar', 'baz'), 'a' => 'b');
        $this->assertEquals($expected, $this->runCollapseArgsCommand('-f=bar -f=baz -a=b', array('f:', 'a:')));
        $expected = array('foo' => array('bar', 'baz'), 'a' => 'b');
        $this->assertEquals($expected, $this->runCollapseArgsCommand('-f=bar -f=baz -a=b', array('f:', 'a:'), array('foo:')));
        /* Even if a short form is used for one invocation, and the long form
           for another. */
        $this->assertEquals($expected, $this->runCollapseArgsCommand('-f=bar -a=b --foo=baz', array('f:', 'a:'), array('foo:')));
        /* If PFX_USAGE_MESSAGE is defined, and the command includes --help (or
           something that maps to it), PFXUtils::collapseArgs() will normally
           print it and exit. */
        $usageMessage = <<<EOF
Some kind of helpful message.
It will probably be spread across several lines.
EOF;
        $this->assertEquals($expected, $this->runCollapseArgsCommand('-f=bar -a=b --foo=baz', array('f:', 'a:'), array('foo:')));
        $this->assertEquals($usageMessage, $this->runCollapseArgsCommand('-f=bar -a=b --foo=baz --help', array('f:', 'a:', 'h'), array('foo:', null, 'help'), null, $usageMessage));
        // This happens even if required arguments are missing
        $this->assertEquals($usageMessage, $this->runCollapseArgsCommand('-h', array('!f:', 'a:', 'h'), array('foo:', null, 'help'), null, $usageMessage));
        // But if we turn this off, we get the normal behavior
        $expected['help'] = true;
        $this->assertEquals($expected, $this->runCollapseArgsCommand('-f=bar -a=b --foo=baz --help', array('f:', 'a:', 'h'), array('foo:', null, 'help'), false, $usageMessage));
        $this->assertThrows('InvalidArgumentException', array($this, 'runCollapseArgsCommand'), array('-h', array('!f:', 'a:', 'h'), array('foo:', null, 'help'), false, $usageMessage));
        $shortUsageMessage = <<<EOF
A shorter message.
Also probably multiple lines.
EOF;
        /* If both a usage message and a short usage message are passed, the
           short one is used if there is an error in the command line arguments,
           while the long one is used if we ask for help. */
        $this->assertEquals($usageMessage, $this->runCollapseArgsCommand('--help', array('!f:', 'h'), array('!foo:', 'help'), true, $usageMessage, $shortUsageMessage, false));
        $this->assertEquals($shortUsageMessage, $this->runCollapseArgsCommand('--baz', array('!f:', 'h'), array('!foo:', 'help'), true, $usageMessage, $shortUsageMessage, false));
    }
Example #9
0
 /**
  * Handles certain setup tasks. This is the kind of thing that a
  * constructor would handle, but since this is an abstract class and I want
  * child classes to be free to accept any arguments they want, I'm not
  * using a constructor here.
  */
 private function _setupInstance()
 {
     if (!self::$_testedGenericSettings) {
         \PFXUtils::validateSettings(self::$_SETTINGS, self::$_SETTING_TESTS);
         self::_buildDefaultHTTPActionMap();
         self::$_validator = new \Validator();
     }
     /* If there is an exception type configured, validate that it really
     		exists. Otherwise default to RuntimeException. */
     if ($this->_EXCEPTION_TYPE) {
         if (!class_exists($this->_EXCEPTION_TYPE) || !is_a($this->_EXCEPTION_TYPE, 'Exception', true)) {
             throw new \Exception('Exception types registered in descendants of this ' . 'class must be valid Exception subclasses.');
         }
     } else {
         $this->_EXCEPTION_TYPE = 'RuntimeException';
     }
     if ($this->_responseFormat && array_key_exists($this->_responseFormat, self::$_STANDARD_PARSE_CALLBACKS)) {
         $callbackData = self::$_STANDARD_PARSE_CALLBACKS[$this->_responseFormat];
         $callback = array_shift($callbackData);
         $this->_registerParseCallback($callback, $callbackData);
     }
     self::$_validator->number($this->_responseTries, 'The number of attempts to use when connecting to API URLs ' . 'must be a non-zero integer.', \Validator::ASSERT_INT_DEFAULT);
     self::$_validator->number($this->_repeatPauseInterval, 'The number of seconds to wait before repeating an API ' . 'request must be a positive integer.', \Validator::ASSERT_INT | \Validator::ASSERT_POSITIVE);
     if ($this->_requestDelayInterval !== null) {
         self::$_validator->number($this->_requestDelayInterval, 'Delay intervals must be positive integers.', \Validator::ASSERT_INT | \Validator::ASSERT_POSITIVE);
         /* In order to observe this delay across processes, we need to have
         			a mutex and use shared memory. */
         try {
             if (!$this->_mutex) {
                 $this->_mutex = new \Mutex($this);
             }
             /* We will be keeping track of a millisecond timestamp, which
             			means 13 digits. */
             $allocBytes = \SharedMemory::getRequiredBytes(array(1000000000000));
             $this->_shmSegment = new \SharedMemory($this->_mutex, $allocBytes);
         } catch (\Exception $e) {
             throw new $this->_EXCEPTION_TYPE('Caught error while configuring shared memory segment.', null, $e);
         }
         /* Not only do we need to keep track of the delay interval, but we
         			also need a variable name that reflects the specific subclass for
         			which we are tracking this interval. */
         $this->_lastRequestTimeVarName = 'req' . get_class($this);
     }
     foreach (self::$_HTTP_RESPONSE_DEFAULT_MAP as $code => $action) {
         /* If the child has already registered certain actions in any of
         			these positions, skip them. */
         if (!array_key_exists($code, $this->_httpResponseActionMap)) {
             $this->_httpResponseActionMap[$code] = $action;
         }
     }
     $this->_instanceSetupComplete = true;
 }
Example #10
0
 /**
  * Establishes default values for identifying the source of emails sent by
  * this class.
  */
 private static function _setDefaults()
 {
     PFXUtils::validateSettings(self::$_SETTINGS, self::$_SETTING_TESTS);
     if (EMAIL_FROM_HOSTNAME) {
         self::$_DEFAULT_FROM_HOSTNAME = EMAIL_FROM_HOSTNAME;
     } else {
         // Look for a HOSTNAME environment variable
         self::$_DEFAULT_FROM_HOSTNAME = getenv('HOSTNAME');
         if (!self::$_DEFAULT_FROM_HOSTNAME) {
             /* Not too creative, but a reasonably sensible default. The
             			dot is necessary because 'localhost' does not pass PHP's email
             			validation filter when used as the domain part. */
             self::$_DEFAULT_FROM_HOSTNAME = 'local.host';
         }
     }
     if (EMAIL_FROM_USER) {
         self::$_DEFAULT_FROM_USER = EMAIL_FROM_USER;
     } else {
         // There really should be a USER environment variable
         self::$_DEFAULT_FROM_USER = getenv('USER');
         if (!self::$_DEFAULT_FROM_USER) {
             // Again, not too creative, but it probably won't come up
             self::$_DEFAULT_FROM_USER = '******';
         }
     }
     if (EMAIL_FROM_NAME) {
         self::$_DEFAULT_FROM_NAME = EMAIL_FROM_NAME;
     } else {
         // Unlike above, this isn't a standard environment variable name
         self::$_DEFAULT_FROM_NAME = getenv('EMAIL_FROM');
         if (!self::$_DEFAULT_FROM_NAME) {
             self::$_DEFAULT_FROM_NAME = 'Nobody';
         }
     }
     if (EMAIL_REPLY_TO) {
         self::$_DEFAULT_REPLY_TO = EMAIL_REPLY_TO;
     } else {
         self::$_DEFAULT_REPLY_TO = self::$_DEFAULT_FROM_USER . '@' . self::$_DEFAULT_FROM_HOSTNAME;
     }
     self::$_validator = new Validator();
     self::$_validator->setExceptionType('EmailException');
     self::$_defaultCharset = ini_get('default_charset');
 }