diff --git a/conf/default.conf.php b/conf/default.conf.php index 000bdc5e56..1c13a4db96 100644 --- a/conf/default.conf.php +++ b/conf/default.conf.php @@ -1,330 +1,337 @@ 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, // The default PHID for users who haven't uploaded a profile image. It should // be 50x50px. 'user.default-profile-image-phid' => 'PHID-FILE-4d61229816cfe6f2b2a3', // -- Access Control -------------------------------------------------------- // // Phabricator users have one of three access levels: "anyone", "verified", // or "admin". "anyone" means every user, including users who do not have // accounts or are not logged into the system. "verified" is users who have // accounts, are logged in, and have satisfied whatever verification steps // the configuration requires (e.g., email verification and/or manual // approval). "admin" is verified users with the "administrator" flag set. // These configuration options control which access level is required to read // data from Phabricator (e.g., view revisions and comments in Differential) // and write data to Phabricator (e.g., upload files and create diffs). By // default they are both set to "verified", meaning only verified user // accounts can interact with the system in any meaningful way. // If you are configuring an install for an open source project, you may // want to reduce the "phabricator.read-access" requirement to "anyone". This // will allow anyone to browse Phabricator content, even without logging in. // Alternatively, you could raise the "phabricator.write-access" requirement // to "admin", effectively creating a read-only install. // Controls the minimum access level required to read data from Phabricator // (e.g., view revisions in Differential). Allowed values are "anyone", // "verified", or "admin". Note that "anyone" includes users who are not // logged in! You should leave this at 'verified' unless you want your data // to be publicly readable (e.g., you are developing open source software). 'phabricator.read-access' => 'verified', // Controls the minimum access level required to write data to Phabricator // (e.g., create new revisions in Differential). Allowed values are // "verified" or "admin". Setting this to "admin" will effectively create a // read-only install. 'phabricator.write-access' => 'verified', // -- 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' => true, // 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. '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 PHP's mail() function. This is appropriate // if mail() 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, // -- 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, // -- 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, // 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, // -- 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 eaily 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, // -- Differential ---------------------------------------------------------- // 'differential.revision-custom-detail-renderer' => null, // -- Maniphest ------------------------------------------------------------- // 'maniphest.enabled' => true, // -- 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', ); diff --git a/externals/mimemailparser/LICENSE b/externals/mimemailparser/LICENSE new file mode 100644 index 0000000000..f13a43b63d --- /dev/null +++ b/externals/mimemailparser/LICENSE @@ -0,0 +1,125 @@ + The "Artistic License" + + Preamble + +The intent of this document is to state the conditions under which a +Package may be copied, such that the Copyright Holder maintains some +semblance of artistic control over the development of the package, +while giving the users of the package the right to use and distribute +the Package in a more-or-less customary fashion, plus the right to make +reasonable modifications. + +Definitions: + + "Package" refers to the collection of files distributed by the + Copyright Holder, and derivatives of that collection of files + created through textual modification. + + "Standard Version" refers to such a Package if it has not been + modified, or has been modified in accordance with the wishes + of the Copyright Holder as specified below. + + "Copyright Holder" is whoever is named in the copyright or + copyrights for the package. + + "You" is you, if you're thinking about copying or distributing + this Package. + + "Reasonable copying fee" is whatever you can justify on the + basis of media cost, duplication charges, time of people involved, + and so on. (You will not be required to justify it to the + Copyright Holder, but only to the computing community at large + as a market that must bear the fee.) + + "Freely Available" means that no fee is charged for the item + itself, though there may be fees involved in handling the item. + It also means that recipients of the item may redistribute it + under the same conditions they received it. + +1. You may make and give away verbatim copies of the source form of the +Standard Version of this Package without restriction, provided that you +duplicate all of the original copyright notices and associated disclaimers. + +2. You may apply bug fixes, portability fixes and other modifications +derived from the Public Domain or from the Copyright Holder. A Package +modified in such a way shall still be considered the Standard Version. + +3. You may otherwise modify your copy of this Package in any way, provided +that you insert a prominent notice in each changed file stating how and +when you changed that file, and provided that you do at least ONE of the +following: + + a) place your modifications in the Public Domain or otherwise make them + Freely Available, such as by posting said modifications to Usenet or + an equivalent medium, or placing the modifications on a major archive + site such as uunet.uu.net, or by allowing the Copyright Holder to include + your modifications in the Standard Version of the Package. + + b) use the modified Package only within your corporation or organization. + + c) rename any non-standard executables so the names do not conflict + with standard executables, which must also be provided, and provide + a separate manual page for each non-standard executable that clearly + documents how it differs from the Standard Version. + + d) make other distribution arrangements with the Copyright Holder. + +4. You may distribute the programs of this Package in object code or +executable form, provided that you do at least ONE of the following: + + a) distribute a Standard Version of the executables and library files, + together with instructions (in the manual page or equivalent) on where + to get the Standard Version. + + b) accompany the distribution with the machine-readable source of + the Package with your modifications. + + c) give non-standard executables non-standard names, and clearly + document the differences in manual pages (or equivalent), together + with instructions on where to get the Standard Version. + + d) make other distribution arrangements with the Copyright Holder. + +5. You may charge a reasonable copying fee for any distribution of this +Package. You may charge any fee you choose for support of this +Package. You may not charge a fee for this Package itself. However, +you may distribute this Package in aggregate with other (possibly +commercial) programs as part of a larger (possibly commercial) software +distribution provided that you do not advertise this Package as a +product of your own. You may embed this Package's interpreter within +an executable of yours (by linking); this shall be construed as a mere +form of aggregation, provided that the complete Standard Version of the +interpreter is so embedded. + +6. The scripts and library files supplied as input to or produced as +output from the programs of this Package do not automatically fall +under the copyright of this Package, but belong to whoever generated +them, and may be sold commercially, and may be aggregated with this +Package. If such scripts or library files are aggregated with this +Package via the so-called "undump" or "unexec" methods of producing a +binary executable image, then distribution of such an image shall +neither be construed as a distribution of this Package nor shall it +fall under the restrictions of Paragraphs 3 and 4, provided that you do +not represent such an executable image as a Standard Version of this +Package. + +7. C subroutines (or comparably compiled subroutines in other +languages) supplied by you and linked into this Package in order to +emulate subroutines and variables of the language defined by this +Package shall not be considered part of this Package, but are the +equivalent of input as in Paragraph 6, provided these subroutines do +not change the language in any way that would cause it to fail the +regression tests for the language. + +8. Aggregation of this Package with a commercial distribution is always +permitted provided that the use of this Package is embedded; that is, +when no overt attempt is made to make this Package's interfaces visible +to the end user of the commercial distribution. Such use shall not be +construed as a distribution of this Package. + +9. The name of the Copyright Holder may not be used to endorse or promote +products derived from this software without specific prior written permission. + +10. THIS PACKAGE IS PROVIDED "AS IS" AND WITHOUT ANY EXPRESS OR +IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED +WARRANTIES OF MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE. diff --git a/externals/mimemailparser/MimeMailParser.class.php b/externals/mimemailparser/MimeMailParser.class.php new file mode 100644 index 0000000000..1447ffc4b8 --- /dev/null +++ b/externals/mimemailparser/MimeMailParser.class.php @@ -0,0 +1,439 @@ +attachment_streams = array(); + } + + /** + * Free the held resouces + * @return void + */ + public function __destruct() { + // clear the email file resource + if (is_resource($this->stream)) { + fclose($this->stream); + } + // clear the MailParse resource + if (is_resource($this->resource)) { + mailparse_msg_free($this->resource); + } + // remove attachment resources + foreach($this->attachment_streams as $stream) { + fclose($stream); + } + } + + /** + * Set the file path we use to get the email text + * @return Object MimeMailParser Instance + * @param $mail_path Object + */ + public function setPath($path) { + // should parse message incrementally from file + $this->resource = mailparse_msg_parse_file($path); + $this->stream = fopen($path, 'r'); + $this->parse(); + return $this; + } + + /** + * Set the Stream resource we use to get the email text + * @return Object MimeMailParser Instance + * @param $stream Resource + */ + public function setStream($stream) { + + // streams have to be cached to file first + if (get_resource_type($stream) == 'stream') { + $tmp_fp = tmpfile(); + if ($tmp_fp) { + while(!feof($stream)) { + fwrite($tmp_fp, fread($stream, 2028)); + } + fseek($tmp_fp, 0); + $this->stream =& $tmp_fp; + } else { + throw new Exception('Could not create temporary files for attachments. Your tmp directory may be unwritable by PHP.'); + return false; + } + fclose($stream); + } else { + $this->stream = $stream; + } + + $this->resource = mailparse_msg_create(); + // parses the message incrementally low memory usage but slower + while(!feof($this->stream)) { + mailparse_msg_parse($this->resource, fread($this->stream, 2082)); + } + $this->parse(); + return $this; + } + + /** + * Set the email text + * @return Object MimeMailParser Instance + * @param $data String + */ + public function setText($data) { + $this->resource = mailparse_msg_create(); + // does not parse incrementally, fast memory hog might explode + mailparse_msg_parse($this->resource, $data); + $this->data = $data; + $this->parse(); + return $this; + } + + /** + * Parse the Message into parts + * @return void + * @private + */ + private function parse() { + $structure = mailparse_msg_get_structure($this->resource); + $this->parts = array(); + foreach($structure as $part_id) { + $part = mailparse_msg_get_part($this->resource, $part_id); + $this->parts[$part_id] = mailparse_msg_get_part_data($part); + } + } + + /** + * Retrieve the Email Headers + * @return Array + */ + public function getHeaders() { + if (isset($this->parts[1])) { + return $this->getPartHeaders($this->parts[1]); + } else { + throw new Exception('MimeMailParser::setPath() or MimeMailParser::setText() must be called before retrieving email headers.'); + } + return false; + } + /** + * Retrieve the raw Email Headers + * @return string + */ + public function getHeadersRaw() { + if (isset($this->parts[1])) { + return $this->getPartHeaderRaw($this->parts[1]); + } else { + throw new Exception('MimeMailParser::setPath() or MimeMailParser::setText() must be called before retrieving email headers.'); + } + return false; + } + + /** + * Retrieve a specific Email Header + * @return String + * @param $name String Header name + */ + public function getHeader($name) { + if (isset($this->parts[1])) { + $headers = $this->getPartHeaders($this->parts[1]); + if (isset($headers[$name])) { + return $headers[$name]; + } + } else { + throw new Exception('MimeMailParser::setPath() or MimeMailParser::setText() must be called before retrieving email headers.'); + } + return false; + } + + /** + * Returns the email message body in the specified format + * @return Mixed String Body or False if not found + * @param $type Object[optional] + */ + public function getMessageBody($type = 'text') { + $body = false; + $mime_types = array( + 'text'=> 'text/plain', + 'html'=> 'text/html' + ); + if (in_array($type, array_keys($mime_types))) { + foreach($this->parts as $part) { + if ($this->getPartContentType($part) == $mime_types[$type]) { + $headers = $this->getPartHeaders($part); + $body = $this->decode($this->getPartBody($part), array_key_exists('content-transfer-encoding', $headers) ? +$headers['content-transfer-encoding'] : ''); + } + } + } else { + throw new Exception('Invalid type specified for MimeMailParser::getMessageBody. "type" can either be text or html.'); + } + return $body; + } + + /** + * get the headers for the message body part. + * @return Array + * @param $type Object[optional] + */ + public function getMessageBodyHeaders($type = 'text') { + $headers = false; + $mime_types = array( + 'text'=> 'text/plain', + 'html'=> 'text/html' + ); + if (in_array($type, array_keys($mime_types))) { + foreach($this->parts as $part) { + if ($this->getPartContentType($part) == $mime_types[$type]) { + $headers = $this->getPartHeaders($part); + } + } + } else { + throw new Exception('Invalid type specified for MimeMailParser::getMessageBody. "type" can either be text or html.'); + } + return $headers; + } + + + /** + * Returns the attachments contents in order of appearance + * @return Array + * @param $type Object[optional] + */ + public function getAttachments() { + $attachments = array(); + $dispositions = array("attachment","inline"); + foreach($this->parts as $part) { + $disposition = $this->getPartContentDisposition($part); + if (in_array($disposition, $dispositions)) { + $attachments[] = new MimeMailParser_attachment( + $part['disposition-filename'], + $this->getPartContentType($part), + $this->getAttachmentStream($part), + $disposition, + $this->getPartHeaders($part) + ); + } + } + return $attachments; + } + + /** + * Return the Headers for a MIME part + * @return Array + * @param $part Array + */ + private function getPartHeaders($part) { + if (isset($part['headers'])) { + return $part['headers']; + } + return false; + } + + /** + * Return a Specific Header for a MIME part + * @return Array + * @param $part Array + * @param $header String Header Name + */ + private function getPartHeader($part, $header) { + if (isset($part['headers'][$header])) { + return $part['headers'][$header]; + } + return false; + } + + /** + * Return the ContentType of the MIME part + * @return String + * @param $part Array + */ + private function getPartContentType($part) { + if (isset($part['content-type'])) { + return $part['content-type']; + } + return false; + } + + /** + * Return the Content Disposition + * @return String + * @param $part Array + */ + private function getPartContentDisposition($part) { + if (isset($part['content-disposition'])) { + return $part['content-disposition']; + } + return false; + } + + /** + * Retrieve the raw Header of a MIME part + * @return String + * @param $part Object + */ + private function getPartHeaderRaw(&$part) { + $header = ''; + if ($this->stream) { + $header = $this->getPartHeaderFromFile($part); + } else if ($this->data) { + $header = $this->getPartHeaderFromText($part); + } else { + throw new Exception('MimeMailParser::setPath() or MimeMailParser::setText() must be called before retrieving email parts.'); + } + return $header; + } + /** + * Retrieve the Body of a MIME part + * @return String + * @param $part Object + */ + private function getPartBody(&$part) { + $body = ''; + if ($this->stream) { + $body = $this->getPartBodyFromFile($part); + } else if ($this->data) { + $body = $this->getPartBodyFromText($part); + } else { + throw new Exception('MimeMailParser::setPath() or MimeMailParser::setText() must be called before retrieving email parts.'); + } + return $body; + } + + /** + * Retrieve the Header from a MIME part from file + * @return String Mime Header Part + * @param $part Array + */ + private function getPartHeaderFromFile(&$part) { + $start = $part['starting-pos']; + $end = $part['starting-pos-body']; + fseek($this->stream, $start, SEEK_SET); + $header = fread($this->stream, $end-$start); + return $header; + } + /** + * Retrieve the Body from a MIME part from file + * @return String Mime Body Part + * @param $part Array + */ + private function getPartBodyFromFile(&$part) { + $start = $part['starting-pos-body']; + $end = $part['ending-pos-body']; + fseek($this->stream, $start, SEEK_SET); + $body = fread($this->stream, $end-$start); + return $body; + } + + /** + * Retrieve the Header from a MIME part from text + * @return String Mime Header Part + * @param $part Array + */ + private function getPartHeaderFromText(&$part) { + $start = $part['starting-pos']; + $end = $part['starting-pos-body']; + $header = substr($this->data, $start, $end-$start); + return $header; + } + /** + * Retrieve the Body from a MIME part from text + * @return String Mime Body Part + * @param $part Array + */ + private function getPartBodyFromText(&$part) { + $start = $part['starting-pos-body']; + $end = $part['ending-pos-body']; + $body = substr($this->data, $start, $end-$start); + return $body; + } + + /** + * Read the attachment Body and save temporary file resource + * @return String Mime Body Part + * @param $part Array + */ + private function getAttachmentStream(&$part) { + $temp_fp = tmpfile(); + + array_key_exists('content-transfer-encoding', $part['headers']) ? $encoding = $part['headers']['content-transfer-encoding'] : $encoding = ''; + + if ($temp_fp) { + if ($this->stream) { + $start = $part['starting-pos-body']; + $end = $part['ending-pos-body']; + fseek($this->stream, $start, SEEK_SET); + $len = $end-$start; + $written = 0; + $write = 2028; + $body = ''; + while($written < $len) { + if (($written+$write < $len )) { + $write = $len - $written; + } + $part = fread($this->stream, $write); + fwrite($temp_fp, $this->decode($part, $encoding)); + $written += $write; + } + } else if ($this->data) { + $attachment = $this->decode($this->getPartBodyFromText($part), $encoding); + fwrite($temp_fp, $attachment, strlen($attachment)); + } + fseek($temp_fp, 0, SEEK_SET); + } else { + throw new Exception('Could not create temporary files for attachments. Your tmp directory may be unwritable by PHP.'); + return false; + } + return $temp_fp; + } + + + /** + * Decode the string depending on encoding type. + * @return String the decoded string. + * @param $encodedString The string in its original encoded state. + * @param $encodingType The encoding type from the Content-Transfer-Encoding header of the part. + */ + private function decode($encodedString, $encodingType) { + if (strtolower($encodingType) == 'base64') { + return base64_decode($encodedString); + } else if (strtolower($encodingType) == 'quoted-printable') { + return quoted_printable_decode($encodedString); + } else { + return $encodedString; + } + } + +} + + +?> diff --git a/externals/mimemailparser/README b/externals/mimemailparser/README new file mode 100644 index 0000000000..2975add6d9 --- /dev/null +++ b/externals/mimemailparser/README @@ -0,0 +1,3 @@ +From: + + http://code.google.com/p/php-mime-mail-parser/ diff --git a/externals/mimemailparser/attachment.class.php b/externals/mimemailparser/attachment.class.php new file mode 100644 index 0000000000..d56aba1f8d --- /dev/null +++ b/externals/mimemailparser/attachment.class.php @@ -0,0 +1,136 @@ +filename = $filename; + $this->content_type = $content_type; + $this->stream = $stream; + $this->content = null; + $this->content_disposition = $content_disposition; + $this->headers = $headers; + } + + /** + * retrieve the attachment filename + * @return String + */ + public function getFilename() { + return $this->filename; + } + + /** + * Retrieve the Attachment Content-Type + * @return String + */ + public function getContentType() { + return $this->content_type; + } + + /** + * Retrieve the Attachment Content-Disposition + * @return String + */ + public function getContentDisposition() { + return $this->content_disposition; + } + + /** + * Retrieve the Attachment Headers + * @return String + */ + public function getHeaders() { + return $this->headers; + } + + /** + * Retrieve the file extension + * @return String + */ + public function getFileExtension() { + if (!$this->extension) { + $ext = substr(strrchr($this->filename, '.'), 1); + if ($ext == 'gz') { + // special case, tar.gz + // todo: other special cases? + $ext = preg_match("/\.tar\.gz$/i", $ext) ? 'tar.gz' : 'gz'; + } + $this->extension = $ext; + } + return $this->extension; + } + + /** + * Read the contents a few bytes at a time until completed + * Once read to completion, it always returns false + * @return String + * @param $bytes Int[optional] + */ + public function read($bytes = 2082) { + return feof($this->stream) ? false : fread($this->stream, $bytes); + } + + /** + * Retrieve the file content in one go + * Once you retreive the content you cannot use MimeMailParser_attachment::read() + * @return String + */ + public function getContent() { + if ($this->content === null) { + fseek($this->stream, 0); + while(($buf = $this->read()) !== false) { + $this->content .= $buf; + } + } + return $this->content; + } + + /** + * Allow the properties + * MimeMailParser_attachment::$name, + * MimeMailParser_attachment::$extension + * to be retrieved as public properties + * @param $name Object + */ + public function __get($name) { + if ($name == 'content') { + return $this->getContent(); + } else if ($name == 'extension') { + return $this->getFileExtension(); + } + return null; + } + +} + +?> diff --git a/resources/sql/patches/036.mailkey.sql b/resources/sql/patches/036.mailkey.sql new file mode 100644 index 0000000000..7831465365 --- /dev/null +++ b/resources/sql/patches/036.mailkey.sql @@ -0,0 +1,19 @@ +ALTER TABLE phabricator_differential.differential_revision + ADD mailKey VARCHAR(40) binary NOT NULL; + +ALTER TABLE phabricator_maniphest.maniphest_task + ADD mailKey VARCHAR(40) binary NOT NULL; + +CREATE TABLE phabricator_metamta.metamta_receivedmail ( + id int unsigned not null primary key auto_increment, + headers longblob not null, + bodies longblob not null, + attachments longblob not null, + relatedPHID varchar(64) binary, + key(relatedPHID), + authorPHID varchar(64) binary, + key(authorPHID), + message longblob, + dateCreated int unsigned not null, + dateModified int unsigned not null +) engine=innodb; \ No newline at end of file diff --git a/scripts/mail/mail_handler.php b/scripts/mail/mail_handler.php new file mode 100755 index 0000000000..f2f5412e75 --- /dev/null +++ b/scripts/mail/mail_handler.php @@ -0,0 +1,54 @@ +#!/usr/bin/php +setText(file_get_contents('php://stdin')); + +$received = new PhabricatorMetaMTAReceivedMail(); +$received->setHeaders($parser->getHeaders()); +$received->setBodies(array( + 'text' => $parser->getMessageBody('text'), + 'html' => $parser->getMessageBody('html'), +)); + +$attachments = array(); +foreach ($received->getAttachments() as $attachment) { + $file = PhabricatorFile::newFromFileData( + $attachment->getContent(), + array( + 'name' => $attachment->getFilename(), + )); + $attachments[] = $file->getPHID(); +} +$received->setAttachments($attachments); +$received->save(); + +$received->processReceivedMail(); diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php index 1751d70910..7e16878725 100644 --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -1,873 +1,879 @@ array( 'Aphront400Response' => 'aphront/response/400', 'Aphront404Response' => 'aphront/response/404', 'AphrontAjaxResponse' => 'aphront/response/ajax', 'AphrontApplicationConfiguration' => 'aphront/applicationconfiguration', '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', 'AphrontFileResponse' => 'aphront/response/file', 'AphrontFormCheckboxControl' => 'view/form/control/checkbox', 'AphrontFormControl' => 'view/form/control/base', 'AphrontFormDividerControl' => 'view/form/control/divider', 'AphrontFormFileControl' => 'view/form/control/file', '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__', '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', '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_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_diffusion_getcommits_Method' => 'applications/conduit/method/diffusion/getcommits', 'ConduitAPI_file_upload_Method' => 'applications/conduit/method/file/upload', 'ConduitAPI_path_getowners_Method' => 'applications/conduit/method/path/getowners', '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', 'DarkConsoleServicesPluginAPI' => 'aphront/console/plugin/services/api', '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', 'DifferentialAttachController' => 'applications/differential/controller/attach', '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', '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', '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', 'DifferentialMarkupEngineFactory' => 'applications/differential/parser/markup', 'DifferentialNewDiffMail' => 'applications/differential/mail/newdiff', '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', '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', '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', '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__', 'ManiphestController' => 'applications/maniphest/controller/base', 'ManiphestDAO' => 'applications/maniphest/storage/base', 'ManiphestTask' => 'applications/maniphest/storage/task', 'ManiphestTaskDetailController' => 'applications/maniphest/controller/taskdetail', 'ManiphestTaskEditController' => 'applications/maniphest/controller/taskedit', 'ManiphestTaskListController' => 'applications/maniphest/controller/tasklist', 'ManiphestTaskListView' => 'applications/maniphest/view/tasklist', 'ManiphestTaskPriority' => 'applications/maniphest/constants/priority', 'ManiphestTaskSelectorSearchController' => 'applications/maniphest/controller/taskselectorsearch', 'ManiphestTaskStatus' => 'applications/maniphest/constants/status', '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', 'ManiphestTransactionSaveController' => 'applications/maniphest/controller/transactionsave', 'ManiphestTransactionType' => 'applications/maniphest/constants/transactiontype', 'Phabricator404Controller' => 'applications/base/controller/404', 'PhabricatorAuthController' => 'applications/auth/controller/base', 'PhabricatorConduitAPIController' => 'applications/conduit/controller/api', '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', 'PhabricatorController' => 'applications/base/controller/base', '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', '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', 'PhabricatorDraft' => 'applications/draft/storage/draft', 'PhabricatorDraftDAO' => 'applications/draft/storage/base', 'PhabricatorEditPreferencesController' => 'applications/preferences/controller/edit', 'PhabricatorEmailLoginController' => 'applications/auth/controller/email', 'PhabricatorEmailTokenController' => 'applications/auth/controller/emailtoken', 'PhabricatorEnv' => 'infrastructure/env', 'PhabricatorFile' => 'applications/files/storage/file', 'PhabricatorFileController' => 'applications/files/controller/base', 'PhabricatorFileDAO' => 'applications/files/storage/base', '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', 'PhabricatorFileURI' => 'applications/files/uri', 'PhabricatorFileUploadController' => 'applications/files/controller/upload', 'PhabricatorFileViewController' => 'applications/files/controller/view', 'PhabricatorGoodForNothingWorker' => 'infrastructure/daemon/workers/worker/goodfornothing', 'PhabricatorHandleObjectSelectorDataView' => 'applications/phid/handle/view/selector', 'PhabricatorLiskDAO' => 'applications/base/storage/lisk', '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', 'PhabricatorMailImplementationTestAdapter' => 'applications/metamta/adapter/test', 'PhabricatorMetaMTAController' => 'applications/metamta/controller/base', 'PhabricatorMetaMTADAO' => 'applications/metamta/storage/base', 'PhabricatorMetaMTADaemon' => 'applications/metamta/daemon/mta', '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', 'PhabricatorMetaMTAViewController' => 'applications/metamta/controller/view', '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', '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', 'PhabricatorPHIDAllocateController' => 'applications/phid/controller/allocate', 'PhabricatorPHIDConstants' => 'applications/phid/constants', 'PhabricatorPHIDController' => 'applications/phid/controller/base', 'PhabricatorPHIDDAO' => 'applications/phid/storage/base', 'PhabricatorPHIDListController' => 'applications/phid/controller/list', 'PhabricatorPHIDLookupController' => 'applications/phid/controller/lookup', 'PhabricatorPeopleController' => 'applications/people/controller/base', 'PhabricatorPeopleEditController' => 'applications/people/controller/edit', 'PhabricatorPeopleListController' => 'applications/people/controller/list', 'PhabricatorPeopleProfileController' => 'applications/people/controller/profile', 'PhabricatorPeopleProfileEditController' => 'applications/people/controller/profileedit', 'PhabricatorPreferencesController' => 'applications/preferences/controller/base', 'PhabricatorProject' => 'applications/project/storage/project', 'PhabricatorProjectAffiliation' => 'applications/project/storage/affiliation', 'PhabricatorProjectAffiliationEditController' => 'applications/project/controller/editaffiliation', 'PhabricatorProjectController' => 'applications/project/controller/base', 'PhabricatorProjectDAO' => 'applications/project/storage/base', 'PhabricatorProjectEditController' => 'applications/project/controller/edit', 'PhabricatorProjectListController' => 'applications/project/controller/list', 'PhabricatorProjectProfile' => 'applications/project/storage/profile', 'PhabricatorProjectProfileController' => 'applications/project/controller/profile', 'PhabricatorRedirectController' => 'applications/base/controller/redirect', 'PhabricatorRemarkupRuleDifferential' => 'infrastructure/markup/remarkup/markuprule/differential', 'PhabricatorRemarkupRuleDiffusion' => 'infrastructure/markup/remarkup/markuprule/diffusion', 'PhabricatorRemarkupRuleImageMacro' => 'infrastructure/markup/remarkup/markuprule/imagemacro', 'PhabricatorRemarkupRuleManiphest' => 'infrastructure/markup/remarkup/markuprule/maniphest', 'PhabricatorRemarkupRuleProxyImage' => 'infrastructure/markup/remarkup/markuprule/proxyimage', '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', 'PhabricatorSearchAbstractDocument' => 'applications/search/index/abstractdocument', 'PhabricatorSearchBaseController' => 'applications/search/controller/base', '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', 'PhabricatorSearchManiphestIndexer' => 'applications/search/index/indexer/maniphest', 'PhabricatorSearchMySQLExecutor' => 'applications/search/execute/mysql', 'PhabricatorSearchQuery' => 'applications/search/storage/query', 'PhabricatorSearchRelationship' => 'applications/search/constants/relationship', 'PhabricatorStandardPageView' => 'view/page/standard', 'PhabricatorStatusController' => 'applications/status/base', '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', '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', 'PhabricatorUserDAO' => 'applications/people/storage/base', 'PhabricatorUserOAuthInfo' => 'applications/people/storage/useroauthinfo', 'PhabricatorUserPreferences' => 'applications/people/storage/preferences', 'PhabricatorUserProfile' => 'applications/people/storage/profile', 'PhabricatorUserSettingsController' => 'applications/people/controller/settings', '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', ), '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_format_relative_time' => 'view/utils', 'phabricator_format_timestamp' => 'view/utils', 'phabricator_format_units_generic' => 'view/utils', 'phabricator_render_form' => 'infrastructure/javelin/markup', '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( 'Aphront400Response' => 'AphrontResponse', 'Aphront404Response' => 'AphrontResponse', 'AphrontAjaxResponse' => 'AphrontResponse', 'AphrontCrumbsView' => 'AphrontView', 'AphrontDefaultApplicationConfiguration' => 'AphrontApplicationConfiguration', 'AphrontDefaultApplicationController' => 'AphrontController', 'AphrontDialogResponse' => 'AphrontResponse', 'AphrontDialogView' => 'AphrontView', 'AphrontErrorView' => 'AphrontView', 'AphrontFileResponse' => 'AphrontResponse', 'AphrontFormCheckboxControl' => 'AphrontFormControl', 'AphrontFormControl' => 'AphrontView', 'AphrontFormDividerControl' => 'AphrontFormControl', 'AphrontFormFileControl' => 'AphrontFormControl', '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', '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', 'AphrontRequestFailureView' => 'AphrontView', 'AphrontSideNavView' => 'AphrontView', 'AphrontTableView' => 'AphrontView', 'AphrontTokenizerTemplateView' => 'AphrontView', 'AphrontTypeaheadTemplateView' => 'AphrontView', 'AphrontWebpageResponse' => 'AphrontResponse', 'CelerityResourceController' => 'AphrontController', 'ConduitAPI_conduit_connect_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_diffusion_getcommits_Method' => 'ConduitAPIMethod', 'ConduitAPI_file_upload_Method' => 'ConduitAPIMethod', 'ConduitAPI_path_getowners_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', 'DifferentialAttachController' => 'DifferentialController', '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', 'DifferentialHunk' => 'DifferentialDAO', 'DifferentialInlineComment' => 'DifferentialDAO', 'DifferentialInlineCommentEditController' => 'DifferentialController', 'DifferentialInlineCommentPreviewController' => 'DifferentialController', 'DifferentialInlineCommentView' => 'AphrontView', 'DifferentialNewDiffMail' => 'DifferentialReviewRequestMail', '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', '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', 'ManiphestController' => 'PhabricatorController', 'ManiphestDAO' => 'PhabricatorLiskDAO', 'ManiphestTask' => 'ManiphestDAO', 'ManiphestTaskDetailController' => 'ManiphestController', 'ManiphestTaskEditController' => 'ManiphestController', 'ManiphestTaskListController' => 'ManiphestController', 'ManiphestTaskListView' => 'AphrontView', 'ManiphestTaskSelectorSearchController' => 'ManiphestController', 'ManiphestTaskSummaryView' => 'AphrontView', 'ManiphestTransaction' => 'ManiphestDAO', 'ManiphestTransactionDetailView' => 'AphrontView', 'ManiphestTransactionListView' => 'AphrontView', 'ManiphestTransactionSaveController' => 'ManiphestController', 'Phabricator404Controller' => 'PhabricatorController', 'PhabricatorAuthController' => 'PhabricatorController', 'PhabricatorConduitAPIController' => 'PhabricatorConduitController', 'PhabricatorConduitConnectionLog' => 'PhabricatorConduitDAO', 'PhabricatorConduitConsoleController' => 'PhabricatorConduitController', 'PhabricatorConduitController' => 'PhabricatorController', 'PhabricatorConduitDAO' => 'PhabricatorLiskDAO', 'PhabricatorConduitLogController' => 'PhabricatorConduitController', 'PhabricatorConduitMethodCallLog' => 'PhabricatorConduitDAO', 'PhabricatorController' => 'AphrontController', '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', 'PhabricatorDirectoryCategory' => 'PhabricatorDirectoryDAO', 'PhabricatorDirectoryCategoryDeleteController' => 'PhabricatorDirectoryController', 'PhabricatorDirectoryCategoryEditController' => 'PhabricatorDirectoryController', 'PhabricatorDirectoryCategoryListController' => 'PhabricatorDirectoryController', 'PhabricatorDirectoryController' => 'PhabricatorController', 'PhabricatorDirectoryDAO' => 'PhabricatorLiskDAO', 'PhabricatorDirectoryItem' => 'PhabricatorDirectoryDAO', 'PhabricatorDirectoryItemDeleteController' => 'PhabricatorDirectoryController', 'PhabricatorDirectoryItemEditController' => 'PhabricatorDirectoryController', 'PhabricatorDirectoryItemListController' => 'PhabricatorDirectoryController', 'PhabricatorDirectoryMainController' => 'PhabricatorDirectoryController', 'PhabricatorDraft' => 'PhabricatorDraftDAO', 'PhabricatorDraftDAO' => 'PhabricatorLiskDAO', 'PhabricatorEditPreferencesController' => 'PhabricatorPreferencesController', 'PhabricatorEmailLoginController' => 'PhabricatorAuthController', 'PhabricatorEmailTokenController' => 'PhabricatorAuthController', 'PhabricatorFile' => 'PhabricatorFileDAO', 'PhabricatorFileController' => 'PhabricatorController', 'PhabricatorFileDAO' => 'PhabricatorLiskDAO', 'PhabricatorFileImageMacro' => 'PhabricatorFileDAO', 'PhabricatorFileListController' => 'PhabricatorFileController', 'PhabricatorFileMacroDeleteController' => 'PhabricatorFileController', 'PhabricatorFileMacroEditController' => 'PhabricatorFileController', 'PhabricatorFileMacroListController' => 'PhabricatorFileController', 'PhabricatorFileProxyController' => 'PhabricatorFileController', 'PhabricatorFileProxyImage' => 'PhabricatorFileDAO', 'PhabricatorFileStorageBlob' => 'PhabricatorFileDAO', 'PhabricatorFileUploadController' => 'PhabricatorFileController', 'PhabricatorFileViewController' => 'PhabricatorFileController', 'PhabricatorGoodForNothingWorker' => 'PhabricatorWorker', 'PhabricatorLiskDAO' => 'LiskDAO', 'PhabricatorLoginController' => 'PhabricatorAuthController', 'PhabricatorLogoutController' => 'PhabricatorAuthController', 'PhabricatorMailImplementationAmazonSESAdapter' => 'PhabricatorMailImplementationPHPMailerLiteAdapter', 'PhabricatorMailImplementationPHPMailerLiteAdapter' => 'PhabricatorMailImplementationAdapter', 'PhabricatorMailImplementationTestAdapter' => 'PhabricatorMailImplementationAdapter', 'PhabricatorMetaMTAController' => 'PhabricatorController', 'PhabricatorMetaMTADAO' => 'PhabricatorLiskDAO', 'PhabricatorMetaMTADaemon' => 'PhabricatorDaemon', 'PhabricatorMetaMTAListController' => 'PhabricatorMetaMTAController', 'PhabricatorMetaMTAMail' => 'PhabricatorMetaMTADAO', 'PhabricatorMetaMTAMailTestCase' => 'PhabricatorTestCase', 'PhabricatorMetaMTAMailingList' => 'PhabricatorMetaMTADAO', 'PhabricatorMetaMTAMailingListEditController' => 'PhabricatorMetaMTAController', 'PhabricatorMetaMTAMailingListsController' => 'PhabricatorMetaMTAController', + 'PhabricatorMetaMTAReceiveController' => 'PhabricatorMetaMTAController', + 'PhabricatorMetaMTAReceivedListController' => 'PhabricatorMetaMTAController', + 'PhabricatorMetaMTAReceivedMail' => 'PhabricatorMetaMTADAO', 'PhabricatorMetaMTASendController' => 'PhabricatorMetaMTAController', 'PhabricatorMetaMTAViewController' => 'PhabricatorMetaMTAController', 'PhabricatorOAuthDefaultRegistrationController' => 'PhabricatorOAuthRegistrationController', 'PhabricatorOAuthDiagnosticsController' => 'PhabricatorAuthController', 'PhabricatorOAuthFailureView' => 'AphrontView', 'PhabricatorOAuthLoginController' => 'PhabricatorAuthController', 'PhabricatorOAuthProviderFacebook' => 'PhabricatorOAuthProvider', 'PhabricatorOAuthProviderGithub' => 'PhabricatorOAuthProvider', 'PhabricatorOAuthRegistrationController' => 'PhabricatorAuthController', 'PhabricatorOAuthUnlinkController' => 'PhabricatorAuthController', 'PhabricatorOwnersController' => 'PhabricatorController', 'PhabricatorOwnersDAO' => 'PhabricatorLiskDAO', 'PhabricatorOwnersDeleteController' => 'PhabricatorOwnersController', 'PhabricatorOwnersDetailController' => 'PhabricatorOwnersController', 'PhabricatorOwnersEditController' => 'PhabricatorOwnersController', 'PhabricatorOwnersListController' => 'PhabricatorOwnersController', 'PhabricatorOwnersOwner' => 'PhabricatorOwnersDAO', 'PhabricatorOwnersPackage' => 'PhabricatorOwnersDAO', 'PhabricatorOwnersPath' => 'PhabricatorOwnersDAO', 'PhabricatorPHID' => 'PhabricatorPHIDDAO', 'PhabricatorPHIDAllocateController' => 'PhabricatorPHIDController', 'PhabricatorPHIDController' => 'PhabricatorController', 'PhabricatorPHIDDAO' => 'PhabricatorLiskDAO', 'PhabricatorPHIDListController' => 'PhabricatorPHIDController', 'PhabricatorPHIDLookupController' => 'PhabricatorPHIDController', 'PhabricatorPeopleController' => 'PhabricatorController', 'PhabricatorPeopleEditController' => 'PhabricatorPeopleController', 'PhabricatorPeopleListController' => 'PhabricatorPeopleController', 'PhabricatorPeopleProfileController' => 'PhabricatorPeopleController', 'PhabricatorPeopleProfileEditController' => 'PhabricatorPeopleController', 'PhabricatorPreferencesController' => 'PhabricatorController', 'PhabricatorProject' => 'PhabricatorProjectDAO', 'PhabricatorProjectAffiliation' => 'PhabricatorProjectDAO', 'PhabricatorProjectAffiliationEditController' => 'PhabricatorProjectController', 'PhabricatorProjectController' => 'PhabricatorController', 'PhabricatorProjectDAO' => 'PhabricatorLiskDAO', 'PhabricatorProjectEditController' => 'PhabricatorProjectController', 'PhabricatorProjectListController' => 'PhabricatorProjectController', 'PhabricatorProjectProfile' => 'PhabricatorProjectDAO', 'PhabricatorProjectProfileController' => 'PhabricatorProjectController', 'PhabricatorRedirectController' => 'PhabricatorController', 'PhabricatorRemarkupRuleDifferential' => 'PhutilRemarkupRule', 'PhabricatorRemarkupRuleDiffusion' => 'PhutilRemarkupRule', 'PhabricatorRemarkupRuleImageMacro' => 'PhutilRemarkupRule', 'PhabricatorRemarkupRuleManiphest' => 'PhutilRemarkupRule', 'PhabricatorRemarkupRuleProxyImage' => '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', 'PhabricatorSearchBaseController' => 'PhabricatorController', 'PhabricatorSearchController' => 'PhabricatorSearchBaseController', 'PhabricatorSearchDAO' => 'PhabricatorLiskDAO', 'PhabricatorSearchDifferentialIndexer' => 'PhabricatorSearchDocumentIndexer', 'PhabricatorSearchDocument' => 'PhabricatorSearchDAO', 'PhabricatorSearchDocumentField' => 'PhabricatorSearchDAO', 'PhabricatorSearchDocumentRelationship' => 'PhabricatorSearchDAO', 'PhabricatorSearchManiphestIndexer' => 'PhabricatorSearchDocumentIndexer', 'PhabricatorSearchMySQLExecutor' => 'PhabricatorSearchExecutor', 'PhabricatorSearchQuery' => 'PhabricatorSearchDAO', 'PhabricatorStandardPageView' => 'AphrontPageView', 'PhabricatorStatusController' => 'PhabricatorController', 'PhabricatorTaskmasterDaemon' => 'PhabricatorDaemon', 'PhabricatorTestCase' => 'ArcanistPhutilTestCase', 'PhabricatorTimelineCursor' => 'PhabricatorTimelineDAO', 'PhabricatorTimelineDAO' => 'PhabricatorLiskDAO', 'PhabricatorTimelineEvent' => 'PhabricatorTimelineDAO', 'PhabricatorTimelineEventData' => 'PhabricatorTimelineDAO', 'PhabricatorTypeaheadCommonDatasourceController' => 'PhabricatorTypeaheadDatasourceController', 'PhabricatorTypeaheadDatasourceController' => 'PhabricatorController', 'PhabricatorUIExampleController' => 'PhabricatorController', 'PhabricatorUIExampleRenderController' => 'PhabricatorUIExampleController', 'PhabricatorUIListFilterExample' => 'PhabricatorUIExample', 'PhabricatorUIPagerExample' => 'PhabricatorUIExample', 'PhabricatorUser' => 'PhabricatorUserDAO', 'PhabricatorUserDAO' => 'PhabricatorLiskDAO', 'PhabricatorUserOAuthInfo' => 'PhabricatorUserDAO', 'PhabricatorUserPreferences' => 'PhabricatorUserDAO', 'PhabricatorUserProfile' => 'PhabricatorUserDAO', 'PhabricatorUserSettingsController' => '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', ), 'requires_interface' => array( ), )); diff --git a/src/aphront/default/configuration/AphrontDefaultApplicationConfiguration.php b/src/aphront/default/configuration/AphrontDefaultApplicationConfiguration.php index 4aa8c1b68b..1342fcd211 100644 --- a/src/aphront/default/configuration/AphrontDefaultApplicationConfiguration.php +++ b/src/aphront/default/configuration/AphrontDefaultApplicationConfiguration.php @@ -1,405 +1,407 @@ getResourceURIMapRules() + array( '/' => array( '$' => 'PhabricatorDirectoryMainController', ), '/directory/' => array( 'item/$' => 'PhabricatorDirectoryItemListController', 'item/edit/(?:(?P\d+)/)?$' => 'PhabricatorDirectoryItemEditController', 'item/delete/(?P\d+)/' => 'PhabricatorDirectoryItemDeleteController', 'category/$' => 'PhabricatorDirectoryCategoryListController', 'category/edit/(?:(?P\d+)/)?$' => 'PhabricatorDirectoryCategoryEditController', 'category/delete/(?P\d+)/' => 'PhabricatorDirectoryCategoryDeleteController', ), '/file/' => array( '$' => 'PhabricatorFileListController', 'upload/$' => 'PhabricatorFileUploadController', '(?Pinfo)/(?P[^/]+)/' => 'PhabricatorFileViewController', '(?Pview)/(?P[^/]+)/' => 'PhabricatorFileViewController', '(?Pdownload)/(?P[^/]+)/' => 'PhabricatorFileViewController', 'macro/' => array( '$' => 'PhabricatorFileMacroListController', 'edit/(?:(?P\d+)/)?$' => 'PhabricatorFileMacroEditController', 'delete/(?P\d+)/$' => 'PhabricatorFileMacroDeleteController', ), 'proxy/$' => 'PhabricatorFileProxyController', ), '/phid/' => array( '$' => 'PhabricatorPHIDLookupController', 'list/$' => 'PhabricatorPHIDListController', 'new/$' => 'PhabricatorPHIDAllocateController', ), '/people/' => array( '$' => 'PhabricatorPeopleListController', 'edit/(?:(?P\w+)/)?$' => 'PhabricatorPeopleEditController', ), '/p/(?P\w+)/$' => 'PhabricatorPeopleProfileController', '/profile/' => array( 'edit/$' => 'PhabricatorPeopleProfileEditController', ), '/conduit/' => array( '$' => 'PhabricatorConduitConsoleController', 'method/(?P[^/]+)$' => 'PhabricatorConduitConsoleController', 'log/$' => 'PhabricatorConduitLogController', ), '/api/(?P[^/]+)$' => 'PhabricatorConduitAPIController', '/D(?P\d+)' => 'DifferentialRevisionViewController', '/differential/' => array( '$' => 'DifferentialRevisionListController', 'filter/(?P\w+)/$' => 'DifferentialRevisionListController', 'diff/' => array( '(?P\d+)/$' => 'DifferentialDiffViewController', 'create/$' => 'DifferentialDiffCreateController', ), 'changeset/$' => 'DifferentialChangesetViewController', 'revision/edit/(?:(?P\d+)/)?$' => 'DifferentialRevisionEditController', 'comment/' => array( 'preview/(?P\d+)/$' => 'DifferentialCommentPreviewController', 'save/$' => 'DifferentialCommentSaveController', 'inline/' => array( 'preview/(?P\d+)/$' => 'DifferentialInlineCommentPreviewController', 'edit/(?P\d+)/$' => 'DifferentialInlineCommentEditController', ), ), 'attach/(?P\d+)/(?P\w+)/$' => 'DifferentialAttachController', 'subscribe/(?Padd|rem)/(?P\d+)/$' => 'DifferentialSubscribeController', ), '/typeahead/' => array( 'common/(?P\w+)/$' => 'PhabricatorTypeaheadCommonDatasourceController', ), '/mail/' => array( '$' => 'PhabricatorMetaMTAListController', 'send/$' => 'PhabricatorMetaMTASendController', 'view/(?P\d+)/$' => 'PhabricatorMetaMTAViewController', 'lists/$' => 'PhabricatorMetaMTAMailingListsController', 'lists/edit/(?:(?P\d+)/)?$' => 'PhabricatorMetaMTAMailingListEditController', + 'receive/$' => 'PhabricatorMetaMTAReceiveController', + 'received/$' => 'PhabricatorMetaMTAReceivedListController', ), '/login/' => array( '$' => 'PhabricatorLoginController', 'email/$' => 'PhabricatorEmailLoginController', 'etoken/(?P\w+)/$' => 'PhabricatorEmailTokenController', ), '/logout/$' => 'PhabricatorLogoutController', '/oauth/' => array( '(?Pgithub|facebook)/' => array( 'login/$' => 'PhabricatorOAuthLoginController', 'diagnose/$' => 'PhabricatorOAuthDiagnosticsController', 'unlink/$' => 'PhabricatorOAuthUnlinkController', ), ), '/xhprof/' => array( 'profile/(?P[^/]+)/$' => 'PhabricatorXHProfProfileController', ), '/~/' => 'DarkConsoleController', '/settings/' => array( '(?:page/(?P[^/]+)/)?$' => 'PhabricatorUserSettingsController', ), '/maniphest/' => array( '$' => 'ManiphestTaskListController', 'view/(?P\w+)/$' => 'ManiphestTaskListController', 'task/' => array( 'create/$' => 'ManiphestTaskEditController', 'edit/(?P\d+)/$' => 'ManiphestTaskEditController', ), 'transaction/' => array( 'save/' => 'ManiphestTransactionSaveController', ), 'select/search/$' => 'ManiphestTaskSelectorSearchController', ), '/T(?P\d+)$' => 'ManiphestTaskDetailController', '/github-post-receive/(?P\d+)/(?P[^/]+)/$' => 'PhabricatorRepositoryGitHubPostReceiveController', '/repository/' => array( '$' => 'PhabricatorRepositoryListController', 'create/$' => 'PhabricatorRepositoryCreateController', 'edit/(?P\d+)/(?:(?P\w+)?/)?$' => 'PhabricatorRepositoryEditController', 'delete/(?P\d+)/$' => 'PhabricatorRepositoryDeleteController', 'project/(?P\d+)/' => 'PhabricatorRepositoryArcanistProjectEditController', ), '/search/' => array( '$' => 'PhabricatorSearchController', '(?P\d+)/$' => 'PhabricatorSearchController', ), '/project/' => array( '$' => 'PhabricatorProjectListController', 'edit/(?:(?P\d+)/)?$' => 'PhabricatorProjectEditController', 'view/(?P\d+)/$' => 'PhabricatorProjectProfileController', 'affiliation/(?P\d+)/$' => 'PhabricatorProjectAffiliationEditController', ), '/r(?P[A-Z]+)(?P[a-z0-9]+)$' => 'DiffusionCommitController', '/diffusion/' => array( '$' => 'DiffusionHomeController', '(?P[A-Z]+)/' => array( '$' => 'DiffusionRepositoryController', 'repository/'. '(?P[^/]+)/'. '$' => 'DiffusionRepositoryController', 'change/'. '(?P.*?)'. '(?:[;](?P[a-z0-9]+))?'. '$' => 'DiffusionChangeController', 'history/'. '(?P.*?)'. '(?:[;](?P[a-z0-9]+))?'. '$' => 'DiffusionHistoryController', 'browse/'. '(?P.*?)'. '(?:[;](?P[a-z0-9]+))?'. '(?:[$](?P\d+))?'. '$' => 'DiffusionBrowseController', 'diff/'. '(?P.*?)'. '(?:[;](?P[a-z0-9]+))?'. '$' => 'DiffusionDiffController', 'lastmodified/'. '(?P.*?)'. '(?:[;](?P[a-z0-9]+))?'. '$' => 'DiffusionLastModifiedController', ), 'services/' => array( 'path/' => array( 'complete/$' => 'DiffusionPathCompleteController', 'validate/$' => 'DiffusionPathValidateController', ), ), ), '/daemon/' => array( 'task/(?P\d+)/$' => 'PhabricatorWorkerTaskDetailController', 'log/' => array( '$' => 'PhabricatorDaemonLogListController', 'combined/$' => 'PhabricatorDaemonCombinedLogController', '(?P\d+)/$' => 'PhabricatorDaemonLogViewController', ), 'timeline/$' => 'PhabricatorDaemonTimelineConsoleController', 'timeline/(?P\d+)/$' => 'PhabricatorDaemonTimelineEventController', '$' => 'PhabricatorDaemonConsoleController', ), '/herald/' => array( '$' => 'HeraldHomeController', 'view/(?P[^/]+)/$' => 'HeraldHomeController', 'new/(?:(?P[^/]+)/)?$' => 'HeraldNewController', 'rule/(?:(?P\d+)/)?$' => 'HeraldRuleController', 'delete/(?P\d+)/$' => 'HeraldDeleteController', 'test/$' => 'HeraldTestConsoleController', 'transcript/$' => 'HeraldTranscriptListController', 'transcript/(?P\d+)/(?:(?P\w+)/)?$' => 'HeraldTranscriptController', ), '/preferences/' => array( '$' => 'PhabricatorEditPreferencesController' ), '/uiexample/' => array( '$' => 'PhabricatorUIExampleRenderController', 'view/(?P[^/]+)/$' => 'PhabricatorUIExampleRenderController', ), '/owners/' => array( '$' => 'PhabricatorOwnersListController', 'view/(?P[^/]+)/$' => 'PhabricatorOwnersListController', 'edit/(?P\d+)/$' => 'PhabricatorOwnersEditController', 'new/$' => 'PhabricatorOwnersEditController', 'package/(?P\d+)/$' => 'PhabricatorOwnersDetailController', 'delete/(?P\d+)/$' => 'PhabricatorOwnersDeleteController', ), '/xhpast/' => array( '$' => 'PhabricatorXHPASTViewRunController', 'view/(?P\d+)/$' => 'PhabricatorXHPASTViewFrameController', 'frameset/(?P\d+)/$' => 'PhabricatorXHPASTViewFramesetController', 'input/(?P\d+)/$' => 'PhabricatorXHPASTViewInputController', 'tree/(?P\d+)/$' => 'PhabricatorXHPASTViewTreeController', 'stream/(?P\d+)/$' => 'PhabricatorXHPASTViewStreamController', ), '/status/$' => 'PhabricatorStatusController', ); } protected function getResourceURIMapRules() { return array( '/res/' => array( '(?Ppkg/)?(?P[a-f0-9]{8})/(?P.+\.(?:css|js))$' => 'CelerityResourceController', ), ); } public function buildRequest() { $request = new AphrontRequest($this->getHost(), $this->getPath()); $request->setRequestData($_GET + $_POST); $request->setApplicationConfiguration($this); return $request; } public function handleException(Exception $ex) { $class = phutil_escape_html(get_class($ex)); $message = phutil_escape_html($ex->getMessage()); $content = '
'. '

