Page MenuHomestyx hydra

No OneTemporary

diff --git a/conf/default.conf.php b/conf/default.conf.php
index 24af0be3bd..55519bd8ae 100644
--- a/conf/default.conf.php
+++ b/conf/default.conf.php
@@ -1,573 +1,580 @@
<?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.
*/
return array(
// The root URI which Phabricator is installed on.
// Example: "http://phabricator.example.com/"
'phabricator.base-uri' => null,
// If you have multiple environments, provide the production environment URI
// here so that emails, etc., generated in development/sandbox environments
// contain the right links.
'phabricator.production-uri' => null,
// Setting this to 'true' will invoke a special setup mode which helps guide
// you through setting up Phabricator.
'phabricator.setup' => false,
// The default PHID for users who haven't uploaded a profile image. It should
// be 50x50px.
'user.default-profile-image-phid' => 'PHID-FILE-4d61229816cfe6f2b2a3',
// -- DarkConsole ----------------------------------------------------------- //
// DarkConsole is a administrative debugging/profiling tool built into
// Phabricator. You can leave it disabled unless you're developing against
// Phabricator.
// Determines whether or not DarkConsole is available. DarkConsole exposes
// some data like queries and stack traces, so you should be careful about
// turning it on in production (although users can not normally see it, even
// if the deployment configuration enables it).
'darkconsole.enabled' => false,
// Always enable DarkConsole, even for logged out users. This potentially
// exposes sensitive information to users, so make sure untrusted users can
// not access an install running in this mode. You should definitely leave
// this off in production. It is only really useful for using DarkConsole
// utilties to debug or profile logged-out pages. You must set
// 'darkconsole.enabled' to use this option.
'darkconsole.always-on' => false,
// Allows you to mask certain configuration values from appearing in the
// "Config" tab of DarkConsole.
'darkconsole.config-mask' => array(
'mysql.pass',
'amazon-ses.secret-key',
'recaptcha.private-key',
'phabricator.csrf-key',
'facebook.application-secret',
'github.application-secret',
),
// -- MySQL --------------------------------------------------------------- //
// The username to use when connecting to MySQL.
'mysql.user' => 'root',
// The password to use when connecting to MySQL.
'mysql.pass' => '',
// The MySQL server to connect to. If you want to connect to a different
// port than the default (which is 3306), specify it in the hostname
// (e.g., db.example.com:1234).
'mysql.host' => 'localhost',
// -- Email ----------------------------------------------------------------- //
// Some Phabricator tools send email notifications, e.g. when Differential
// revisions are updated or Maniphest tasks are changed. These options allow
// you to configure how email is delivered.
// You can test your mail setup by going to "MetaMTA" in the web interface,
// clicking "Send New Message", and then composing a message.
// Default address to send mail "From".
'metamta.default-address' => 'noreply@example.com',
// Domain used to generate Message-IDs.
'metamta.domain' => 'example.com',
// When a user takes an action which generates an email notification (like
// commenting on a Differential revision), Phabricator can either send that
// mail "From" the user's email address (like "alincoln@logcabin.com") or
// "From" the 'metamta.default-address' address. The user experience is
// generally better if Phabricator uses the user's real address as the "From"
// since the messages are easier to organize when they appear in mail clients,
// but this will only work if the server is authorized to send email on behalf
// of the "From" domain. Practically, this means:
// - If you are doing an install for Example Corp and all the users will
// have corporate @corp.example.com addresses and any hosts Phabricator
// is running on are authorized to send email from corp.example.com,
// you can enable this to make the user experience a little better.
// - If you are doing an install for an open source project and your
// users will be registering via Facebook and using personal email
// addresses, you MUST NOT enable this or virtually all of your outgoing
// email will vanish into SFP blackholes.
// - If your install is anything else, you're much safer leaving this
// off since the risk in turning it on is that your outgoing mail will
// mostly never arrive.
'metamta.can-send-as-user' => false,
// Adapter class to use to transmit mail to the MTA. The default uses
// PHPMailerLite, which will invoke "sendmail". This is appropriate
// if sendmail actually works on your host, but if you haven't configured mail
// it may not be so great. You can also use Amazon SES, by changing this to
// 'PhabricatorMailImplementationAmazonSESAdapter', signing up for SES, and
// filling in your 'amazon-ses.access-key' and 'amazon-ses.secret-key' below.
'metamta.mail-adapter' =>
'PhabricatorMailImplementationPHPMailerLiteAdapter',
// When email is sent, try to hand it off to the MTA immediately. This may
// be worth disabling if your MTA infrastructure is slow or unreliable. If you
// disable this option, you must run the 'metamta_mta.php' daemon or mail
// won't be handed off to the MTA. If you're using Amazon SES it can be a
// little slugish sometimes so it may be worth disabling this and moving to
// the daemon after you've got your install up and running. If you have a
// properly configured local MTA it should not be necessary to disable this.
'metamta.send-immediately' => true,
// If you're using Amazon SES to send email, provide your AWS access key
// and AWS secret key here. To set up Amazon SES with Phabricator, you need
// to:
// - Make sure 'metamta.mail-adapter' is set to:
// "PhabricatorMailImplementationAmazonSESAdapter"
// - Make sure 'metamta.can-send-as-user' is false.
// - Make sure 'metamta.default-address' is configured to something sensible.
// - Make sure 'metamta.default-address' is a validated SES "From" address.
'amazon-ses.access-key' => null,
'amazon-ses.secret-key' => null,
// If you're using Sendgrid to send email, provide your access credentials
// here. This will use the REST API. You can also use Sendgrid as a normal
// SMTP service.
'sendgrid.api-user' => null,
'sendgrid.api-key' => null,
// You can configure a reply handler domain so that email sent from Maniphest
// will have a special "Reply To" address like "T123+82+af19f@example.com"
// that allows recipients to reply by email and interact with tasks. For
// instructions on configurating reply handlers, see the article
// "Configuring Inbound Email" in the Phabricator documentation. By default,
// this is set to 'null' and Phabricator will use a generic 'noreply@' address
// or the address of the acting user instead of a special reply handler
// address (see 'metamta.default-address'). If you set a domain here,
// Phabricator will begin generating private reply handler addresses. See
// also 'metamta.maniphest.reply-handler' to further configure behavior.
// This key should be set to the domain part after the @, like "example.com".
'metamta.maniphest.reply-handler-domain' => null,
// You can follow the instructions in "Configuring Inbound Email" in the
// Phabricator documentation and set 'metamta.maniphest.reply-handler-domain'
// to support updating Maniphest tasks by email. If you want more advanced
// customization than this provides, you can override the reply handler
// class with an implementation of your own. This will allow you to do things
// like have a single public reply handler or change how private reply
// handlers are generated and validated.
// This key should be set to a loadable subclass of
// PhabricatorMailReplyHandler (and possibly of ManiphestReplyHandler).
'metamta.maniphest.reply-handler' => 'ManiphestReplyHandler',
// Prefix prepended to mail sent by Maniphest. You can change this to
// distinguish between testing and development installs, for example.
'metamta.maniphest.subject-prefix' => '[Maniphest]',
// See 'metamta.maniphest.reply-handler-domain'. This does the same thing,
// but allows email replies via Differential.
'metamta.differential.reply-handler-domain' => null,
// See 'metamta.maniphest.reply-handler'. This does the same thing, but
// affects Differential.
'metamta.differential.reply-handler' => 'DifferentialReplyHandler',
// Prefix prepended to mail sent by Differential.
'metamta.differential.subject-prefix' => '[Differential]',
// By default, Phabricator generates unique reply-to addresses and sends a
// separate email to each recipient when you enable reply handling. This is
// more secure than using "From" to establish user identity, but can mean
// users may receive multiple emails when they are on mailing lists. Instead,
// you can use a single, non-unique reply to address and authenticate users
// based on the "From" address by setting this to 'true'. This trades away
// a little bit of security for convenience, but it's reasonable in many
// installs. Object interactions are still protected using hashes in the
// single public email address, so objects can not be replied to blindly.
'metamta.public-replies' => false,
// You can configure an email address like "bugs@phabricator.example.com"
// which will automatically create Maniphest tasks when users send email
// to it. This relies on the "From" address to authenticate users, so it is
// is not completely secure. To set this up, enter a complete email
// address like "bugs@phabricator.example.com" and then configure mail to
// that address so it routed to Phabricator (if you've already configured
// reply handlers, you're probably already done). See "Configuring Inbound
// Email" in the documentation for more information.
'metamta.maniphest.public-create-email' => null,
// -- Auth ------------------------------------------------------------------ //
// Can users login with a username/password, or by following the link from
// a password reset email? You can disable this and configure one or more
// OAuth providers instead.
'auth.password-auth-enabled' => true,
// Maximum number of simultaneous web sessions each user is permitted to have.
// Setting this to "1" will prevent a user from logging in on more than one
// browser at the same time.
'auth.sessions.web' => 5,
// Maximum number of simultaneous Conduit sessions each user is permitted
// to have.
'auth.sessions.conduit' => 3,
// Set this true to enable the Settings -> SSH Public Keys panel, which will
// allow users to associated SSH public keys with their accounts. This is only
// really useful if you're setting up services over SSH and want to use
// Phabricator for authentication; in most situations you can leave this
// disabled.
'auth.sshkeys.enabled' => false,
// -- Accounts -------------------------------------------------------------- //
// Is basic account information (email, real name, profile picture) editable?
// If you set up Phabricator to automatically synchronize account information
// from some other authoritative system, you can disable this to ensure
// information remains consistent across both systems.
'account.editable' => true,
// -- Facebook ------------------------------------------------------------ //
// Can users use Facebook credentials to login to Phabricator?
'facebook.auth-enabled' => false,
// Can users use Facebook credentials to create new Phabricator accounts?
'facebook.registration-enabled' => true,
// Are Facebook accounts permanently linked to Phabricator accounts, or can
// the user unlink them?
'facebook.auth-permanent' => false,
// The Facebook "Application ID" to use for Facebook API access.
'facebook.application-id' => null,
// The Facebook "Application Secret" to use for Facebook API access.
'facebook.application-secret' => null,
// -- Github ---------------------------------------------------------------- //
// Can users use Github credentials to login to Phabricator?
'github.auth-enabled' => false,
// Can users use Github credentials to create new Phabricator accounts?
'github.registration-enabled' => true,
// Are Github accounts permanently linked to Phabricator accounts, or can
// the user unlink them?
'github.auth-permanent' => false,
// The Github "Client ID" to use for Github API access.
'github.application-id' => null,
// The Github "Secret" to use for Github API access.
'github.application-secret' => null,
// -- Recaptcha ------------------------------------------------------------- //
// Is Recaptcha enabled? If disabled, captchas will not appear.
'recaptcha.enabled' => false,
// Your Recaptcha public key, obtained from Recaptcha.
'recaptcha.public-key' => null,
// Your Recaptcha private key, obtained from Recaptcha.
'recaptcha.private-key' => null,
// -- Misc ------------------------------------------------------------------ //
// This is hashed with other inputs to generate CSRF tokens. If you want, you
// can change it to some other string which is unique to your install. This
// will make your install more secure in a vague, mostly theoretical way. But
// it will take you like 3 seconds of mashing on your keyboard to set it up so
// you might as well.
'phabricator.csrf-key' => '0b7ec0592e0a2829d8b71df2fa269b2c6172eca3',
// This is hashed with other inputs to generate mail tokens. If you want, you
// can change it to some other string which is unique to your install. In
// particular, you will want to do this if you accidentally send a bunch of
// mail somewhere you shouldn't have, to invalidate all old reply-to
// addresses.
'phabricator.mail-key' => '5ce3e7e8787f6e40dfae861da315a5cdf1018f12',
// Version string displayed in the footer. You probably should leave this
// alone.
'phabricator.version' => 'UNSTABLE',
// PHP requires that you set a timezone in your php.ini before using date
// functions, or it will emit a warning. If this isn't possible (for instance,
// because you are using HPHP) you can set some valid constant for
// date_default_timezone_set() here and Phabricator will set it on your
// behalf, silencing the warning.
'phabricator.timezone' => null,
// -- Files ----------------------------------------------------------------- //
// Lists which uploaded file types may be viewed in the browser. If a file
// has a mime type which does not appear in this list, it will always be
// downloaded instead of displayed. This is a security consideration: if a
// user uploads a file of type "text/html" and it is displayed as
// "text/html", they can easily execute XSS attacks. This is also a usability
// consideration, since browsers tend to freak out when viewing enormous
// binary files.
//
// The keys in this array are viewable mime types; the values are the mime
// types they will be delivered as when they are viewed in the browser.
'files.viewable-mime-types' => array(
'image/jpeg' => 'image/jpeg',
'image/jpg' => 'image/jpg',
'image/png' => 'image/png',
'image/gif' => 'image/gif',
'text/plain' => 'text/plain; charset=utf-8',
),
// Phabricator can proxy images from other servers so you can paste the URI
// to a funny picture of a cat into the comment box and have it show up as an
// image. However, this means the webserver Phabricator is running on will
// make HTTP requests to arbitrary URIs. If the server has access to internal
// resources, this could be a security risk. You should only enable it if you
// are installed entirely a VPN and VPN access is required to access
// Phabricator, or if the webserver has no special access to anything. If
// unsure, it is safer to leave this disabled.
'files.enable-proxy' => false,
// -- Storage --------------------------------------------------------------- //
// Phabricator allows users to upload files, and can keep them in various
// storage engines. This section allows you to configure which engines
// Phabricator will use, and how it will use them.
// The largest filesize Phabricator will store in the MySQL BLOB storage
// engine, which just uses a database table to store files. While this isn't a
// best practice, it's really easy to set up. This is hard-limited by the
// value of 'max_allowed_packet' in MySQL (since this often defaults to 1MB,
// the default here is slightly smaller than 1MB). Set this to 0 to disable
// use of the MySQL blob engine.
'storage.mysql-engine.max-size' => 1000000,
// Phabricator provides a local disk storage engine, which just writes files
// to some directory on local disk. The webserver must have read/write
// permissions on this directory. This is straightforward and suitable for
// most installs, but will not scale past one web frontend unless the path
// is actually an NFS mount, since you'll end up with some of the files
// written to each web frontend and no way for them to share. To use the
// local disk storage engine, specify the path to a directory here. To
// disable it, specify null.
'storage.local-disk.path' => null,
- // TODO: Implement S3.
+ // If you want to store files in Amazon S3, specify an AWS access and secret
+ // key here and a bucket name below.
+ 'amazon-s3.access-key' => null,
+ 'amazon-s3.secret-key' => null,
+
+ // Set this to a valid Amazon S3 bucket to store files there. You must also
+ // configure S3 access keys above.
+ 'storage.s3.bucket' => null,
// Phabricator uses a storage engine selector to choose which storage engine
// to use when writing file data. If you add new storage engines or want to
// provide very custom rules (e.g., write images to one storage engine and
// other files to a different one), you can provide an alternate
// implementation here. The default engine will use choose MySQL, Local Disk,
// and S3, in that order, if they have valid configurations above and a file
// fits within configured limits.
'storage.engine-selector' => 'PhabricatorDefaultFileStorageEngineSelector',
// -- Differential ---------------------------------------------------------- //
'differential.revision-custom-detail-renderer' => null,
// Array for custom remarkup rules. The array should have a list of
// class names of classes that extend PhutilRemarkupRule
'differential.custom-remarkup-rules' => null,
// Array for custom remarkup block rules. The array should have a list of
// class names of classes that extend PhutilRemarkupEngineBlockRule
'differential.custom-remarkup-block-rules' => null,
// Set display word-wrap widths for Differential. Specify a dictionary of
// regular expressions mapping to column widths. The filename will be matched
// against each regexp in order until one matches. The default configuration
// uses a width of 100 for Java and 80 for other languages. Note that 80 is
// the greatest column width of all time. Changes here will not be immediately
// reflected in old revisions unless you purge the render cache.
'differential.wordwrap' => array(
'/\.java$/' => 100,
'/.*/' => 80,
),
// Class for appending custom fields to be included in the commit
// messages generated by "arc amend". Should inherit
// DifferentialCommitMessageModifier
'differential.modify-commit-message-class' => null,
// List of file regexps were whitespace is meaningful and should not
// use 'ignore-all' by default
'differential.whitespace-matters' => array(
'/\.py$/',
),
// -- Maniphest ------------------------------------------------------------- //
'maniphest.enabled' => true,
// Array of custom fields for Maniphest tasks. The array should contain
// arrays of field specifications keyed with 'type', 'label', 'caption',
// 'required' and whatever specific options exist for the given field
// type.
'maniphest.custom-fields' => array(),
// -- Remarkup -------------------------------------------------------------- //
// If you enable this, linked YouTube videos will be embeded inline. This has
// mild security implications (you'll leak referrers to YouTube) and is pretty
// silly (but sort of awesome).
'remarkup.enable-embedded-youtube' => false,
// -- Garbage Collection ---------------------------------------------------- //
// Phabricator generates various logs and caches in the database which can
// be garbage collected after a while to make the total data size more
// manageable. To run garbage collection, launch a
// PhabricatorGarbageCollector daemon.
// Since the GC daemon can issue large writes and table scans, you may want to
// run it only during off hours or make sure it is scheduled so it doesn't
// overlap with backups. This determines when the daemon can start running
// each day.
'gcdaemon.run-at' => '12 AM',
// How many seconds after 'gcdaemon.run-at' the daemon may collect garbage
// for. By default it runs continuously, but you can set it to run for a
// limited period of time. For instance, if you do backups at 3 AM, you might
// run garbage collection for an hour beforehand. This is not a high-precision
// limit so you may want to leave some room for the GC to actually stop, and
// if you set it to something like 3 seconds you're on your own.
'gcdaemon.run-for' => 24 * 60 * 60,
// These 'ttl' keys configure how much old data the GC daemon keeps around.
// Objects older than the ttl will be collected. Set any value to 0 to store
// data indefinitely.
'gcdaemon.ttl.herald-transcripts' => 30 * (24 * 60 * 60),
'gcdaemon.ttl.daemon-logs' => 7 * (24 * 60 * 60),
'gcdaemon.ttl.differential-parse-cache' => 14 * (24 * 60 * 60),
// -- Feed ------------------------------------------------------------------ //
// If you set this to true, you can embed Phabricator activity feeds in other
// pages using iframes. These feeds are completely public, and a login is not
// required to view them! This is intended for things like open source
// projects that want to expose an activity feed on the project homepage.
'feed.public' => false,
// -- Customization --------------------------------------------------------- //
// Paths to additional phutil libraries to load.
'load-libraries' => array(),
'aphront.default-application-configuration-class' =>
'AphrontDefaultApplicationConfiguration',
'controller.oauth-registration' =>
'PhabricatorOAuthDefaultRegistrationController',
// Directory that phd (the Phabricator daemon control script) should use to
// track running daemons.
'phd.pid-directory' => '/var/tmp/phd',
// This value is an input to the hash function when building resource hashes.
// It has no security value, but if you accidentally poison user caches (by
// pushing a bad patch or having something go wrong with a CDN, e.g.) you can
// change this to something else and rebuild the Celerity map to break user
// caches. Unless you are doing Celerity development, it is exceptionally
// unlikely that you need to modify this.
'celerity.resource-hash' => 'd9455ea150622ee044f7931dabfa52aa',
// In a development environment, it is desirable to force static resources
// (CSS and JS) to be read from disk on every request, so that edits to them
// appear when you reload the page even if you haven't updated the resource
// maps. This setting ensures requests will be verified against the state on
// disk. Generally, you should leave this off in production (caching behavior
// and performance improve with it off) but turn it on in development. (These
// settings are the defaults.)
'celerity.force-disk-reads' => false,
// -- Pygments ------------------------------------------------------------ //
// Phabricator can highlight PHP by default, but if you want syntax
// highlighting for other languages you should install the python package
// 'Pygments', make sure the 'pygmentize' script is available in the
// $PATH of the webserver, and then enable this.
'pygments.enabled' => false,
// In places that we display a dropdown to syntax-highlight code,
// this is where that list is defined.
// Syntax is 'lexer-name' => 'Display Name',
'pygments.dropdown-choices' => array(
'apacheconf' => 'Apache Configuration',
'bash' => 'Bash Scripting',
'brainfuck' => 'Brainf*ck',
'c' => 'C',
'cpp' => 'C++',
'css' => 'CSS',
'diff' => 'Diff',
'django' => 'Django Templating',
'erb' => 'Embedded Ruby/ERB',
'erlang' => 'Erlang',
'html' => 'HTML',
'infer' => 'Infer from title (extension)',
'java' => 'Java',
'js' => 'Javascript',
'mysql' => 'MySQL',
'perl' => 'Perl',
'php' => 'PHP',
'text' => 'Plain Text',
'python' => 'Python',
'rainbow' => 'Rainbow',
'remarkup' => 'Remarkup',
'ruby' => 'Ruby',
'xml' => 'XML',
),
'pygments.dropdown-default' => 'infer',
// This is an override list of regular expressions which allows you to choose
// what language files are highlighted as. If your projects have certain rules
// about filenames or use unusual or ambiguous language extensions, you can
// create a mapping here. This is an ordered dictionary of regular expressions
// which will be tested against the filename. They should map to either an
// explicit language as a string value, or a numeric index into the captured
// groups as an integer.
'syntax.filemap' => array(
// Example: Treat all '*.xyz' files as PHP.
// '@\\.xyz$@' => 'php',
// Example: Treat 'httpd.conf' as 'apacheconf'.
// '@/httpd\\.conf$@' => 'apacheconf',
// Example: Treat all '*.x.bak' file as '.x'. NOTE: we map to capturing
// group 1 by specifying the mapping as "1".
// '@\\.([^.]+)\\.bak$@' => 1,
),
);
diff --git a/externals/s3/README.txt b/externals/s3/README.txt
new file mode 100644
index 0000000000..28a9e73f92
--- /dev/null
+++ b/externals/s3/README.txt
@@ -0,0 +1,105 @@
+AMAZON S3 PHP CLASS
+
+
+USING THE CLASS
+
+OO method (e,g; $s3->getObject(...)):
+$s3 = new S3(awsAccessKey, awsSecretKey);
+
+Statically (e,g; S3::getObject(...)):
+S3::setAuth(awsAccessKey, awsSecretKey);
+
+
+For class documentation see:
+http://undesigned.org.za/files/s3-class-documentation/index.html
+
+
+OBJECTS
+
+
+Put an object from a string:
+ $s3->putObject($string, $bucketName, $uploadName, S3::ACL_PUBLIC_READ)
+ Legacy function: $s3->putObjectString($string, $bucketName, $uploadName, S3::ACL_PUBLIC_READ)
+
+
+Put an object from a file:
+ $s3->putObject($s3->inputFile($file, false), $bucketName, $uploadName, S3::ACL_PUBLIC_READ)
+ Legacy function: $s3->putObjectFile($uploadFile, $bucketName, $uploadName, S3::ACL_PUBLIC_READ)
+
+
+Put an object from a resource (buffer/file size is required):
+ Please note: the resource will be fclose()'d automatically
+ $s3->putObject($s3->inputResource(fopen($file, 'rb'), filesize($file)), $bucketName, $uploadName, S3::ACL_PUBLIC_READ)
+
+
+Get an object:
+ $s3->getObject($bucketName, $uploadName)
+
+
+Save an object to file:
+ $s3->getObject($bucketName, $uploadName, $saveName)
+
+
+Save an object to a resource of any type:
+ $s3->getObject($bucketName, $uploadName, fopen('savefile.txt', 'wb'))
+
+
+Copy an object:
+ $s3->copyObject($srcBucket, $srcName, $bucketName, $saveName, $metaHeaders = array(), $requestHeaders = array())
+
+
+Delete an object:
+ $s3->deleteObject($bucketName, $uploadName)
+
+
+
+BUCKETS
+
+
+Get a list of buckets:
+ $s3->listBuckets() // Simple bucket list
+ $s3->listBuckets(true) // Detailed bucket list
+
+
+Create a public-read bucket:
+ $s3->putBucket($bucketName, S3::ACL_PUBLIC_READ)
+ $s3->putBucket($bucketName, S3::ACL_PUBLIC_READ, 'EU') // EU-hosted bucket
+
+
+Get the contents of a bucket:
+ $s3->getBucket($bucketName)
+
+
+Get a bucket's location:
+ $s3->getBucketLocation($bucketName)
+
+
+Delete a bucket:
+ $s3->deleteBucket($bucketName)
+
+
+
+
+KNOWN ISSUES
+
+ Files larger than 2GB are not supported on 32 bit systems due to PHP’s signed integer problem
+
+
+
+MORE INFORMATION
+
+
+ Project URL:
+ http://undesigned.org.za/2007/10/22/amazon-s3-php-class
+
+ Class documentation:
+ http://undesigned.org.za/files/s3-class-documentation/index.html
+
+ Bug reports:
+ https://github.com/tpyo/amazon-s3-php-class/issues
+
+ Amazon S3 documentation:
+ http://docs.amazonwebservices.com/AmazonS3/2006-03-01/
+
+
+EOF
diff --git a/externals/s3/S3.php b/externals/s3/S3.php
new file mode 100644
index 0000000000..6a507338c7
--- /dev/null
+++ b/externals/s3/S3.php
@@ -0,0 +1,1923 @@
+<?php
+/**
+* $Id$
+*
+* Copyright (c) 2011, Donovan Schönknecht. All rights reserved.
+*
+* Redistribution and use in source and binary forms, with or without
+* modification, are permitted provided that the following conditions are met:
+*
+* - Redistributions of source code must retain the above copyright notice,
+* this list of conditions and the following disclaimer.
+* - Redistributions in binary form must reproduce the above copyright
+* notice, this list of conditions and the following disclaimer in the
+* documentation and/or other materials provided with the distribution.
+*
+* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+* POSSIBILITY OF SUCH DAMAGE.
+*
+* Amazon S3 is a trademark of Amazon.com, Inc. or its affiliates.
+*/
+
+/**
+* Amazon S3 PHP class
+*
+* @link http://undesigned.org.za/2007/10/22/amazon-s3-php-class
+* @version 0.5.0-dev
+*/
+class S3
+{
+ // ACL flags
+ const ACL_PRIVATE = 'private';
+ const ACL_PUBLIC_READ = 'public-read';
+ const ACL_PUBLIC_READ_WRITE = 'public-read-write';
+ const ACL_AUTHENTICATED_READ = 'authenticated-read';
+
+ const STORAGE_CLASS_STANDARD = 'STANDARD';
+ const STORAGE_CLASS_RRS = 'REDUCED_REDUNDANCY';
+
+ private static $__accessKey = null; // AWS Access key
+ private static $__secretKey = null; // AWS Secret key
+ private static $__sslKey = null;
+
+ public static $endpoint = 's3.amazonaws.com';
+ public static $proxy = null;
+
+ public static $useSSL = false;
+ public static $useSSLValidation = true;
+ public static $useExceptions = false;
+
+ // SSL CURL SSL options - only needed if you are experiencing problems with your OpenSSL configuration
+ public static $sslKey = null;
+ public static $sslCert = null;
+ public static $sslCACert = null;
+
+ private static $__signingKeyPairId = null; // AWS Key Pair ID
+ private static $__signingKeyResource = false; // Key resource, freeSigningKey() must be called to clear it from memory
+
+
+ /**
+ * Constructor - if you're not using the class statically
+ *
+ * @param string $accessKey Access key
+ * @param string $secretKey Secret key
+ * @param boolean $useSSL Enable SSL
+ * @return void
+ */
+ public function __construct($accessKey = null, $secretKey = null, $useSSL = false, $endpoint = 's3.amazonaws.com')
+ {
+ if ($accessKey !== null && $secretKey !== null)
+ self::setAuth($accessKey, $secretKey);
+ self::$useSSL = $useSSL;
+ self::$endpoint = $endpoint;
+ }
+
+
+ /**
+ * Set the sertvice endpoint
+ *
+ * @param string $host Hostname
+ * @return void
+ */
+ public function setEndpoint($host)
+ {
+ self::$endpoint = $host;
+ }
+
+ /**
+ * Set AWS access key and secret key
+ *
+ * @param string $accessKey Access key
+ * @param string $secretKey Secret key
+ * @return void
+ */
+ public static function setAuth($accessKey, $secretKey)
+ {
+ self::$__accessKey = $accessKey;
+ self::$__secretKey = $secretKey;
+ }
+
+
+ /**
+ * Check if AWS keys have been set
+ *
+ * @return boolean
+ */
+ public static function hasAuth() {
+ return (self::$__accessKey !== null && self::$__secretKey !== null);
+ }
+
+
+ /**
+ * Set SSL on or off
+ *
+ * @param boolean $enabled SSL enabled
+ * @param boolean $validate SSL certificate validation
+ * @return void
+ */
+ public static function setSSL($enabled, $validate = true)
+ {
+ self::$useSSL = $enabled;
+ self::$useSSLValidation = $validate;
+ }
+
+
+ /**
+ * Set SSL client certificates (experimental)
+ *
+ * @param string $sslCert SSL client certificate
+ * @param string $sslKey SSL client key
+ * @param string $sslCACert SSL CA cert (only required if you are having problems with your system CA cert)
+ * @return void
+ */
+ public static function setSSLAuth($sslCert = null, $sslKey = null, $sslCACert = null)
+ {
+ self::$sslCert = $sslCert;
+ self::$sslKey = $sslKey;
+ self::$sslCACert = $sslCACert;
+ }
+
+
+ /**
+ * Set proxy information
+ *
+ * @param string $host Proxy hostname and port (localhost:1234)
+ * @param string $user Proxy username
+ * @param string $pass Proxy password
+ * @param constant $type CURL proxy type
+ * @return void
+ */
+ public static function setProxy($host, $user = null, $pass = null, $type = CURLPROXY_SOCKS5)
+ {
+ self::$proxy = array('host' => $host, 'type' => $type, 'user' => null, 'pass' => 'null');
+ }
+
+
+ /**
+ * Set the error mode to exceptions
+ *
+ * @param boolean $enabled Enable exceptions
+ * @return void
+ */
+ public static function setExceptions($enabled = true)
+ {
+ self::$useExceptions = $enabled;
+ }
+
+
+ /**
+ * Set signing key
+ *
+ * @param string $keyPairId AWS Key Pair ID
+ * @param string $signingKey Private Key
+ * @param boolean $isFile Load private key from file, set to false to load string
+ * @return boolean
+ */
+ public static function setSigningKey($keyPairId, $signingKey, $isFile = true)
+ {
+ self::$__signingKeyPairId = $keyPairId;
+ if ((self::$__signingKeyResource = openssl_pkey_get_private($isFile ?
+ file_get_contents($signingKey) : $signingKey)) !== false) return true;
+ self::__triggerError('S3::setSigningKey(): Unable to open load private key: '.$signingKey, __FILE__, __LINE__);
+ return false;
+ }
+
+
+ /**
+ * Free signing key from memory, MUST be called if you are using setSigningKey()
+ *
+ * @return void
+ */
+ public static function freeSigningKey()
+ {
+ if (self::$__signingKeyResource !== false)
+ openssl_free_key(self::$__signingKeyResource);
+ }
+
+
+ /**
+ * Internal error handler
+ *
+ * @internal Internal error handler
+ * @param string $message Error message
+ * @param string $file Filename
+ * @param integer $line Line number
+ * @param integer $code Error code
+ * @return void
+ */
+ private static function __triggerError($message, $file, $line, $code = 0)
+ {
+ if (self::$useExceptions)
+ throw new S3Exception($message, $file, $line, $code);
+ else
+ trigger_error($message, E_USER_WARNING);
+ }
+
+
+ /**
+ * Get a list of buckets
+ *
+ * @param boolean $detailed Returns detailed bucket list when true
+ * @return array | false
+ */
+ public static function listBuckets($detailed = false)
+ {
+ $rest = new S3Request('GET', '', '', self::$endpoint);
+ $rest = $rest->getResponse();
+ if ($rest->error === false && $rest->code !== 200)
+ $rest->error = array('code' => $rest->code, 'message' => 'Unexpected HTTP status');
+ if ($rest->error !== false)
+ {
+ self::__triggerError(sprintf("S3::listBuckets(): [%s] %s", $rest->error['code'],
+ $rest->error['message']), __FILE__, __LINE__);
+ return false;
+ }
+ $results = array();
+ if (!isset($rest->body->Buckets)) return $results;
+
+ if ($detailed)
+ {
+ if (isset($rest->body->Owner, $rest->body->Owner->ID, $rest->body->Owner->DisplayName))
+ $results['owner'] = array(
+ 'id' => (string)$rest->body->Owner->ID, 'name' => (string)$rest->body->Owner->ID
+ );
+ $results['buckets'] = array();
+ foreach ($rest->body->Buckets->Bucket as $b)
+ $results['buckets'][] = array(
+ 'name' => (string)$b->Name, 'time' => strtotime((string)$b->CreationDate)
+ );
+ } else
+ foreach ($rest->body->Buckets->Bucket as $b) $results[] = (string)$b->Name;
+
+ return $results;
+ }
+
+
+ /*
+ * Get contents for a bucket
+ *
+ * If maxKeys is null this method will loop through truncated result sets
+ *
+ * @param string $bucket Bucket name
+ * @param string $prefix Prefix
+ * @param string $marker Marker (last file listed)
+ * @param string $maxKeys Max keys (maximum number of keys to return)
+ * @param string $delimiter Delimiter
+ * @param boolean $returnCommonPrefixes Set to true to return CommonPrefixes
+ * @return array | false
+ */
+ public static function getBucket($bucket, $prefix = null, $marker = null, $maxKeys = null, $delimiter = null, $returnCommonPrefixes = false)
+ {
+ $rest = new S3Request('GET', $bucket, '', self::$endpoint);
+ if ($maxKeys == 0) $maxKeys = null;
+ if ($prefix !== null && $prefix !== '') $rest->setParameter('prefix', $prefix);
+ if ($marker !== null && $marker !== '') $rest->setParameter('marker', $marker);
+ if ($maxKeys !== null && $maxKeys !== '') $rest->setParameter('max-keys', $maxKeys);
+ if ($delimiter !== null && $delimiter !== '') $rest->setParameter('delimiter', $delimiter);
+ $response = $rest->getResponse();
+ if ($response->error === false && $response->code !== 200)
+ $response->error = array('code' => $response->code, 'message' => 'Unexpected HTTP status');
+ if ($response->error !== false)
+ {
+ self::__triggerError(sprintf("S3::getBucket(): [%s] %s",
+ $response->error['code'], $response->error['message']), __FILE__, __LINE__);
+ return false;
+ }
+
+ $results = array();
+
+ $nextMarker = null;
+ if (isset($response->body, $response->body->Contents))
+ foreach ($response->body->Contents as $c)
+ {
+ $results[(string)$c->Key] = array(
+ 'name' => (string)$c->Key,
+ 'time' => strtotime((string)$c->LastModified),
+ 'size' => (int)$c->Size,
+ 'hash' => substr((string)$c->ETag, 1, -1)
+ );
+ $nextMarker = (string)$c->Key;
+ }
+
+ if ($returnCommonPrefixes && isset($response->body, $response->body->CommonPrefixes))
+ foreach ($response->body->CommonPrefixes as $c)
+ $results[(string)$c->Prefix] = array('prefix' => (string)$c->Prefix);
+
+ if (isset($response->body, $response->body->IsTruncated) &&
+ (string)$response->body->IsTruncated == 'false') return $results;
+
+ if (isset($response->body, $response->body->NextMarker))
+ $nextMarker = (string)$response->body->NextMarker;
+
+ // Loop through truncated results if maxKeys isn't specified
+ if ($maxKeys == null && $nextMarker !== null && (string)$response->body->IsTruncated == 'true')
+ do
+ {
+ $rest = new S3Request('GET', $bucket, '', self::$endpoint);
+ if ($prefix !== null && $prefix !== '') $rest->setParameter('prefix', $prefix);
+ $rest->setParameter('marker', $nextMarker);
+ if ($delimiter !== null && $delimiter !== '') $rest->setParameter('delimiter', $delimiter);
+
+ if (($response = $rest->getResponse(true)) == false || $response->code !== 200) break;
+
+ if (isset($response->body, $response->body->Contents))
+ foreach ($response->body->Contents as $c)
+ {
+ $results[(string)$c->Key] = array(
+ 'name' => (string)$c->Key,
+ 'time' => strtotime((string)$c->LastModified),
+ 'size' => (int)$c->Size,
+ 'hash' => substr((string)$c->ETag, 1, -1)
+ );
+ $nextMarker = (string)$c->Key;
+ }
+
+ if ($returnCommonPrefixes && isset($response->body, $response->body->CommonPrefixes))
+ foreach ($response->body->CommonPrefixes as $c)
+ $results[(string)$c->Prefix] = array('prefix' => (string)$c->Prefix);
+
+ if (isset($response->body, $response->body->NextMarker))
+ $nextMarker = (string)$response->body->NextMarker;
+
+ } while ($response !== false && (string)$response->body->IsTruncated == 'true');
+
+ return $results;
+ }
+
+
+ /**
+ * Put a bucket
+ *
+ * @param string $bucket Bucket name
+ * @param constant $acl ACL flag
+ * @param string $location Set as "EU" to create buckets hosted in Europe
+ * @return boolean
+ */
+ public static function putBucket($bucket, $acl = self::ACL_PRIVATE, $location = false)
+ {
+ $rest = new S3Request('PUT', $bucket, '', self::$endpoint);
+ $rest->setAmzHeader('x-amz-acl', $acl);
+
+ if ($location !== false)
+ {
+ $dom = new DOMDocument;
+ $createBucketConfiguration = $dom->createElement('CreateBucketConfiguration');
+ $locationConstraint = $dom->createElement('LocationConstraint', strtoupper($location));
+ $createBucketConfiguration->appendChild($locationConstraint);
+ $dom->appendChild($createBucketConfiguration);
+ $rest->data = $dom->saveXML();
+ $rest->size = strlen($rest->data);
+ $rest->setHeader('Content-Type', 'application/xml');
+ }
+ $rest = $rest->getResponse();
+
+ if ($rest->error === false && $rest->code !== 200)
+ $rest->error = array('code' => $rest->code, 'message' => 'Unexpected HTTP status');
+ if ($rest->error !== false)
+ {
+ self::__triggerError(sprintf("S3::putBucket({$bucket}, {$acl}, {$location}): [%s] %s",
+ $rest->error['code'], $rest->error['message']), __FILE__, __LINE__);
+ return false;
+ }
+ return true;
+ }
+
+
+ /**
+ * Delete an empty bucket
+ *
+ * @param string $bucket Bucket name
+ * @return boolean
+ */
+ public static function deleteBucket($bucket)
+ {
+ $rest = new S3Request('DELETE', $bucket, '', self::$endpoint);
+ $rest = $rest->getResponse();
+ if ($rest->error === false && $rest->code !== 204)
+ $rest->error = array('code' => $rest->code, 'message' => 'Unexpected HTTP status');
+ if ($rest->error !== false)
+ {
+ self::__triggerError(sprintf("S3::deleteBucket({$bucket}): [%s] %s",
+ $rest->error['code'], $rest->error['message']), __FILE__, __LINE__);
+ return false;
+ }
+ return true;
+ }
+
+
+ /**
+ * Create input info array for putObject()
+ *
+ * @param string $file Input file
+ * @param mixed $md5sum Use MD5 hash (supply a string if you want to use your own)
+ * @return array | false
+ */
+ public static function inputFile($file, $md5sum = true)
+ {
+ if (!file_exists($file) || !is_file($file) || !is_readable($file))
+ {
+ self::__triggerError('S3::inputFile(): Unable to open input file: '.$file, __FILE__, __LINE__);
+ return false;
+ }
+ return array('file' => $file, 'size' => filesize($file), 'md5sum' => $md5sum !== false ?
+ (is_string($md5sum) ? $md5sum : base64_encode(md5_file($file, true))) : '');
+ }
+
+
+ /**
+ * Create input array info for putObject() with a resource
+ *
+ * @param string $resource Input resource to read from
+ * @param integer $bufferSize Input byte size
+ * @param string $md5sum MD5 hash to send (optional)
+ * @return array | false
+ */
+ public static function inputResource(&$resource, $bufferSize, $md5sum = '')
+ {
+ if (!is_resource($resource) || $bufferSize < 0)
+ {
+ self::__triggerError('S3::inputResource(): Invalid resource or buffer size', __FILE__, __LINE__);
+ return false;
+ }
+ $input = array('size' => $bufferSize, 'md5sum' => $md5sum);
+ $input['fp'] =& $resource;
+ return $input;
+ }
+
+
+ /**
+ * Put an object
+ *
+ * @param mixed $input Input data
+ * @param string $bucket Bucket name
+ * @param string $uri Object URI
+ * @param constant $acl ACL constant
+ * @param array $metaHeaders Array of x-amz-meta-* headers
+ * @param array $requestHeaders Array of request headers or content type as a string
+ * @param constant $storageClass Storage class constant
+ * @return boolean
+ */
+ public static function putObject($input, $bucket, $uri, $acl = self::ACL_PRIVATE, $metaHeaders = array(), $requestHeaders = array(), $storageClass = self::STORAGE_CLASS_STANDARD)
+ {
+ if ($input === false) return false;
+ $rest = new S3Request('PUT', $bucket, $uri, self::$endpoint);
+
+ if (is_string($input)) $input = array(
+ 'data' => $input, 'size' => strlen($input),
+ 'md5sum' => base64_encode(md5($input, true))
+ );
+
+ // Data
+ if (isset($input['fp']))
+ $rest->fp =& $input['fp'];
+ elseif (isset($input['file']))
+ $rest->fp = @fopen($input['file'], 'rb');
+ elseif (isset($input['data']))
+ $rest->data = $input['data'];
+
+ // Content-Length (required)
+ if (isset($input['size']) && $input['size'] >= 0)
+ $rest->size = $input['size'];
+ else {
+ if (isset($input['file']))
+ $rest->size = filesize($input['file']);
+ elseif (isset($input['data']))
+ $rest->size = strlen($input['data']);
+ }
+
+ // Custom request headers (Content-Type, Content-Disposition, Content-Encoding)
+ if (is_array($requestHeaders))
+ foreach ($requestHeaders as $h => $v) $rest->setHeader($h, $v);
+ elseif (is_string($requestHeaders)) // Support for legacy contentType parameter
+ $input['type'] = $requestHeaders;
+
+ // Content-Type
+ if (!isset($input['type']))
+ {
+ if (isset($requestHeaders['Content-Type']))
+ $input['type'] =& $requestHeaders['Content-Type'];
+ elseif (isset($input['file']))
+ $input['type'] = self::__getMimeType($input['file']);
+ else
+ $input['type'] = 'application/octet-stream';
+ }
+
+ if ($storageClass !== self::STORAGE_CLASS_STANDARD) // Storage class
+ $rest->setAmzHeader('x-amz-storage-class', $storageClass);
+
+ // We need to post with Content-Length and Content-Type, MD5 is optional
+ if ($rest->size >= 0 && ($rest->fp !== false || $rest->data !== false))
+ {
+ $rest->setHeader('Content-Type', $input['type']);
+ if (isset($input['md5sum'])) $rest->setHeader('Content-MD5', $input['md5sum']);
+
+ $rest->setAmzHeader('x-amz-acl', $acl);
+ foreach ($metaHeaders as $h => $v) $rest->setAmzHeader('x-amz-meta-'.$h, $v);
+ $rest->getResponse();
+ } else
+ $rest->response->error = array('code' => 0, 'message' => 'Missing input parameters');
+
+ if ($rest->response->error === false && $rest->response->code !== 200)
+ $rest->response->error = array('code' => $rest->response->code, 'message' => 'Unexpected HTTP status');
+
+ if ($rest->response->error !== false)
+ {
+ self::__triggerError(sprintf("S3::putObject(): [%s] %s",
+ $rest->response->error['code'], $rest->response->error['message']), __FILE__, __LINE__);
+ return false;
+ }
+ return true;
+ }
+
+
+ /**
+ * Put an object from a file (legacy function)
+ *
+ * @param string $file Input file path
+ * @param string $bucket Bucket name
+ * @param string $uri Object URI
+ * @param constant $acl ACL constant
+ * @param array $metaHeaders Array of x-amz-meta-* headers
+ * @param string $contentType Content type
+ * @return boolean
+ */
+ public static function putObjectFile($file, $bucket, $uri, $acl = self::ACL_PRIVATE, $metaHeaders = array(), $contentType = null)
+ {
+ return self::putObject(self::inputFile($file), $bucket, $uri, $acl, $metaHeaders, $contentType);
+ }
+
+
+ /**
+ * Put an object from a string (legacy function)
+ *
+ * @param string $string Input data
+ * @param string $bucket Bucket name
+ * @param string $uri Object URI
+ * @param constant $acl ACL constant
+ * @param array $metaHeaders Array of x-amz-meta-* headers
+ * @param string $contentType Content type
+ * @return boolean
+ */
+ public static function putObjectString($string, $bucket, $uri, $acl = self::ACL_PRIVATE, $metaHeaders = array(), $contentType = 'text/plain')
+ {
+ return self::putObject($string, $bucket, $uri, $acl, $metaHeaders, $contentType);
+ }
+
+
+ /**
+ * Get an object
+ *
+ * @param string $bucket Bucket name
+ * @param string $uri Object URI
+ * @param mixed $saveTo Filename or resource to write to
+ * @return mixed
+ */
+ public static function getObject($bucket, $uri, $saveTo = false)
+ {
+ $rest = new S3Request('GET', $bucket, $uri, self::$endpoint);
+ if ($saveTo !== false)
+ {
+ if (is_resource($saveTo))
+ $rest->fp =& $saveTo;
+ else
+ if (($rest->fp = @fopen($saveTo, 'wb')) !== false)
+ $rest->file = realpath($saveTo);
+ else
+ $rest->response->error = array('code' => 0, 'message' => 'Unable to open save file for writing: '.$saveTo);
+ }
+ if ($rest->response->error === false) $rest->getResponse();
+
+ if ($rest->response->error === false && $rest->response->code !== 200)
+ $rest->response->error = array('code' => $rest->response->code, 'message' => 'Unexpected HTTP status');
+ if ($rest->response->error !== false)
+ {
+ self::__triggerError(sprintf("S3::getObject({$bucket}, {$uri}): [%s] %s",
+ $rest->response->error['code'], $rest->response->error['message']), __FILE__, __LINE__);
+ return false;
+ }
+ return $rest->response;
+ }
+
+
+ /**
+ * Get object information
+ *
+ * @param string $bucket Bucket name
+ * @param string $uri Object URI
+ * @param boolean $returnInfo Return response information
+ * @return mixed | false
+ */
+ public static function getObjectInfo($bucket, $uri, $returnInfo = true)
+ {
+ $rest = new S3Request('HEAD', $bucket, $uri, self::$endpoint);
+ $rest = $rest->getResponse();
+ if ($rest->error === false && ($rest->code !== 200 && $rest->code !== 404))
+ $rest->error = array('code' => $rest->code, 'message' => 'Unexpected HTTP status');
+ if ($rest->error !== false)
+ {
+ self::__triggerError(sprintf("S3::getObjectInfo({$bucket}, {$uri}): [%s] %s",
+ $rest->error['code'], $rest->error['message']), __FILE__, __LINE__);
+ return false;
+ }
+ return $rest->code == 200 ? $returnInfo ? $rest->headers : true : false;
+ }
+
+
+ /**
+ * Copy an object
+ *
+ * @param string $bucket Source bucket name
+ * @param string $uri Source object URI
+ * @param string $bucket Destination bucket name
+ * @param string $uri Destination object URI
+ * @param constant $acl ACL constant
+ * @param array $metaHeaders Optional array of x-amz-meta-* headers
+ * @param array $requestHeaders Optional array of request headers (content type, disposition, etc.)
+ * @param constant $storageClass Storage class constant
+ * @return mixed | false
+ */
+ public static function copyObject($srcBucket, $srcUri, $bucket, $uri, $acl = self::ACL_PRIVATE, $metaHeaders = array(), $requestHeaders = array(), $storageClass = self::STORAGE_CLASS_STANDARD)
+ {
+ $rest = new S3Request('PUT', $bucket, $uri, self::$endpoint);
+ $rest->setHeader('Content-Length', 0);
+ foreach ($requestHeaders as $h => $v) $rest->setHeader($h, $v);
+ foreach ($metaHeaders as $h => $v) $rest->setAmzHeader('x-amz-meta-'.$h, $v);
+ if ($storageClass !== self::STORAGE_CLASS_STANDARD) // Storage class
+ $rest->setAmzHeader('x-amz-storage-class', $storageClass);
+ $rest->setAmzHeader('x-amz-acl', $acl); // Added rawurlencode() for $srcUri (thanks a.yamanoi)
+ $rest->setAmzHeader('x-amz-copy-source', sprintf('/%s/%s', $srcBucket, rawurlencode($srcUri)));
+ if (sizeof($requestHeaders) > 0 || sizeof($metaHeaders) > 0)
+ $rest->setAmzHeader('x-amz-metadata-directive', 'REPLACE');
+
+ $rest = $rest->getResponse();
+ if ($rest->error === false && $rest->code !== 200)
+ $rest->error = array('code' => $rest->code, 'message' => 'Unexpected HTTP status');
+ if ($rest->error !== false)
+ {
+ self::__triggerError(sprintf("S3::copyObject({$srcBucket}, {$srcUri}, {$bucket}, {$uri}): [%s] %s",
+ $rest->error['code'], $rest->error['message']), __FILE__, __LINE__);
+ return false;
+ }
+ return isset($rest->body->LastModified, $rest->body->ETag) ? array(
+ 'time' => strtotime((string)$rest->body->LastModified),
+ 'hash' => substr((string)$rest->body->ETag, 1, -1)
+ ) : false;
+ }
+
+
+ /**
+ * Set logging for a bucket
+ *
+ * @param string $bucket Bucket name
+ * @param string $targetBucket Target bucket (where logs are stored)
+ * @param string $targetPrefix Log prefix (e,g; domain.com-)
+ * @return boolean
+ */
+ public static function setBucketLogging($bucket, $targetBucket, $targetPrefix = null)
+ {
+ // The S3 log delivery group has to be added to the target bucket's ACP
+ if ($targetBucket !== null && ($acp = self::getAccessControlPolicy($targetBucket, '')) !== false)
+ {
+ // Only add permissions to the target bucket when they do not exist
+ $aclWriteSet = false;
+ $aclReadSet = false;
+ foreach ($acp['acl'] as $acl)
+ if ($acl['type'] == 'Group' && $acl['uri'] == 'http://acs.amazonaws.com/groups/s3/LogDelivery')
+ {
+ if ($acl['permission'] == 'WRITE') $aclWriteSet = true;
+ elseif ($acl['permission'] == 'READ_ACP') $aclReadSet = true;
+ }
+ if (!$aclWriteSet) $acp['acl'][] = array(
+ 'type' => 'Group', 'uri' => 'http://acs.amazonaws.com/groups/s3/LogDelivery', 'permission' => 'WRITE'
+ );
+ if (!$aclReadSet) $acp['acl'][] = array(
+ 'type' => 'Group', 'uri' => 'http://acs.amazonaws.com/groups/s3/LogDelivery', 'permission' => 'READ_ACP'
+ );
+ if (!$aclReadSet || !$aclWriteSet) self::setAccessControlPolicy($targetBucket, '', $acp);
+ }
+
+ $dom = new DOMDocument;
+ $bucketLoggingStatus = $dom->createElement('BucketLoggingStatus');
+ $bucketLoggingStatus->setAttribute('xmlns', 'http://s3.amazonaws.com/doc/2006-03-01/');
+ if ($targetBucket !== null)
+ {
+ if ($targetPrefix == null) $targetPrefix = $bucket . '-';
+ $loggingEnabled = $dom->createElement('LoggingEnabled');
+ $loggingEnabled->appendChild($dom->createElement('TargetBucket', $targetBucket));
+ $loggingEnabled->appendChild($dom->createElement('TargetPrefix', $targetPrefix));
+ // TODO: Add TargetGrants?
+ $bucketLoggingStatus->appendChild($loggingEnabled);
+ }
+ $dom->appendChild($bucketLoggingStatus);
+
+ $rest = new S3Request('PUT', $bucket, '', self::$endpoint);
+ $rest->setParameter('logging', null);
+ $rest->data = $dom->saveXML();
+ $rest->size = strlen($rest->data);
+ $rest->setHeader('Content-Type', 'application/xml');
+ $rest = $rest->getResponse();
+ if ($rest->error === false && $rest->code !== 200)
+ $rest->error = array('code' => $rest->code, 'message' => 'Unexpected HTTP status');
+ if ($rest->error !== false)
+ {
+ self::__triggerError(sprintf("S3::setBucketLogging({$bucket}, {$uri}): [%s] %s",
+ $rest->error['code'], $rest->error['message']), __FILE__, __LINE__);
+ return false;
+ }
+ return true;
+ }
+
+
+ /**
+ * Get logging status for a bucket
+ *
+ * This will return false if logging is not enabled.
+ * Note: To enable logging, you also need to grant write access to the log group
+ *
+ * @param string $bucket Bucket name
+ * @return array | false
+ */
+ public static function getBucketLogging($bucket)
+ {
+ $rest = new S3Request('GET', $bucket, '', self::$endpoint);
+ $rest->setParameter('logging', null);
+ $rest = $rest->getResponse();
+ if ($rest->error === false && $rest->code !== 200)
+ $rest->error = array('code' => $rest->code, 'message' => 'Unexpected HTTP status');
+ if ($rest->error !== false)
+ {
+ self::__triggerError(sprintf("S3::getBucketLogging({$bucket}): [%s] %s",
+ $rest->error['code'], $rest->error['message']), __FILE__, __LINE__);
+ return false;
+ }
+ if (!isset($rest->body->LoggingEnabled)) return false; // No logging
+ return array(
+ 'targetBucket' => (string)$rest->body->LoggingEnabled->TargetBucket,
+ 'targetPrefix' => (string)$rest->body->LoggingEnabled->TargetPrefix,
+ );
+ }
+
+
+ /**
+ * Disable bucket logging
+ *
+ * @param string $bucket Bucket name
+ * @return boolean
+ */
+ public static function disableBucketLogging($bucket)
+ {
+ return self::setBucketLogging($bucket, null);
+ }
+
+
+ /**
+ * Get a bucket's location
+ *
+ * @param string $bucket Bucket name
+ * @return string | false
+ */
+ public static function getBucketLocation($bucket)
+ {
+ $rest = new S3Request('GET', $bucket, '', self::$endpoint);
+ $rest->setParameter('location', null);
+ $rest = $rest->getResponse();
+ if ($rest->error === false && $rest->code !== 200)
+ $rest->error = array('code' => $rest->code, 'message' => 'Unexpected HTTP status');
+ if ($rest->error !== false)
+ {
+ self::__triggerError(sprintf("S3::getBucketLocation({$bucket}): [%s] %s",
+ $rest->error['code'], $rest->error['message']), __FILE__, __LINE__);
+ return false;
+ }
+ return (isset($rest->body[0]) && (string)$rest->body[0] !== '') ? (string)$rest->body[0] : 'US';
+ }
+
+
+ /**
+ * Set object or bucket Access Control Policy
+ *
+ * @param string $bucket Bucket name
+ * @param string $uri Object URI
+ * @param array $acp Access Control Policy Data (same as the data returned from getAccessControlPolicy)
+ * @return boolean
+ */
+ public static function setAccessControlPolicy($bucket, $uri = '', $acp = array())
+ {
+ $dom = new DOMDocument;
+ $dom->formatOutput = true;
+ $accessControlPolicy = $dom->createElement('AccessControlPolicy');
+ $accessControlList = $dom->createElement('AccessControlList');
+
+ // It seems the owner has to be passed along too
+ $owner = $dom->createElement('Owner');
+ $owner->appendChild($dom->createElement('ID', $acp['owner']['id']));
+ $owner->appendChild($dom->createElement('DisplayName', $acp['owner']['name']));
+ $accessControlPolicy->appendChild($owner);
+
+ foreach ($acp['acl'] as $g)
+ {
+ $grant = $dom->createElement('Grant');
+ $grantee = $dom->createElement('Grantee');
+ $grantee->setAttribute('xmlns:xsi', 'http://www.w3.org/2001/XMLSchema-instance');
+ if (isset($g['id']))
+ { // CanonicalUser (DisplayName is omitted)
+ $grantee->setAttribute('xsi:type', 'CanonicalUser');
+ $grantee->appendChild($dom->createElement('ID', $g['id']));
+ }
+ elseif (isset($g['email']))
+ { // AmazonCustomerByEmail
+ $grantee->setAttribute('xsi:type', 'AmazonCustomerByEmail');
+ $grantee->appendChild($dom->createElement('EmailAddress', $g['email']));
+ }
+ elseif ($g['type'] == 'Group')
+ { // Group
+ $grantee->setAttribute('xsi:type', 'Group');
+ $grantee->appendChild($dom->createElement('URI', $g['uri']));
+ }
+ $grant->appendChild($grantee);
+ $grant->appendChild($dom->createElement('Permission', $g['permission']));
+ $accessControlList->appendChild($grant);
+ }
+
+ $accessControlPolicy->appendChild($accessControlList);
+ $dom->appendChild($accessControlPolicy);
+
+ $rest = new S3Request('PUT', $bucket, $uri, self::$endpoint);
+ $rest->setParameter('acl', null);
+ $rest->data = $dom->saveXML();
+ $rest->size = strlen($rest->data);
+ $rest->setHeader('Content-Type', 'application/xml');
+ $rest = $rest->getResponse();
+ if ($rest->error === false && $rest->code !== 200)
+ $rest->error = array('code' => $rest->code, 'message' => 'Unexpected HTTP status');
+ if ($rest->error !== false)
+ {
+ self::__triggerError(sprintf("S3::setAccessControlPolicy({$bucket}, {$uri}): [%s] %s",
+ $rest->error['code'], $rest->error['message']), __FILE__, __LINE__);
+ return false;
+ }
+ return true;
+ }
+
+
+ /**
+ * Get object or bucket Access Control Policy
+ *
+ * @param string $bucket Bucket name
+ * @param string $uri Object URI
+ * @return mixed | false
+ */
+ public static function getAccessControlPolicy($bucket, $uri = '')
+ {
+ $rest = new S3Request('GET', $bucket, $uri, self::$endpoint);
+ $rest->setParameter('acl', null);
+ $rest = $rest->getResponse();
+ if ($rest->error === false && $rest->code !== 200)
+ $rest->error = array('code' => $rest->code, 'message' => 'Unexpected HTTP status');
+ if ($rest->error !== false)
+ {
+ self::__triggerError(sprintf("S3::getAccessControlPolicy({$bucket}, {$uri}): [%s] %s",
+ $rest->error['code'], $rest->error['message']), __FILE__, __LINE__);
+ return false;
+ }
+
+ $acp = array();
+ if (isset($rest->body->Owner, $rest->body->Owner->ID, $rest->body->Owner->DisplayName))
+ $acp['owner'] = array(
+ 'id' => (string)$rest->body->Owner->ID, 'name' => (string)$rest->body->Owner->DisplayName
+ );
+
+ if (isset($rest->body->AccessControlList))
+ {
+ $acp['acl'] = array();
+ foreach ($rest->body->AccessControlList->Grant as $grant)
+ {
+ foreach ($grant->Grantee as $grantee)
+ {
+ if (isset($grantee->ID, $grantee->DisplayName)) // CanonicalUser
+ $acp['acl'][] = array(
+ 'type' => 'CanonicalUser',
+ 'id' => (string)$grantee->ID,
+ 'name' => (string)$grantee->DisplayName,
+ 'permission' => (string)$grant->Permission
+ );
+ elseif (isset($grantee->EmailAddress)) // AmazonCustomerByEmail
+ $acp['acl'][] = array(
+ 'type' => 'AmazonCustomerByEmail',
+ 'email' => (string)$grantee->EmailAddress,
+ 'permission' => (string)$grant->Permission
+ );
+ elseif (isset($grantee->URI)) // Group
+ $acp['acl'][] = array(
+ 'type' => 'Group',
+ 'uri' => (string)$grantee->URI,
+ 'permission' => (string)$grant->Permission
+ );
+ else continue;
+ }
+ }
+ }
+ return $acp;
+ }
+
+
+ /**
+ * Delete an object
+ *
+ * @param string $bucket Bucket name
+ * @param string $uri Object URI
+ * @return boolean
+ */
+ public static function deleteObject($bucket, $uri)
+ {
+ $rest = new S3Request('DELETE', $bucket, $uri, self::$endpoint);
+ $rest = $rest->getResponse();
+ if ($rest->error === false && $rest->code !== 204)
+ $rest->error = array('code' => $rest->code, 'message' => 'Unexpected HTTP status');
+ if ($rest->error !== false)
+ {
+ self::__triggerError(sprintf("S3::deleteObject(): [%s] %s",
+ $rest->error['code'], $rest->error['message']), __FILE__, __LINE__);
+ return false;
+ }
+ return true;
+ }
+
+
+ /**
+ * Get a query string authenticated URL
+ *
+ * @param string $bucket Bucket name
+ * @param string $uri Object URI
+ * @param integer $lifetime Lifetime in seconds
+ * @param boolean $hostBucket Use the bucket name as the hostname
+ * @param boolean $https Use HTTPS ($hostBucket should be false for SSL verification)
+ * @return string
+ */
+ public static function getAuthenticatedURL($bucket, $uri, $lifetime, $hostBucket = false, $https = false)
+ {
+ $expires = time() + $lifetime;
+ $uri = str_replace('%2F', '/', rawurlencode($uri)); // URI should be encoded (thanks Sean O'Dea)
+ return sprintf(($https ? 'https' : 'http').'://%s/%s?AWSAccessKeyId=%s&Expires=%u&Signature=%s',
+ $hostBucket ? $bucket : $bucket.'.s3.amazonaws.com', $uri, self::$__accessKey, $expires,
+ urlencode(self::__getHash("GET\n\n\n{$expires}\n/{$bucket}/{$uri}")));
+ }
+
+
+ /**
+ * Get a CloudFront signed policy URL
+ *
+ * @param array $policy Policy
+ * @return string
+ */
+ public static function getSignedPolicyURL($policy)
+ {
+ $data = json_encode($policy);
+ $signature = '';
+ if (!openssl_sign($data, $signature, self::$__signingKeyResource)) return false;
+
+ $encoded = str_replace(array('+', '='), array('-', '_', '~'), base64_encode($data));
+ $signature = str_replace(array('+', '='), array('-', '_', '~'), base64_encode($signature));
+
+ $url = $policy['Statement'][0]['Resource'] . '?';
+
+ foreach (array('Policy' => $encoded, 'Signature' => $signature, 'Key-Pair-Id' => self::$__signingKeyPairId) as $k => $v)
+ $url .= $k.'='.str_replace('%2F', '/', rawurlencode($v)).'&';
+ return substr($url, 0, -1);
+ }
+
+
+ /**
+ * Get a CloudFront canned policy URL
+ *
+ * @param string $string URL to sign
+ * @param integer $lifetime URL lifetime
+ * @return string
+ */
+ public static function getSignedCannedURL($url, $lifetime)
+ {
+ return self::getSignedPolicyURL(array(
+ 'Statement' => array(
+ array('Resource' => $url, 'Condition' => array(
+ 'DateLessThan' => array('AWS:EpochTime' => time() + $lifetime)
+ ))
+ )
+ ));
+ }
+
+
+ /**
+ * Get upload POST parameters for form uploads
+ *
+ * @param string $bucket Bucket name
+ * @param string $uriPrefix Object URI prefix
+ * @param constant $acl ACL constant
+ * @param integer $lifetime Lifetime in seconds
+ * @param integer $maxFileSize Maximum filesize in bytes (default 5MB)
+ * @param string $successRedirect Redirect URL or 200 / 201 status code
+ * @param array $amzHeaders Array of x-amz-meta-* headers
+ * @param array $headers Array of request headers or content type as a string
+ * @param boolean $flashVars Includes additional "Filename" variable posted by Flash
+ * @return object
+ */
+ public static function getHttpUploadPostParams($bucket, $uriPrefix = '', $acl = self::ACL_PRIVATE, $lifetime = 3600,
+ $maxFileSize = 5242880, $successRedirect = "201", $amzHeaders = array(), $headers = array(), $flashVars = false)
+ {
+ // Create policy object
+ $policy = new stdClass;
+ $policy->expiration = gmdate('Y-m-d\TH:i:s\Z', (time() + $lifetime));
+ $policy->conditions = array();
+ $obj = new stdClass; $obj->bucket = $bucket; array_push($policy->conditions, $obj);
+ $obj = new stdClass; $obj->acl = $acl; array_push($policy->conditions, $obj);
+
+ $obj = new stdClass; // 200 for non-redirect uploads
+ if (is_numeric($successRedirect) && in_array((int)$successRedirect, array(200, 201)))
+ $obj->success_action_status = (string)$successRedirect;
+ else // URL
+ $obj->success_action_redirect = $successRedirect;
+ array_push($policy->conditions, $obj);
+
+ if ($acl !== self::ACL_PUBLIC_READ)
+ array_push($policy->conditions, array('eq', '$acl', $acl));
+
+ array_push($policy->conditions, array('starts-with', '$key', $uriPrefix));
+ if ($flashVars) array_push($policy->conditions, array('starts-with', '$Filename', ''));
+ foreach (array_keys($headers) as $headerKey)
+ array_push($policy->conditions, array('starts-with', '$'.$headerKey, ''));
+ foreach ($amzHeaders as $headerKey => $headerVal)
+ {
+ $obj = new stdClass;
+ $obj->{$headerKey} = (string)$headerVal;
+ array_push($policy->conditions, $obj);
+ }
+ array_push($policy->conditions, array('content-length-range', 0, $maxFileSize));
+ $policy = base64_encode(str_replace('\/', '/', json_encode($policy)));
+
+ // Create parameters
+ $params = new stdClass;
+ $params->AWSAccessKeyId = self::$__accessKey;
+ $params->key = $uriPrefix.'${filename}';
+ $params->acl = $acl;
+ $params->policy = $policy; unset($policy);
+ $params->signature = self::__getHash($params->policy);
+ if (is_numeric($successRedirect) && in_array((int)$successRedirect, array(200, 201)))
+ $params->success_action_status = (string)$successRedirect;
+ else
+ $params->success_action_redirect = $successRedirect;
+ foreach ($headers as $headerKey => $headerVal) $params->{$headerKey} = (string)$headerVal;
+ foreach ($amzHeaders as $headerKey => $headerVal) $params->{$headerKey} = (string)$headerVal;
+ return $params;
+ }
+
+
+ /**
+ * Create a CloudFront distribution
+ *
+ * @param string $bucket Bucket name
+ * @param boolean $enabled Enabled (true/false)
+ * @param array $cnames Array containing CNAME aliases
+ * @param string $comment Use the bucket name as the hostname
+ * @param string $defaultRootObject Default root object
+ * @param string $originAccessIdentity Origin access identity
+ * @param array $trustedSigners Array of trusted signers
+ * @return array | false
+ */
+ public static function createDistribution($bucket, $enabled = true, $cnames = array(), $comment = null, $defaultRootObject = null, $originAccessIdentity = null, $trustedSigners = array())
+ {
+ if (!extension_loaded('openssl'))
+ {
+ self::__triggerError(sprintf("S3::createDistribution({$bucket}, ".(int)$enabled.", [], '$comment'): %s",
+ "CloudFront functionality requires SSL"), __FILE__, __LINE__);
+ return false;
+ }
+ $useSSL = self::$useSSL;
+
+ self::$useSSL = true; // CloudFront requires SSL
+ $rest = new S3Request('POST', '', '2010-11-01/distribution', 'cloudfront.amazonaws.com');
+ $rest->data = self::__getCloudFrontDistributionConfigXML(
+ $bucket.'.s3.amazonaws.com',
+ $enabled,
+ (string)$comment,
+ (string)microtime(true),
+ $cnames,
+ $defaultRootObject,
+ $originAccessIdentity,
+ $trustedSigners
+ );
+
+ $rest->size = strlen($rest->data);
+ $rest->setHeader('Content-Type', 'application/xml');
+ $rest = self::__getCloudFrontResponse($rest);
+
+ self::$useSSL = $useSSL;
+
+ if ($rest->error === false && $rest->code !== 201)
+ $rest->error = array('code' => $rest->code, 'message' => 'Unexpected HTTP status');
+ if ($rest->error !== false)
+ {
+ self::__triggerError(sprintf("S3::createDistribution({$bucket}, ".(int)$enabled.", [], '$comment'): [%s] %s",
+ $rest->error['code'], $rest->error['message']), __FILE__, __LINE__);
+ return false;
+ } elseif ($rest->body instanceof SimpleXMLElement)
+ return self::__parseCloudFrontDistributionConfig($rest->body);
+ return false;
+ }
+
+
+ /**
+ * Get CloudFront distribution info
+ *
+ * @param string $distributionId Distribution ID from listDistributions()
+ * @return array | false
+ */
+ public static function getDistribution($distributionId)
+ {
+ if (!extension_loaded('openssl'))
+ {
+ self::__triggerError(sprintf("S3::getDistribution($distributionId): %s",
+ "CloudFront functionality requires SSL"), __FILE__, __LINE__);
+ return false;
+ }
+ $useSSL = self::$useSSL;
+
+ self::$useSSL = true; // CloudFront requires SSL
+ $rest = new S3Request('GET', '', '2010-11-01/distribution/'.$distributionId, 'cloudfront.amazonaws.com');
+ $rest = self::__getCloudFrontResponse($rest);
+
+ self::$useSSL = $useSSL;
+
+ if ($rest->error === false && $rest->code !== 200)
+ $rest->error = array('code' => $rest->code, 'message' => 'Unexpected HTTP status');
+ if ($rest->error !== false)
+ {
+ self::__triggerError(sprintf("S3::getDistribution($distributionId): [%s] %s",
+ $rest->error['code'], $rest->error['message']), __FILE__, __LINE__);
+ return false;
+ }
+ elseif ($rest->body instanceof SimpleXMLElement)
+ {
+ $dist = self::__parseCloudFrontDistributionConfig($rest->body);
+ $dist['hash'] = $rest->headers['hash'];
+ $dist['id'] = $distributionId;
+ return $dist;
+ }
+ return false;
+ }
+
+
+ /**
+ * Update a CloudFront distribution
+ *
+ * @param array $dist Distribution array info identical to output of getDistribution()
+ * @return array | false
+ */
+ public static function updateDistribution($dist)
+ {
+ if (!extension_loaded('openssl'))
+ {
+ self::__triggerError(sprintf("S3::updateDistribution({$dist['id']}): %s",
+ "CloudFront functionality requires SSL"), __FILE__, __LINE__);
+ return false;
+ }
+
+ $useSSL = self::$useSSL;
+
+ self::$useSSL = true; // CloudFront requires SSL
+ $rest = new S3Request('PUT', '', '2010-11-01/distribution/'.$dist['id'].'/config', 'cloudfront.amazonaws.com');
+ $rest->data = self::__getCloudFrontDistributionConfigXML(
+ $dist['origin'],
+ $dist['enabled'],
+ $dist['comment'],
+ $dist['callerReference'],
+ $dist['cnames'],
+ $dist['defaultRootObject'],
+ $dist['originAccessIdentity'],
+ $dist['trustedSigners']
+ );
+
+ $rest->size = strlen($rest->data);
+ $rest->setHeader('If-Match', $dist['hash']);
+ $rest = self::__getCloudFrontResponse($rest);
+
+ self::$useSSL = $useSSL;
+
+ if ($rest->error === false && $rest->code !== 200)
+ $rest->error = array('code' => $rest->code, 'message' => 'Unexpected HTTP status');
+ if ($rest->error !== false)
+ {
+ self::__triggerError(sprintf("S3::updateDistribution({$dist['id']}): [%s] %s",
+ $rest->error['code'], $rest->error['message']), __FILE__, __LINE__);
+ return false;
+ } else {
+ $dist = self::__parseCloudFrontDistributionConfig($rest->body);
+ $dist['hash'] = $rest->headers['hash'];
+ return $dist;
+ }
+ return false;
+ }
+
+
+ /**
+ * Delete a CloudFront distribution
+ *
+ * @param array $dist Distribution array info identical to output of getDistribution()
+ * @return boolean
+ */
+ public static function deleteDistribution($dist)
+ {
+ if (!extension_loaded('openssl'))
+ {
+ self::__triggerError(sprintf("S3::deleteDistribution({$dist['id']}): %s",
+ "CloudFront functionality requires SSL"), __FILE__, __LINE__);
+ return false;
+ }
+
+ $useSSL = self::$useSSL;
+
+ self::$useSSL = true; // CloudFront requires SSL
+ $rest = new S3Request('DELETE', '', '2008-06-30/distribution/'.$dist['id'], 'cloudfront.amazonaws.com');
+ $rest->setHeader('If-Match', $dist['hash']);
+ $rest = self::__getCloudFrontResponse($rest);
+
+ self::$useSSL = $useSSL;
+
+ if ($rest->error === false && $rest->code !== 204)
+ $rest->error = array('code' => $rest->code, 'message' => 'Unexpected HTTP status');
+ if ($rest->error !== false)
+ {
+ self::__triggerError(sprintf("S3::deleteDistribution({$dist['id']}): [%s] %s",
+ $rest->error['code'], $rest->error['message']), __FILE__, __LINE__);
+ return false;
+ }
+ return true;
+ }
+
+
+ /**
+ * Get a list of CloudFront distributions
+ *
+ * @return array
+ */
+ public static function listDistributions()
+ {
+ if (!extension_loaded('openssl'))
+ {
+ self::__triggerError(sprintf("S3::listDistributions(): [%s] %s",
+ "CloudFront functionality requires SSL"), __FILE__, __LINE__);
+ return false;
+ }
+
+ $useSSL = self::$useSSL;
+ self::$useSSL = true; // CloudFront requires SSL
+ $rest = new S3Request('GET', '', '2010-11-01/distribution', 'cloudfront.amazonaws.com');
+ $rest = self::__getCloudFrontResponse($rest);
+ self::$useSSL = $useSSL;
+
+ if ($rest->error === false && $rest->code !== 200)
+ $rest->error = array('code' => $rest->code, 'message' => 'Unexpected HTTP status');
+ if ($rest->error !== false)
+ {
+ self::__triggerError(sprintf("S3::listDistributions(): [%s] %s",
+ $rest->error['code'], $rest->error['message']), __FILE__, __LINE__);
+ return false;
+ }
+ elseif ($rest->body instanceof SimpleXMLElement && isset($rest->body->DistributionSummary))
+ {
+ $list = array();
+ if (isset($rest->body->Marker, $rest->body->MaxItems, $rest->body->IsTruncated))
+ {
+ //$info['marker'] = (string)$rest->body->Marker;
+ //$info['maxItems'] = (int)$rest->body->MaxItems;
+ //$info['isTruncated'] = (string)$rest->body->IsTruncated == 'true' ? true : false;
+ }
+ foreach ($rest->body->DistributionSummary as $summary)
+ $list[(string)$summary->Id] = self::__parseCloudFrontDistributionConfig($summary);
+
+ return $list;
+ }
+ return array();
+ }
+
+ /**
+ * List CloudFront Origin Access Identities
+ *
+ * @return array
+ */
+ public static function listOriginAccessIdentities()
+ {
+ if (!extension_loaded('openssl'))
+ {
+ self::__triggerError(sprintf("S3::listOriginAccessIdentities(): [%s] %s",
+ "CloudFront functionality requires SSL"), __FILE__, __LINE__);
+ return false;
+ }
+
+ self::$useSSL = true; // CloudFront requires SSL
+ $rest = new S3Request('GET', '', '2010-11-01/origin-access-identity/cloudfront', 'cloudfront.amazonaws.com');
+ $rest = self::__getCloudFrontResponse($rest);
+ $useSSL = self::$useSSL;
+
+ if ($rest->error === false && $rest->code !== 200)
+ $rest->error = array('code' => $rest->code, 'message' => 'Unexpected HTTP status');
+ if ($rest->error !== false)
+ {
+ trigger_error(sprintf("S3::listOriginAccessIdentities(): [%s] %s",
+ $rest->error['code'], $rest->error['message']), E_USER_WARNING);
+ return false;
+ }
+
+ if (isset($rest->body->CloudFrontOriginAccessIdentitySummary))
+ {
+ $identities = array();
+ foreach ($rest->body->CloudFrontOriginAccessIdentitySummary as $identity)
+ if (isset($identity->S3CanonicalUserId))
+ $identities[(string)$identity->Id] = array('id' => (string)$identity->Id, 's3CanonicalUserId' => (string)$identity->S3CanonicalUserId);
+ return $identities;
+ }
+ return false;
+ }
+
+
+ /**
+ * Invalidate objects in a CloudFront distribution
+ *
+ * Thanks to Martin Lindkvist for S3::invalidateDistribution()
+ *
+ * @param string $distributionId Distribution ID from listDistributions()
+ * @param array $paths Array of object paths to invalidate
+ * @return boolean
+ */
+ public static function invalidateDistribution($distributionId, $paths)
+ {
+ if (!extension_loaded('openssl'))
+ {
+ self::__triggerError(sprintf("S3::invalidateDistribution(): [%s] %s",
+ "CloudFront functionality requires SSL"), __FILE__, __LINE__);
+ return false;
+ }
+
+ $useSSL = self::$useSSL;
+ self::$useSSL = true; // CloudFront requires SSL
+ $rest = new S3Request('POST', '', '2010-08-01/distribution/'.$distributionId.'/invalidation', 'cloudfront.amazonaws.com');
+ $rest->data = self::__getCloudFrontInvalidationBatchXML($paths, (string)microtime(true));
+ $rest->size = strlen($rest->data);
+ $rest = self::__getCloudFrontResponse($rest);
+ self::$useSSL = $useSSL;
+
+ if ($rest->error === false && $rest->code !== 201)
+ $rest->error = array('code' => $rest->code, 'message' => 'Unexpected HTTP status');
+ if ($rest->error !== false)
+ {
+ trigger_error(sprintf("S3::invalidate('{$distributionId}',{$paths}): [%s] %s",
+ $rest->error['code'], $rest->error['message']), E_USER_WARNING);
+ return false;
+ }
+ return true;
+ }
+
+
+ /**
+ * Get a InvalidationBatch DOMDocument
+ *
+ * @internal Used to create XML in invalidateDistribution()
+ * @param array $paths Paths to objects to invalidateDistribution
+ * @return string
+ */
+ private static function __getCloudFrontInvalidationBatchXML($paths, $callerReference = '0') {
+ $dom = new DOMDocument('1.0', 'UTF-8');
+ $dom->formatOutput = true;
+ $invalidationBatch = $dom->createElement('InvalidationBatch');
+ foreach ($paths as $path)
+ $invalidationBatch->appendChild($dom->createElement('Path', $path));
+
+ $invalidationBatch->appendChild($dom->createElement('CallerReference', $callerReference));
+ $dom->appendChild($invalidationBatch);
+ return $dom->saveXML();
+ }
+
+
+ /**
+ * Get a DistributionConfig DOMDocument
+ *
+ * http://docs.amazonwebservices.com/AmazonCloudFront/latest/APIReference/index.html?PutConfig.html
+ *
+ * @internal Used to create XML in createDistribution() and updateDistribution()
+ * @param string $bucket S3 Origin bucket
+ * @param boolean $enabled Enabled (true/false)
+ * @param string $comment Comment to append
+ * @param string $callerReference Caller reference
+ * @param array $cnames Array of CNAME aliases
+ * @param string $defaultRootObject Default root object
+ * @param string $originAccessIdentity Origin access identity
+ * @param array $trustedSigners Array of trusted signers
+ * @return string
+ */
+ private static function __getCloudFrontDistributionConfigXML($bucket, $enabled, $comment, $callerReference = '0', $cnames = array(), $defaultRootObject = null, $originAccessIdentity = null, $trustedSigners = array())
+ {
+ $dom = new DOMDocument('1.0', 'UTF-8');
+ $dom->formatOutput = true;
+ $distributionConfig = $dom->createElement('DistributionConfig');
+ $distributionConfig->setAttribute('xmlns', 'http://cloudfront.amazonaws.com/doc/2010-11-01/');
+
+ $origin = $dom->createElement('S3Origin');
+ $origin->appendChild($dom->createElement('DNSName', $bucket));
+ if ($originAccessIdentity !== null) $origin->appendChild($dom->createElement('OriginAccessIdentity', $originAccessIdentity));
+ $distributionConfig->appendChild($origin);
+
+ if ($defaultRootObject !== null) $distributionConfig->appendChild($dom->createElement('DefaultRootObject', $defaultRootObject));
+
+ $distributionConfig->appendChild($dom->createElement('CallerReference', $callerReference));
+ foreach ($cnames as $cname)
+ $distributionConfig->appendChild($dom->createElement('CNAME', $cname));
+ if ($comment !== '') $distributionConfig->appendChild($dom->createElement('Comment', $comment));
+ $distributionConfig->appendChild($dom->createElement('Enabled', $enabled ? 'true' : 'false'));
+
+ $trusted = $dom->createElement('TrustedSigners');
+ foreach ($trustedSigners as $id => $type)
+ $trusted->appendChild($id !== '' ? $dom->createElement($type, $id) : $dom->createElement($type));
+ $distributionConfig->appendChild($trusted);
+
+ $dom->appendChild($distributionConfig);
+ //var_dump($dom->saveXML());
+ return $dom->saveXML();
+ }
+
+
+ /**
+ * Parse a CloudFront distribution config
+ *
+ * See http://docs.amazonwebservices.com/AmazonCloudFront/latest/APIReference/index.html?GetDistribution.html
+ *
+ * @internal Used to parse the CloudFront DistributionConfig node to an array
+ * @param object &$node DOMNode
+ * @return array
+ */
+ private static function __parseCloudFrontDistributionConfig(&$node)
+ {
+ if (isset($node->DistributionConfig))
+ return self::__parseCloudFrontDistributionConfig($node->DistributionConfig);
+
+ $dist = array();
+ if (isset($node->Id, $node->Status, $node->LastModifiedTime, $node->DomainName))
+ {
+ $dist['id'] = (string)$node->Id;
+ $dist['status'] = (string)$node->Status;
+ $dist['time'] = strtotime((string)$node->LastModifiedTime);
+ $dist['domain'] = (string)$node->DomainName;
+ }
+
+ if (isset($node->CallerReference))
+ $dist['callerReference'] = (string)$node->CallerReference;
+
+ if (isset($node->Enabled))
+ $dist['enabled'] = (string)$node->Enabled == 'true' ? true : false;
+
+ if (isset($node->S3Origin))
+ {
+ if (isset($node->S3Origin->DNSName))
+ $dist['origin'] = (string)$node->S3Origin->DNSName;
+
+ $dist['originAccessIdentity'] = isset($node->S3Origin->OriginAccessIdentity) ?
+ (string)$node->S3Origin->OriginAccessIdentity : null;
+ }
+
+ $dist['defaultRootObject'] = isset($node->DefaultRootObject) ? (string)$node->DefaultRootObject : null;
+
+ $dist['cnames'] = array();
+ if (isset($node->CNAME))
+ foreach ($node->CNAME as $cname)
+ $dist['cnames'][(string)$cname] = (string)$cname;
+
+ $dist['trustedSigners'] = array();
+ if (isset($node->TrustedSigners))
+ foreach ($node->TrustedSigners as $signer)
+ {
+ if (isset($signer->Self))
+ $dist['trustedSigners'][''] = 'Self';
+ elseif (isset($signer->KeyPairId))
+ $dist['trustedSigners'][(string)$signer->KeyPairId] = 'KeyPairId';
+ elseif (isset($signer->AwsAccountNumber))
+ $dist['trustedSigners'][(string)$signer->AwsAccountNumber] = 'AwsAccountNumber';
+ }
+
+ $dist['comment'] = isset($node->Comment) ? (string)$node->Comment : null;
+ return $dist;
+ }
+
+
+ /**
+ * Grab CloudFront response
+ *
+ * @internal Used to parse the CloudFront S3Request::getResponse() output
+ * @param object &$rest S3Request instance
+ * @return object
+ */
+ private static function __getCloudFrontResponse(&$rest)
+ {
+ $rest->getResponse();
+ if ($rest->response->error === false && isset($rest->response->body) &&
+ is_string($rest->response->body) && substr($rest->response->body, 0, 5) == '<?xml')
+ {
+ $rest->response->body = simplexml_load_string($rest->response->body);
+ // Grab CloudFront errors
+ if (isset($rest->response->body->Error, $rest->response->body->Error->Code,
+ $rest->response->body->Error->Message))
+ {
+ $rest->response->error = array(
+ 'code' => (string)$rest->response->body->Error->Code,
+ 'message' => (string)$rest->response->body->Error->Message
+ );
+ unset($rest->response->body);
+ }
+ }
+ return $rest->response;
+ }
+
+
+ /**
+ * Get MIME type for file
+ *
+ * @internal Used to get mime types
+ * @param string &$file File path
+ * @return string
+ */
+ public static function __getMimeType(&$file)
+ {
+ $type = false;
+ // Fileinfo documentation says fileinfo_open() will use the
+ // MAGIC env var for the magic file
+ if (extension_loaded('fileinfo') && isset($_ENV['MAGIC']) &&
+ ($finfo = finfo_open(FILEINFO_MIME, $_ENV['MAGIC'])) !== false)
+ {
+ if (($type = finfo_file($finfo, $file)) !== false)
+ {
+ // Remove the charset and grab the last content-type
+ $type = explode(' ', str_replace('; charset=', ';charset=', $type));
+ $type = array_pop($type);
+ $type = explode(';', $type);
+ $type = trim(array_shift($type));
+ }
+ finfo_close($finfo);
+
+ // If anyone is still using mime_content_type()
+ } elseif (function_exists('mime_content_type'))
+ $type = trim(mime_content_type($file));
+
+ if ($type !== false && strlen($type) > 0) return $type;
+
+ // Otherwise do it the old fashioned way
+ static $exts = array(
+ 'jpg' => 'image/jpeg', 'gif' => 'image/gif', 'png' => 'image/png',
+ 'tif' => 'image/tiff', 'tiff' => 'image/tiff', 'ico' => 'image/x-icon',
+ 'swf' => 'application/x-shockwave-flash', 'pdf' => 'application/pdf',
+ 'zip' => 'application/zip', 'gz' => 'application/x-gzip',
+ 'tar' => 'application/x-tar', 'bz' => 'application/x-bzip',
+ 'bz2' => 'application/x-bzip2', 'txt' => 'text/plain',
+ 'asc' => 'text/plain', 'htm' => 'text/html', 'html' => 'text/html',
+ 'css' => 'text/css', 'js' => 'text/javascript',
+ 'xml' => 'text/xml', 'xsl' => 'application/xsl+xml',
+ 'ogg' => 'application/ogg', 'mp3' => 'audio/mpeg', 'wav' => 'audio/x-wav',
+ 'avi' => 'video/x-msvideo', 'mpg' => 'video/mpeg', 'mpeg' => 'video/mpeg',
+ 'mov' => 'video/quicktime', 'flv' => 'video/x-flv', 'php' => 'text/x-php'
+ );
+ $ext = strtolower(pathInfo($file, PATHINFO_EXTENSION));
+ return isset($exts[$ext]) ? $exts[$ext] : 'application/octet-stream';
+ }
+
+
+ /**
+ * Generate the auth string: "AWS AccessKey:Signature"
+ *
+ * @internal Used by S3Request::getResponse()
+ * @param string $string String to sign
+ * @return string
+ */
+ public static function __getSignature($string)
+ {
+ return 'AWS '.self::$__accessKey.':'.self::__getHash($string);
+ }
+
+
+ /**
+ * Creates a HMAC-SHA1 hash
+ *
+ * This uses the hash extension if loaded
+ *
+ * @internal Used by __getSignature()
+ * @param string $string String to sign
+ * @return string
+ */
+ private static function __getHash($string)
+ {
+ return base64_encode(extension_loaded('hash') ?
+ hash_hmac('sha1', $string, self::$__secretKey, true) : pack('H*', sha1(
+ (str_pad(self::$__secretKey, 64, chr(0x00)) ^ (str_repeat(chr(0x5c), 64))) .
+ pack('H*', sha1((str_pad(self::$__secretKey, 64, chr(0x00)) ^
+ (str_repeat(chr(0x36), 64))) . $string)))));
+ }
+
+}
+
+final class S3Request
+{
+ private $endpoint, $verb, $bucket, $uri, $resource = '', $parameters = array(),
+ $amzHeaders = array(), $headers = array(
+ 'Host' => '', 'Date' => '', 'Content-MD5' => '', 'Content-Type' => ''
+ );
+ public $fp = false, $size = 0, $data = false, $response;
+
+
+ /**
+ * Constructor
+ *
+ * @param string $verb Verb
+ * @param string $bucket Bucket name
+ * @param string $uri Object URI
+ * @return mixed
+ */
+ function __construct($verb, $bucket = '', $uri = '', $endpoint = 's3.amazonaws.com')
+ {
+ $this->endpoint = $endpoint;
+ $this->verb = $verb;
+ $this->bucket = $bucket;
+ $this->uri = $uri !== '' ? '/'.str_replace('%2F', '/', rawurlencode($uri)) : '/';
+
+ if ($this->bucket !== '')
+ {
+ $this->headers['Host'] = $this->bucket.'.'.$this->endpoint;
+ $this->resource = '/'.$this->bucket.$this->uri;
+ }
+ else
+ {
+ $this->headers['Host'] = $this->endpoint;
+ $this->resource = $this->uri;
+ }
+ $this->headers['Date'] = gmdate('D, d M Y H:i:s T');
+
+ $this->response = new STDClass;
+ $this->response->error = false;
+ }
+
+
+ /**
+ * Set request parameter
+ *
+ * @param string $key Key
+ * @param string $value Value
+ * @return void
+ */
+ public function setParameter($key, $value)
+ {
+ $this->parameters[$key] = $value;
+ }
+
+
+ /**
+ * Set request header
+ *
+ * @param string $key Key
+ * @param string $value Value
+ * @return void
+ */
+ public function setHeader($key, $value)
+ {
+ $this->headers[$key] = $value;
+ }
+
+
+ /**
+ * Set x-amz-meta-* header
+ *
+ * @param string $key Key
+ * @param string $value Value
+ * @return void
+ */
+ public function setAmzHeader($key, $value)
+ {
+ $this->amzHeaders[$key] = $value;
+ }
+
+
+ /**
+ * Get the S3 response
+ *
+ * @return object | false
+ */
+ public function getResponse()
+ {
+ $query = '';
+ if (sizeof($this->parameters) > 0)
+ {
+ $query = substr($this->uri, -1) !== '?' ? '?' : '&';
+ foreach ($this->parameters as $var => $value)
+ if ($value == null || $value == '') $query .= $var.'&';
+ // Parameters should be encoded (thanks Sean O'Dea)
+ else $query .= $var.'='.rawurlencode($value).'&';
+ $query = substr($query, 0, -1);
+ $this->uri .= $query;
+
+ if (array_key_exists('acl', $this->parameters) ||
+ array_key_exists('location', $this->parameters) ||
+ array_key_exists('torrent', $this->parameters) ||
+ array_key_exists('logging', $this->parameters))
+ $this->resource .= $query;
+ }
+ $url = (S3::$useSSL ? 'https://' : 'http://') . $this->headers['Host'].$this->uri;
+ //var_dump($this->bucket, $this->uri, $this->resource, $url);
+
+ // Basic setup
+ $curl = curl_init();
+ curl_setopt($curl, CURLOPT_USERAGENT, 'S3/php');
+
+ if (S3::$useSSL)
+ {
+ // SSL Validation can now be optional for those with broken OpenSSL installations
+ curl_setopt($curl, CURLOPT_SSL_VERIFYHOST, S3::$useSSLValidation ? 1 : 0);
+ curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, S3::$useSSLValidation ? 1 : 0);
+
+ if (S3::$sslKey !== null) curl_setopt($curl, CURLOPT_SSLKEY, S3::$sslKey);
+ if (S3::$sslCert !== null) curl_setopt($curl, CURLOPT_SSLCERT, S3::$sslCert);
+ if (S3::$sslCACert !== null) curl_setopt($curl, CURLOPT_CAINFO, S3::$sslCACert);
+ }
+
+ curl_setopt($curl, CURLOPT_URL, $url);
+
+ if (S3::$proxy != null && isset(S3::$proxy['host']))
+ {
+ curl_setopt($curl, CURLOPT_PROXY, S3::$proxy['host']);
+ curl_setopt($curl, CURLOPT_PROXYTYPE, S3::$proxy['type']);
+ if (isset(S3::$proxy['user'], S3::$proxy['pass']) && $proxy['user'] != null && $proxy['pass'] != null)
+ curl_setopt($curl, CURLOPT_PROXYUSERPWD, sprintf('%s:%s', S3::$proxy['user'], S3::$proxy['pass']));
+ }
+
+ // Headers
+ $headers = array(); $amz = array();
+ foreach ($this->amzHeaders as $header => $value)
+ if (strlen($value) > 0) $headers[] = $header.': '.$value;
+ foreach ($this->headers as $header => $value)
+ if (strlen($value) > 0) $headers[] = $header.': '.$value;
+
+ // Collect AMZ headers for signature
+ foreach ($this->amzHeaders as $header => $value)
+ if (strlen($value) > 0) $amz[] = strtolower($header).':'.$value;
+
+ // AMZ headers must be sorted
+ if (sizeof($amz) > 0)
+ {
+ sort($amz);
+ $amz = "\n".implode("\n", $amz);
+ } else $amz = '';
+
+ if (S3::hasAuth())
+ {
+ // Authorization string (CloudFront stringToSign should only contain a date)
+ $headers[] = 'Authorization: ' . S3::__getSignature(
+ $this->headers['Host'] == 'cloudfront.amazonaws.com' ? $this->headers['Date'] :
+ $this->verb."\n".$this->headers['Content-MD5']."\n".
+ $this->headers['Content-Type']."\n".$this->headers['Date'].$amz."\n".$this->resource
+ );
+ }
+
+ curl_setopt($curl, CURLOPT_HTTPHEADER, $headers);
+ curl_setopt($curl, CURLOPT_HEADER, false);
+ curl_setopt($curl, CURLOPT_RETURNTRANSFER, false);
+ curl_setopt($curl, CURLOPT_WRITEFUNCTION, array(&$this, '__responseWriteCallback'));
+ curl_setopt($curl, CURLOPT_HEADERFUNCTION, array(&$this, '__responseHeaderCallback'));
+ curl_setopt($curl, CURLOPT_FOLLOWLOCATION, true);
+
+ // Request types
+ switch ($this->verb)
+ {
+ case 'GET': break;
+ case 'PUT': case 'POST': // POST only used for CloudFront
+ if ($this->fp !== false)
+ {
+ curl_setopt($curl, CURLOPT_PUT, true);
+ curl_setopt($curl, CURLOPT_INFILE, $this->fp);
+ if ($this->size >= 0)
+ curl_setopt($curl, CURLOPT_INFILESIZE, $this->size);
+ }
+ elseif ($this->data !== false)
+ {
+ curl_setopt($curl, CURLOPT_CUSTOMREQUEST, $this->verb);
+ curl_setopt($curl, CURLOPT_POSTFIELDS, $this->data);
+ }
+ else
+ curl_setopt($curl, CURLOPT_CUSTOMREQUEST, $this->verb);
+ break;
+ case 'HEAD':
+ curl_setopt($curl, CURLOPT_CUSTOMREQUEST, 'HEAD');
+ curl_setopt($curl, CURLOPT_NOBODY, true);
+ break;
+ case 'DELETE':
+ curl_setopt($curl, CURLOPT_CUSTOMREQUEST, 'DELETE');
+ break;
+ default: break;
+ }
+
+ // Execute, grab errors
+ if (curl_exec($curl))
+ $this->response->code = curl_getinfo($curl, CURLINFO_HTTP_CODE);
+ else
+ $this->response->error = array(
+ 'code' => curl_errno($curl),
+ 'message' => curl_error($curl),
+ 'resource' => $this->resource
+ );
+
+ @curl_close($curl);
+
+ // Parse body into XML
+ if ($this->response->error === false && isset($this->response->headers['type']) &&
+ $this->response->headers['type'] == 'application/xml' && isset($this->response->body))
+ {
+ $this->response->body = simplexml_load_string($this->response->body);
+
+ // Grab S3 errors
+ if (!in_array($this->response->code, array(200, 204, 206)) &&
+ isset($this->response->body->Code, $this->response->body->Message))
+ {
+ $this->response->error = array(
+ 'code' => (string)$this->response->body->Code,
+ 'message' => (string)$this->response->body->Message
+ );
+ if (isset($this->response->body->Resource))
+ $this->response->error['resource'] = (string)$this->response->body->Resource;
+ unset($this->response->body);
+ }
+ }
+
+ // Clean up file resources
+ if ($this->fp !== false && is_resource($this->fp)) fclose($this->fp);
+
+ return $this->response;
+ }
+
+
+ /**
+ * CURL write callback
+ *
+ * @param resource &$curl CURL resource
+ * @param string &$data Data
+ * @return integer
+ */
+ private function __responseWriteCallback(&$curl, &$data)
+ {
+ if (in_array($this->response->code, array(200, 206)) && $this->fp !== false)
+ return fwrite($this->fp, $data);
+ else
+ $this->response->body .= $data;
+ return strlen($data);
+ }
+
+
+ /**
+ * CURL header callback
+ *
+ * @param resource &$curl CURL resource
+ * @param string &$data Data
+ * @return integer
+ */
+ private function __responseHeaderCallback(&$curl, &$data)
+ {
+ if (($strlen = strlen($data)) <= 2) return $strlen;
+ if (substr($data, 0, 4) == 'HTTP')
+ $this->response->code = (int)substr($data, 9, 3);
+ else
+ {
+ $data = trim($data);
+ if (strpos($data, ': ') === false) return $strlen;
+ list($header, $value) = explode(': ', $data, 2);
+ if ($header == 'Last-Modified')
+ $this->response->headers['time'] = strtotime($value);
+ elseif ($header == 'Content-Length')
+ $this->response->headers['size'] = (int)$value;
+ elseif ($header == 'Content-Type')
+ $this->response->headers['type'] = $value;
+ elseif ($header == 'ETag')
+ $this->response->headers['hash'] = $value{0} == '"' ? substr($value, 1, -1) : $value;
+ elseif (preg_match('/^x-amz-meta-.*$/', $header))
+ $this->response->headers[$header] = is_numeric($value) ? (int)$value : $value;
+ }
+ return $strlen;
+ }
+
+}
+
+class S3Exception extends Exception {
+ function __construct($message, $file, $line, $code = 0)
+ {
+ parent::__construct($message, $code);
+ $this->file = $file;
+ $this->line = $line;
+ }
+}
diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php
index 912f746f88..632acb085b 100644
--- a/src/__phutil_library_map__.php
+++ b/src/__phutil_library_map__.php
@@ -1,1157 +1,1159 @@
<?php
/**
* This file is automatically generated. Use 'phutil_mapper.php' to rebuild it.
* @generated
*/
phutil_register_library_map(array(
'class' =>
array(
'Aphront304Response' => 'aphront/response/304',
'Aphront400Response' => 'aphront/response/400',
'Aphront404Response' => 'aphront/response/404',
'AphrontAjaxResponse' => 'aphront/response/ajax',
'AphrontApplicationConfiguration' => 'aphront/applicationconfiguration',
'AphrontAttachedFileView' => 'view/control/attachedfile',
'AphrontCSRFException' => 'aphront/exception/csrf',
'AphrontController' => 'aphront/controller',
'AphrontCrumbsView' => 'view/layout/crumbs',
'AphrontDatabaseConnection' => 'storage/connection/base',
'AphrontDefaultApplicationConfiguration' => 'aphront/default/configuration',
'AphrontDefaultApplicationController' => 'aphront/default/controller',
'AphrontDialogResponse' => 'aphront/response/dialog',
'AphrontDialogView' => 'view/dialog',
'AphrontErrorView' => 'view/form/error',
'AphrontException' => 'aphront/exception/base',
'AphrontFilePreviewView' => 'view/layout/filepreview',
'AphrontFileResponse' => 'aphront/response/file',
'AphrontFormCheckboxControl' => 'view/form/control/checkbox',
'AphrontFormControl' => 'view/form/control/base',
'AphrontFormDividerControl' => 'view/form/control/divider',
'AphrontFormDragAndDropUploadControl' => 'view/form/control/draganddropupload',
'AphrontFormFileControl' => 'view/form/control/file',
'AphrontFormLayoutView' => 'view/form/layout',
'AphrontFormMarkupControl' => 'view/form/control/markup',
'AphrontFormPasswordControl' => 'view/form/control/password',
'AphrontFormRecaptchaControl' => 'view/form/control/recaptcha',
'AphrontFormSelectControl' => 'view/form/control/select',
'AphrontFormStaticControl' => 'view/form/control/static',
'AphrontFormSubmitControl' => 'view/form/control/submit',
'AphrontFormTextAreaControl' => 'view/form/control/textarea',
'AphrontFormTextControl' => 'view/form/control/text',
'AphrontFormToggleButtonsControl' => 'view/form/control/togglebuttons',
'AphrontFormTokenizerControl' => 'view/form/control/tokenizer',
'AphrontFormView' => 'view/form/base',
'AphrontHeadsupActionListView' => 'view/layout/headsup/actionlist',
'AphrontHeadsupActionView' => 'view/layout/headsup/action',
'AphrontIsolatedDatabaseConnection' => 'storage/connection/isolated',
'AphrontIsolatedDatabaseConnectionTestCase' => 'storage/connection/isolated/__tests__',
'AphrontKeyboardShortcutsAvailableView' => 'view/widget/keyboardshortcuts',
'AphrontListFilterView' => 'view/layout/listfilter',
'AphrontMySQLDatabaseConnection' => 'storage/connection/mysql',
'AphrontNullView' => 'view/null',
'AphrontPageView' => 'view/page/base',
'AphrontPagerView' => 'view/control/pager',
'AphrontPanelView' => 'view/layout/panel',
'AphrontQueryAccessDeniedException' => 'storage/exception/accessdenied',
'AphrontQueryConnectionException' => 'storage/exception/connection',
'AphrontQueryConnectionLostException' => 'storage/exception/connectionlost',
'AphrontQueryCountException' => 'storage/exception/count',
'AphrontQueryDuplicateKeyException' => 'storage/exception/duplicatekey',
'AphrontQueryException' => 'storage/exception/base',
'AphrontQueryObjectMissingException' => 'storage/exception/objectmissing',
'AphrontQueryParameterException' => 'storage/exception/parameter',
'AphrontQueryRecoverableException' => 'storage/exception/recoverable',
'AphrontRedirectException' => 'aphront/exception/redirect',
'AphrontRedirectResponse' => 'aphront/response/redirect',
'AphrontReloadResponse' => 'aphront/response/reload',
'AphrontRequest' => 'aphront/request',
'AphrontRequestFailureView' => 'view/page/failure',
'AphrontResponse' => 'aphront/response/base',
'AphrontSideNavView' => 'view/layout/sidenav',
'AphrontTableView' => 'view/control/table',
'AphrontTokenizerTemplateView' => 'view/control/tokenizer',
'AphrontTypeaheadTemplateView' => 'view/control/typeahead',
'AphrontURIMapper' => 'aphront/mapper',
'AphrontView' => 'view/base',
'AphrontWebpageResponse' => 'aphront/response/webpage',
'CelerityAPI' => 'infrastructure/celerity/api',
'CelerityResourceController' => 'infrastructure/celerity/controller',
'CelerityResourceMap' => 'infrastructure/celerity/map',
'CelerityStaticResourceResponse' => 'infrastructure/celerity/response',
'ConduitAPIMethod' => 'applications/conduit/method/base',
'ConduitAPIRequest' => 'applications/conduit/protocol/request',
'ConduitAPI_conduit_connect_Method' => 'applications/conduit/method/conduit/connect',
'ConduitAPI_conduit_getcertificate_Method' => 'applications/conduit/method/conduit/getcertificate',
'ConduitAPI_conduit_ping_Method' => 'applications/conduit/method/conduit/ping',
'ConduitAPI_daemon_launched_Method' => 'applications/conduit/method/daemon/launched',
'ConduitAPI_daemon_log_Method' => 'applications/conduit/method/daemon/log',
'ConduitAPI_differential_creatediff_Method' => 'applications/conduit/method/differential/creatediff',
'ConduitAPI_differential_createrevision_Method' => 'applications/conduit/method/differential/createrevision',
'ConduitAPI_differential_find_Method' => 'applications/conduit/method/differential/find',
'ConduitAPI_differential_getalldiffs_Method' => 'applications/conduit/method/differential/getalldiffs',
'ConduitAPI_differential_getcommitmessage_Method' => 'applications/conduit/method/differential/getcommitmessage',
'ConduitAPI_differential_getcommitpaths_Method' => 'applications/conduit/method/differential/getcommitpaths',
'ConduitAPI_differential_getdiff_Method' => 'applications/conduit/method/differential/getdiff',
'ConduitAPI_differential_getrevision_Method' => 'applications/conduit/method/differential/getrevision',
'ConduitAPI_differential_getrevisionfeedback_Method' => 'applications/conduit/method/differential/getrevisionfeedback',
'ConduitAPI_differential_markcommitted_Method' => 'applications/conduit/method/differential/markcommitted',
'ConduitAPI_differential_parsecommitmessage_Method' => 'applications/conduit/method/differential/parsecommitmessage',
'ConduitAPI_differential_setdiffproperty_Method' => 'applications/conduit/method/differential/setdiffproperty',
'ConduitAPI_differential_updaterevision_Method' => 'applications/conduit/method/differential/updaterevision',
'ConduitAPI_differential_updatetaskrevisionassoc_Method' => 'applications/conduit/method/differential/updatetaskrevisionassoc',
'ConduitAPI_differential_updateunitresults_Method' => 'applications/conduit/method/differential/updateunitresults',
'ConduitAPI_diffusion_getcommits_Method' => 'applications/conduit/method/diffusion/getcommits',
'ConduitAPI_diffusion_getrecentcommitsbypath_Method' => 'applications/conduit/method/diffusion/getrecentcommitsbypath',
'ConduitAPI_feed_publish_Method' => 'applications/conduit/method/feed/publish',
'ConduitAPI_file_download_Method' => 'applications/conduit/method/file/download',
'ConduitAPI_file_info_Method' => 'applications/conduit/method/file/info',
'ConduitAPI_file_upload_Method' => 'applications/conduit/method/file/upload',
'ConduitAPI_maniphest_info_Method' => 'applications/conduit/method/maniphest/info',
'ConduitAPI_paste_Method' => 'applications/conduit/method/paste/base',
'ConduitAPI_paste_create_Method' => 'applications/conduit/method/paste/create',
'ConduitAPI_paste_info_Method' => 'applications/conduit/method/paste/info',
'ConduitAPI_path_getowners_Method' => 'applications/conduit/method/path/getowners',
'ConduitAPI_slowvote_info_Method' => 'applications/conduit/method/slowvote/info',
'ConduitAPI_user_find_Method' => 'applications/conduit/method/user/find',
'ConduitAPI_user_whoami_Method' => 'applications/conduit/method/user/whoami',
'ConduitException' => 'applications/conduit/protocol/exception',
'DarkConsole' => 'aphront/console/api',
'DarkConsoleConfigPlugin' => 'aphront/console/plugin/config',
'DarkConsoleController' => 'aphront/console/controller',
'DarkConsoleCore' => 'aphront/console/core',
'DarkConsoleErrorLogPlugin' => 'aphront/console/plugin/errorlog',
'DarkConsoleErrorLogPluginAPI' => 'aphront/console/plugin/errorlog/api',
'DarkConsolePlugin' => 'aphront/console/plugin/base',
'DarkConsoleRequestPlugin' => 'aphront/console/plugin/request',
'DarkConsoleServicesPlugin' => 'aphront/console/plugin/services',
'DarkConsoleXHProfPlugin' => 'aphront/console/plugin/xhprof',
'DarkConsoleXHProfPluginAPI' => 'aphront/console/plugin/xhprof/api',
'DatabaseConfigurationProvider' => 'applications/base/storage/configuration',
'DifferentialAction' => 'applications/differential/constants/action',
'DifferentialAddCommentView' => 'applications/differential/view/addcomment',
'DifferentialCCWelcomeMail' => 'applications/differential/mail/ccwelcome',
'DifferentialChangeType' => 'applications/differential/constants/changetype',
'DifferentialChangeset' => 'applications/differential/storage/changeset',
'DifferentialChangesetDetailView' => 'applications/differential/view/changesetdetailview',
'DifferentialChangesetListView' => 'applications/differential/view/changesetlistview',
'DifferentialChangesetParser' => 'applications/differential/parser/changeset',
'DifferentialChangesetViewController' => 'applications/differential/controller/changesetview',
'DifferentialComment' => 'applications/differential/storage/comment',
'DifferentialCommentEditor' => 'applications/differential/editor/comment',
'DifferentialCommentMail' => 'applications/differential/mail/comment',
'DifferentialCommentPreviewController' => 'applications/differential/controller/commentpreview',
'DifferentialCommentSaveController' => 'applications/differential/controller/commentsave',
'DifferentialCommitMessage' => 'applications/differential/parser/commitmessage',
'DifferentialCommitMessageData' => 'applications/differential/data/commitmessage',
'DifferentialCommitMessageField' => 'applications/differential/data/commitmessage',
'DifferentialCommitMessageModifier' => 'applications/differential/data/commitmessage',
'DifferentialCommitMessageParserException' => 'applications/differential/parser/commitmessage/exception',
'DifferentialController' => 'applications/differential/controller/base',
'DifferentialDAO' => 'applications/differential/storage/base',
'DifferentialDiff' => 'applications/differential/storage/diff',
'DifferentialDiffContentMail' => 'applications/differential/mail/diffcontent',
'DifferentialDiffCreateController' => 'applications/differential/controller/diffcreate',
'DifferentialDiffProperty' => 'applications/differential/storage/diffproperty',
'DifferentialDiffTableOfContentsView' => 'applications/differential/view/difftableofcontents',
'DifferentialDiffViewController' => 'applications/differential/controller/diffview',
'DifferentialExceptionMail' => 'applications/differential/mail/exception',
'DifferentialHunk' => 'applications/differential/storage/hunk',
'DifferentialInlineComment' => 'applications/differential/storage/inlinecomment',
'DifferentialInlineCommentEditController' => 'applications/differential/controller/inlinecommentedit',
'DifferentialInlineCommentPreviewController' => 'applications/differential/controller/inlinecommentpreview',
'DifferentialInlineCommentView' => 'applications/differential/view/inlinecomment',
'DifferentialLintStatus' => 'applications/differential/constants/lintstatus',
'DifferentialMail' => 'applications/differential/mail/base',
'DifferentialNewDiffMail' => 'applications/differential/mail/newdiff',
'DifferentialPrimaryPaneView' => 'applications/differential/view/primarypane',
'DifferentialReplyHandler' => 'applications/differential/replyhandler',
'DifferentialReviewRequestMail' => 'applications/differential/mail/reviewrequest',
'DifferentialRevision' => 'applications/differential/storage/revision',
'DifferentialRevisionCommentListView' => 'applications/differential/view/revisioncommentlist',
'DifferentialRevisionCommentView' => 'applications/differential/view/revisioncomment',
'DifferentialRevisionControlSystem' => 'applications/differential/constants/revisioncontrolsystem',
'DifferentialRevisionDetailRenderer' => 'applications/differential/controller/customrenderer',
'DifferentialRevisionDetailView' => 'applications/differential/view/revisiondetail',
'DifferentialRevisionEditController' => 'applications/differential/controller/revisionedit',
'DifferentialRevisionEditor' => 'applications/differential/editor/revision',
'DifferentialRevisionListController' => 'applications/differential/controller/revisionlist',
'DifferentialRevisionListData' => 'applications/differential/data/revisionlist',
'DifferentialRevisionStatus' => 'applications/differential/constants/revisionstatus',
'DifferentialRevisionUpdateHistoryView' => 'applications/differential/view/revisionupdatehistory',
'DifferentialRevisionViewController' => 'applications/differential/controller/revisionview',
'DifferentialSubscribeController' => 'applications/differential/controller/subscribe',
'DifferentialTasksAttacher' => 'applications/differential/tasks',
'DifferentialUnitStatus' => 'applications/differential/constants/unitstatus',
'DifferentialUnitTestResult' => 'applications/differential/constants/unittestresult',
'DifferentialViewTime' => 'applications/differential/storage/viewtime',
'DiffusionBranchInformation' => 'applications/diffusion/data/branch',
'DiffusionBranchQuery' => 'applications/diffusion/query/branch/base',
'DiffusionBranchTableView' => 'applications/diffusion/view/branchtable',
'DiffusionBrowseController' => 'applications/diffusion/controller/browse',
'DiffusionBrowseFileController' => 'applications/diffusion/controller/file',
'DiffusionBrowseQuery' => 'applications/diffusion/query/browse/base',
'DiffusionBrowseTableView' => 'applications/diffusion/view/browsetable',
'DiffusionChangeController' => 'applications/diffusion/controller/change',
'DiffusionCommitChangeTableView' => 'applications/diffusion/view/commitchangetable',
'DiffusionCommitController' => 'applications/diffusion/controller/commit',
'DiffusionCommitListController' => 'applications/diffusion/controller/commitlist',
'DiffusionController' => 'applications/diffusion/controller/base',
'DiffusionDiffController' => 'applications/diffusion/controller/diff',
'DiffusionDiffQuery' => 'applications/diffusion/query/diff/base',
'DiffusionFileContent' => 'applications/diffusion/data/filecontent',
'DiffusionFileContentQuery' => 'applications/diffusion/query/filecontent/base',
'DiffusionGitBranchQuery' => 'applications/diffusion/query/branch/git',
'DiffusionGitBrowseQuery' => 'applications/diffusion/query/browse/git',
'DiffusionGitDiffQuery' => 'applications/diffusion/query/diff/git',
'DiffusionGitFileContentQuery' => 'applications/diffusion/query/filecontent/git',
'DiffusionGitHistoryQuery' => 'applications/diffusion/query/history/git',
'DiffusionGitLastModifiedQuery' => 'applications/diffusion/query/lastmodified/git',
'DiffusionGitPathIDQuery' => 'applications/diffusion/query/pathid/base',
'DiffusionGitRequest' => 'applications/diffusion/request/git',
'DiffusionHistoryController' => 'applications/diffusion/controller/history',
'DiffusionHistoryQuery' => 'applications/diffusion/query/history/base',
'DiffusionHistoryTableView' => 'applications/diffusion/view/historytable',
'DiffusionHomeController' => 'applications/diffusion/controller/home',
'DiffusionLastModifiedController' => 'applications/diffusion/controller/lastmodified',
'DiffusionLastModifiedQuery' => 'applications/diffusion/query/lastmodified/base',
'DiffusionPathChange' => 'applications/diffusion/data/pathchange',
'DiffusionPathChangeQuery' => 'applications/diffusion/query/pathchange/base',
'DiffusionPathCompleteController' => 'applications/diffusion/controller/pathcomplete',
'DiffusionPathValidateController' => 'applications/diffusion/controller/pathvalidate',
'DiffusionRepositoryController' => 'applications/diffusion/controller/repository',
'DiffusionRepositoryPath' => 'applications/diffusion/data/repositorypath',
'DiffusionRequest' => 'applications/diffusion/request/base',
'DiffusionSvnBrowseQuery' => 'applications/diffusion/query/browse/svn',
'DiffusionSvnDiffQuery' => 'applications/diffusion/query/diff/svn',
'DiffusionSvnFileContentQuery' => 'applications/diffusion/query/filecontent/svn',
'DiffusionSvnHistoryQuery' => 'applications/diffusion/query/history/svn',
'DiffusionSvnLastModifiedQuery' => 'applications/diffusion/query/lastmodified/svn',
'DiffusionSvnRequest' => 'applications/diffusion/request/svn',
'DiffusionView' => 'applications/diffusion/view/base',
'HeraldAction' => 'applications/herald/storage/action',
'HeraldActionConfig' => 'applications/herald/config/action',
'HeraldApplyTranscript' => 'applications/herald/storage/transcript/apply',
'HeraldCommitAdapter' => 'applications/herald/adapter/commit',
'HeraldCondition' => 'applications/herald/storage/condition',
'HeraldConditionConfig' => 'applications/herald/config/condition',
'HeraldConditionTranscript' => 'applications/herald/storage/transcript/condition',
'HeraldContentTypeConfig' => 'applications/herald/config/contenttype',
'HeraldController' => 'applications/herald/controller/base',
'HeraldDAO' => 'applications/herald/storage/base',
'HeraldDeleteController' => 'applications/herald/controller/delete',
'HeraldDifferentialRevisionAdapter' => 'applications/herald/adapter/differential',
'HeraldDryRunAdapter' => 'applications/herald/adapter/dryrun',
'HeraldEffect' => 'applications/herald/engine/effect',
'HeraldEngine' => 'applications/herald/engine/engine',
'HeraldFieldConfig' => 'applications/herald/config/field',
'HeraldHomeController' => 'applications/herald/controller/home',
'HeraldInvalidConditionException' => 'applications/herald/engine/engine/exception',
'HeraldInvalidFieldException' => 'applications/herald/engine/engine/exception',
'HeraldNewController' => 'applications/herald/controller/new',
'HeraldObjectAdapter' => 'applications/herald/adapter/base',
'HeraldObjectTranscript' => 'applications/herald/storage/transcript/object',
'HeraldRecursiveConditionsException' => 'applications/herald/engine/engine/exception',
'HeraldRepetitionPolicyConfig' => 'applications/herald/config/repetitionpolicy',
'HeraldRule' => 'applications/herald/storage/rule',
'HeraldRuleController' => 'applications/herald/controller/rule',
'HeraldRuleTranscript' => 'applications/herald/storage/transcript/rule',
'HeraldTestConsoleController' => 'applications/herald/controller/test',
'HeraldTranscript' => 'applications/herald/storage/transcript/base',
'HeraldTranscriptController' => 'applications/herald/controller/transcript',
'HeraldTranscriptListController' => 'applications/herald/controller/transcriptlist',
'HeraldValueTypeConfig' => 'applications/herald/config/valuetype',
'Javelin' => 'infrastructure/javelin/api',
'LiskDAO' => 'storage/lisk/dao',
'LiskIsolationTestCase' => 'storage/lisk/dao/__tests__',
'LiskIsolationTestDAO' => 'storage/lisk/dao/__tests__',
'LiskIsolationTestDAOException' => 'storage/lisk/dao/__tests__',
'ManiphestAuxiliaryFieldDefaultSpecification' => 'applications/maniphest/auxiliaryfield/default',
'ManiphestAuxiliaryFieldSpecification' => 'applications/maniphest/auxiliaryfield/base',
'ManiphestAuxiliaryFieldTypeException' => 'applications/maniphest/auxiliaryfield/typeexception',
'ManiphestAuxiliaryFieldValidationException' => 'applications/maniphest/auxiliaryfield/validationexception',
'ManiphestConstants' => 'applications/maniphest/constants/base',
'ManiphestController' => 'applications/maniphest/controller/base',
'ManiphestDAO' => 'applications/maniphest/storage/base',
'ManiphestDefaultTaskExtensions' => 'applications/maniphest/extensions/task',
'ManiphestReplyHandler' => 'applications/maniphest/replyhandler',
'ManiphestTask' => 'applications/maniphest/storage/task',
'ManiphestTaskAuxiliaryStorage' => 'applications/maniphest/storage/auxiliary',
'ManiphestTaskDescriptionChangeController' => 'applications/maniphest/controller/descriptionchange',
'ManiphestTaskDetailController' => 'applications/maniphest/controller/taskdetail',
'ManiphestTaskEditController' => 'applications/maniphest/controller/taskedit',
'ManiphestTaskListController' => 'applications/maniphest/controller/tasklist',
'ManiphestTaskListView' => 'applications/maniphest/view/tasklist',
'ManiphestTaskOwner' => 'applications/maniphest/constants/owner',
'ManiphestTaskPriority' => 'applications/maniphest/constants/priority',
'ManiphestTaskProject' => 'applications/maniphest/storage/taskproject',
'ManiphestTaskQuery' => 'applications/maniphest/query',
'ManiphestTaskStatus' => 'applications/maniphest/constants/status',
'ManiphestTaskSubscriber' => 'applications/maniphest/storage/subscriber',
'ManiphestTaskSummaryView' => 'applications/maniphest/view/tasksummary',
'ManiphestTransaction' => 'applications/maniphest/storage/transaction',
'ManiphestTransactionDetailView' => 'applications/maniphest/view/transactiondetail',
'ManiphestTransactionEditor' => 'applications/maniphest/editor/transaction',
'ManiphestTransactionListView' => 'applications/maniphest/view/transactionlist',
'ManiphestTransactionPreviewController' => 'applications/maniphest/controller/transactionpreview',
'ManiphestTransactionSaveController' => 'applications/maniphest/controller/transactionsave',
'ManiphestTransactionType' => 'applications/maniphest/constants/transactiontype',
'ManiphestView' => 'applications/maniphest/view/base',
'Phabricator404Controller' => 'applications/base/controller/404',
'PhabricatorAuthController' => 'applications/auth/controller/base',
'PhabricatorConduitAPIController' => 'applications/conduit/controller/api',
'PhabricatorConduitCertificateToken' => 'applications/conduit/storage/token',
'PhabricatorConduitConnectionLog' => 'applications/conduit/storage/connectionlog',
'PhabricatorConduitConsoleController' => 'applications/conduit/controller/console',
'PhabricatorConduitController' => 'applications/conduit/controller/base',
'PhabricatorConduitDAO' => 'applications/conduit/storage/base',
'PhabricatorConduitLogController' => 'applications/conduit/controller/log',
'PhabricatorConduitMethodCallLog' => 'applications/conduit/storage/methodcalllog',
'PhabricatorConduitTokenController' => 'applications/conduit/controller/token',
'PhabricatorController' => 'applications/base/controller/base',
'PhabricatorCountdownController' => 'applications/countdown/controller/base',
'PhabricatorCountdownDAO' => 'applications/countdown/storage/base',
'PhabricatorCountdownDeleteController' => 'applications/countdown/controller/delete',
'PhabricatorCountdownEditController' => 'applications/countdown/controller/edit',
'PhabricatorCountdownListController' => 'applications/countdown/controller/list',
'PhabricatorCountdownViewController' => 'applications/countdown/controller/view',
'PhabricatorDaemon' => 'infrastructure/daemon/base',
'PhabricatorDaemonCombinedLogController' => 'applications/daemon/controller/combined',
'PhabricatorDaemonConsoleController' => 'applications/daemon/controller/console',
'PhabricatorDaemonControl' => 'infrastructure/daemon/control',
'PhabricatorDaemonController' => 'applications/daemon/controller/base',
'PhabricatorDaemonDAO' => 'infrastructure/daemon/storage/base',
'PhabricatorDaemonLog' => 'infrastructure/daemon/storage/log',
'PhabricatorDaemonLogEvent' => 'infrastructure/daemon/storage/event',
'PhabricatorDaemonLogEventsView' => 'applications/daemon/view/daemonlogevents',
'PhabricatorDaemonLogListController' => 'applications/daemon/controller/loglist',
'PhabricatorDaemonLogListView' => 'applications/daemon/view/daemonloglist',
'PhabricatorDaemonLogViewController' => 'applications/daemon/controller/logview',
'PhabricatorDaemonReference' => 'infrastructure/daemon/control/reference',
'PhabricatorDaemonTimelineConsoleController' => 'applications/daemon/controller/timeline',
'PhabricatorDaemonTimelineEventController' => 'applications/daemon/controller/timelineevent',
'PhabricatorDefaultFileStorageEngineSelector' => 'applications/files/engineselector/default',
'PhabricatorDifferenceEngine' => 'infrastructure/diff/engine',
'PhabricatorDirectoryCategory' => 'applications/directory/storage/category',
'PhabricatorDirectoryCategoryDeleteController' => 'applications/directory/controller/categorydelete',
'PhabricatorDirectoryCategoryEditController' => 'applications/directory/controller/categoryedit',
'PhabricatorDirectoryCategoryListController' => 'applications/directory/controller/categorylist',
'PhabricatorDirectoryController' => 'applications/directory/controller/base',
'PhabricatorDirectoryDAO' => 'applications/directory/storage/base',
'PhabricatorDirectoryItem' => 'applications/directory/storage/item',
'PhabricatorDirectoryItemDeleteController' => 'applications/directory/controller/itemdelete',
'PhabricatorDirectoryItemEditController' => 'applications/directory/controller/itemedit',
'PhabricatorDirectoryItemListController' => 'applications/directory/controller/itemlist',
'PhabricatorDirectoryMainController' => 'applications/directory/controller/main',
'PhabricatorDisabledUserController' => 'applications/auth/controller/disabled',
'PhabricatorDraft' => 'applications/draft/storage/draft',
'PhabricatorDraftDAO' => 'applications/draft/storage/base',
'PhabricatorEmailLoginController' => 'applications/auth/controller/email',
'PhabricatorEmailTokenController' => 'applications/auth/controller/emailtoken',
'PhabricatorEnv' => 'infrastructure/env',
'PhabricatorFeedConstants' => 'applications/feed/constants/base',
'PhabricatorFeedController' => 'applications/feed/controller/base',
'PhabricatorFeedDAO' => 'applications/feed/storage/base',
'PhabricatorFeedPublicStreamController' => 'applications/feed/controller/publicstream',
'PhabricatorFeedQuery' => 'applications/feed/query',
'PhabricatorFeedStory' => 'applications/feed/story/base',
'PhabricatorFeedStoryData' => 'applications/feed/storage/story',
'PhabricatorFeedStoryDifferential' => 'applications/feed/story/differential',
'PhabricatorFeedStoryPhriction' => 'applications/feed/story/phriction',
'PhabricatorFeedStoryPublisher' => 'applications/feed/publisher',
'PhabricatorFeedStoryReference' => 'applications/feed/storage/storyreference',
'PhabricatorFeedStoryStatus' => 'applications/feed/story/status',
'PhabricatorFeedStoryTypeConstants' => 'applications/feed/constants/story',
'PhabricatorFeedStoryUnknown' => 'applications/feed/story/unknown',
'PhabricatorFeedStoryView' => 'applications/feed/view/story',
'PhabricatorFeedStreamController' => 'applications/feed/controller/stream',
'PhabricatorFeedView' => 'applications/feed/view/base',
'PhabricatorFile' => 'applications/files/storage/file',
'PhabricatorFileController' => 'applications/files/controller/base',
'PhabricatorFileDAO' => 'applications/files/storage/base',
'PhabricatorFileDropUploadController' => 'applications/files/controller/dropupload',
'PhabricatorFileImageMacro' => 'applications/files/storage/imagemacro',
'PhabricatorFileListController' => 'applications/files/controller/list',
'PhabricatorFileMacroDeleteController' => 'applications/files/controller/macrodelete',
'PhabricatorFileMacroEditController' => 'applications/files/controller/macroedit',
'PhabricatorFileMacroListController' => 'applications/files/controller/macrolist',
'PhabricatorFileProxyController' => 'applications/files/controller/proxy',
'PhabricatorFileProxyImage' => 'applications/files/storage/proxyimage',
'PhabricatorFileStorageBlob' => 'applications/files/storage/storageblob',
'PhabricatorFileStorageEngine' => 'applications/files/engine/base',
'PhabricatorFileStorageEngineSelector' => 'applications/files/engineselector/base',
'PhabricatorFileTransformController' => 'applications/files/controller/transform',
'PhabricatorFileURI' => 'applications/files/uri',
'PhabricatorFileUploadController' => 'applications/files/controller/upload',
'PhabricatorFileViewController' => 'applications/files/controller/view',
'PhabricatorGarbageCollectorDaemon' => 'infrastructure/daemon/garbagecollector',
'PhabricatorGoodForNothingWorker' => 'infrastructure/daemon/workers/worker/goodfornothing',
'PhabricatorHandleObjectSelectorDataView' => 'applications/phid/handle/view/selector',
'PhabricatorHelpController' => 'applications/help/controller/base',
'PhabricatorHelpKeyboardShortcutController' => 'applications/help/controller/keyboardshortcut',
'PhabricatorIRCBot' => 'infrastructure/daemon/irc/bot',
'PhabricatorIRCHandler' => 'infrastructure/daemon/irc/handler/base',
'PhabricatorIRCMessage' => 'infrastructure/daemon/irc/message',
'PhabricatorIRCObjectNameHandler' => 'infrastructure/daemon/irc/handler/objectname',
'PhabricatorIRCProtocolHandler' => 'infrastructure/daemon/irc/handler/protocol',
'PhabricatorImageTransformer' => 'applications/files/transform',
'PhabricatorJavelinLinter' => 'infrastructure/lint/linter/javelin',
'PhabricatorLintEngine' => 'infrastructure/lint/engine',
'PhabricatorLiskDAO' => 'applications/base/storage/lisk',
'PhabricatorLocalDiskFileStorageEngine' => 'applications/files/engine/localdisk',
'PhabricatorLoginController' => 'applications/auth/controller/login',
'PhabricatorLogoutController' => 'applications/auth/controller/logout',
'PhabricatorMailImplementationAdapter' => 'applications/metamta/adapter/base',
'PhabricatorMailImplementationAmazonSESAdapter' => 'applications/metamta/adapter/amazonses',
'PhabricatorMailImplementationPHPMailerLiteAdapter' => 'applications/metamta/adapter/phpmailerlite',
'PhabricatorMailImplementationSendGridAdapter' => 'applications/metamta/adapter/sendgrid',
'PhabricatorMailImplementationTestAdapter' => 'applications/metamta/adapter/test',
'PhabricatorMailReplyHandler' => 'applications/metamta/replyhandler/base',
'PhabricatorMarkupEngine' => 'applications/markup/engine',
'PhabricatorMetaMTAController' => 'applications/metamta/controller/base',
'PhabricatorMetaMTADAO' => 'applications/metamta/storage/base',
'PhabricatorMetaMTADaemon' => 'applications/metamta/daemon/mta',
'PhabricatorMetaMTAEmailBodyParser' => 'applications/metamta/parser',
'PhabricatorMetaMTAEmailBodyParserTestCase' => 'applications/metamta/parser/__tests__',
'PhabricatorMetaMTAListController' => 'applications/metamta/controller/list',
'PhabricatorMetaMTAMail' => 'applications/metamta/storage/mail',
'PhabricatorMetaMTAMailTestCase' => 'applications/metamta/storage/mail/__tests__',
'PhabricatorMetaMTAMailingList' => 'applications/metamta/storage/mailinglist',
'PhabricatorMetaMTAMailingListEditController' => 'applications/metamta/controller/mailinglistedit',
'PhabricatorMetaMTAMailingListsController' => 'applications/metamta/controller/mailinglists',
'PhabricatorMetaMTAReceiveController' => 'applications/metamta/controller/receive',
'PhabricatorMetaMTAReceivedListController' => 'applications/metamta/controller/receivedlist',
'PhabricatorMetaMTAReceivedMail' => 'applications/metamta/storage/receivedmail',
'PhabricatorMetaMTASendController' => 'applications/metamta/controller/send',
'PhabricatorMetaMTASendGridReceiveController' => 'applications/metamta/controller/sendgridreceive',
'PhabricatorMetaMTAViewController' => 'applications/metamta/controller/view',
'PhabricatorMySQLFileStorageEngine' => 'applications/files/engine/mysql',
'PhabricatorOAuthDefaultRegistrationController' => 'applications/auth/controller/oauthregistration/default',
'PhabricatorOAuthDiagnosticsController' => 'applications/auth/controller/oauthdiagnostics',
'PhabricatorOAuthFailureView' => 'applications/auth/view/oauthfailure',
'PhabricatorOAuthLoginController' => 'applications/auth/controller/oauth',
'PhabricatorOAuthProvider' => 'applications/auth/oauth/provider/base',
'PhabricatorOAuthProviderFacebook' => 'applications/auth/oauth/provider/facebook',
'PhabricatorOAuthProviderGithub' => 'applications/auth/oauth/provider/github',
'PhabricatorOAuthRegistrationController' => 'applications/auth/controller/oauthregistration/base',
'PhabricatorOAuthUnlinkController' => 'applications/auth/controller/unlink',
'PhabricatorObjectGraph' => 'applications/phid/graph',
'PhabricatorObjectHandle' => 'applications/phid/handle',
'PhabricatorObjectHandleData' => 'applications/phid/handle/data',
'PhabricatorObjectSelectorDialog' => 'view/control/objectselector',
'PhabricatorOwnersController' => 'applications/owners/controller/base',
'PhabricatorOwnersDAO' => 'applications/owners/storage/base',
'PhabricatorOwnersDeleteController' => 'applications/owners/controller/delete',
'PhabricatorOwnersDetailController' => 'applications/owners/controller/detail',
'PhabricatorOwnersEditController' => 'applications/owners/controller/edit',
'PhabricatorOwnersListController' => 'applications/owners/controller/list',
'PhabricatorOwnersOwner' => 'applications/owners/storage/owner',
'PhabricatorOwnersPackage' => 'applications/owners/storage/package',
'PhabricatorOwnersPath' => 'applications/owners/storage/path',
'PhabricatorPHID' => 'applications/phid/storage/phid',
'PhabricatorPHIDConstants' => 'applications/phid/constants',
'PhabricatorPHIDController' => 'applications/phid/controller/base',
'PhabricatorPHIDDAO' => 'applications/phid/storage/base',
'PhabricatorPHIDListController' => 'applications/phid/controller/list',
'PhabricatorPHIDLookupController' => 'applications/phid/controller/lookup',
'PhabricatorPaste' => 'applications/paste/storage/paste',
'PhabricatorPasteController' => 'applications/paste/controller/base',
'PhabricatorPasteCreateController' => 'applications/paste/controller/create',
'PhabricatorPasteDAO' => 'applications/paste/storage/base',
'PhabricatorPasteListController' => 'applications/paste/controller/list',
'PhabricatorPasteViewController' => 'applications/paste/controller/view',
'PhabricatorPeopleController' => 'applications/people/controller/base',
'PhabricatorPeopleEditController' => 'applications/people/controller/edit',
'PhabricatorPeopleListController' => 'applications/people/controller/list',
'PhabricatorPeopleLogsController' => 'applications/people/controller/logs',
'PhabricatorPeopleProfileController' => 'applications/people/controller/profile',
'PhabricatorProfileView' => 'view/layout/profile',
'PhabricatorProject' => 'applications/project/storage/project',
'PhabricatorProjectAffiliation' => 'applications/project/storage/affiliation',
'PhabricatorProjectAffiliationEditController' => 'applications/project/controller/editaffiliation',
'PhabricatorProjectController' => 'applications/project/controller/base',
'PhabricatorProjectCreateController' => 'applications/project/controller/create',
'PhabricatorProjectDAO' => 'applications/project/storage/base',
'PhabricatorProjectListController' => 'applications/project/controller/list',
'PhabricatorProjectProfile' => 'applications/project/storage/profile',
'PhabricatorProjectProfileController' => 'applications/project/controller/profile',
'PhabricatorProjectProfileEditController' => 'applications/project/controller/profileedit',
'PhabricatorProjectStatus' => 'applications/project/constants/status',
'PhabricatorProjectSubproject' => 'applications/project/storage/subproject',
'PhabricatorRedirectController' => 'applications/base/controller/redirect',
'PhabricatorRefreshCSRFController' => 'applications/auth/controller/refresh',
'PhabricatorRemarkupRuleDifferential' => 'infrastructure/markup/remarkup/markuprule/differential',
'PhabricatorRemarkupRuleDiffusion' => 'infrastructure/markup/remarkup/markuprule/diffusion',
'PhabricatorRemarkupRuleEmbedFile' => 'infrastructure/markup/remarkup/markuprule/embedobject',
'PhabricatorRemarkupRuleImageMacro' => 'infrastructure/markup/remarkup/markuprule/imagemacro',
'PhabricatorRemarkupRuleManiphest' => 'infrastructure/markup/remarkup/markuprule/maniphest',
'PhabricatorRemarkupRuleMention' => 'infrastructure/markup/remarkup/markuprule/mention',
'PhabricatorRemarkupRuleObjectName' => 'infrastructure/markup/remarkup/markuprule/objectname',
'PhabricatorRemarkupRulePaste' => 'infrastructure/markup/remarkup/markuprule/paste',
'PhabricatorRemarkupRulePhriction' => 'infrastructure/markup/remarkup/markuprule/phriction',
'PhabricatorRemarkupRuleProxyImage' => 'infrastructure/markup/remarkup/markuprule/proxyimage',
'PhabricatorRemarkupRuleYoutube' => 'infrastructure/markup/remarkup/markuprule/youtube',
'PhabricatorRepository' => 'applications/repository/storage/repository',
'PhabricatorRepositoryArcanistProject' => 'applications/repository/storage/arcanistproject',
'PhabricatorRepositoryArcanistProjectEditController' => 'applications/repository/controller/arcansistprojectedit',
'PhabricatorRepositoryCommit' => 'applications/repository/storage/commit',
'PhabricatorRepositoryCommitChangeParserWorker' => 'applications/repository/worker/commitchangeparser/base',
'PhabricatorRepositoryCommitData' => 'applications/repository/storage/commitdata',
'PhabricatorRepositoryCommitDiscoveryDaemon' => 'applications/repository/daemon/commitdiscovery/base',
'PhabricatorRepositoryCommitHeraldWorker' => 'applications/repository/worker/herald',
'PhabricatorRepositoryCommitMessageDetailParser' => 'applications/repository/parser/base',
'PhabricatorRepositoryCommitMessageParserWorker' => 'applications/repository/worker/commitmessageparser/base',
'PhabricatorRepositoryCommitParserWorker' => 'applications/repository/worker/base',
'PhabricatorRepositoryCommitTaskDaemon' => 'applications/repository/daemon/committask',
'PhabricatorRepositoryController' => 'applications/repository/controller/base',
'PhabricatorRepositoryCreateController' => 'applications/repository/controller/create',
'PhabricatorRepositoryDAO' => 'applications/repository/storage/base',
'PhabricatorRepositoryDaemon' => 'applications/repository/daemon/base',
'PhabricatorRepositoryDefaultCommitMessageDetailParser' => 'applications/repository/parser/default',
'PhabricatorRepositoryDeleteController' => 'applications/repository/controller/delete',
'PhabricatorRepositoryEditController' => 'applications/repository/controller/edit',
'PhabricatorRepositoryGitCommitChangeParserWorker' => 'applications/repository/worker/commitchangeparser/git',
'PhabricatorRepositoryGitCommitDiscoveryDaemon' => 'applications/repository/daemon/commitdiscovery/git',
'PhabricatorRepositoryGitCommitMessageParserWorker' => 'applications/repository/worker/commitmessageparser/git',
'PhabricatorRepositoryGitFetchDaemon' => 'applications/repository/daemon/gitfetch',
'PhabricatorRepositoryGitHubNotification' => 'applications/repository/storage/githubnotification',
'PhabricatorRepositoryGitHubPostReceiveController' => 'applications/repository/controller/github-post-receive',
'PhabricatorRepositoryListController' => 'applications/repository/controller/list',
'PhabricatorRepositoryShortcut' => 'applications/repository/storage/shortcut',
'PhabricatorRepositorySvnCommitChangeParserWorker' => 'applications/repository/worker/commitchangeparser/svn',
'PhabricatorRepositorySvnCommitDiscoveryDaemon' => 'applications/repository/daemon/commitdiscovery/svn',
'PhabricatorRepositorySvnCommitMessageParserWorker' => 'applications/repository/worker/commitmessageparser/svn',
'PhabricatorRepositoryType' => 'applications/repository/constants/repositorytype',
+ 'PhabricatorS3FileStorageEngine' => 'applications/files/engine/s3',
'PhabricatorSQLPatchList' => 'infrastructure/setup/sql',
'PhabricatorSearchAbstractDocument' => 'applications/search/index/abstractdocument',
'PhabricatorSearchAttachController' => 'applications/search/controller/attach',
'PhabricatorSearchBaseController' => 'applications/search/controller/base',
'PhabricatorSearchCommitIndexer' => 'applications/search/index/indexer/repository',
'PhabricatorSearchController' => 'applications/search/controller/search',
'PhabricatorSearchDAO' => 'applications/search/storage/base',
'PhabricatorSearchDifferentialIndexer' => 'applications/search/index/indexer/differential',
'PhabricatorSearchDocument' => 'applications/search/storage/document/document',
'PhabricatorSearchDocumentField' => 'applications/search/storage/document/field',
'PhabricatorSearchDocumentIndexer' => 'applications/search/index/indexer/base',
'PhabricatorSearchDocumentRelationship' => 'applications/search/storage/document/relationship',
'PhabricatorSearchExecutor' => 'applications/search/execute/base',
'PhabricatorSearchField' => 'applications/search/constants/field',
'PhabricatorSearchIndexController' => 'applications/search/controller/index',
'PhabricatorSearchManiphestIndexer' => 'applications/search/index/indexer/maniphest',
'PhabricatorSearchMySQLExecutor' => 'applications/search/execute/mysql',
'PhabricatorSearchPhrictionIndexer' => 'applications/search/index/indexer/phriction',
'PhabricatorSearchQuery' => 'applications/search/storage/query',
'PhabricatorSearchRelationship' => 'applications/search/constants/relationship',
'PhabricatorSearchResultView' => 'applications/search/view/searchresult',
'PhabricatorSearchSelectController' => 'applications/search/controller/select',
'PhabricatorSearchUserIndexer' => 'applications/search/index/indexer/user',
'PhabricatorSetup' => 'infrastructure/setup',
'PhabricatorSlowvoteChoice' => 'applications/slowvote/storage/choice',
'PhabricatorSlowvoteComment' => 'applications/slowvote/storage/comment',
'PhabricatorSlowvoteController' => 'applications/slowvote/controller/base',
'PhabricatorSlowvoteCreateController' => 'applications/slowvote/controller/create',
'PhabricatorSlowvoteDAO' => 'applications/slowvote/storage/base',
'PhabricatorSlowvoteListController' => 'applications/slowvote/controller/list',
'PhabricatorSlowvoteOption' => 'applications/slowvote/storage/option',
'PhabricatorSlowvotePoll' => 'applications/slowvote/storage/poll',
'PhabricatorSlowvotePollController' => 'applications/slowvote/controller/poll',
'PhabricatorStandardPageView' => 'view/page/standard',
'PhabricatorStatusController' => 'applications/status/base',
'PhabricatorSyntaxHighlighter' => 'applications/markup/syntax',
'PhabricatorTaskmasterDaemon' => 'infrastructure/daemon/workers/taskmaster',
'PhabricatorTestCase' => 'infrastructure/testing/testcase',
'PhabricatorTimelineCursor' => 'infrastructure/daemon/timeline/storage/cursor',
'PhabricatorTimelineDAO' => 'infrastructure/daemon/timeline/storage/base',
'PhabricatorTimelineEvent' => 'infrastructure/daemon/timeline/storage/event',
'PhabricatorTimelineEventData' => 'infrastructure/daemon/timeline/storage/eventdata',
'PhabricatorTimelineIterator' => 'infrastructure/daemon/timeline/cursor/iterator',
'PhabricatorTimer' => 'applications/countdown/storage/timer',
'PhabricatorTransformedFile' => 'applications/files/storage/transformed',
'PhabricatorTrivialTestCase' => 'infrastructure/testing/testcase/__tests__',
'PhabricatorTypeaheadCommonDatasourceController' => 'applications/typeahead/controller/common',
'PhabricatorTypeaheadDatasourceController' => 'applications/typeahead/controller/base',
'PhabricatorUIExample' => 'applications/uiexample/examples/base',
'PhabricatorUIExampleController' => 'applications/uiexample/controller/base',
'PhabricatorUIExampleRenderController' => 'applications/uiexample/controller/render',
'PhabricatorUIListFilterExample' => 'applications/uiexample/examples/listfilter',
'PhabricatorUIPagerExample' => 'applications/uiexample/examples/pager',
'PhabricatorUser' => 'applications/people/storage/user',
'PhabricatorUserAccountSettingsPanelController' => 'applications/people/controller/settings/panels/account',
'PhabricatorUserConduitSettingsPanelController' => 'applications/people/controller/settings/panels/conduit',
'PhabricatorUserDAO' => 'applications/people/storage/base',
'PhabricatorUserEmailSettingsPanelController' => 'applications/people/controller/settings/panels/email',
'PhabricatorUserLog' => 'applications/people/storage/log',
'PhabricatorUserOAuthInfo' => 'applications/people/storage/useroauthinfo',
'PhabricatorUserOAuthSettingsPanelController' => 'applications/people/controller/settings/panels/oauth',
'PhabricatorUserPreferenceSettingsPanelController' => 'applications/people/controller/settings/panels/preferences',
'PhabricatorUserPreferences' => 'applications/people/storage/preferences',
'PhabricatorUserProfile' => 'applications/people/storage/profile',
'PhabricatorUserProfileSettingsPanelController' => 'applications/people/controller/settings/panels/profile',
'PhabricatorUserSSHKey' => 'applications/people/storage/usersshkey',
'PhabricatorUserSSHKeysSettingsPanelController' => 'applications/people/controller/settings/panels/sshkeys',
'PhabricatorUserSettingsController' => 'applications/people/controller/settings',
'PhabricatorUserSettingsPanelController' => 'applications/people/controller/settings/panels/base',
'PhabricatorWorker' => 'infrastructure/daemon/workers/worker',
'PhabricatorWorkerDAO' => 'infrastructure/daemon/workers/storage/base',
'PhabricatorWorkerTask' => 'infrastructure/daemon/workers/storage/task',
'PhabricatorWorkerTaskData' => 'infrastructure/daemon/workers/storage/taskdata',
'PhabricatorWorkerTaskDetailController' => 'applications/daemon/controller/workertaskdetail',
'PhabricatorXHPASTViewController' => 'applications/xhpastview/controller/base',
'PhabricatorXHPASTViewDAO' => 'applications/xhpastview/storage/base',
'PhabricatorXHPASTViewFrameController' => 'applications/xhpastview/controller/viewframe',
'PhabricatorXHPASTViewFramesetController' => 'applications/xhpastview/controller/viewframeset',
'PhabricatorXHPASTViewInputController' => 'applications/xhpastview/controller/viewinput',
'PhabricatorXHPASTViewPanelController' => 'applications/xhpastview/controller/viewpanel',
'PhabricatorXHPASTViewParseTree' => 'applications/xhpastview/storage/parsetree',
'PhabricatorXHPASTViewRunController' => 'applications/xhpastview/controller/run',
'PhabricatorXHPASTViewStreamController' => 'applications/xhpastview/controller/viewstream',
'PhabricatorXHPASTViewTreeController' => 'applications/xhpastview/controller/viewtree',
'PhabricatorXHProfController' => 'applications/xhprof/controller/base',
'PhabricatorXHProfProfileController' => 'applications/xhprof/controller/profile',
'PhabricatorXHProfProfileSymbolView' => 'applications/xhprof/view/symbol',
'PhabricatorXHProfProfileTopLevelView' => 'applications/xhprof/view/toplevel',
'PhrictionActionConstants' => 'applications/phriction/constants/action',
'PhrictionConstants' => 'applications/phriction/constants/base',
'PhrictionContent' => 'applications/phriction/storage/content',
'PhrictionController' => 'applications/phriction/controller/base',
'PhrictionDAO' => 'applications/phriction/storage/base',
'PhrictionDiffController' => 'applications/phriction/controller/diff',
'PhrictionDocument' => 'applications/phriction/storage/document',
'PhrictionDocumentController' => 'applications/phriction/controller/document',
'PhrictionDocumentPreviewController' => 'applications/phriction/controller/documentpreview',
'PhrictionDocumentTestCase' => 'applications/phriction/storage/document/__tests__',
'PhrictionEditController' => 'applications/phriction/controller/edit',
'PhrictionHistoryController' => 'applications/phriction/controller/history',
'PhrictionListController' => 'applications/phriction/controller/list',
),
'function' =>
array(
'_qsprintf_check_scalar_type' => 'storage/qsprintf',
'_qsprintf_check_type' => 'storage/qsprintf',
'celerity_generate_unique_node_id' => 'infrastructure/celerity/api',
'celerity_register_resource_map' => 'infrastructure/celerity/map',
'javelin_render_tag' => 'infrastructure/javelin/markup',
'phabricator_date' => 'view/utils',
'phabricator_datetime' => 'view/utils',
'phabricator_format_relative_time' => 'view/utils',
'phabricator_format_timestamp' => 'view/utils',
'phabricator_format_units_generic' => 'view/utils',
'phabricator_render_form' => 'infrastructure/javelin/markup',
'phabricator_time' => 'view/utils',
'qsprintf' => 'storage/qsprintf',
'queryfx' => 'storage/queryfx',
'queryfx_all' => 'storage/queryfx',
'queryfx_one' => 'storage/queryfx',
'require_celerity_resource' => 'infrastructure/celerity/api',
'vqsprintf' => 'storage/qsprintf',
'vqueryfx' => 'storage/queryfx',
'vqueryfx_all' => 'storage/queryfx',
'xsprintf_query' => 'storage/qsprintf',
),
'requires_class' =>
array(
'Aphront304Response' => 'AphrontResponse',
'Aphront400Response' => 'AphrontResponse',
'Aphront404Response' => 'AphrontResponse',
'AphrontAjaxResponse' => 'AphrontResponse',
'AphrontAttachedFileView' => 'AphrontView',
'AphrontCSRFException' => 'AphrontException',
'AphrontCrumbsView' => 'AphrontView',
'AphrontDefaultApplicationConfiguration' => 'AphrontApplicationConfiguration',
'AphrontDefaultApplicationController' => 'AphrontController',
'AphrontDialogResponse' => 'AphrontResponse',
'AphrontDialogView' => 'AphrontView',
'AphrontErrorView' => 'AphrontView',
'AphrontFilePreviewView' => 'AphrontView',
'AphrontFileResponse' => 'AphrontResponse',
'AphrontFormCheckboxControl' => 'AphrontFormControl',
'AphrontFormControl' => 'AphrontView',
'AphrontFormDividerControl' => 'AphrontFormControl',
'AphrontFormDragAndDropUploadControl' => 'AphrontFormControl',
'AphrontFormFileControl' => 'AphrontFormControl',
'AphrontFormLayoutView' => 'AphrontView',
'AphrontFormMarkupControl' => 'AphrontFormControl',
'AphrontFormPasswordControl' => 'AphrontFormControl',
'AphrontFormRecaptchaControl' => 'AphrontFormControl',
'AphrontFormSelectControl' => 'AphrontFormControl',
'AphrontFormStaticControl' => 'AphrontFormControl',
'AphrontFormSubmitControl' => 'AphrontFormControl',
'AphrontFormTextAreaControl' => 'AphrontFormControl',
'AphrontFormTextControl' => 'AphrontFormControl',
'AphrontFormToggleButtonsControl' => 'AphrontFormControl',
'AphrontFormTokenizerControl' => 'AphrontFormControl',
'AphrontFormView' => 'AphrontView',
'AphrontHeadsupActionListView' => 'AphrontView',
'AphrontHeadsupActionView' => 'AphrontView',
'AphrontIsolatedDatabaseConnection' => 'AphrontDatabaseConnection',
'AphrontIsolatedDatabaseConnectionTestCase' => 'PhabricatorTestCase',
'AphrontKeyboardShortcutsAvailableView' => 'AphrontView',
'AphrontListFilterView' => 'AphrontView',
'AphrontMySQLDatabaseConnection' => 'AphrontDatabaseConnection',
'AphrontNullView' => 'AphrontView',
'AphrontPageView' => 'AphrontView',
'AphrontPagerView' => 'AphrontView',
'AphrontPanelView' => 'AphrontView',
'AphrontQueryAccessDeniedException' => 'AphrontQueryRecoverableException',
'AphrontQueryConnectionException' => 'AphrontQueryException',
'AphrontQueryConnectionLostException' => 'AphrontQueryRecoverableException',
'AphrontQueryCountException' => 'AphrontQueryException',
'AphrontQueryDuplicateKeyException' => 'AphrontQueryException',
'AphrontQueryObjectMissingException' => 'AphrontQueryException',
'AphrontQueryParameterException' => 'AphrontQueryException',
'AphrontQueryRecoverableException' => 'AphrontQueryException',
'AphrontRedirectException' => 'AphrontException',
'AphrontRedirectResponse' => 'AphrontResponse',
'AphrontReloadResponse' => 'AphrontRedirectResponse',
'AphrontRequestFailureView' => 'AphrontView',
'AphrontSideNavView' => 'AphrontView',
'AphrontTableView' => 'AphrontView',
'AphrontTokenizerTemplateView' => 'AphrontView',
'AphrontTypeaheadTemplateView' => 'AphrontView',
'AphrontWebpageResponse' => 'AphrontResponse',
'CelerityResourceController' => 'AphrontController',
'ConduitAPI_conduit_connect_Method' => 'ConduitAPIMethod',
'ConduitAPI_conduit_getcertificate_Method' => 'ConduitAPIMethod',
'ConduitAPI_conduit_ping_Method' => 'ConduitAPIMethod',
'ConduitAPI_daemon_launched_Method' => 'ConduitAPIMethod',
'ConduitAPI_daemon_log_Method' => 'ConduitAPIMethod',
'ConduitAPI_differential_creatediff_Method' => 'ConduitAPIMethod',
'ConduitAPI_differential_createrevision_Method' => 'ConduitAPIMethod',
'ConduitAPI_differential_find_Method' => 'ConduitAPIMethod',
'ConduitAPI_differential_getalldiffs_Method' => 'ConduitAPIMethod',
'ConduitAPI_differential_getcommitmessage_Method' => 'ConduitAPIMethod',
'ConduitAPI_differential_getcommitpaths_Method' => 'ConduitAPIMethod',
'ConduitAPI_differential_getdiff_Method' => 'ConduitAPIMethod',
'ConduitAPI_differential_getrevision_Method' => 'ConduitAPIMethod',
'ConduitAPI_differential_getrevisionfeedback_Method' => 'ConduitAPIMethod',
'ConduitAPI_differential_markcommitted_Method' => 'ConduitAPIMethod',
'ConduitAPI_differential_parsecommitmessage_Method' => 'ConduitAPIMethod',
'ConduitAPI_differential_setdiffproperty_Method' => 'ConduitAPIMethod',
'ConduitAPI_differential_updaterevision_Method' => 'ConduitAPIMethod',
'ConduitAPI_differential_updatetaskrevisionassoc_Method' => 'ConduitAPIMethod',
'ConduitAPI_differential_updateunitresults_Method' => 'ConduitAPIMethod',
'ConduitAPI_diffusion_getcommits_Method' => 'ConduitAPIMethod',
'ConduitAPI_diffusion_getrecentcommitsbypath_Method' => 'ConduitAPIMethod',
'ConduitAPI_feed_publish_Method' => 'ConduitAPIMethod',
'ConduitAPI_file_download_Method' => 'ConduitAPIMethod',
'ConduitAPI_file_info_Method' => 'ConduitAPIMethod',
'ConduitAPI_file_upload_Method' => 'ConduitAPIMethod',
'ConduitAPI_maniphest_info_Method' => 'ConduitAPIMethod',
'ConduitAPI_paste_Method' => 'ConduitAPIMethod',
'ConduitAPI_paste_create_Method' => 'ConduitAPI_paste_Method',
'ConduitAPI_paste_info_Method' => 'ConduitAPI_paste_Method',
'ConduitAPI_path_getowners_Method' => 'ConduitAPIMethod',
'ConduitAPI_slowvote_info_Method' => 'ConduitAPIMethod',
'ConduitAPI_user_find_Method' => 'ConduitAPIMethod',
'ConduitAPI_user_whoami_Method' => 'ConduitAPIMethod',
'DarkConsoleConfigPlugin' => 'DarkConsolePlugin',
'DarkConsoleController' => 'PhabricatorController',
'DarkConsoleErrorLogPlugin' => 'DarkConsolePlugin',
'DarkConsoleRequestPlugin' => 'DarkConsolePlugin',
'DarkConsoleServicesPlugin' => 'DarkConsolePlugin',
'DarkConsoleXHProfPlugin' => 'DarkConsolePlugin',
'DifferentialAddCommentView' => 'AphrontView',
'DifferentialCCWelcomeMail' => 'DifferentialReviewRequestMail',
'DifferentialChangeset' => 'DifferentialDAO',
'DifferentialChangesetDetailView' => 'AphrontView',
'DifferentialChangesetListView' => 'AphrontView',
'DifferentialChangesetViewController' => 'DifferentialController',
'DifferentialComment' => 'DifferentialDAO',
'DifferentialCommentMail' => 'DifferentialMail',
'DifferentialCommentPreviewController' => 'DifferentialController',
'DifferentialCommentSaveController' => 'DifferentialController',
'DifferentialController' => 'PhabricatorController',
'DifferentialDAO' => 'PhabricatorLiskDAO',
'DifferentialDiff' => 'DifferentialDAO',
'DifferentialDiffContentMail' => 'DifferentialMail',
'DifferentialDiffCreateController' => 'DifferentialController',
'DifferentialDiffProperty' => 'DifferentialDAO',
'DifferentialDiffTableOfContentsView' => 'AphrontView',
'DifferentialDiffViewController' => 'DifferentialController',
'DifferentialExceptionMail' => 'DifferentialMail',
'DifferentialHunk' => 'DifferentialDAO',
'DifferentialInlineComment' => 'DifferentialDAO',
'DifferentialInlineCommentEditController' => 'DifferentialController',
'DifferentialInlineCommentPreviewController' => 'DifferentialController',
'DifferentialInlineCommentView' => 'AphrontView',
'DifferentialNewDiffMail' => 'DifferentialReviewRequestMail',
'DifferentialPrimaryPaneView' => 'AphrontView',
'DifferentialReplyHandler' => 'PhabricatorMailReplyHandler',
'DifferentialReviewRequestMail' => 'DifferentialMail',
'DifferentialRevision' => 'DifferentialDAO',
'DifferentialRevisionCommentListView' => 'AphrontView',
'DifferentialRevisionCommentView' => 'AphrontView',
'DifferentialRevisionDetailView' => 'AphrontView',
'DifferentialRevisionEditController' => 'DifferentialController',
'DifferentialRevisionListController' => 'DifferentialController',
'DifferentialRevisionUpdateHistoryView' => 'AphrontView',
'DifferentialRevisionViewController' => 'DifferentialController',
'DifferentialSubscribeController' => 'DifferentialController',
'DifferentialViewTime' => 'DifferentialDAO',
'DiffusionBranchTableView' => 'DiffusionView',
'DiffusionBrowseController' => 'DiffusionController',
'DiffusionBrowseFileController' => 'DiffusionController',
'DiffusionBrowseTableView' => 'DiffusionView',
'DiffusionChangeController' => 'DiffusionController',
'DiffusionCommitChangeTableView' => 'DiffusionView',
'DiffusionCommitController' => 'DiffusionController',
'DiffusionCommitListController' => 'DiffusionController',
'DiffusionController' => 'PhabricatorController',
'DiffusionDiffController' => 'DiffusionController',
'DiffusionGitBranchQuery' => 'DiffusionBranchQuery',
'DiffusionGitBrowseQuery' => 'DiffusionBrowseQuery',
'DiffusionGitDiffQuery' => 'DiffusionDiffQuery',
'DiffusionGitFileContentQuery' => 'DiffusionFileContentQuery',
'DiffusionGitHistoryQuery' => 'DiffusionHistoryQuery',
'DiffusionGitLastModifiedQuery' => 'DiffusionLastModifiedQuery',
'DiffusionGitRequest' => 'DiffusionRequest',
'DiffusionHistoryController' => 'DiffusionController',
'DiffusionHistoryTableView' => 'DiffusionView',
'DiffusionHomeController' => 'DiffusionController',
'DiffusionLastModifiedController' => 'DiffusionController',
'DiffusionPathCompleteController' => 'DiffusionController',
'DiffusionPathValidateController' => 'DiffusionController',
'DiffusionRepositoryController' => 'DiffusionController',
'DiffusionSvnBrowseQuery' => 'DiffusionBrowseQuery',
'DiffusionSvnDiffQuery' => 'DiffusionDiffQuery',
'DiffusionSvnFileContentQuery' => 'DiffusionFileContentQuery',
'DiffusionSvnHistoryQuery' => 'DiffusionHistoryQuery',
'DiffusionSvnLastModifiedQuery' => 'DiffusionLastModifiedQuery',
'DiffusionSvnRequest' => 'DiffusionRequest',
'DiffusionView' => 'AphrontView',
'HeraldAction' => 'HeraldDAO',
'HeraldApplyTranscript' => 'HeraldDAO',
'HeraldCommitAdapter' => 'HeraldObjectAdapter',
'HeraldCondition' => 'HeraldDAO',
'HeraldController' => 'PhabricatorController',
'HeraldDAO' => 'PhabricatorLiskDAO',
'HeraldDeleteController' => 'HeraldController',
'HeraldDifferentialRevisionAdapter' => 'HeraldObjectAdapter',
'HeraldDryRunAdapter' => 'HeraldObjectAdapter',
'HeraldHomeController' => 'HeraldController',
'HeraldNewController' => 'HeraldController',
'HeraldRule' => 'HeraldDAO',
'HeraldRuleController' => 'HeraldController',
'HeraldTestConsoleController' => 'HeraldController',
'HeraldTranscript' => 'HeraldDAO',
'HeraldTranscriptController' => 'HeraldController',
'HeraldTranscriptListController' => 'HeraldController',
'LiskIsolationTestCase' => 'PhabricatorTestCase',
'LiskIsolationTestDAO' => 'LiskDAO',
'ManiphestAuxiliaryFieldDefaultSpecification' => 'ManiphestAuxiliaryFieldSpecification',
'ManiphestController' => 'PhabricatorController',
'ManiphestDAO' => 'PhabricatorLiskDAO',
'ManiphestReplyHandler' => 'PhabricatorMailReplyHandler',
'ManiphestTask' => 'ManiphestDAO',
'ManiphestTaskAuxiliaryStorage' => 'ManiphestDAO',
'ManiphestTaskDescriptionChangeController' => 'ManiphestController',
'ManiphestTaskDetailController' => 'ManiphestController',
'ManiphestTaskEditController' => 'ManiphestController',
'ManiphestTaskListController' => 'ManiphestController',
'ManiphestTaskListView' => 'ManiphestView',
'ManiphestTaskOwner' => 'ManiphestConstants',
'ManiphestTaskPriority' => 'ManiphestConstants',
'ManiphestTaskProject' => 'ManiphestDAO',
'ManiphestTaskStatus' => 'ManiphestConstants',
'ManiphestTaskSubscriber' => 'ManiphestDAO',
'ManiphestTaskSummaryView' => 'ManiphestView',
'ManiphestTransaction' => 'ManiphestDAO',
'ManiphestTransactionDetailView' => 'ManiphestView',
'ManiphestTransactionListView' => 'ManiphestView',
'ManiphestTransactionPreviewController' => 'ManiphestController',
'ManiphestTransactionSaveController' => 'ManiphestController',
'ManiphestTransactionType' => 'ManiphestConstants',
'ManiphestView' => 'AphrontView',
'Phabricator404Controller' => 'PhabricatorController',
'PhabricatorAuthController' => 'PhabricatorController',
'PhabricatorConduitAPIController' => 'PhabricatorConduitController',
'PhabricatorConduitCertificateToken' => 'PhabricatorConduitDAO',
'PhabricatorConduitConnectionLog' => 'PhabricatorConduitDAO',
'PhabricatorConduitConsoleController' => 'PhabricatorConduitController',
'PhabricatorConduitController' => 'PhabricatorController',
'PhabricatorConduitDAO' => 'PhabricatorLiskDAO',
'PhabricatorConduitLogController' => 'PhabricatorConduitController',
'PhabricatorConduitMethodCallLog' => 'PhabricatorConduitDAO',
'PhabricatorConduitTokenController' => 'PhabricatorConduitController',
'PhabricatorController' => 'AphrontController',
'PhabricatorCountdownController' => 'PhabricatorController',
'PhabricatorCountdownDAO' => 'PhabricatorLiskDAO',
'PhabricatorCountdownDeleteController' => 'PhabricatorCountdownController',
'PhabricatorCountdownEditController' => 'PhabricatorCountdownController',
'PhabricatorCountdownListController' => 'PhabricatorCountdownController',
'PhabricatorCountdownViewController' => 'PhabricatorCountdownController',
'PhabricatorDaemon' => 'PhutilDaemon',
'PhabricatorDaemonCombinedLogController' => 'PhabricatorDaemonController',
'PhabricatorDaemonConsoleController' => 'PhabricatorDaemonController',
'PhabricatorDaemonController' => 'PhabricatorController',
'PhabricatorDaemonDAO' => 'PhabricatorLiskDAO',
'PhabricatorDaemonLog' => 'PhabricatorDaemonDAO',
'PhabricatorDaemonLogEvent' => 'PhabricatorDaemonDAO',
'PhabricatorDaemonLogEventsView' => 'AphrontView',
'PhabricatorDaemonLogListController' => 'PhabricatorDaemonController',
'PhabricatorDaemonLogListView' => 'AphrontView',
'PhabricatorDaemonLogViewController' => 'PhabricatorDaemonController',
'PhabricatorDaemonTimelineConsoleController' => 'PhabricatorDaemonController',
'PhabricatorDaemonTimelineEventController' => 'PhabricatorDaemonController',
'PhabricatorDefaultFileStorageEngineSelector' => 'PhabricatorFileStorageEngineSelector',
'PhabricatorDirectoryCategory' => 'PhabricatorDirectoryDAO',
'PhabricatorDirectoryCategoryDeleteController' => 'PhabricatorDirectoryController',
'PhabricatorDirectoryCategoryEditController' => 'PhabricatorDirectoryController',
'PhabricatorDirectoryCategoryListController' => 'PhabricatorDirectoryController',
'PhabricatorDirectoryController' => 'PhabricatorController',
'PhabricatorDirectoryDAO' => 'PhabricatorLiskDAO',
'PhabricatorDirectoryItem' => 'PhabricatorDirectoryDAO',
'PhabricatorDirectoryItemDeleteController' => 'PhabricatorDirectoryController',
'PhabricatorDirectoryItemEditController' => 'PhabricatorDirectoryController',
'PhabricatorDirectoryItemListController' => 'PhabricatorDirectoryController',
'PhabricatorDirectoryMainController' => 'PhabricatorDirectoryController',
'PhabricatorDisabledUserController' => 'PhabricatorAuthController',
'PhabricatorDraft' => 'PhabricatorDraftDAO',
'PhabricatorDraftDAO' => 'PhabricatorLiskDAO',
'PhabricatorEmailLoginController' => 'PhabricatorAuthController',
'PhabricatorEmailTokenController' => 'PhabricatorAuthController',
'PhabricatorFeedController' => 'PhabricatorController',
'PhabricatorFeedDAO' => 'PhabricatorLiskDAO',
'PhabricatorFeedPublicStreamController' => 'PhabricatorFeedController',
'PhabricatorFeedStoryData' => 'PhabricatorFeedDAO',
'PhabricatorFeedStoryDifferential' => 'PhabricatorFeedStory',
'PhabricatorFeedStoryPhriction' => 'PhabricatorFeedStory',
'PhabricatorFeedStoryReference' => 'PhabricatorFeedDAO',
'PhabricatorFeedStoryStatus' => 'PhabricatorFeedStory',
'PhabricatorFeedStoryTypeConstants' => 'PhabricatorFeedConstants',
'PhabricatorFeedStoryUnknown' => 'PhabricatorFeedStory',
'PhabricatorFeedStoryView' => 'PhabricatorFeedView',
'PhabricatorFeedStreamController' => 'PhabricatorFeedController',
'PhabricatorFeedView' => 'AphrontView',
'PhabricatorFile' => 'PhabricatorFileDAO',
'PhabricatorFileController' => 'PhabricatorController',
'PhabricatorFileDAO' => 'PhabricatorLiskDAO',
'PhabricatorFileDropUploadController' => 'PhabricatorFileController',
'PhabricatorFileImageMacro' => 'PhabricatorFileDAO',
'PhabricatorFileListController' => 'PhabricatorFileController',
'PhabricatorFileMacroDeleteController' => 'PhabricatorFileController',
'PhabricatorFileMacroEditController' => 'PhabricatorFileController',
'PhabricatorFileMacroListController' => 'PhabricatorFileController',
'PhabricatorFileProxyController' => 'PhabricatorFileController',
'PhabricatorFileProxyImage' => 'PhabricatorFileDAO',
'PhabricatorFileStorageBlob' => 'PhabricatorFileDAO',
'PhabricatorFileTransformController' => 'PhabricatorFileController',
'PhabricatorFileUploadController' => 'PhabricatorFileController',
'PhabricatorFileViewController' => 'PhabricatorFileController',
'PhabricatorGarbageCollectorDaemon' => 'PhabricatorDaemon',
'PhabricatorGoodForNothingWorker' => 'PhabricatorWorker',
'PhabricatorHelpController' => 'PhabricatorController',
'PhabricatorHelpKeyboardShortcutController' => 'PhabricatorHelpController',
'PhabricatorIRCBot' => 'PhabricatorDaemon',
'PhabricatorIRCObjectNameHandler' => 'PhabricatorIRCHandler',
'PhabricatorIRCProtocolHandler' => 'PhabricatorIRCHandler',
'PhabricatorJavelinLinter' => 'ArcanistLinter',
'PhabricatorLintEngine' => 'PhutilLintEngine',
'PhabricatorLiskDAO' => 'LiskDAO',
'PhabricatorLocalDiskFileStorageEngine' => 'PhabricatorFileStorageEngine',
'PhabricatorLoginController' => 'PhabricatorAuthController',
'PhabricatorLogoutController' => 'PhabricatorAuthController',
'PhabricatorMailImplementationAmazonSESAdapter' => 'PhabricatorMailImplementationPHPMailerLiteAdapter',
'PhabricatorMailImplementationPHPMailerLiteAdapter' => 'PhabricatorMailImplementationAdapter',
'PhabricatorMailImplementationSendGridAdapter' => 'PhabricatorMailImplementationAdapter',
'PhabricatorMailImplementationTestAdapter' => 'PhabricatorMailImplementationAdapter',
'PhabricatorMetaMTAController' => 'PhabricatorController',
'PhabricatorMetaMTADAO' => 'PhabricatorLiskDAO',
'PhabricatorMetaMTADaemon' => 'PhabricatorDaemon',
'PhabricatorMetaMTAEmailBodyParserTestCase' => 'PhabricatorTestCase',
'PhabricatorMetaMTAListController' => 'PhabricatorMetaMTAController',
'PhabricatorMetaMTAMail' => 'PhabricatorMetaMTADAO',
'PhabricatorMetaMTAMailTestCase' => 'PhabricatorTestCase',
'PhabricatorMetaMTAMailingList' => 'PhabricatorMetaMTADAO',
'PhabricatorMetaMTAMailingListEditController' => 'PhabricatorMetaMTAController',
'PhabricatorMetaMTAMailingListsController' => 'PhabricatorMetaMTAController',
'PhabricatorMetaMTAReceiveController' => 'PhabricatorMetaMTAController',
'PhabricatorMetaMTAReceivedListController' => 'PhabricatorMetaMTAController',
'PhabricatorMetaMTAReceivedMail' => 'PhabricatorMetaMTADAO',
'PhabricatorMetaMTASendController' => 'PhabricatorMetaMTAController',
'PhabricatorMetaMTASendGridReceiveController' => 'PhabricatorMetaMTAController',
'PhabricatorMetaMTAViewController' => 'PhabricatorMetaMTAController',
'PhabricatorMySQLFileStorageEngine' => 'PhabricatorFileStorageEngine',
'PhabricatorOAuthDefaultRegistrationController' => 'PhabricatorOAuthRegistrationController',
'PhabricatorOAuthDiagnosticsController' => 'PhabricatorAuthController',
'PhabricatorOAuthFailureView' => 'AphrontView',
'PhabricatorOAuthLoginController' => 'PhabricatorAuthController',
'PhabricatorOAuthProviderFacebook' => 'PhabricatorOAuthProvider',
'PhabricatorOAuthProviderGithub' => 'PhabricatorOAuthProvider',
'PhabricatorOAuthRegistrationController' => 'PhabricatorAuthController',
'PhabricatorOAuthUnlinkController' => 'PhabricatorAuthController',
'PhabricatorObjectGraph' => 'AbstractDirectedGraph',
'PhabricatorOwnersController' => 'PhabricatorController',
'PhabricatorOwnersDAO' => 'PhabricatorLiskDAO',
'PhabricatorOwnersDeleteController' => 'PhabricatorOwnersController',
'PhabricatorOwnersDetailController' => 'PhabricatorOwnersController',
'PhabricatorOwnersEditController' => 'PhabricatorOwnersController',
'PhabricatorOwnersListController' => 'PhabricatorOwnersController',
'PhabricatorOwnersOwner' => 'PhabricatorOwnersDAO',
'PhabricatorOwnersPackage' => 'PhabricatorOwnersDAO',
'PhabricatorOwnersPath' => 'PhabricatorOwnersDAO',
'PhabricatorPHID' => 'PhabricatorPHIDDAO',
'PhabricatorPHIDController' => 'PhabricatorController',
'PhabricatorPHIDDAO' => 'PhabricatorLiskDAO',
'PhabricatorPHIDListController' => 'PhabricatorPHIDController',
'PhabricatorPHIDLookupController' => 'PhabricatorPHIDController',
'PhabricatorPaste' => 'PhabricatorPasteDAO',
'PhabricatorPasteController' => 'PhabricatorController',
'PhabricatorPasteCreateController' => 'PhabricatorPasteController',
'PhabricatorPasteDAO' => 'PhabricatorLiskDAO',
'PhabricatorPasteListController' => 'PhabricatorPasteController',
'PhabricatorPasteViewController' => 'PhabricatorPasteController',
'PhabricatorPeopleController' => 'PhabricatorController',
'PhabricatorPeopleEditController' => 'PhabricatorPeopleController',
'PhabricatorPeopleListController' => 'PhabricatorPeopleController',
'PhabricatorPeopleLogsController' => 'PhabricatorPeopleController',
'PhabricatorPeopleProfileController' => 'PhabricatorPeopleController',
'PhabricatorProfileView' => 'AphrontView',
'PhabricatorProject' => 'PhabricatorProjectDAO',
'PhabricatorProjectAffiliation' => 'PhabricatorProjectDAO',
'PhabricatorProjectAffiliationEditController' => 'PhabricatorProjectController',
'PhabricatorProjectController' => 'PhabricatorController',
'PhabricatorProjectCreateController' => 'PhabricatorProjectController',
'PhabricatorProjectDAO' => 'PhabricatorLiskDAO',
'PhabricatorProjectListController' => 'PhabricatorProjectController',
'PhabricatorProjectProfile' => 'PhabricatorProjectDAO',
'PhabricatorProjectProfileController' => 'PhabricatorProjectController',
'PhabricatorProjectProfileEditController' => 'PhabricatorProjectController',
'PhabricatorProjectSubproject' => 'PhabricatorProjectDAO',
'PhabricatorRedirectController' => 'PhabricatorController',
'PhabricatorRefreshCSRFController' => 'PhabricatorAuthController',
'PhabricatorRemarkupRuleDifferential' => 'PhabricatorRemarkupRuleObjectName',
'PhabricatorRemarkupRuleDiffusion' => 'PhutilRemarkupRule',
'PhabricatorRemarkupRuleEmbedFile' => 'PhutilRemarkupRule',
'PhabricatorRemarkupRuleImageMacro' => 'PhutilRemarkupRule',
'PhabricatorRemarkupRuleManiphest' => 'PhabricatorRemarkupRuleObjectName',
'PhabricatorRemarkupRuleMention' => 'PhutilRemarkupRule',
'PhabricatorRemarkupRuleObjectName' => 'PhutilRemarkupRule',
'PhabricatorRemarkupRulePaste' => 'PhabricatorRemarkupRuleObjectName',
'PhabricatorRemarkupRulePhriction' => 'PhutilRemarkupRule',
'PhabricatorRemarkupRuleProxyImage' => 'PhutilRemarkupRule',
'PhabricatorRemarkupRuleYoutube' => 'PhutilRemarkupRule',
'PhabricatorRepository' => 'PhabricatorRepositoryDAO',
'PhabricatorRepositoryArcanistProject' => 'PhabricatorRepositoryDAO',
'PhabricatorRepositoryArcanistProjectEditController' => 'PhabricatorRepositoryController',
'PhabricatorRepositoryCommit' => 'PhabricatorRepositoryDAO',
'PhabricatorRepositoryCommitChangeParserWorker' => 'PhabricatorRepositoryCommitParserWorker',
'PhabricatorRepositoryCommitData' => 'PhabricatorRepositoryDAO',
'PhabricatorRepositoryCommitDiscoveryDaemon' => 'PhabricatorRepositoryDaemon',
'PhabricatorRepositoryCommitHeraldWorker' => 'PhabricatorRepositoryCommitParserWorker',
'PhabricatorRepositoryCommitMessageParserWorker' => 'PhabricatorRepositoryCommitParserWorker',
'PhabricatorRepositoryCommitParserWorker' => 'PhabricatorWorker',
'PhabricatorRepositoryCommitTaskDaemon' => 'PhabricatorRepositoryDaemon',
'PhabricatorRepositoryController' => 'PhabricatorController',
'PhabricatorRepositoryCreateController' => 'PhabricatorRepositoryController',
'PhabricatorRepositoryDAO' => 'PhabricatorLiskDAO',
'PhabricatorRepositoryDaemon' => 'PhabricatorDaemon',
'PhabricatorRepositoryDefaultCommitMessageDetailParser' => 'PhabricatorRepositoryCommitMessageDetailParser',
'PhabricatorRepositoryDeleteController' => 'PhabricatorRepositoryController',
'PhabricatorRepositoryEditController' => 'PhabricatorRepositoryController',
'PhabricatorRepositoryGitCommitChangeParserWorker' => 'PhabricatorRepositoryCommitChangeParserWorker',
'PhabricatorRepositoryGitCommitDiscoveryDaemon' => 'PhabricatorRepositoryCommitDiscoveryDaemon',
'PhabricatorRepositoryGitCommitMessageParserWorker' => 'PhabricatorRepositoryCommitMessageParserWorker',
'PhabricatorRepositoryGitFetchDaemon' => 'PhabricatorRepositoryDaemon',
'PhabricatorRepositoryGitHubNotification' => 'PhabricatorRepositoryDAO',
'PhabricatorRepositoryGitHubPostReceiveController' => 'PhabricatorRepositoryController',
'PhabricatorRepositoryListController' => 'PhabricatorRepositoryController',
'PhabricatorRepositoryShortcut' => 'PhabricatorRepositoryDAO',
'PhabricatorRepositorySvnCommitChangeParserWorker' => 'PhabricatorRepositoryCommitChangeParserWorker',
'PhabricatorRepositorySvnCommitDiscoveryDaemon' => 'PhabricatorRepositoryCommitDiscoveryDaemon',
'PhabricatorRepositorySvnCommitMessageParserWorker' => 'PhabricatorRepositoryCommitMessageParserWorker',
+ 'PhabricatorS3FileStorageEngine' => 'PhabricatorFileStorageEngine',
'PhabricatorSearchAttachController' => 'PhabricatorSearchController',
'PhabricatorSearchBaseController' => 'PhabricatorController',
'PhabricatorSearchCommitIndexer' => 'PhabricatorSearchDocumentIndexer',
'PhabricatorSearchController' => 'PhabricatorSearchBaseController',
'PhabricatorSearchDAO' => 'PhabricatorLiskDAO',
'PhabricatorSearchDifferentialIndexer' => 'PhabricatorSearchDocumentIndexer',
'PhabricatorSearchDocument' => 'PhabricatorSearchDAO',
'PhabricatorSearchDocumentField' => 'PhabricatorSearchDAO',
'PhabricatorSearchDocumentRelationship' => 'PhabricatorSearchDAO',
'PhabricatorSearchIndexController' => 'PhabricatorSearchBaseController',
'PhabricatorSearchManiphestIndexer' => 'PhabricatorSearchDocumentIndexer',
'PhabricatorSearchMySQLExecutor' => 'PhabricatorSearchExecutor',
'PhabricatorSearchPhrictionIndexer' => 'PhabricatorSearchDocumentIndexer',
'PhabricatorSearchQuery' => 'PhabricatorSearchDAO',
'PhabricatorSearchResultView' => 'AphrontView',
'PhabricatorSearchSelectController' => 'PhabricatorSearchController',
'PhabricatorSearchUserIndexer' => 'PhabricatorSearchDocumentIndexer',
'PhabricatorSlowvoteChoice' => 'PhabricatorSlowvoteDAO',
'PhabricatorSlowvoteComment' => 'PhabricatorSlowvoteDAO',
'PhabricatorSlowvoteController' => 'PhabricatorController',
'PhabricatorSlowvoteCreateController' => 'PhabricatorSlowvoteController',
'PhabricatorSlowvoteDAO' => 'PhabricatorLiskDAO',
'PhabricatorSlowvoteListController' => 'PhabricatorSlowvoteController',
'PhabricatorSlowvoteOption' => 'PhabricatorSlowvoteDAO',
'PhabricatorSlowvotePoll' => 'PhabricatorSlowvoteDAO',
'PhabricatorSlowvotePollController' => 'PhabricatorSlowvoteController',
'PhabricatorStandardPageView' => 'AphrontPageView',
'PhabricatorStatusController' => 'PhabricatorController',
'PhabricatorTaskmasterDaemon' => 'PhabricatorDaemon',
'PhabricatorTestCase' => 'ArcanistPhutilTestCase',
'PhabricatorTimelineCursor' => 'PhabricatorTimelineDAO',
'PhabricatorTimelineDAO' => 'PhabricatorLiskDAO',
'PhabricatorTimelineEvent' => 'PhabricatorTimelineDAO',
'PhabricatorTimelineEventData' => 'PhabricatorTimelineDAO',
'PhabricatorTimer' => 'PhabricatorCountdownDAO',
'PhabricatorTransformedFile' => 'PhabricatorFileDAO',
'PhabricatorTrivialTestCase' => 'PhabricatorTestCase',
'PhabricatorTypeaheadCommonDatasourceController' => 'PhabricatorTypeaheadDatasourceController',
'PhabricatorTypeaheadDatasourceController' => 'PhabricatorController',
'PhabricatorUIExampleController' => 'PhabricatorController',
'PhabricatorUIExampleRenderController' => 'PhabricatorUIExampleController',
'PhabricatorUIListFilterExample' => 'PhabricatorUIExample',
'PhabricatorUIPagerExample' => 'PhabricatorUIExample',
'PhabricatorUser' => 'PhabricatorUserDAO',
'PhabricatorUserAccountSettingsPanelController' => 'PhabricatorUserSettingsPanelController',
'PhabricatorUserConduitSettingsPanelController' => 'PhabricatorUserSettingsPanelController',
'PhabricatorUserDAO' => 'PhabricatorLiskDAO',
'PhabricatorUserEmailSettingsPanelController' => 'PhabricatorUserSettingsPanelController',
'PhabricatorUserLog' => 'PhabricatorUserDAO',
'PhabricatorUserOAuthInfo' => 'PhabricatorUserDAO',
'PhabricatorUserOAuthSettingsPanelController' => 'PhabricatorUserSettingsPanelController',
'PhabricatorUserPreferenceSettingsPanelController' => 'PhabricatorUserSettingsPanelController',
'PhabricatorUserPreferences' => 'PhabricatorUserDAO',
'PhabricatorUserProfile' => 'PhabricatorUserDAO',
'PhabricatorUserProfileSettingsPanelController' => 'PhabricatorUserSettingsPanelController',
'PhabricatorUserSSHKey' => 'PhabricatorUserDAO',
'PhabricatorUserSSHKeysSettingsPanelController' => 'PhabricatorUserSettingsPanelController',
'PhabricatorUserSettingsController' => 'PhabricatorPeopleController',
'PhabricatorUserSettingsPanelController' => 'PhabricatorPeopleController',
'PhabricatorWorkerDAO' => 'PhabricatorLiskDAO',
'PhabricatorWorkerTask' => 'PhabricatorWorkerDAO',
'PhabricatorWorkerTaskData' => 'PhabricatorWorkerDAO',
'PhabricatorWorkerTaskDetailController' => 'PhabricatorDaemonController',
'PhabricatorXHPASTViewController' => 'PhabricatorController',
'PhabricatorXHPASTViewDAO' => 'PhabricatorLiskDAO',
'PhabricatorXHPASTViewFrameController' => 'PhabricatorXHPASTViewController',
'PhabricatorXHPASTViewFramesetController' => 'PhabricatorXHPASTViewController',
'PhabricatorXHPASTViewInputController' => 'PhabricatorXHPASTViewPanelController',
'PhabricatorXHPASTViewPanelController' => 'PhabricatorXHPASTViewController',
'PhabricatorXHPASTViewParseTree' => 'PhabricatorXHPASTViewDAO',
'PhabricatorXHPASTViewRunController' => 'PhabricatorXHPASTViewController',
'PhabricatorXHPASTViewStreamController' => 'PhabricatorXHPASTViewPanelController',
'PhabricatorXHPASTViewTreeController' => 'PhabricatorXHPASTViewPanelController',
'PhabricatorXHProfController' => 'PhabricatorController',
'PhabricatorXHProfProfileController' => 'PhabricatorXHProfController',
'PhabricatorXHProfProfileSymbolView' => 'AphrontView',
'PhabricatorXHProfProfileTopLevelView' => 'AphrontView',
'PhrictionActionConstants' => 'PhrictionConstants',
'PhrictionContent' => 'PhrictionDAO',
'PhrictionController' => 'PhabricatorController',
'PhrictionDAO' => 'PhabricatorLiskDAO',
'PhrictionDiffController' => 'PhrictionController',
'PhrictionDocument' => 'PhrictionDAO',
'PhrictionDocumentController' => 'PhrictionController',
'PhrictionDocumentPreviewController' => 'PhrictionController',
'PhrictionDocumentTestCase' => 'PhabricatorTestCase',
'PhrictionEditController' => 'PhrictionController',
'PhrictionHistoryController' => 'PhrictionController',
'PhrictionListController' => 'PhrictionController',
),
'requires_interface' =>
array(
),
));
diff --git a/src/applications/files/engine/s3/PhabricatorS3FileStorageEngine.php b/src/applications/files/engine/s3/PhabricatorS3FileStorageEngine.php
new file mode 100644
index 0000000000..fa3eb607dd
--- /dev/null
+++ b/src/applications/files/engine/s3/PhabricatorS3FileStorageEngine.php
@@ -0,0 +1,131 @@
+<?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.
+ */
+
+/**
+ * Amazon S3 file storage engine. This engine scales well but is relatively
+ * high-latency since data has to be pulled off S3.
+ *
+ * @task impl Implementation
+ * @task internal Internals
+ * @group filestorage
+ */
+final class PhabricatorS3FileStorageEngine
+ extends PhabricatorFileStorageEngine {
+
+/* -( Implementation )----------------------------------------------------- */
+
+
+ /**
+ * This engine identifies as "amazon-s3".
+ *
+ * @task impl
+ */
+ public function getEngineIdentifier() {
+ return 'amazon-s3';
+ }
+
+
+ /**
+ * Write file data into S3.
+ * @task impl
+ */
+ public function writeFile($data, array $params) {
+ $s3 = $this->newS3API();
+
+ $name = 'phabricator/'.sha1(Filesystem::readRandomBytes(20));
+
+ $s3->putObject(
+ $data,
+ $this->getBucketName(),
+ $name,
+ $acl = 'private');
+
+ return $name;
+ }
+
+
+ /**
+ * Load a stored blob from S3.
+ * @task impl
+ */
+ public function readFile($handle) {
+ $result = $this->newS3API()->getObject(
+ $this->getBucketName(),
+ $handle);
+ return $result->body;
+ }
+
+
+ /**
+ * Delete a blob from S3.
+ * @task impl
+ */
+ public function deleteFile($handle) {
+ $this->newS3API()->deleteObject(
+ $this->getBucketName(),
+ $handle);
+ }
+
+
+/* -( Internals )---------------------------------------------------------- */
+
+
+ /**
+ * Retrieve the S3 bucket name.
+ *
+ * @task internal
+ */
+ private function getBucketName() {
+ $bucket = PhabricatorEnv::getEnvConfig('storage.s3.bucket');
+ if (!$bucket) {
+ throw new Exception("No 'storage.s3.bucket' specified!");
+ }
+ return $bucket;
+ }
+
+ /**
+ * Create a new S3 API object.
+ *
+ * @task internal
+ */
+ private function newS3API() {
+ $libroot = dirname(phutil_get_library_root('phabricator'));
+ require_once $libroot.'/externals/s3/S3.php';
+
+ $access_key = PhabricatorEnv::getEnvConfig('amazon-s3.access-key');
+ $secret_key = PhabricatorEnv::getEnvConfig('amazon-s3.secret-key');
+
+ if (!$access_key || !$secret_key) {
+ throw new Exception(
+ "Specify 'amazon-s3.access-key' and 'amazon-s3.secret-key'!");
+ }
+
+ $s3 = newv(
+ 'S3',
+ array(
+ $access_key,
+ $secret_key,
+ $use_ssl = true,
+ ));
+
+ $s3->setExceptions(true);
+
+ return $s3;
+ }
+
+}
diff --git a/src/applications/files/engine/s3/__init__.php b/src/applications/files/engine/s3/__init__.php
new file mode 100644
index 0000000000..80acf0c1d2
--- /dev/null
+++ b/src/applications/files/engine/s3/__init__.php
@@ -0,0 +1,17 @@
+<?php
+/**
+ * This file is automatically generated. Lint this module to rebuild it.
+ * @generated
+ */
+
+
+
+phutil_require_module('phabricator', 'applications/files/engine/base');
+phutil_require_module('phabricator', 'infrastructure/env');
+
+phutil_require_module('phutil', 'filesystem');
+phutil_require_module('phutil', 'moduleutils');
+phutil_require_module('phutil', 'utils');
+
+
+phutil_require_source('PhabricatorS3FileStorageEngine.php');
diff --git a/src/applications/files/engineselector/default/PhabricatorDefaultFileStorageEngineSelector.php b/src/applications/files/engineselector/default/PhabricatorDefaultFileStorageEngineSelector.php
index e66b1dda7d..f3e23f1e20 100644
--- a/src/applications/files/engineselector/default/PhabricatorDefaultFileStorageEngineSelector.php
+++ b/src/applications/files/engineselector/default/PhabricatorDefaultFileStorageEngineSelector.php
@@ -1,65 +1,70 @@
<?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.
*/
/**
* Default storage engine selector. See
* @{class:PhabricatorFileStorageEngineSelector} and @{article:File Storage
* Technical Documentation} for more information.
*
* @group filestorage
*/
final class PhabricatorDefaultFileStorageEngineSelector
extends PhabricatorFileStorageEngineSelector {
/**
* Select viable default storage engines according to configuration. We'll
* select the MySQL and Local Disk storage engines if they are configured
* to allow a given file.
*/
public function selectStorageEngines($data, array $params) {
$length = strlen($data);
$mysql_key = 'storage.mysql-engine.max-size';
$mysql_limit = PhabricatorEnv::getEnvConfig($mysql_key);
$engines = array();
if ($mysql_limit && $length <= $mysql_limit) {
$engines[] = new PhabricatorMySQLFileStorageEngine();
}
$local_key = 'storage.local-disk.path';
$local_path = PhabricatorEnv::getEnvConfig($local_key);
if ($local_path) {
$engines[] = new PhabricatorLocalDiskFileStorageEngine();
}
+ $s3_key = 'storage.s3.bucket';
+ if (PhabricatorEnv::getEnvConfig($s3_key)) {
+ $engines[] = new PhabricatorS3FileStorageEngine();
+ }
+
if ($mysql_limit && empty($engines)) {
// If we return no engines, an exception will be thrown but it will be
// a little vague ("No valid storage engines"). Since this is a default
// case, throw a more specific exception.
throw new Exception(
"This file exceeds the configured MySQL storage engine filesize ".
"limit, but no other storage engines are configured. Increase the ".
"MySQL storage engine limit or configure a storage engine suitable ".
"for larger files.");
}
return $engines;
}
}
diff --git a/src/applications/files/engineselector/default/__init__.php b/src/applications/files/engineselector/default/__init__.php
index 1a67641d06..3b869431c5 100644
--- a/src/applications/files/engineselector/default/__init__.php
+++ b/src/applications/files/engineselector/default/__init__.php
@@ -1,15 +1,16 @@
<?php
/**
* This file is automatically generated. Lint this module to rebuild it.
* @generated
*/
phutil_require_module('phabricator', 'applications/files/engine/localdisk');
phutil_require_module('phabricator', 'applications/files/engine/mysql');
+phutil_require_module('phabricator', 'applications/files/engine/s3');
phutil_require_module('phabricator', 'applications/files/engineselector/base');
phutil_require_module('phabricator', 'infrastructure/env');
phutil_require_source('PhabricatorDefaultFileStorageEngineSelector.php');

File Metadata

Mime Type
text/x-diff
Expires
Tue, Dec 2, 7:20 PM (10 h, 40 m)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
432507
Default Alt Text
(183 KB)

Event Timeline