Page MenuHomestyx hydra

No OneTemporary

diff --git a/scripts/sql/upgrade_schema.php b/scripts/sql/upgrade_schema.php
index f7a718f575..10d0159885 100755
--- a/scripts/sql/upgrade_schema.php
+++ b/scripts/sql/upgrade_schema.php
@@ -1,195 +1,196 @@
#!/usr/bin/env php
<?php
/*
* Copyright 2011 Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
$root = dirname(dirname(dirname(__FILE__)));
require_once $root.'/scripts/__init_script__.php';
require_once $root.'/scripts/__init_env__.php';
phutil_require_module('phutil', 'console');
phutil_require_module('phabricator', 'infrastructure/setup/sql');
define('SCHEMA_VERSION_TABLE_NAME', 'schema_version');
// TODO: getopt() is super terrible, move to something less terrible.
$options = getopt('fhv:u:p:') + array(
'v' => null, // Upgrade from specific version
'u' => null, // Override MySQL User
'p' => null, // Override MySQL Pass
);
foreach (array('h', 'f') as $key) {
// By default, these keys are set to 'false' to indicate that the flag was
// passed.
if (array_key_exists($key, $options)) {
$options[$key] = true;
}
}
if (!empty($options['h']) || ($options['v'] && !is_numeric($options['v']))) {
usage();
}
if (empty($options['f'])) {
echo phutil_console_wrap(
"Before running this script, you should take down the Phabricator web ".
"interface and stop any running Phabricator daemons.");
if (!phutil_console_confirm('Are you ready to continue?')) {
echo "Cancelled.\n";
exit(1);
}
}
// Use always the version from the commandline if it is defined
$next_version = isset($options['v']) ? (int)$options['v'] : null;
-// TODO: Get this stuff from DatabaseConfigurationProvider?
+$conf = DatabaseConfigurationProvider::getConfiguration();
+
if ($options['u']) {
$conn_user = $options['u'];
$conn_pass = $options['p'];
} else {
- $conn_user = PhabricatorEnv::getEnvConfig('mysql.user');
- $conn_pass = PhabricatorEnv::getEnvConfig('mysql.pass');
+ $conn_user = $conf->getUser();
+ $conn_pass = $conf->getPassword();
}
-$conn_host = PhabricatorEnv::getEnvConfig('mysql.host');
+$conn_host = $conf->getHost();
// Split out port information, since the command-line client requires a
// separate flag for the port.
$uri = new PhutilURI('mysql://'.$conn_host);
if ($uri->getPort()) {
$conn_port = $uri->getPort();
$conn_bare_hostname = $uri->getDomain();
} else {
$conn_port = null;
$conn_bare_hostname = $conn_host;
}
$conn = new AphrontMySQLDatabaseConnection(
array(
'user' => $conn_user,
'pass' => $conn_pass,
'host' => $conn_host,
'database' => null,
));
try {
$create_sql = <<<END
CREATE DATABASE IF NOT EXISTS `phabricator_meta_data`;
END;
queryfx($conn, $create_sql);
$create_sql = <<<END
CREATE TABLE IF NOT EXISTS phabricator_meta_data.`schema_version` (
`version` INTEGER not null
);
END;
queryfx($conn, $create_sql);
// Get the version only if commandline argument wasn't given
if ($next_version === null) {
$version = queryfx_one(
$conn,
'SELECT * FROM phabricator_meta_data.%T',
SCHEMA_VERSION_TABLE_NAME);
if (!$version) {
print "*** No version information in the database ***\n";
print "*** Give the first patch version which to ***\n";
print "*** apply as the command line argument ***\n";
exit(-1);
}
$next_version = $version['version'] + 1;
}
$patches = PhabricatorSQLPatchList::getPatchList();
$patch_applied = false;
foreach ($patches as $patch) {
if ($patch['version'] < $next_version) {
continue;
}
$short_name = basename($patch['path']);
print "Applying patch {$short_name}...\n";
if ($conn_port) {
$port = '--port='.(int)$conn_port;
} else {
$port = null;
}
list($stdout, $stderr) = execx(
"mysql --user=%s --password=%s --host=%s {$port} < %s",
$conn_user,
$conn_pass,
$conn_bare_hostname,
$patch['path']);
if ($stderr) {
print $stderr;
exit(-1);
}
// Patch was successful, update the db with the latest applied patch version
// 'DELETE' and 'INSERT' instead of update, because the table might be empty
queryfx(
$conn,
'DELETE FROM phabricator_meta_data.%T',
SCHEMA_VERSION_TABLE_NAME);
queryfx(
$conn,
'INSERT INTO phabricator_meta_data.%T VALUES (%d)',
SCHEMA_VERSION_TABLE_NAME,
$patch['version']);
$patch_applied = true;
}
if (!$patch_applied) {
print "Your database is already up-to-date.\n";
}
} catch (AphrontQueryAccessDeniedException $ex) {
echo
"ACCESS DENIED\n".
"The user '{$conn_user}' does not have sufficient MySQL privileges to\n".
"execute the schema upgrade. Use the -u and -p flags to run as a user\n".
"with more privileges (e.g., root).".
"\n\n".
"EXCEPTION:\n".
$ex->getMessage().
"\n\n";
exit(1);
}
function usage() {
echo
"usage: upgrade_schema.php [-v version] [-u user -p pass] [-f] [-h]".
"\n\n".
"Run 'upgrade_schema.php -u root -p hunter2' to override the configured ".
"default user.\n".
"Run 'upgrade_schema.php -v 12' to apply all patches starting from ".
"version 12. It is very unlikely you need to do this.\n".
"Use the -f flag to upgrade noninteractively, without prompting.\n".
"Use the -h flag to show this help.\n";
exit(1);
}
diff --git a/src/applications/base/storage/configuration/DatabaseConfigurationProvider.php b/src/applications/base/storage/configuration/DatabaseConfigurationProvider.php
index 6ec9771dd9..cdc9652890 100644
--- a/src/applications/base/storage/configuration/DatabaseConfigurationProvider.php
+++ b/src/applications/base/storage/configuration/DatabaseConfigurationProvider.php
@@ -1,51 +1,62 @@
<?php
/*
* Copyright 2011 Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
class DatabaseConfigurationProvider {
private $dao;
private $mode;
public function __construct(LiskDAO $dao, $mode) {
$this->dao = $dao;
$this->mode = $mode;
}
public function getUser() {
return PhabricatorEnv::getEnvConfig('mysql.user');
}
public function getPassword() {
return PhabricatorEnv::getEnvConfig('mysql.pass');
}
public function getHost() {
return PhabricatorEnv::getEnvConfig('mysql.host');
}
public function getDatabase() {
return 'phabricator_'.$this->getDao()->getApplicationName();
}
final protected function getDao() {
return $this->dao;
}
final protected function getMode() {
return $this->mode;
}
+
+ public static function getConfiguration() {
+ // Get DB info. Note that we are using a dummy PhabricatorUser object in
+ // creating the DatabaseConfigurationProvider, which is not used at all.
+ $conf_provider = PhabricatorEnv::getEnvConfig(
+ 'mysql.configuration_provider', 'DatabaseConfigurationProvider');
+ PhutilSymbolLoader::loadClass($conf_provider);
+ $conf = newv($conf_provider, array(new PhabricatorUser(), 'r'));
+ return $conf;
+ }
+
}
diff --git a/src/applications/base/storage/configuration/__init__.php b/src/applications/base/storage/configuration/__init__.php
index bf6cd309d0..5aa96d158c 100644
--- a/src/applications/base/storage/configuration/__init__.php
+++ b/src/applications/base/storage/configuration/__init__.php
@@ -1,12 +1,16 @@
<?php
/**
* This file is automatically generated. Lint this module to rebuild it.
* @generated
*/
+phutil_require_module('phabricator', 'applications/people/storage/user');
phutil_require_module('phabricator', 'infrastructure/env');
+phutil_require_module('phutil', 'symbols');
+phutil_require_module('phutil', 'utils');
+
phutil_require_source('DatabaseConfigurationProvider.php');
diff --git a/src/infrastructure/setup/PhabricatorSetup.php b/src/infrastructure/setup/PhabricatorSetup.php
index ff4e559b79..c8f97b47e3 100644
--- a/src/infrastructure/setup/PhabricatorSetup.php
+++ b/src/infrastructure/setup/PhabricatorSetup.php
@@ -1,480 +1,481 @@
<?php
/*
* Copyright 2011 Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
class PhabricatorSetup {
const EXPECTED_SCHEMA_VERSION = 36;
public static function runSetup() {
header("Content-Type: text/plain");
self::write("PHABRICATOR SETUP\n\n");
// Force browser to stop buffering.
self::write(str_repeat(' ', 2048));
usleep(250000);
self::write("This setup mode will guide you through setting up your ".
"Phabricator configuration.\n");
self::writeHeader("REQUIRED PHP EXTENSIONS");
$extensions = array(
'mysql',
'hash',
'json',
'openssl',
// There is a chance we might not need this, but some configurations (like
// Amazon SES) will require it. Just mark it 'required' since it's widely
// available and relatively core.
'curl',
);
foreach ($extensions as $extension) {
$ok = self::requireExtension($extension);
if (!$ok) {
self::writeFailure();
self::write("Setup failure! Install PHP extension '{$extension}'.");
return;
}
}
$root = dirname(phutil_get_library_root('phabricator'));
// On RHEL6, doing a distro install of pcntl makes it available from the
// CLI binary but not from the Apache module. This isn't entirely
// unreasonable and we don't need it from Apache, so do an explicit test
// for CLI availability.
list($err, $stdout, $stderr) = exec_manual(
'%s/scripts/setup/pcntl_available.php',
$root);
if ($err) {
self::writeFailure();
self::write("Unable to execute scripts/setup/pcntl_available.php.");
return;
} else {
if (trim($stdout) == 'YES') {
self::write(" okay pcntl is available from the command line.\n");
- self::write("[OKAY] All extensions OKAY\n\n");
+ self::write("[OKAY] All extensions OKAY\n");
} else {
self::write(" warn pcntl is not available!\n");
self::write("[WARN] *** WARNING *** pcntl extension not available. ".
"You will not be able to run daemons.\n");
}
}
self::writeHeader("GIT SUBMODULES");
if (!Filesystem::pathExists($root.'/.git')) {
self::write(" skip Not a git clone.\n\n");
} else {
list($info) = execx(
'(cd %s && git submodule status)',
$root);
foreach (explode("\n", rtrim($info)) as $line) {
$matches = null;
if (!preg_match('/^(.)([0-9a-f]{40}) (\S+)(?: |$)/', $line, $matches)) {
self::writeFailure();
self::write(
"Setup failure! 'git submodule' produced unexpected output:\n".
$line);
return;
}
$status = $matches[1];
$module = $matches[3];
switch ($status) {
case '-':
case '+':
case 'U':
self::writeFailure();
self::write(
"Setup failure! Git submodule '{$module}' is not up to date. ".
"Run:\n\n".
" cd {$root} && git submodule update --init\n\n".
"...to update submodules.");
return;
case ' ':
self::write(" okay Git submodule '{$module}' up to date.\n");
break;
default:
self::writeFailure();
self::write(
"Setup failure! 'git submodule' reported unknown status ".
"'{$status}' for submodule '{$module}'. This is a bug; report ".
"it to the Phabricator maintainers.");
return;
}
}
}
- self::write("[OKAY] All submodules OKAY.");
+ self::write("[OKAY] All submodules OKAY.\n");
self::writeHeader("BASIC CONFIGURATION");
$env = PhabricatorEnv::getEnvConfig('phabricator.env');
if ($env == 'production' || $env == 'default' || $env == 'development') {
self::writeFailure();
self::write(
"Setup failure! Your PHABRICATOR_ENV is set to '{$env}', which is ".
"a Phabricator environmental default. You should create a custom ".
"environmental configuration instead of editing the defaults ".
"directly. See this document for instructions:\n");
self::writeDoc('article/Configuration_Guide.html');
return;
} else {
$host = PhabricatorEnv::getEnvConfig('phabricator.base-uri');
$protocol = id(new PhutilURI($host))->getProtocol();
$allowed_protocols = array(
'http' => true,
'https' => true,
);
if (empty($allowed_protocols[$protocol])) {
self::writeFailure();
self::write(
"You must specify the protocol over which your host works (e.g.: ".
"\"http:// or https://\")\nin your custom config file.\nRefer to ".
"'default.conf.php' for documentation on configuration options.\n");
return;
}
if (preg_match('/.*\/$/', $host)) {
self::write(" okay phabricator.base-uri\n");
} else {
self::writeFailure();
self::write(
"You must add a trailing slash at the end of the host\n(e.g.: ".
"\"http://phabricator.example.com/ instead of ".
"http://phabricator.example.com\")\nin your custom config file.".
"\nRefer to 'default.conf.php' for documentation on configuration ".
"options.\n");
return;
}
}
self::write("[OKAY] Basic configuration OKAY\n");
$issue_gd_warning = false;
self::writeHeader('GD LIBRARY');
if (extension_loaded('gd')) {
self::write(" okay Extension 'gd' is loaded.\n");
$image_type_map = array(
'imagepng' => 'PNG',
'imagegif' => 'GIF',
'imagejpeg' => 'JPEG',
);
foreach ($image_type_map as $function => $image_type) {
if (function_exists($function)) {
self::write(" okay Support for '{$image_type}' is available.\n");
} else {
self::write(" warn Support for '{$image_type}' is not available!\n");
$issue_gd_warning = true;
}
}
} else {
self::write(" warn Extension 'gd' is not loaded.\n");
$issue_gd_warning = true;
}
if ($issue_gd_warning) {
self::write(
"[WARN] The 'gd' library is missing or lacks full support. ".
"Phabricator will not be able to generate image thumbnails without ".
"gd.\n");
} else {
self::write("[OKAY] 'gd' loaded and has full image type support.\n");
}
self::writeHeader('FACEBOOK INTEGRATION');
$fb_auth = PhabricatorEnv::getEnvConfig('facebook.auth-enabled');
if (!$fb_auth) {
self::write(" skip 'facebook.auth-enabled' not enabled.\n");
} else {
self::write(" okay 'facebook.auth-enabled' is enabled.\n");
$app_id = PhabricatorEnv::getEnvConfig('facebook.application-id');
$app_secret = PhabricatorEnv::getEnvConfig('facebook.application-secret');
if (!$app_id) {
self::writeFailure();
self::write(
"Setup failure! 'facebook.auth-enabled' is true but there is no ".
"setting for 'facebook.application-id'.\n");
return;
} else {
self::write(" okay 'facebook.application-id' is set.\n");
}
if (!is_string($app_id)) {
self::writeFailure();
self::write(
"Setup failure! 'facebook.application-id' should be a string.");
return;
} else {
self::write(" okay 'facebook.application-id' is string.\n");
}
if (!$app_secret) {
self::writeFailure();
self::write(
"Setup failure! 'facebook.auth-enabled' is true but there is no ".
"setting for 'facebook.application-secret'.");
return;
} else {
self::write(" okay 'facebook.application-secret is set.\n");
}
self::write("[OKAY] Facebook integration OKAY\n");
}
self::writeHeader("MySQL DATABASE CONFIGURATION");
- $conn_user = PhabricatorEnv::getEnvConfig('mysql.user');
- $conn_pass = PhabricatorEnv::getEnvConfig('mysql.pass');
- $conn_host = PhabricatorEnv::getEnvConfig('mysql.host');
+ $conf = DatabaseConfigurationProvider::getConfiguration();
+ $conn_user = $conf->getUser();
+ $conn_pass = $conf->getPassword();
+ $conn_host = $conf->getHost();
$timeout = ini_get('mysql.connect_timeout');
if ($timeout > 5) {
self::writeNote(
"Your MySQL connect timeout is very high ({$timeout} seconds). ".
"Consider reducing it by setting 'mysql.connect_timeout' in your ".
"php.ini.");
}
self::write(" okay Trying to connect to MySQL database ".
"{$conn_user}@{$conn_host}...\n");
ini_set('mysql.connect_timeout', 2);
$conn_raw = new AphrontMySQLDatabaseConnection(
array(
'user' => $conn_user,
'pass' => $conn_pass,
'host' => $conn_host,
'database' => null,
));
try {
queryfx($conn_raw, 'SELECT 1');
self::write(" okay Connection successful!\n");
} catch (AphrontQueryConnectionException $ex) {
self::writeFailure();
self::write(
"Setup failure! Unable to connect to MySQL database ".
"'{$conn_host}' with user '{$conn_user}'. Edit Phabricator ".
"configuration keys 'mysql.user', 'mysql.host' and 'mysql.pass' to ".
"enable Phabricator to connect.");
return;
}
$databases = queryfx_all($conn_raw, 'SHOW DATABASES');
$databases = ipull($databases, 'Database');
$databases = array_fill_keys($databases, true);
if (empty($databases['phabricator_meta_data'])) {
self::writeFailure();
self::write(
"Setup failure! You haven't loaded the 'initialize.sql' file into ".
"MySQL. This file initializes necessary databases. See this guide for ".
"instructions:\n");
self::writeDoc('article/Configuration_Guide.html');
return;
} else {
self::write(" okay Databases have been initialized.\n");
}
$schema_version = queryfx_one(
$conn_raw,
'SELECT version FROM phabricator_meta_data.schema_version');
$schema_version = idx($schema_version, 'version', 'null');
$expect = PhabricatorSQLPatchList::getExpectedSchemaVersion();
if ($schema_version != $expect) {
self::writeFailure();
self::write(
"Setup failure! You haven't upgraded your database schema to the ".
"latest version. Expected version is '{$expect}', but your local ".
"version is '{$schema_version}'. See this guide for instructions:\n");
self::writeDoc('article/Upgrading_Schema.html');
return;
} else {
self::write(" okay Database schema are up to date (v{$expect}).\n");
}
self::write("[OKAY] Database configuration OKAY\n");
self::writeHeader("OUTBOUND EMAIL CONFIGURATION");
$have_adapter = false;
$is_ses = false;
$adapter = PhabricatorEnv::getEnvConfig('metamta.mail-adapter');
switch ($adapter) {
case 'PhabricatorMailImplementationPHPMailerLiteAdapter':
$have_adapter = true;
if (!Filesystem::pathExists('/usr/bin/sendmail')) {
self::writeFailure();
self::write(
"Setup failure! You don't have a 'sendmail' binary on this system ".
"but outbound email is configured to use sendmail. Install an MTA ".
"(like sendmail, qmail or postfix) or use a different outbound ".
"mail configuration. See this guide for configuring outbound ".
"email:\n");
self::writeDoc('article/Configuring_Outbound_Email.html');
return;
} else {
self::write(" okay Sendmail is configured.\n");
}
break;
case 'PhabricatorMailImplementationAmazonSESAdapter':
$is_ses = true;
$have_adapter = true;
if (PhabricatorEnv::getEnvConfig('metamta.can-send-as-user')) {
self::writeFailure();
self::write(
"Setup failure! 'metamta.can-send-as-user' must be false when ".
"configured with Amazon SES.");
return;
} else {
self::write(" okay Sender config looks okay.\n");
}
if (!PhabricatorEnv::getEnvConfig('amazon-ses.access-key')) {
self::writeFailure();
self::write(
"Setup failure! 'amazon-ses.access-key' is not set, but ".
"outbound mail is configured to deliver via Amazon SES.");
return;
} else {
self::write(" okay Amazon SES access key is set.\n");
}
if (!PhabricatorEnv::getEnvConfig('amazon-ses.secret-key')) {
self::writeFailure();
self::write(
"Setup failure! 'amazon-ses.secret-key' is not set, but ".
"outbound mail is configured to deliver via Amazon SES.");
return;
} else {
self::write(" okay Amazon SES secret key is set.\n");
}
if (PhabricatorEnv::getEnvConfig('metamta.send-immediately')) {
self::writeNote(
"Your configuration uses Amazon SES to deliver email but tries ".
"to send it immediately. This will work, but it's slow. ".
"Consider configuring the MetaMTA daemon.");
}
break;
case 'PhabricatorMailImplementationTestAdapter':
self::write(" skip You have disabled outbound email.\n");
break;
default:
self::write(" skip Configured with a custom adapter.\n");
break;
}
if ($have_adapter) {
$default = PhabricatorEnv::getEnvConfig('metamta.default-address');
if (!$default || $default == 'noreply@example.com') {
self::writeFailure();
self::write(
"Setup failure! You have not set 'metamta.default-address'.");
return;
} else {
self::write(" okay metamta.default-address is set.\n");
}
if ($is_ses) {
self::writeNote(
"Make sure you've verified your 'from' address ('{$default}') with ".
"Amazon SES. Until you verify it, you will be unable to send mail ".
"using Amazon SES.");
}
$domain = PhabricatorEnv::getEnvConfig('metamta.domain');
if (!$domain || $domain == 'example.com') {
self::writeFailure();
self::write(
"Setup failure! You have not set 'metamta.domain'.");
return;
} else {
self::write(" okay metamta.domain is set.\n");
}
self::write("[OKAY] Mail configuration OKAY\n");
}
self::writeHeader('SUCCESS!');
self::write(
"Congratulations! Your setup seems mostly correct, or at least fairly ".
"reasonable.\n\n".
"*** NEXT STEP ***\n".
"Edit your configuration file (conf/{$env}.conf.php) and remove the ".
"'phabricator.setup' line to finish installation.");
}
public static function requireExtension($extension) {
if (extension_loaded($extension)) {
self::write(" okay Extension '{$extension}' installed.\n");
return true;
} else {
self::write("[FAIL] Extension '{$extension}' is NOT INSTALLED!\n");
return false;
}
}
private static function writeFailure() {
self::write("\n\n<<< *** FAILURE! *** >>>\n");
}
private static function write($str) {
echo $str;
ob_flush();
flush();
// This, uh, makes it look cool. -_-
usleep(40000);
}
private static function writeNote($note) {
self::write(
'Note: '.wordwrap($note, 75, "\n ", true)."\n\n");
}
public static function writeHeader($header) {
$template = '>>>'.str_repeat('-', 77);
$template = substr_replace(
$template,
' '.$header.' ',
3,
strlen($header) + 4);
self::write("\n\n{$template}\n\n");
}
public static function writeDoc($doc) {
self::write(
"\n".
' http://phabricator.com/docs/phabricator/'.$doc.
"\n\n");
}
}
diff --git a/src/infrastructure/setup/__init__.php b/src/infrastructure/setup/__init__.php
index b2b41afd60..61104d7777 100644
--- a/src/infrastructure/setup/__init__.php
+++ b/src/infrastructure/setup/__init__.php
@@ -1,18 +1,22 @@
<?php
/**
* This file is automatically generated. Lint this module to rebuild it.
* @generated
*/
+
+
+phutil_require_module('phabricator', 'applications/base/storage/configuration');
phutil_require_module('phabricator', 'infrastructure/env');
phutil_require_module('phabricator', 'infrastructure/setup/sql');
phutil_require_module('phabricator', 'storage/connection/mysql');
phutil_require_module('phabricator', 'storage/queryfx');
phutil_require_module('phutil', 'filesystem');
phutil_require_module('phutil', 'future/exec');
phutil_require_module('phutil', 'moduleutils');
phutil_require_module('phutil', 'parser/uri');
phutil_require_module('phutil', 'utils');
-phutil_require_source('PhabricatorSetup.php');
\ No newline at end of file
+
+phutil_require_source('PhabricatorSetup.php');

File Metadata

Mime Type
text/x-diff
Expires
Sun, Jul 27, 2:36 PM (1 w, 6 h ago)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
185911
Default Alt Text
(26 KB)

Event Timeline