Unhandled Exception "'.$class.'": '.$message.'

'. ''.phutil_escape_html((string)$ex).''. '
'; $user = $this->getRequest()->getUser(); if (!$user) { // If we hit an exception very early, we won't have a user. $user = new PhabricatorUser(); } $dialog = new AphrontDialogView(); $dialog ->setTitle('Exception!') ->setClass('aphront-exception-dialog') ->setUser($user) ->appendChild($content) ->addCancelButton('/'); $response = new AphrontDialogResponse(); $response->setDialog($dialog); return $response; } public function willSendResponse(AphrontResponse $response) { $request = $this->getRequest(); if ($response instanceof AphrontDialogResponse) { if (!$request->isAjax()) { $view = new PhabricatorStandardPageView(); $view->setRequest($request); $view->appendChild( '
'. $response->buildResponseString(). '
'); $response = new AphrontWebpageResponse(); $response->setContent($view->render()); return $response; } else { return id(new AphrontAjaxResponse()) ->setContent(array( 'dialog' => $response->buildResponseString(), )); } } else if ($response instanceof AphrontRedirectResponse) { if ($request->isAjax()) { return id(new AphrontAjaxResponse()) ->setContent( array( 'redirect' => $response->getURI(), )); } } else if ($response instanceof Aphront404Response) { $failure = new AphrontRequestFailureView(); $failure->setHeader('404 Not Found'); $failure->appendChild( '

The page you requested was not found.

'); $view = new PhabricatorStandardPageView(); $view->setTitle('404 Not Found'); $view->setRequest($this->getRequest()); $view->appendChild($failure); $response = new AphrontWebpageResponse(); $response->setContent($view->render()); $response->setHTTPResponseCode(404); return $response; } return $response; } public function build404Controller() { return array(new Phabricator404Controller($this->getRequest()), array()); } public function buildRedirectController($uri) { return array( new PhabricatorRedirectController($this->getRequest()), array( 'uri' => $uri, )); } } diff --git a/src/applications/differential/storage/revision/DifferentialRevision.php b/src/applications/differential/storage/revision/DifferentialRevision.php index 162e777d50..fd432fce1f 100644 --- a/src/applications/differential/storage/revision/DifferentialRevision.php +++ b/src/applications/differential/storage/revision/DifferentialRevision.php @@ -1,158 +1,167 @@ true, self::CONFIG_SERIALIZATION => array( 'attached' => self::SERIALIZATION_JSON, 'unsubscribed' => self::SERIALIZATION_JSON, ), ) + parent::getConfiguration(); } public function loadCommitPHIDs() { if (!$this->getID()) { return ($this->commits = array()); } $commits = queryfx_all( $this->establishConnection('r'), 'SELECT commitPHID FROM %T WHERE revisionID = %d', self::TABLE_COMMIT, $this->getID()); $commits = ipull($commits, 'commitPHID'); return ($this->commits = $commits); } public function getCommitPHIDs() { if ($this->commits === null) { throw new Exception("Must load commits!"); } return $this->commits; } public function getAttachedPHIDs($type) { return array_keys(idx($this->attached, $type, array())); } public function setAttachedPHIDs($type, array $phids) { $this->attached[$type] = array_fill_keys($phids, array()); return $this; } public function generatePHID() { return PhabricatorPHID::generateNewPHID( PhabricatorPHIDConstants::PHID_TYPE_DREV); } public function loadDiffs() { if (!$this->getID()) { return array(); } return id(new DifferentialDiff())->loadAllWhere( 'revisionID = %d', $this->getID()); } public function loadComments() { if (!$this->getID()) { return array(); } return id(new DifferentialComment())->loadAllWhere( 'revisionID = %d', $this->getID()); } public function loadActiveDiff() { return id(new DifferentialDiff())->loadOneWhere( 'revisionID = %d ORDER BY id DESC LIMIT 1', $this->getID()); } + public function save() { + if (!$this->getMailKey()) { + $this->mailKey = sha1(Filesystem::readRandomBytes(20)); + } + return parent::save(); + } + public function loadRelationships() { if (!$this->getID()) { $this->relationships = array(); return; } $data = queryfx_all( $this->establishConnection('r'), 'SELECT * FROM %T WHERE revisionID = %d ORDER BY sequence', self::RELATIONSHIP_TABLE, $this->getID()); $this->relationships = igroup($data, 'relation'); return $this; } public function getReviewers() { return $this->getRelatedPHIDs(self::RELATION_REVIEWER); } public function getCCPHIDs() { return $this->getRelatedPHIDs(self::RELATION_SUBSCRIBED); } private function getRelatedPHIDs($relation) { if ($this->relationships === null) { throw new Exception("Must load relationships!"); } return ipull($this->getRawRelations($relation), 'objectPHID'); } public function getRawRelations($relation) { return idx($this->relationships, $relation, array()); } public function getUnsubscribedPHIDs() { return array_keys($this->getUnsubscribed()); } } diff --git a/src/applications/differential/storage/revision/__init__.php b/src/applications/differential/storage/revision/__init__.php index a6690c2650..bee7373c37 100644 --- a/src/applications/differential/storage/revision/__init__.php +++ b/src/applications/differential/storage/revision/__init__.php @@ -1,19 +1,20 @@ true, self::CONFIG_SERIALIZATION => array( 'ccPHIDs' => self::SERIALIZATION_JSON, 'attached' => self::SERIALIZATION_JSON, 'projectPHIDs' => self::SERIALIZATION_JSON, ), ) + parent::getConfiguration(); } public function getAttachedPHIDs($type) { return array_keys(idx($this->attached, $type, array())); } public function generatePHID() { return PhabricatorPHID::generateNewPHID( PhabricatorPHIDConstants::PHID_TYPE_TASK); } public function getCCPHIDs() { return nonempty($this->ccPHIDs, array()); } + public function save() { + if (!$this->mailKey) { + $this->mailKey = sha1(Filesystem::readRandomBytes(20)); + } + return parent::save(); + } + } diff --git a/src/applications/maniphest/storage/task/__init__.php b/src/applications/maniphest/storage/task/__init__.php index 95791239e1..22a5346f8c 100644 --- a/src/applications/maniphest/storage/task/__init__.php +++ b/src/applications/maniphest/storage/task/__init__.php @@ -1,16 +1,17 @@ buildStandardPageView(); $page->setApplicationName('MetaMTA'); $page->setBaseURI('/mail/'); $page->setTitle(idx($data, 'title')); $page->setTabs( array( 'queue' => array( 'name' => 'Mail Queue', 'href' => '/mail/', ), 'lists' => array( 'name' => 'Mailing Lists', 'href' => '/mail/lists/', ), + 'received' => array( + 'name' => 'Received', + 'href' => '/mail/received/', + ), ), idx($data, 'tab')); $page->setGlyph("@"); $page->appendChild($view); $response = new AphrontWebpageResponse(); return $response->setContent($page->render()); } } diff --git a/src/applications/metamta/controller/receive/PhabricatorMetaMTAReceiveController.php b/src/applications/metamta/controller/receive/PhabricatorMetaMTAReceiveController.php new file mode 100644 index 0000000000..900d67c2b7 --- /dev/null +++ b/src/applications/metamta/controller/receive/PhabricatorMetaMTAReceiveController.php @@ -0,0 +1,91 @@ +getRequest(); + $user = $request->getUser(); + + if ($request->isFormPost()) { + $receiver = PhabricatorMetaMTAReceivedMail::loadReceiverObject( + $request->getStr('obj')); + if (!$receiver) { + throw new Exception("No such task or revision!"); + } + + $hash = PhabricatorMetaMTAReceivedMail::computeMailHash( + $receiver, + $user); + + $received = new PhabricatorMetaMTAReceivedMail(); + $received->setHeaders( + array( + 'to' => $request->getStr('obj').'+'.$user->getID().'+'.$hash.'@', + )); + $received->setBodies( + array( + 'text' => $request->getStr('body'), + )); + $received->save(); + + $received->processReceivedMail(); + + $phid = $receiver->getPHID(); + $handles = id(new PhabricatorObjectHandleData(array($phid))) + ->loadHandles(); + $uri = $handles[$phid]->getURI(); + + return id(new AphrontRedirectResponse())->setURI($uri); + } + + $form = new AphrontFormView(); + $form->setUser($request->getUser()); + $form->setAction('/mail/receive/'); + $form + ->appendChild( + '

This form will simulate '. + 'sending mail to an object.

') + ->appendChild( + id(new AphrontFormTextControl()) + ->setLabel('To') + ->setName('obj') + ->setCaption('e.g. D1234 or T1234')) + ->appendChild( + id(new AphrontFormTextAreaControl()) + ->setLabel('Body') + ->setName('body')) + ->appendChild( + id(new AphrontFormSubmitControl()) + ->setValue('Receive Mail')); + + $panel = new AphrontPanelView(); + $panel->setHeader('Receive Email'); + $panel->appendChild($form); + $panel->setWidth(AphrontPanelView::WIDTH_WIDE); + + return $this->buildStandardPageResponse( + $panel, + array( + 'title' => 'Receive Mail', + )); + } + +} diff --git a/src/applications/metamta/controller/receive/__init__.php b/src/applications/metamta/controller/receive/__init__.php new file mode 100644 index 0000000000..d7dcd448f5 --- /dev/null +++ b/src/applications/metamta/controller/receive/__init__.php @@ -0,0 +1,22 @@ +getRequest(); + + $pager = new AphrontPagerView(); + $pager->setOffset($request->getInt('page')); + $pager->setURI($request->getRequestURI(), 'page'); + + $mails = id(new PhabricatorMetaMTAReceivedMail())->loadAllWhere( + '1 = 1 ORDER BY id DESC LIMIT %d, %d', + $pager->getOffset(), + $pager->getPageSize() + 1); + $mails = $pager->sliceResults($mails); + + $phids = array_merge( + mpull($mails, 'getAuthorPHID'), + mpull($mails, 'getRelatedPHID') + ); + $phids = array_unique(array_filter($phids)); + + $handles = id(new PhabricatorObjectHandleData($phids))->loadHandles(); + + $rows = array(); + foreach ($mails as $mail) { + $rows[] = array( + $mail->getID(), + date('M jS Y', $mail->getDateCreated()), + date('g:i:s A', $mail->getDateCreated()), + $mail->getAuthorPHID() + ? $handles[$mail->getAuthorPHID()]->renderLink() + : '-', + $mail->getRelatedPHID() + ? $handles[$mail->getRelatedPHID()]->renderLink() + : '-', + phutil_escape_html($mail->getMessage()), + ); + } + + $table = new AphrontTableView($rows); + $table->setHeaders( + array( + 'ID', + 'Date', + 'Time', + 'Author', + 'Object', + 'Message', + )); + $table->setColumnClasses( + array( + null, + null, + 'right', + null, + null, + 'wide', + )); + + $panel = new AphrontPanelView(); + $panel->setHeader('Received Mail'); + $panel->setCreateButton('Test Receiver', '/mail/receive/'); + $panel->appendChild($table); + $panel->appendChild($pager); + + return $this->buildStandardPageResponse( + $panel, + array( + 'title' => 'Received Mail', + 'tab' => 'received', + )); + } +} diff --git a/src/applications/metamta/controller/receivedlist/__init__.php b/src/applications/metamta/controller/receivedlist/__init__.php new file mode 100644 index 0000000000..c111e1ef8f --- /dev/null +++ b/src/applications/metamta/controller/receivedlist/__init__.php @@ -0,0 +1,20 @@ + array( + 'headers' => self::SERIALIZATION_JSON, + 'bodies' => self::SERIALIZATION_JSON, + 'attachments' => self::SERIALIZATION_JSON, + ), + ) + parent::getConfiguration(); + } + + public function processReceivedMail() { + $to = idx($this->headers, 'to'); + + $matches = null; + $ok = preg_match( + '/^((?:D|T)\d+)\+(\d+)\+([a-f0-9]{16})@/', + $to, + $matches); + + if (!$ok) { + return $this->setMessage("Unrecognized 'to' format: {$to}")->save(); + } + + $receiver_name = $matches[1]; + $user_id = $matches[2]; + $hash = $matches[3]; + + $user = id(new PhabricatorUser())->load($user_id); + if (!$user) { + return $this->setMessage("Invalid user '{$user_id}'")->save(); + } + + $this->setAuthorPHID($user->getPHID()); + + $receiver = self::loadReceiverObject($receiver_name); + if (!$receiver) { + return $this->setMessage("Invalid object '{$receiver_name}'")->save(); + } + + $this->setRelatedPHID($receiver->getPHID()); + + $expect_hash = self::computeMailHash($receiver, $user); + if ($expect_hash != $hash) { + return $this->setMessage("Invalid mail hash!")->save(); + } + + // TODO: Move this into the application logic instead. + if ($receiver instanceof ManiphestTask) { + $this->processManiphestMail($receiver, $user); + } else if ($receiver instanceof DifferentialRevision) { + $this->processDifferentialMail($receiver, $user); + } + + $this->setMessage('OK'); + + return $this->save(); + } + + private function processManiphestMail( + ManiphestTask $task, + PhabricatorUser $user) { + + // TODO: implement this + + } + + private function processDifferentialMail( + DifferentialRevision $revision, + PhabricatorUser $user) { + + // TODO: Support actions + + $editor = new DifferentialCommentEditor( + $revision, + $user->getPHID(), + DifferentialAction::ACTION_COMMENT); + $editor->setMessage($this->getCleanTextBody()); + $editor->save(); + + } + + private function getCleanTextBody() { + $body = idx($this->bodies, 'text'); + + // TODO: Detect quoted content and exclude it. + + return $body; + } + + public static function loadReceiverObject($receiver_name) { + if (!$receiver_name) { + return null; + } + + $receiver_type = $receiver_name[0]; + $receiver_id = substr($receiver_name, 1); + + $class_obj = null; + switch ($receiver_type) { + case 'T': + $class_obj = newv('ManiphestTask', array()); + break; + case 'D': + $class_obj = newv('DifferentialRevision', array()); + break; + default: + return null; + } + + return $class_obj->load($receiver_id); + } + + public static function computeMailHash( + $mail_receiver, + PhabricatorUser $user) { + $global_mail_key = PhabricatorEnv::getEnvConfig('phabricator.mail-key'); + $local_mail_key = $mail_receiver->getMailKey(); + + $hash = sha1($local_mail_key.$global_mail_key.$user->getPHID()); + return substr($hash, 0, 16); + } + + +} diff --git a/src/applications/metamta/storage/receivedmail/__init__.php b/src/applications/metamta/storage/receivedmail/__init__.php new file mode 100644 index 0000000000..b48e2acdbe --- /dev/null +++ b/src/applications/metamta/storage/receivedmail/__init__.php @@ -0,0 +1,18 @@ + /path/to/phabricator/scripts/mail/mail_handler.php" + +...where is the PHABRICATOR_ENV the script should run under. Run +##sudo newaliases##. Now you likely need to symlink this script into +##/etc/smrsh/##: + + sudo ln -s /path/to/phabricator/scripts/mail/mail_handler.php /etc/smrsh/ + +Finally, edit ##/etc/mail/virtusertable## and add an entry like this: + + @yourdomain.com phabricator@localhost + +That will forward all mail to @yourdomain.com to the Phabricator processing +script. Run ##sudo /etc/mail/make## or similar and then restart sendmail with +##sudo /etc/init.d/sendmail restart## +