diff --git a/conf/default.conf.php b/conf/default.conf.php index 7cef111ba..e441e130d 100644 --- a/conf/default.conf.php +++ b/conf/default.conf.php @@ -1,1082 +1,1089 @@ 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, // -- IMPORTANT! Security! -------------------------------------------------- // // IMPORTANT: By default, Phabricator serves files from the same domain the // application lives on. This is convenient but not secure: it creates a large // class of vulnerabilities which can not be generally mitigated. // // To avoid this, you should configure a second domain in the same way you // have the primary domain configured (e.g., point it at the same machine and // set up the same vhost rules) and provide it here. For instance, if your // primary install is on "http://www.phabricator-example.com/", you could // configure "http://www.phabricator-files.com/" and specify the entire // domain (with protocol) here. This will enforce that files are // served only from the alternate domain. Ideally, you should use a // completely separate domain name rather than just a different subdomain. // // It is STRONGLY RECOMMENDED that you configure this. Your install is NOT // SECURE unless you do so. 'security.alternate-file-domain' => null, // Default key for HMAC digests where the key is not important (i.e., the // hash itself is secret). You can change this if you want (to any other // string), but doing so will break existing sessions and CSRF tokens. 'security.hmac-key' => '[D\t~Y7eNmnQGJ;rnH6aF;m2!vJ8@v8C=Cs:aQS\.Qw', // If the web server responds to both HTTP and HTTPS requests but you want // users to connect with only HTTPS, you can set this to true to make // Phabricator redirect HTTP requests to HTTPS. // // Normally, you should just configure your server not to accept HTTP traffic, // but this setting may be useful if you originally used HTTP and have now // switched to HTTPS but don't want to break old links, or if your webserver // sits behind a load balancer which terminates HTTPS connections and you // can not reasonably configure more granular behavior there. // // NOTE: Phabricator determines if a request is HTTPS or not by examining the // PHP $_SERVER['HTTPS'] variable. If you run Apache/mod_php this will // probably be set correctly for you automatically, but if you run Phabricator // as CGI/FCGI (e.g., through nginx or lighttpd), you need to configure your // web server so that it passes the value correctly based on the connection // type. Alternatively, you can add a PHP snippet to the top of this // configuration file to directly set $_SERVER['HTTPS'] to the correct value. 'security.require-https' => false, // Is Phabricator permitted to make outbound HTTP requests? 'security.allow-outbound-http' => true, // -- Internationalization -------------------------------------------------- // // This allows customizing texts used in Phabricator. The class must extend // PhabricatorTranslation. 'translation.provider' => 'PhabricatorEnglishTranslation', // You can use 'translation.override' if you don't want to create a full // translation to give users an option for switching to it and you just want // to override some strings in the default translation. 'translation.override' => array(), // -- Access Policies ------------------------------------------------------- // // Phabricator allows you to set the visibility of objects (like repositories // and source code) to "Public", which means anyone on the internet can see // them, even without being logged in. This is great for open source, but // some installs may never want to make anything public, so this policy is // disabled by default. You can enable it here, which will let you set the // policy for objects to "Public". With this option disabled, the most open // policy is "All Users", which means users must be logged in to view things. 'policy.allow-public' => false, // -- Logging --------------------------------------------------------------- // // To enable the Phabricator access log, specify a path here. The Phabricator // access log can provide more detailed information about Phabricator access // than normal HTTP access logs (for instance, it can show logged-in users, // controllers, and other application data). If not set, no log will be // written. // // Make sure the PHP process can write to the log! 'log.access.path' => null, // Format for the access log. If not set, the default format will be used: // // "[%D]\t%h\t%u\t%M\t%C\t%m\t%U\t%c\t%T" // // Available variables are: // // - %c The HTTP response code. // - %C The controller which handled the request. // - %D The request date. // - %e Epoch timestamp. // - %h The webserver's host name. // - %p The PID of the server process. // - %R The HTTP referrer. // - %r The remote IP. // - %T The request duration, in microseconds. // - %U The request path. // - %u The logged-in username, if one is logged in. // - %P The logged-in user PHID, if one is logged in. // - %M The HTTP method. // - %m For conduit, the Conduit method which was invoked. // // If a variable isn't available (for example, %m appears in the file format // but the request is not a Conduit request), it will be rendered as "-". // // Note that the default format is subject to change in the future, so if you // rely on the log's format, specify it explicitly. 'log.access.format' => null, // -- DarkConsole ----------------------------------------------------------- // // DarkConsole is a administrative debugging/profiling tool built into // Phabricator. You can leave it disabled unless you're developing against // Phabricator. // Determines whether or not DarkConsole is available. DarkConsole exposes // some data like queries and stack traces, so you should be careful about // turning it on in production (although users can not normally see it, even // if the deployment configuration enables it). 'darkconsole.enabled' => false, // Always enable DarkConsole, even for logged out users. This potentially // exposes sensitive information to users, so make sure untrusted users can // not access an install running in this mode. You should definitely leave // this off in production. It is only really useful for using DarkConsole // utilities to debug or profile logged-out pages. You must set // 'darkconsole.enabled' to use this option. 'darkconsole.always-on' => false, // Map of additional configuration values to lock. 'config.lock' => array(), // Map of additional configuration values to hide. 'config.hide' => array(), // Map of additional configuration values to mask. 'config.mask' => array(), // Ignore setup warnings of the following issues. 'config.ignore-issues' => array(), // -- MySQL --------------------------------------------------------------- // // Class providing database configuration. It must implement // DatabaseConfigurationProvider. 'mysql.configuration-provider' => 'DefaultDatabaseConfigurationProvider', // 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', // If you want to connect to a different port than the default (which is 3306) 'mysql.port' => null, // Phabricator supports PHP extensions MySQL and MySQLi. It is possible to // implement also other access mechanism (e.g. PDO_MySQL). The class must // extend AphrontMySQLDatabaseConnectionBase. 'mysql.implementation' => (extension_loaded('mysqli') ? 'AphrontMySQLiDatabaseConnection' : 'AphrontMySQLDatabaseConnection'), // -- Notifications --------------------------------------------------------- // // Set this to true to enable real-time notifications. You must also run a // notification server for this to work. Consult the documentation in // "Notifications User Guide: Setup and Configuration" for instructions. 'notification.enabled' => false, // Client port for the realtime server to listen on, and for realtime clients // to connect to. Use "localhost" if you are running the notification server // on the same host as the web server. 'notification.client-uri' => 'http://localhost:22280/', // URI and port for the notification root server. 'notification.server-uri' => 'http://localhost:22281/', // The server must be started as root so it can bind to privileged ports, but // if you specify a user here it will drop permissions after binding. 'notification.user' => null, // Location where the server should log to. 'notification.log' => '/var/log/aphlict.log', // PID file to use. 'notification.pidfile' => '/var/run/aphlict.pid', // Enable this option to get additional debug output in the browser. 'notification.debug' => false, // -- 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 message is sent to multiple recipients (for example, several // reviewers on a code review), Phabricator can either deliver one email to // everyone (e.g., "To: alincoln, usgrant, htaft") or separate emails to each // user (e.g., "To: alincoln", "To: usgrant", "To: htaft"). The major // advantages and disadvantages of each approach are: // // - One mail to everyone: // - Recipients can see To/Cc at a glance. // - If you use mailing lists, you won't get duplicate mail if you're // a normal recipient and also Cc'd on a mailing list. // - Getting threading to work properly is harder, and probably requires // making mail less useful by turning off options. // - Sometimes people will "Reply All" and everyone will get two mails, // one from the user and one from Phabricator turning their mail into // a comment. // - Not supported with a private reply-to address. // - Mails are sent in the server default translation. // - One mail to each user: // - Recipients need to look in the mail body to see To/Cc. // - If you use mailing lists, recipients may sometimes get duplicate // mail. // - Getting threading to work properly is easier, and threading settings // can be customzied by each user. // - "Reply All" no longer spams all other users. // - Required if private reply-to addresses are configured. // - Mails are sent in the language of user preference. // // In the code, splitting one outbound email into one-per-recipient is // sometimes referred to as "multiplexing". 'metamta.one-mail-per-recipient' => true, // When sending a message that has no To recipient (i.e. all recipients // are CC'd, for example when multiplexing mail), set the To field to the // following value. If no value is set, messages with no To will have // their CCs upgraded to To. 'metamta.placeholder-to-recipient' => null, // 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, // Limit the maximum size of the body of an email generated for a diff // (in bytes). 'metamta.email-body-limit' => 524288, // Adapter class to use to transmit mail to the MTA. The default uses // PHPMailerLite, which will invoke "sendmail". This is appropriate // if sendmail actually works on your host, but if you haven't configured mail // it may not be so great. A number of other mailers are available (e.g., SES, // SendGrid, SMTP, custom mailers), consult "Configuring Outbound Email" in // the documentation for details. 'metamta.mail-adapter' => 'PhabricatorMailImplementationPHPMailerLiteAdapter', // When email is sent, what format should Phabricator use for user's // email addresses? Valid values are: // - 'short' - 'gwashington ' // - 'real' - 'George Washington ' // - 'full' - 'gwashington (George Washington) ' // The default is 'full'. 'metamta.user-address-format' => 'full', // If you're using PHPMailer to send email, provide the mailer and options // here. PHPMailer is much more enormous than PHPMailerLite, and provides more // mailers and greater enormity. You need it when you want to use SMTP // instead of sendmail as the mailer. 'phpmailer.mailer' => 'smtp', 'phpmailer.smtp-host' => '', 'phpmailer.smtp-port' => 25, // When using PHPMailer with SMTP, you can set this to one of "tls" or "ssl" // to use TLS or SSL. Leave it blank for vanilla SMTP. If you're sending // via Gmail, set it to "ssl". 'phpmailer.smtp-protocol' => '', // Set following if your smtp server requires authentication. 'phpmailer.smtp-user' => null, 'phpmailer.smtp-password' => null, // If you're using Amazon SES to send email, provide your AWS access key // and AWS secret key here. To set up Amazon SES with Phabricator, you need // to: // - Make sure 'metamta.mail-adapter' is set to: // "PhabricatorMailImplementationAmazonSESAdapter" // - Make sure 'metamta.can-send-as-user' is false. // - Make sure 'metamta.default-address' is configured to something sensible. // - Make sure 'metamta.default-address' is a validated SES "From" address. 'amazon-ses.access-key' => null, 'amazon-ses.secret-key' => null, // If you're using Sendgrid to send email, provide your access credentials // here. This will use the REST API. You can also use Sendgrid as a normal // SMTP service. 'sendgrid.api-user' => null, 'sendgrid.api-key' => null, // You can configure a reply handler domain so that email sent from Maniphest // will have a special "Reply To" address like "T123+82+af19f@example.com" // that allows recipients to reply by email and interact with tasks. For // instructions on configurating reply handlers, see the article // "Configuring Inbound Email" in the Phabricator documentation. By default, // this is set to 'null' and Phabricator will use a generic 'noreply@' address // or the address of the acting user instead of a special reply handler // address (see 'metamta.default-address'). If you set a domain here, // Phabricator will begin generating private reply handler addresses. See // also 'metamta.maniphest.reply-handler' to further configure behavior. // This key should be set to the domain part after the @, like "example.com". 'metamta.maniphest.reply-handler-domain' => null, // You can follow the instructions in "Configuring Inbound Email" in the // Phabricator documentation and set 'metamta.maniphest.reply-handler-domain' // to support updating Maniphest tasks by email. If you want more advanced // customization than this provides, you can override the reply handler // class with an implementation of your own. This will allow you to do things // like have a single public reply handler or change how private reply // handlers are generated and validated. // // This key should be set to a loadable subclass of // PhabricatorMailReplyHandler. 'metamta.maniphest.reply-handler' => 'ManiphestReplyHandler', // If you don't want phabricator to take up an entire domain // (or subdomain for that matter), you can use this and set a common // prefix for mail sent by phabricator. It will make use of the fact that // a mail-address such as phabricator+D123+1hjk213h@example.com will be // delivered to the phabricator users mailbox. // Set this to the left part of the email address and it well get // prepended to all outgoing mail. If you want to use e.g. // 'phabricator@example.com' this should be set to 'phabricator'. 'metamta.single-reply-handler-prefix' => null, // Prefix prepended to mail sent by Maniphest. You can change this to // distinguish between testing and development installs, for example. 'metamta.maniphest.subject-prefix' => '[Maniphest]', // See 'metamta.maniphest.reply-handler-domain'. This does the same thing, but // affects Pholio. 'metamta.pholio.reply-handler-domain' => null, // Prefix prepended to mail sent by Pholio. 'metamta.pholio.subject-prefix' => '[Pholio]', // See 'metamta.maniphest.reply-handler-domain'. This does the same thing, but // affects Macro. 'metamta.macro.reply-handler-domain' => null, // Prefix prepended to mail sent by Macro. 'metamta.macro.subject-prefix' => '[Macro]', // See 'metamta.maniphest.reply-handler-domain'. This does the same thing, // but allows email replies via Differential. 'metamta.differential.reply-handler-domain' => null, // See 'metamta.maniphest.reply-handler'. This does the same thing, but // affects Differential. 'metamta.differential.reply-handler' => 'DifferentialReplyHandler', // Prefix prepended to mail sent by Differential. 'metamta.differential.subject-prefix' => '[Differential]', // Set this to true if you want patches to be attached to mail from // Differential. This won't work if you are using SendGrid as your mail // adapter. 'metamta.differential.attach-patches' => false, // To include patches in email bodies, set this to a positive integer. Patches // will be inlined if they are at most that many lines. For instance, a value // of 100 means "inline patches if they are no longer than 100 lines". By // default, patches are not inlined. 'metamta.differential.inline-patches' => 0, // If you enable either of the options above, you can choose what format // patches are sent in. Valid options are 'unified' (like diff -u) or 'git'. 'metamta.differential.patch-format' => 'unified', // Enables a different format for comments in differential emails. // Differential will create unified diffs around the comment, which // will give enough context for people who are only viewing the // reviews in email to understand what is going on. The context will // be created based on the range of the comment. 'metamta.differential.unified-comment-context' => false, // Prefix prepended to mail sent by Diffusion. 'metamta.diffusion.subject-prefix' => '[Diffusion]', // See 'metamta.maniphest.reply-handler-domain'. This does the same thing, // but allows email replies via Diffusion. 'metamta.diffusion.reply-handler-domain' => null, // See 'metamta.maniphest.reply-handler'. This does the same thing, but // affects Diffusion. 'metamta.diffusion.reply-handler' => 'PhabricatorAuditReplyHandler', // Set this to true if you want patches to be attached to commit notifications // from Diffusion. This won't work with SendGrid. 'metamta.diffusion.attach-patches' => false, // To include patches in Diffusion email bodies, set this to a positive // integer. Patches will be inlined if they are at most that many lines. // By default, patches are not inlined. 'metamta.diffusion.inline-patches' => 0, // If you've enabled attached patches or inline patches for commit emails, you // can establish a hard byte limit on their size. You should generally set // reasonable byte and time limits (defaults are 1MB and 60 seconds) to avoid // sending ridiculously enormous email for changes like "importing an external // library" or "accidentally committed this full-length movie as text". 'metamta.diffusion.byte-limit' => 1024 * 1024, // If you've enabled attached patches or inline patches for commit emails, you // can establish a hard time limit on generating them. 'metamta.diffusion.time-limit' => 60, // Prefix prepended to mail sent by Package. 'metamta.package.subject-prefix' => '[Package]', // See 'metamta.maniphest.reply-handler'. This does similar thing for package // except that it only supports sending out mail and doesn't handle incoming // email. 'metamta.package.reply-handler' => 'OwnersPackageReplyHandler', // By default, Phabricator generates unique reply-to addresses and sends a // separate email to each recipient when you enable reply handling. This is // more secure than using "From" to establish user identity, but can mean // users may receive multiple emails when they are on mailing lists. Instead, // you can use a single, non-unique reply to address and authenticate users // based on the "From" address by setting this to 'true'. This trades away // a little bit of security for convenience, but it's reasonable in many // installs. Object interactions are still protected using hashes in the // single public email address, so objects can not be replied to blindly. 'metamta.public-replies' => false, // You can configure an email address like "bugs@phabricator.example.com" // which will automatically create Maniphest tasks when users send email // to it. This relies on the "From" address to authenticate users, so it is // is not completely secure. To set this up, enter a complete email // address like "bugs@phabricator.example.com" and then configure mail to // that address so it routed to Phabricator (if you've already configured // reply handlers, you're probably already done). See "Configuring Inbound // Email" in the documentation for more information. 'metamta.maniphest.public-create-email' => null, // If you enable 'metamta.public-replies', Phabricator uses "From" to // authenticate users. You can additionally enable this setting to try to // authenticate with 'Reply-To'. Note that this is completely spoofable and // insecure (any user can set any 'Reply-To' address) but depending on the // nature of your install or other deliverability conditions this might be // okay. Generally, you can't do much more by spoofing Reply-To than be // annoying (you can write but not read content). But, you know, this is // still **COMPLETELY INSECURE**. 'metamta.insecure-auth-with-reply-to' => false, // If you enable 'metamta.maniphest.public-create-email' and create an // email address like "bugs@phabricator.example.com", it will default to // rejecting mail which doesn't come from a known user. However, you might // want to let anyone send email to this address; to do so, set a default // author here (a Phabricator username). A typical use of this might be to // create a "System Agent" user called "bugs" and use that name here. If you // specify a valid username, mail will always be accepted and used to create // a task, even if the sender is not a system user. The original email // address will be stored in an 'From Email' field on the task. 'metamta.maniphest.default-public-author' => null, 'metamta.herald.show-hints' => true, // You can disable the hints under "REPLY HANDLER ACTIONS" if users prefer // smaller messages. The actions themselves will still work properly. 'metamta.reply.show-hints' => true, // You can disable the "To:" and "Cc:" footers in mail if users prefer // smaller messages. 'metamta.recipients.show-hints' => true, // If this option is enabled, Phabricator will add a "Precedence: bulk" // header to transactional mail (e.g., Differential, Maniphest and Herald // notifications). This may improve the behavior of some auto-responder // software and prevent it from replying. However, it may also cause // deliverability issues -- notably, you currently can not send this header // via Amazon SES, and enabling this option with SES will prevent delivery // of any affected mail. 'metamta.precedence-bulk' => false, // Mail.app on OS X Lion won't respect threading headers unless the subject // is prefixed with "Re:". If you enable this option, Phabricator will add // "Re:" to the subject line of all mail which is expected to thread. If // you've set 'metamta.one-mail-per-recipient', users can override this // setting in their preferences. 'metamta.re-prefix' => false, // If true, allow MetaMTA to change mail subjects to put text like // '[Accepted]' and '[Commented]' in them. This makes subjects more useful, // but might break threading on some clients. If you've set // 'metamta.one-mail-per-recipient', users can override this setting in their // preferences. 'metamta.vary-subjects' => true, // -- Auth ------------------------------------------------------------------ // // If true, email addresses must be verified (by clicking a link in an // email) before a user can login. By default, verification is optional // unless 'auth.email-domains' is nonempty (see below). 'auth.require-email-verification' => false, // You can restrict allowed email addresses to certain domains (like // "yourcompany.com") by setting a list of allowed domains here. Users will // only be allowed to register using email addresses at one of the domains, // and will only be able to add new email addresses for these domains. If // you configure this, it implies 'auth.require-email-verification'. // // To configure email domains, set a list of domains like this: // // array( // 'yourcompany.com', // 'yourcompany.co.uk', // ) // // You should omit the "@" from domains. Note that the domain must match // exactly. If you allow "yourcompany.com", that permits "joe@yourcompany.com" // but rejects "joe@mail.yourcompany.com". 'auth.email-domains' => array(), // You can provide an arbitrary block of HTML here, which will appear on the // login screen. Normally, you'd use this to provide login or registration // instructions to users. 'auth.login-message' => null, // -- 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, // When users set or reset a password, it must have at least this many // characters. 'account.minimum-password-length' => 8, // -- Recaptcha ------------------------------------------------------------- // // Is Recaptcha enabled? If disabled, captchas will not appear. You should // enable Recaptcha if your install is public-facing, as it hinders // brute-force attacks. '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', // 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, // Show stack traces when unhandled exceptions occur, force reloading of // static resources (skipping the cache), show an error callout if a page // generated PHP errors, warnings, or notices, force disk reads when // reloading, and generally make development easier. This option should not // be enabled in production. 'phabricator.developer-mode' => false, // Should Phabricator show beta applications on the homepage 'phabricator.show-beta-applications' => false, // Contains a list of uninstalled applications 'phabricator.uninstalled-applications' => array(), // Allowing non-members to interact with tasks over email. 'phabricator.allow-email-users' => false, // -- Welcome Screen -------------------------------------------------------- // // The custom HTML content for the Phabricator welcome screen. 'welcome.html' => null, // -- Files ----------------------------------------------------------------- // // Lists which uploaded file types may be viewed in the browser. If a file // has a mime type which does not appear in this list, it will always be // downloaded instead of displayed. This is mainly 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. // // IMPORTANT: Configure 'security.alternate-file-domain' above! Your install // is NOT safe if it is left unconfigured. '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', 'text/x-diff' => 'text/plain; charset=utf-8', // ".ico" favicon files, which have mime type diversity. See: // http://en.wikipedia.org/wiki/ICO_(file_format)#MIME_type 'image/x-ico' => 'image/x-icon', 'image/x-icon' => 'image/x-icon', 'image/vnd.microsoft.icon' => 'image/x-icon', ), // List of mime types which can be used as the source for an tag. // This should be a subset of 'files.viewable-mime-types' and exclude files // like text. 'files.image-mime-types' => array( 'image/jpeg' => true, 'image/jpg' => true, 'image/png' => true, 'image/gif' => true, 'image/x-ico' => true, 'image/x-icon' => true, 'image/vnd.microsoft.icon' => true, ), // Configuration option for enabling imagemagick // to resize animated profile pictures (gif) 'files.enable-imagemagick' => false, // -- Storage --------------------------------------------------------------- // // Phabricator allows users to upload files, and can keep them in various // storage engines. This section allows you to configure which engines // Phabricator will use, and how it will use them. // The largest filesize Phabricator will store in the MySQL BLOB storage // engine, which just uses a database table to store files. While this isn't a // best practice, it's really easy to set up. Set this to 0 to disable use of // the MySQL blob engine. 'storage.mysql-engine.max-size' => 1000000, // Phabricator provides a local disk storage engine, which just writes files // to some directory on local disk. The webserver must have read/write // permissions on this directory. This is straightforward and suitable for // most installs, but will not scale past one web frontend unless the path // is actually an NFS mount, since you'll end up with some of the files // written to each web frontend and no way for them to share. To use the // local disk storage engine, specify the path to a directory here. To // disable it, specify null. 'storage.local-disk.path' => null, // If you want to store files in Amazon S3, specify an AWS access and secret // key here and a bucket name below. 'amazon-s3.access-key' => null, 'amazon-s3.secret-key' => null, // To use a custom endpoint, specify it here. Normally, you do not need to // configure this. 'amazon-s3.endpoint' => null, // Set this to a valid Amazon S3 bucket to store files there. You must also // configure S3 access keys above. 'storage.s3.bucket' => null, // Phabricator uses a storage engine selector to choose which storage engine // to use when writing file data. If you add new storage engines or want to // provide very custom rules (e.g., write images to one storage engine and // other files to a different one), you can provide an alternate // implementation here. The default engine will use choose MySQL, Local Disk, // and S3, in that order, if they have valid configurations above and a file // fits within configured limits. 'storage.engine-selector' => 'PhabricatorDefaultFileStorageEngineSelector', // Set the size of the largest file a user may upload. This is used to render // text like "Maximum file size: 10MB" on interfaces where users can upload // files, and files larger than this size will be rejected. // // Specify this limit in bytes, or using a "K", "M", or "G" suffix. // // NOTE: Setting this to a large size is NOT sufficient to allow users to // upload large files. You must also configure a number of other settings. To // configure file upload limits, consult the article "Configuring File Upload // Limits" in the documentation. Once you've configured some limit across all // levels of the server, you can set this limit to an appropriate value and // the UI will then reflect the actual configured limit. 'storage.upload-size-limit' => null, // Phabricator puts databases in a namespace, which defualts to "phabricator" // -- for instance, the Differential database is named // "phabricator_differential" by default. You can change this namespace if you // want. Normally, you should not do this unless you are developing // Phabricator and using namespaces to separate multiple sandbox datasets. 'storage.default-namespace' => 'phabricator', // -- Search ---------------------------------------------------------------- // // Phabricator supports Elastic Search; to use it, specify a host like // 'http://elastic.example.com:9200/' here. 'search.elastic.host' => null, // Phabricator uses a search engine selector to choose which search engine // to use when indexing and reconstructing documents, and when executing // queries. You can override the engine selector to provide a new selector // class which can select some custom engine you implement, if you want to // store your documents in some search engine which does not have default // support. 'search.engine-selector' => 'PhabricatorDefaultSearchEngineSelector', // -- Differential ---------------------------------------------------------- // // List of file regexps where whitespace is meaningful and should not // use 'ignore-all' by default 'differential.whitespace-matters' => array( '/\.py$/', '/\.l?hs$/', ), // Differential has a required "Test Plan" field by default. You can make it // optional by setting this to false. You can also completely remove it above, // if you prefer. 'differential.require-test-plan-field' => true, // If you set this to true, users can "!accept" revisions via email (normally, // they can take other actions but can not "!accept"). This action is disabled // by default because email authentication can be configured to be very weak, // and, socially, email "!accept" is kind of sketchy and implies revisions may // not actually be receiving thorough review. 'differential.enable-email-accept' => false, // List of file regexps that should be treated as if they are generated by // an automatic process, and thus get hidden by default in differential. 'differential.generated-paths' => array( // '/config\.h$/', // '#/autobuilt/#', ), // If you set this to true, users can accept their own revisions. This action // is disabled by default because it's most likely not a behavior you want, // but it proves useful if you are working alone on a project and want to make // use of all of differential's features. 'differential.allow-self-accept' => false, // If you set this to true, any user can close any revision so long as it has // been accepted. This can be useful depending on your development model. For // example, github-style pull requests where the reviewer is often the // actual committer can benefit from turning this option to true. If false, // only the submitter can close a revision. 'differential.always-allow-close' => false, // If you set this to true, any user can reopen a revision so long as it has // been closed. This can be useful if a revision is accidentally closed or // if a developer changes his or her mind after closing a revision. If it is // false, reopening is not allowed. 'differential.allow-reopen' => false, // Revisions newer than this number of days are marked as fresh in Action // Required and Revisions Waiting on You views. Only work days (not weekends // and holidays) are included. Set to 0 to disable this feature. 'differential.days-fresh' => 1, // Similar to 'differential.days-fresh' but marks stale revisions. If the // revision is even older than it is marked as old. 'differential.days-stale' => 3, // -- Repositories ---------------------------------------------------------- // // The default location in which to store local copies of repositories. // Anything stored in this directory will be assumed to be under the // control of phabricator, which means that Phabricator will try to do some // maintenance on working copies if there are problems (such as a change // to the remote origin url). This maintenance may include completely // removing (and recloning) anything in this directory. // // When set to null, this option is ignored (i.e. Phabricator will not fully // control any working copies). 'repository.default-local-path' => null, // -- Maniphest ------------------------------------------------------------- // // What should the default task priority be in create flows? // See the constants in @{class:ManiphestTaskPriority} for valid values. // Defaults to "needs triage". 'maniphest.default-priority' => 90, // -- Phame ----------------------------------------------------------------- // // Should Phame users have Disqus comment widget, and if so what's the // website shortname to use? For example, secure.phabricator.org uses // "phabricator", which we registered with Disqus. If you aren't familiar // with Disqus, see: // Disqus quick start guide - http://docs.disqus.com/help/4/ // Information on shortnames - http://docs.disqus.com/help/68/ 'disqus.shortname' => null, // Directories to look for Phame skins inside of. 'phame.skins' => array( 'externals/skins/', ), // -- Remarkup -------------------------------------------------------------- // // If you enable this, linked YouTube videos will be embeded inline. This has // mild security implications (you'll leak referrers to YouTube) and is pretty // silly (but sort of awesome). 'remarkup.enable-embedded-youtube' => false, // -- Cache ----------------------------------------------------------------- // // Set this to false to disable the use of gzdeflate()-based compression in // some caches. This may give you less performant (but more debuggable) // caching. 'cache.enable-deflate' => true, // -- Garbage Collection ---------------------------------------------------- // // Phabricator generates various logs and caches in the database which can // be garbage collected after a while to make the total data size more // manageable. To run garbage collection, launch a // PhabricatorGarbageCollector daemon. // These 'ttl' keys configure how much old data the GC daemon keeps around. // Objects older than the ttl will be collected. Set any value to 0 to store // data indefinitely. 'gcdaemon.ttl.herald-transcripts' => 30 * (24 * 60 * 60), 'gcdaemon.ttl.daemon-logs' => 7 * (24 * 60 * 60), 'gcdaemon.ttl.differential-parse-cache' => 14 * (24 * 60 * 60), 'gcdaemon.ttl.markup-cache' => 30 * (24 * 60 * 60), 'gcdaemon.ttl.task-archive' => 14 * (24 * 60 * 60), 'gcdaemon.ttl.general-cache' => 30 * (24 * 60 * 60), // -- Feed ------------------------------------------------------------------ // // If you set this to true, you can embed Phabricator activity feeds in other // pages using iframes. These feeds are completely public, and a login is not // required to view them! This is intended for things like open source // projects that want to expose an activity feed on the project homepage. // // NOTE: You must also set `policy.allow-public` to true for this setting // to work properly. 'feed.public' => false, // If you set this to a list of http URIs, when a feed story is published a // task will be created for each uri that posts the story data to the uri. // Daemons automagically retry failures 100 times, waiting $fail_count * 60s // between each subsequent failure. Be sure to keep the daemon console // (/daemon/) open while developing and testing your end points. You may need // to restart your daemons to start sending http requests. // // NOTE: URIs are not validated, the URI must return http status 200 within // 30 seconds, and no permission checks are performed. 'feed.http-hooks' => array(), // -- Drydock --------------------------------------------------------------- // // If you want to use Drydock's builtin EC2 Blueprints, configure your AWS // EC2 credentials here. 'amazon-ec2.access-key' => null, 'amazon-ec2.secret-key' => null, // -- Customization --------------------------------------------------------- // // Paths to additional phutil libraries to load. 'load-libraries' => array(), 'aphront.default-application-configuration-class' => 'AphrontDefaultApplicationConfiguration', // Directory that phd (the Phabricator daemon control script) should use to // track running daemons. 'phd.pid-directory' => '/var/tmp/phd/pid', // Directory that the Phabricator daemons should use to store the log file 'phd.log-directory' => '/var/tmp/phd/log', // Number of "TaskMaster" daemons that "phd start" should start. You can // raise this if you have a task backlog, or explicitly launch more with // "phd launch taskmaster". 'phd.start-taskmasters' => 4, // Launch daemons in "verbose" mode by default. This creates a lot of output, // but can help debug issues. Daemons launched in debug mode with "phd debug" // are always launched in verbose mode. See also 'phd.trace'. 'phd.verbose' => false, // Launch daemons in "trace" mode by default. This creates an ENORMOUS amount // of output, but can help debug issues. Daemons launched in debug mode with // "phd debug" are always launched in trace mdoe. See also 'phd.verbose'. 'phd.trace' => false, // This value is an input to the hash function when building resource hashes. // It has no security value, but if you accidentally poison user caches (by // pushing a bad patch or having something go wrong with a CDN, e.g.) you can // change this to something else and rebuild the Celerity map to break user // caches. Unless you are doing Celerity development, it is exceptionally // unlikely that you need to modify this. 'celerity.resource-hash' => 'd9455ea150622ee044f7931dabfa52aa', // Minify static resources by removing whitespace and comments. You should // enable this in production, but disable it in development. 'celerity.minify' => true, // You can respond to various application events by installing listeners, // which will receive callbacks when interesting things occur. Specify a list // of classes which extend PhabricatorEventListener here. 'events.listeners' => array(), // -- Syntax Highlighting --------------------------------------------------- // // Phabricator can highlight PHP by default and use Pygments for other // languages if enabled. You can provide a custom highlighter engine by // extending class PhutilSyntaxHighlighterEngine. 'syntax-highlighter.engine' => 'PhutilDefaultSyntaxHighlighterEngine', // If you want syntax highlighting for other languages than PHP then you can // install the python package 'Pygments', make sure the 'pygmentize' script is // available in the $PATH of the webserver, and then enable this. 'pygments.enabled' => false, // In places that we display a dropdown to syntax-highlight code, // this is where that list is defined. // Syntax is 'lexer-name' => 'Display Name', 'pygments.dropdown-choices' => array( 'apacheconf' => 'Apache Configuration', 'bash' => 'Bash Scripting', 'brainfuck' => 'Brainf*ck', 'c' => 'C', + 'coffee-script' => 'CoffeeScript', 'cpp' => 'C++', 'css' => 'CSS', 'd' => 'D', 'diff' => 'Diff', 'django' => 'Django Templating', 'erb' => 'Embedded Ruby/ERB', 'erlang' => 'Erlang', + 'go' => 'Golang', + 'groovy' => 'Groovy', 'haskell' => 'Haskell', 'html' => 'HTML', 'java' => 'Java', 'js' => 'Javascript', + 'json' => 'JSON', 'mysql' => 'MySQL', 'objc' => 'Objective-C', 'perl' => 'Perl', 'php' => 'PHP', + 'puppet' => 'Puppet', 'rest' => 'reStructuredText', 'text' => 'Plain Text', 'python' => 'Python', 'rainbow' => 'Rainbow', 'remarkup' => 'Remarkup', 'ruby' => 'Ruby', 'xml' => 'XML', + 'yaml' => 'YAML', ), // This is an override list of regular expressions which allows you to choose // what language files are highlighted as. If your projects have certain rules // about filenames or use unusual or ambiguous language extensions, you can // create a mapping here. This is an ordered dictionary of regular expressions // which will be tested against the filename. They should map to either an // explicit language as a string value, or a numeric index into the captured // groups as an integer. 'syntax.filemap' => array( // Example: Treat all '*.xyz' files as PHP. // '@\\.xyz$@' => 'php', // Example: Treat 'httpd.conf' as 'apacheconf'. // '@/httpd\\.conf$@' => 'apacheconf', // Example: Treat all '*.x.bak' file as '.x'. NOTE: we map to capturing // group 1 by specifying the mapping as "1". // '@\\.([^.]+)\\.bak$@' => 1, '@\.arcconfig$@' => 'js', + '@\.arclint$@' => 'js', '@\.divinerconfig$@' => 'js', ), // Set the default monospaced font style for users who haven't set a custom // style. 'style.monospace' => '10px "Menlo", "Consolas", "Monaco", monospace', 'style.monospace.windows' => '11px "Menlo", "Consolas", "Monaco", monospace', // -- Debugging ------------------------------------------------------------- // // Enable this to change HTTP redirects into normal pages with a link to the // redirection target. For example, after you submit a form you'll get a page // saying "normally, you'd be redirected...". This is useful to examine // service or profiler information on write pathways, or debug redirects. It // also makes the UX horrible for normal use, so you should enable it only // when debugging. // // NOTE: This does not currently work for forms with Javascript "workflow", // since the redirect happens in Javascript. 'debug.stop-on-redirect' => false, // Set the rate for how often to do sampled profiling. On average, one // request for every number of requests specified here will be sampled. // Set this value to 0 to completely disable profiling. In a production // environment, this value should either be set to 0 (to disable) or to // a large number (to sample only a few requests). 'debug.profile-rate' => 0, // -- Environment ---------------------------------------------------------- // // Phabricator occasionally shells out to other binaries on the server. // An example of this is the "pygmentize" command, used to syntax-highlight // code written in languages other than PHP. By default, it is assumed that // these binaries are in the $PATH of the user running Phabricator (normally // 'apache', 'httpd', or 'nobody'). Here you can add extra directories to // the $PATH environment variable, for when these binaries are in non-standard // locations. 'environment.append-paths' => array(), // -- Audit ---------------------------------------------------------- // // Controls whether or not task creator can Close Audits 'audit.can-author-close-audit' => false, ); diff --git a/resources/celerity/map.php b/resources/celerity/map.php index 832717065..268675360 100644 --- a/resources/celerity/map.php +++ b/resources/celerity/map.php @@ -1,2295 +1,2292 @@ array( - 'core.pkg.css' => 'ac63f8ac', + 'core.pkg.css' => 'c94a698c', 'core.pkg.js' => '7db41c19', 'darkconsole.pkg.js' => 'ca8671ce', 'differential.pkg.css' => 'fbf57382', 'differential.pkg.js' => '74cb0d29', 'diffusion.pkg.css' => '3783278d', 'diffusion.pkg.js' => '077e3ad0', 'maniphest.pkg.css' => 'f88a8402', 'maniphest.pkg.js' => 'd1347a35', 'rsrc/css/aphront/aphront-bars.css' => '231ac33c', 'rsrc/css/aphront/context-bar.css' => '1c3b0529', 'rsrc/css/aphront/dark-console.css' => '6378ef3d', 'rsrc/css/aphront/dialog-view.css' => 'c01d24b4', 'rsrc/css/aphront/error-view.css' => '9f1d5518', 'rsrc/css/aphront/lightbox-attachment.css' => '7acac05d', 'rsrc/css/aphront/list-filter-view.css' => '2ae43867', 'rsrc/css/aphront/multi-column.css' => '1b95ab2e', 'rsrc/css/aphront/notification.css' => 'ef2c9b34', 'rsrc/css/aphront/pager-view.css' => '2e3539af', 'rsrc/css/aphront/panel-view.css' => '5846dfa2', 'rsrc/css/aphront/phabricator-nav-view.css' => '9283c2df', 'rsrc/css/aphront/request-failure-view.css' => 'da14df31', 'rsrc/css/aphront/table-view.css' => '88e80148', 'rsrc/css/aphront/tokenizer.css' => '82ce2142', 'rsrc/css/aphront/tooltip.css' => '9c90229d', 'rsrc/css/aphront/transaction.css' => 'ce491938', 'rsrc/css/aphront/two-column.css' => '16ab3ad2', 'rsrc/css/aphront/typeahead.css' => 'a989b5b3', 'rsrc/css/application/auth/auth.css' => '1e655982', 'rsrc/css/application/base/main-menu-view.css' => '72d1d2ef', 'rsrc/css/application/base/notification-menu.css' => '8637a3db', 'rsrc/css/application/base/phabricator-application-launch-view.css' => '137e794e', 'rsrc/css/application/base/standard-page-view.css' => '517cdfb1', 'rsrc/css/application/chatlog/chatlog.css' => '852140ff', 'rsrc/css/application/config/config-options.css' => '7fedf08b', 'rsrc/css/application/config/config-template.css' => '25d446d6', 'rsrc/css/application/config/setup-issue.css' => '69e640e7', 'rsrc/css/application/conpherence/menu.css' => '828ddd3e', 'rsrc/css/application/conpherence/message-pane.css' => '7703a9a9', 'rsrc/css/application/conpherence/notification.css' => '04a6e10a', 'rsrc/css/application/conpherence/update.css' => '1099a660', 'rsrc/css/application/conpherence/widget-pane.css' => 'bf275a6c', 'rsrc/css/application/contentsource/content-source-view.css' => '4b8b05d4', 'rsrc/css/application/countdown/timer.css' => '86b7b0a0', 'rsrc/css/application/dashboard/dashboard.css' => 'f593f8c2', 'rsrc/css/application/diff/inline-comment-summary.css' => '8cfd34e8', 'rsrc/css/application/differential/add-comment.css' => 'c478bcaa', 'rsrc/css/application/differential/changeset-view.css' => 'f234b888', 'rsrc/css/application/differential/core.css' => '7ac3cabc', 'rsrc/css/application/differential/results-table.css' => '239924f9', 'rsrc/css/application/differential/revision-comment.css' => '48186045', 'rsrc/css/application/differential/revision-history.css' => '0e8eb855', 'rsrc/css/application/differential/revision-list.css' => 'f3c47d33', 'rsrc/css/application/differential/table-of-contents.css' => '6bf8e1d2', 'rsrc/css/application/diffusion/commit-view.css' => '92d1e8f9', 'rsrc/css/application/diffusion/diffusion-icons.css' => '384a0f7d', 'rsrc/css/application/diffusion/diffusion-source.css' => '66fdf661', - 'rsrc/css/application/directory/phabricator-jump-nav.css' => 'f0c5e726', 'rsrc/css/application/feed/feed.css' => 'dd43ce00', 'rsrc/css/application/files/global-drag-and-drop.css' => '697324ad', 'rsrc/css/application/flag/flag.css' => '5337623f', 'rsrc/css/application/harbormaster/harbormaster.css' => 'cec833b7', 'rsrc/css/application/herald/herald-test.css' => '778b008e', 'rsrc/css/application/herald/herald.css' => 'c544dd1c', 'rsrc/css/application/maniphest/batch-editor.css' => '8f380ebc', 'rsrc/css/application/maniphest/report.css' => '6fc16517', 'rsrc/css/application/maniphest/task-edit.css' => '8e23031b', 'rsrc/css/application/maniphest/task-summary.css' => '00c3be7a', 'rsrc/css/application/objectselector/object-selector.css' => '029a133d', 'rsrc/css/application/owners/owners-path-editor.css' => '2f00933b', 'rsrc/css/application/paste/paste.css' => 'aa1767d1', 'rsrc/css/application/people/people-profile.css' => 'ba7b2762', 'rsrc/css/application/phame/phame.css' => '19ecc703', 'rsrc/css/application/pholio/pholio-edit.css' => 'b9e59b6d', 'rsrc/css/application/pholio/pholio-inline-comments.css' => '52be33f0', 'rsrc/css/application/pholio/pholio.css' => 'e059f955', 'rsrc/css/application/phortune/phortune-credit-card-form.css' => 'b25b4beb', 'rsrc/css/application/phrequent/phrequent.css' => 'ffc185ad', 'rsrc/css/application/phriction/phriction-document-css.css' => '7d7f0071', 'rsrc/css/application/policy/policy-edit.css' => '05cca26a', 'rsrc/css/application/policy/policy-transaction-detail.css' => '82100a43', 'rsrc/css/application/policy/policy.css' => '957ea14c', 'rsrc/css/application/ponder/comments.css' => '6cdccea7', 'rsrc/css/application/ponder/feed.css' => 'e62615b6', 'rsrc/css/application/ponder/post.css' => 'ebab8a70', 'rsrc/css/application/ponder/vote.css' => '8ed6ed8b', 'rsrc/css/application/profile/profile-view.css' => '33e6f703', 'rsrc/css/application/projects/project-icon.css' => 'c2ecb7f1', 'rsrc/css/application/projects/project-tag.css' => '095c9404', 'rsrc/css/application/releeph/releeph-core.css' => '9b3c5733', 'rsrc/css/application/releeph/releeph-preview-branch.css' => 'b7a6f4a5', 'rsrc/css/application/releeph/releeph-request-differential-create-dialog.css' => '8d8b92cd', 'rsrc/css/application/releeph/releeph-request-typeahead.css' => '667a48ae', 'rsrc/css/application/search/search-results.css' => 'f240504c', 'rsrc/css/application/settings/settings.css' => 'ea8f5915', 'rsrc/css/application/slowvote/slowvote.css' => '266df6a1', 'rsrc/css/application/subscriptions/subscribers-list.css' => '5bb30c78', 'rsrc/css/application/tokens/tokens.css' => '3d0f239e', 'rsrc/css/application/uiexample/example.css' => '528b19de', 'rsrc/css/core/core.css' => '40151074', 'rsrc/css/core/remarkup.css' => '80c3a48c', 'rsrc/css/core/syntax.css' => '3c18c1cb', 'rsrc/css/core/z-index.css' => 'efb673ac', 'rsrc/css/diviner/diviner-shared.css' => '38813222', 'rsrc/css/font/font-awesome.css' => '73d075c3', 'rsrc/css/font/font-source-sans-pro.css' => '91d53463', 'rsrc/css/font/phui-font-icon-base.css' => 'eb84f033', 'rsrc/css/layout/phabricator-action-header-view.css' => '33a4590a', 'rsrc/css/layout/phabricator-action-list-view.css' => 'dcbfc854', 'rsrc/css/layout/phabricator-crumbs-view.css' => '989a48b6', 'rsrc/css/layout/phabricator-filetree-view.css' => 'a8c86ace', 'rsrc/css/layout/phabricator-hovercard-view.css' => '46a13cf0', 'rsrc/css/layout/phabricator-side-menu-view.css' => 'c1986b85', 'rsrc/css/layout/phabricator-source-code-view.css' => '62a99814', 'rsrc/css/phui/calendar/phui-calendar-day.css' => 'de035c8a', 'rsrc/css/phui/calendar/phui-calendar-list.css' => 'c1d0ca59', 'rsrc/css/phui/calendar/phui-calendar-month.css' => 'a92e47d2', 'rsrc/css/phui/calendar/phui-calendar.css' => '5e1ad989', 'rsrc/css/phui/phui-box.css' => '7b3a2eed', 'rsrc/css/phui/phui-button.css' => 'd26cad6e', 'rsrc/css/phui/phui-document.css' => '3b078dc0', 'rsrc/css/phui/phui-feed-story.css' => 'e2c9bc83', 'rsrc/css/phui/phui-fontkit.css' => 'de84aa4a', 'rsrc/css/phui/phui-form-view.css' => 'ed856191', 'rsrc/css/phui/phui-form.css' => 'b78ec020', 'rsrc/css/phui/phui-header-view.css' => '689dbc38', 'rsrc/css/phui/phui-icon.css' => 'cdcf2aca', 'rsrc/css/phui/phui-info-panel.css' => '27ea50a1', 'rsrc/css/phui/phui-list.css' => '43ed2d93', 'rsrc/css/phui/phui-object-box.css' => 'ce92d8ec', - 'rsrc/css/phui/phui-object-item-list-view.css' => '16003f41', + 'rsrc/css/phui/phui-object-item-list-view.css' => '15c582b1', 'rsrc/css/phui/phui-pinboard-view.css' => '874c22f9', 'rsrc/css/phui/phui-property-list-view.css' => '2f7199e8', 'rsrc/css/phui/phui-remarkup-preview.css' => '19ad512b', 'rsrc/css/phui/phui-spacing.css' => '042804d6', 'rsrc/css/phui/phui-status.css' => '2f562399', 'rsrc/css/phui/phui-tag-view.css' => '74051f4e', 'rsrc/css/phui/phui-text.css' => '23e9b4b7', 'rsrc/css/phui/phui-timeline-view.css' => 'bbd990d0', 'rsrc/css/phui/phui-workboard-view.css' => '2bf82d00', 'rsrc/css/phui/phui-workpanel-view.css' => 'fddd97bf', 'rsrc/css/sprite-apps-large.css' => '12ea1ced', 'rsrc/css/sprite-apps.css' => '37ee4f4e', 'rsrc/css/sprite-buttonbar.css' => 'ba1c5738', 'rsrc/css/sprite-conpherence.css' => '3b4a0487', 'rsrc/css/sprite-docs.css' => '5f65d0da', 'rsrc/css/sprite-gradient.css' => '487b5761', 'rsrc/css/sprite-login.css' => '67ff30b2', 'rsrc/css/sprite-main-header.css' => '92720ee2', 'rsrc/css/sprite-menu.css' => '1605a7fb', 'rsrc/css/sprite-minicons.css' => 'df4f76fe', 'rsrc/css/sprite-payments.css' => 'cc085d44', 'rsrc/css/sprite-projects.css' => '7578fa56', 'rsrc/css/sprite-remarkup.css' => '5c396a57', 'rsrc/css/sprite-tokens.css' => '1706b943', 'rsrc/externals/font/fontawesome/fontawesome-webfont.eot' => '1cab0752', 'rsrc/externals/font/fontawesome/fontawesome-webfont.ttf' => '2ff84fd2', 'rsrc/externals/font/fontawesome/fontawesome-webfont.woff' => 'a119ee5e', 'rsrc/externals/font/sourcesans/SourceSansPro.woff' => '3614608c', 'rsrc/externals/font/sourcesans/SourceSansProBold.woff' => 'cbf46566', 'rsrc/externals/javelin/core/Event.js' => '69815cac', 'rsrc/externals/javelin/core/Stratcom.js' => 'c293f7b9', 'rsrc/externals/javelin/core/__tests__/event-stop-and-kill.js' => '717554e4', 'rsrc/externals/javelin/core/__tests__/install.js' => 'c432ee85', 'rsrc/externals/javelin/core/__tests__/stratcom.js' => 'da194d4b', 'rsrc/externals/javelin/core/__tests__/util.js' => 'd3b157a9', 'rsrc/externals/javelin/core/init.js' => 'b88ab49e', 'rsrc/externals/javelin/core/init_node.js' => 'd7dde471', 'rsrc/externals/javelin/core/install.js' => '52a92793', 'rsrc/externals/javelin/core/util.js' => '65b0b249', 'rsrc/externals/javelin/docs/Base.js' => '897bb199', 'rsrc/externals/javelin/docs/onload.js' => '81fb4862', 'rsrc/externals/javelin/ext/fx/Color.js' => '7e41274a', 'rsrc/externals/javelin/ext/fx/FX.js' => '54b612ba', 'rsrc/externals/javelin/ext/reactor/core/DynVal.js' => 'f6555212', 'rsrc/externals/javelin/ext/reactor/core/Reactor.js' => '77b1cf6f', 'rsrc/externals/javelin/ext/reactor/core/ReactorNode.js' => 'b4c30592', 'rsrc/externals/javelin/ext/reactor/core/ReactorNodeCalmer.js' => '76f4ebed', 'rsrc/externals/javelin/ext/reactor/dom/RDOM.js' => 'b6d401d6', 'rsrc/externals/javelin/ext/view/HTMLView.js' => 'e5b406f9', 'rsrc/externals/javelin/ext/view/View.js' => '0f764c35', 'rsrc/externals/javelin/ext/view/ViewInterpreter.js' => '0c33c1a0', 'rsrc/externals/javelin/ext/view/ViewPlaceholder.js' => '2fa810fc', 'rsrc/externals/javelin/ext/view/ViewRenderer.js' => '6c2b09a2', 'rsrc/externals/javelin/ext/view/ViewVisitor.js' => 'efe49472', 'rsrc/externals/javelin/ext/view/__tests__/HTMLView.js' => 'f92d7bcb', 'rsrc/externals/javelin/ext/view/__tests__/View.js' => 'bda69c40', 'rsrc/externals/javelin/ext/view/__tests__/ViewInterpreter.js' => '7a94d6a5', 'rsrc/externals/javelin/ext/view/__tests__/ViewRenderer.js' => '5426001c', 'rsrc/externals/javelin/lib/Cookie.js' => '6b3dcf44', 'rsrc/externals/javelin/lib/DOM.js' => '07d99a3d', 'rsrc/externals/javelin/lib/History.js' => 'c60f4327', 'rsrc/externals/javelin/lib/JSON.js' => '08e56a4e', 'rsrc/externals/javelin/lib/Mask.js' => 'b9f26029', 'rsrc/externals/javelin/lib/Request.js' => '7bad574b', 'rsrc/externals/javelin/lib/Resource.js' => '356de121', 'rsrc/externals/javelin/lib/Routable.js' => 'b3e7d692', 'rsrc/externals/javelin/lib/Router.js' => '29274e2b', 'rsrc/externals/javelin/lib/URI.js' => 'd9a9b862', 'rsrc/externals/javelin/lib/Vector.js' => 'bd0aedcd', 'rsrc/externals/javelin/lib/Workflow.js' => '09b15cf1', 'rsrc/externals/javelin/lib/__tests__/Cookie.js' => '5ed109e8', 'rsrc/externals/javelin/lib/__tests__/DOM.js' => 'c984504b', 'rsrc/externals/javelin/lib/__tests__/JSON.js' => '2295d074', 'rsrc/externals/javelin/lib/__tests__/URI.js' => '003ed329', 'rsrc/externals/javelin/lib/__tests__/behavior.js' => '1ea62783', 'rsrc/externals/javelin/lib/behavior.js' => '8a3ed18b', 'rsrc/externals/javelin/lib/control/tokenizer/Tokenizer.js' => 'e7c21fb3', 'rsrc/externals/javelin/lib/control/typeahead/Typeahead.js' => 'c54eeefb', 'rsrc/externals/javelin/lib/control/typeahead/normalizer/TypeaheadNormalizer.js' => '5f850b5c', 'rsrc/externals/javelin/lib/control/typeahead/source/TypeaheadCompositeSource.js' => '84f34ab1', 'rsrc/externals/javelin/lib/control/typeahead/source/TypeaheadOnDemandSource.js' => 'a79b75a4', 'rsrc/externals/javelin/lib/control/typeahead/source/TypeaheadPreloadedSource.js' => '66815d9c', 'rsrc/externals/javelin/lib/control/typeahead/source/TypeaheadSource.js' => '62e18640', 'rsrc/externals/javelin/lib/control/typeahead/source/TypeaheadStaticSource.js' => 'cdde23f1', 'rsrc/externals/raphael/g.raphael.js' => '40dde778', 'rsrc/externals/raphael/g.raphael.line.js' => '40da039e', 'rsrc/externals/raphael/raphael.js' => '51ee6b43', 'rsrc/image/BFCFDA.png' => 'd5ec91f4', 'rsrc/image/actions/edit.png' => '2fc41442', 'rsrc/image/apple-touch-icon.png' => '8458dda7', 'rsrc/image/avatar.png' => '3eb28cd9', 'rsrc/image/checker_dark.png' => 'd8e65881', 'rsrc/image/checker_light.png' => 'a0155918', 'rsrc/image/credit_cards.png' => '72b8ede8', 'rsrc/image/darkload.gif' => '1ffd3ec6', 'rsrc/image/divot.png' => '94dded62', 'rsrc/image/grippy_texture.png' => 'aca81e2f', 'rsrc/image/icon/fatcow/arrow_branch.png' => '2537c01c', 'rsrc/image/icon/fatcow/arrow_merge.png' => '21b660e0', 'rsrc/image/icon/fatcow/bullet_black.png' => 'ff190031', 'rsrc/image/icon/fatcow/bullet_orange.png' => 'e273e5bb', 'rsrc/image/icon/fatcow/bullet_red.png' => 'c0b75434', 'rsrc/image/icon/fatcow/calendar_edit.png' => '24632275', 'rsrc/image/icon/fatcow/document_black.png' => '45fe1c60', 'rsrc/image/icon/fatcow/flag_blue.png' => 'a01abb1d', 'rsrc/image/icon/fatcow/flag_finish.png' => '67825cee', 'rsrc/image/icon/fatcow/flag_ghost.png' => '20ca8783', 'rsrc/image/icon/fatcow/flag_green.png' => '7e0eaa7a', 'rsrc/image/icon/fatcow/flag_orange.png' => '9e73df66', 'rsrc/image/icon/fatcow/flag_pink.png' => '7e92f3b2', 'rsrc/image/icon/fatcow/flag_purple.png' => 'cc517522', 'rsrc/image/icon/fatcow/flag_red.png' => '04ec726f', 'rsrc/image/icon/fatcow/flag_yellow.png' => '73946fd4', 'rsrc/image/icon/fatcow/folder.png' => '95a435af', 'rsrc/image/icon/fatcow/folder_go.png' => '001cbc94', 'rsrc/image/icon/fatcow/key_question.png' => '52a0c26a', 'rsrc/image/icon/fatcow/link.png' => '7afd4d5e', 'rsrc/image/icon/fatcow/page_white_edit.png' => '39a2eed8', 'rsrc/image/icon/fatcow/page_white_link.png' => 'a90023c7', 'rsrc/image/icon/fatcow/page_white_put.png' => '08c95a0c', 'rsrc/image/icon/fatcow/page_white_text.png' => '1e1f79c3', 'rsrc/image/icon/fatcow/source/conduit.png' => '4ea01d2f', 'rsrc/image/icon/fatcow/source/email.png' => '9bab3239', 'rsrc/image/icon/fatcow/source/fax.png' => '04195e68', 'rsrc/image/icon/fatcow/source/mobile.png' => 'f1321264', 'rsrc/image/icon/fatcow/source/tablet.png' => '49396799', 'rsrc/image/icon/fatcow/source/web.png' => '136ccb5d', 'rsrc/image/icon/fatcow/thumbnails/default160x120.png' => 'f2e8a2eb', 'rsrc/image/icon/fatcow/thumbnails/default60x45.png' => '0118abed', 'rsrc/image/icon/fatcow/thumbnails/image160x120.png' => '79bb556a', 'rsrc/image/icon/fatcow/thumbnails/image60x45.png' => 'c5e1685e', 'rsrc/image/icon/fatcow/thumbnails/pdf160x120.png' => 'ac9edbf5', 'rsrc/image/icon/fatcow/thumbnails/pdf60x45.png' => 'c0db4143', 'rsrc/image/icon/fatcow/thumbnails/zip160x120.png' => '75f9cd0f', 'rsrc/image/icon/fatcow/thumbnails/zip60x45.png' => 'af11bf3e', 'rsrc/image/icon/lightbox/close-2.png' => 'cc40e7c8', 'rsrc/image/icon/lightbox/close-hover-2.png' => 'fb5d6d9e', 'rsrc/image/icon/lightbox/left-arrow-2.png' => '8426133b', 'rsrc/image/icon/lightbox/left-arrow-hover-2.png' => '701e5ee3', 'rsrc/image/icon/lightbox/right-arrow-2.png' => '6d5519a0', 'rsrc/image/icon/lightbox/right-arrow-hover-2.png' => '3a04aa21', 'rsrc/image/icon/subscribe.png' => 'd03ed5a5', 'rsrc/image/icon/tango/attachment.png' => 'ecc8022e', 'rsrc/image/icon/tango/edit.png' => '929a1363', 'rsrc/image/icon/tango/go-down.png' => '96d95e43', 'rsrc/image/icon/tango/log.png' => 'b08cc63a', 'rsrc/image/icon/tango/upload.png' => '7bbb7984', 'rsrc/image/icon/unsubscribe.png' => '25725013', 'rsrc/image/lightblue-header.png' => '5c168b6d', 'rsrc/image/loading.gif' => '75d384cc', 'rsrc/image/loading/boating_24.gif' => '5c90f086', 'rsrc/image/loading/compass_24.gif' => 'b36b4f46', 'rsrc/image/loading/loading_24.gif' => '26bc9adc', 'rsrc/image/loading/loading_48.gif' => '6a4994c7', 'rsrc/image/loading/loading_d48.gif' => 'cdcbe900', 'rsrc/image/loading/loading_w24.gif' => '7662fa2b', 'rsrc/image/main_texture.png' => '29a2c5ad', 'rsrc/image/menu_texture.png' => '5a17580d', 'rsrc/image/people/harding.png' => '45aa614e', 'rsrc/image/people/jefferson.png' => 'afca0e53', 'rsrc/image/people/lincoln.png' => '9369126d', 'rsrc/image/people/mckinley.png' => 'fb8f16ce', 'rsrc/image/people/taft.png' => 'd7bc402c', 'rsrc/image/people/washington.png' => '40dd301c', 'rsrc/image/phrequent_active.png' => 'a466a8ed', 'rsrc/image/phrequent_inactive.png' => 'bfc15a69', 'rsrc/image/search-white.png' => '64cc0d45', 'rsrc/image/search.png' => '82625a7e', 'rsrc/image/sprite-apps-X2.png' => '10fe124a', 'rsrc/image/sprite-apps-X4.png' => '9c151271', 'rsrc/image/sprite-apps-large-X2.png' => '4a828a0f', 'rsrc/image/sprite-apps-large.png' => '141d8c93', 'rsrc/image/sprite-apps-xlarge.png' => 'a751a580', 'rsrc/image/sprite-apps.png' => 'f6a0599f', 'rsrc/image/sprite-buttonbar-X2.png' => '2c09a184', 'rsrc/image/sprite-buttonbar.png' => 'e98e96af', 'rsrc/image/sprite-conpherence-X2.png' => 'cd2d08d7', 'rsrc/image/sprite-conpherence.png' => 'a5ab2eb7', 'rsrc/image/sprite-docs-X2.png' => '6dc1adad', 'rsrc/image/sprite-docs.png' => '4636297f', 'rsrc/image/sprite-gradient.png' => '6f37c182', 'rsrc/image/sprite-login-X2.png' => '5f75d60b', 'rsrc/image/sprite-login.png' => 'efe40ddb', 'rsrc/image/sprite-main-header.png' => '83521873', 'rsrc/image/sprite-menu-X2.png' => '5a3c3459', 'rsrc/image/sprite-menu.png' => '33682c5c', 'rsrc/image/sprite-minicons-X2.png' => '55377e4e', 'rsrc/image/sprite-minicons.png' => '272644ea', 'rsrc/image/sprite-payments.png' => 'd8576309', 'rsrc/image/sprite-projects-X2.png' => '218fdc8b', 'rsrc/image/sprite-projects.png' => '631ff9a7', 'rsrc/image/sprite-remarkup-X2.png' => '7ee1dc28', 'rsrc/image/sprite-remarkup.png' => 'b4421f07', 'rsrc/image/sprite-tokens-X2.png' => 'b4776580', 'rsrc/image/sprite-tokens.png' => '25b75533', 'rsrc/image/texture/card-gradient.png' => '815f26e8', 'rsrc/image/texture/dark-menu-hover.png' => '5fa7ece8', 'rsrc/image/texture/dark-menu.png' => '7e22296e', 'rsrc/image/texture/grip.png' => '719404f3', 'rsrc/image/texture/panel-header-gradient.png' => 'e3b8dcfe', 'rsrc/image/texture/phlnx-bg.png' => '8d819209', 'rsrc/image/texture/pholio-background.gif' => 'ba29239c', 'rsrc/image/texture/table_header.png' => '5c433037', 'rsrc/image/texture/table_header_hover.png' => '038ec3b9', 'rsrc/image/texture/table_header_tall.png' => 'd56b434f', 'rsrc/js/application/aphlict/Aphlict.js' => '493665ee', 'rsrc/js/application/aphlict/behavior-aphlict-dropdown.js' => '2a2dba85', 'rsrc/js/application/aphlict/behavior-aphlict-listen.js' => '0a6c2de6', 'rsrc/js/application/auth/behavior-persona-login.js' => '9414ff18', 'rsrc/js/application/config/behavior-reorder-fields.js' => '938aed89', 'rsrc/js/application/conpherence/behavior-menu.js' => '7ee23816', 'rsrc/js/application/conpherence/behavior-pontificate.js' => '53f6f2dd', 'rsrc/js/application/conpherence/behavior-widget-pane.js' => '40b1ff90', 'rsrc/js/application/countdown/timer.js' => '889c96f3', 'rsrc/js/application/dashboard/behavior-dashboard-async-panel.js' => '469c0d9e', 'rsrc/js/application/dashboard/behavior-dashboard-move-panels.js' => 'fa187a68', 'rsrc/js/application/differential/DifferentialInlineCommentEditor.js' => 'f2441746', 'rsrc/js/application/differential/behavior-add-reviewers-and-ccs.js' => '533a187b', 'rsrc/js/application/differential/behavior-comment-jump.js' => '71755c79', 'rsrc/js/application/differential/behavior-comment-preview.js' => '127f2018', 'rsrc/js/application/differential/behavior-diff-radios.js' => 'e1ff79b1', 'rsrc/js/application/differential/behavior-dropdown-menus.js' => '9f0dfafa', 'rsrc/js/application/differential/behavior-edit-inline-comments.js' => '00861799', 'rsrc/js/application/differential/behavior-keyboard-nav.js' => '173ce7e7', 'rsrc/js/application/differential/behavior-populate.js' => 'dfdf9f34', 'rsrc/js/application/differential/behavior-show-all-comments.js' => '7c273581', 'rsrc/js/application/differential/behavior-show-field-details.js' => '441f2137', 'rsrc/js/application/differential/behavior-show-more.js' => 'dd7e8ef5', 'rsrc/js/application/differential/behavior-toggle-files.js' => 'ca3f91eb', 'rsrc/js/application/differential/behavior-user-select.js' => 'a8d8459d', 'rsrc/js/application/diffusion/DiffusionLocateFileSource.js' => '5afdb2f8', 'rsrc/js/application/diffusion/behavior-audit-preview.js' => 'be81801d', 'rsrc/js/application/diffusion/behavior-commit-branches.js' => 'bdaf4d04', 'rsrc/js/application/diffusion/behavior-commit-graph.js' => 'f7f1289f', 'rsrc/js/application/diffusion/behavior-jump-to.js' => '9db3d160', 'rsrc/js/application/diffusion/behavior-load-blame.js' => '42126667', 'rsrc/js/application/diffusion/behavior-locate-file.js' => '6d3e1947', 'rsrc/js/application/diffusion/behavior-pull-lastmodified.js' => '2b228192', 'rsrc/js/application/doorkeeper/behavior-doorkeeper-tag.js' => 'e5822781', 'rsrc/js/application/files/behavior-icon-composer.js' => '8ef9ab58', 'rsrc/js/application/files/behavior-launch-icon-composer.js' => '48086888', 'rsrc/js/application/harbormaster/behavior-reorder-steps.js' => '957a7fde', 'rsrc/js/application/herald/HeraldRuleEditor.js' => '22d2966a', 'rsrc/js/application/herald/PathTypeahead.js' => 'f7fc67ec', 'rsrc/js/application/herald/herald-rule-editor.js' => '7ebaeed3', 'rsrc/js/application/maniphest/behavior-batch-editor.js' => 'fe80fb6d', 'rsrc/js/application/maniphest/behavior-batch-selector.js' => 'ead554ec', 'rsrc/js/application/maniphest/behavior-line-chart.js' => '64ef2fd2', 'rsrc/js/application/maniphest/behavior-list-edit.js' => 'cf76cfd5', 'rsrc/js/application/maniphest/behavior-subpriorityeditor.js' => '84845b5b', 'rsrc/js/application/maniphest/behavior-transaction-controls.js' => 'dddd43ac', 'rsrc/js/application/maniphest/behavior-transaction-expand.js' => '2f2e18aa', 'rsrc/js/application/maniphest/behavior-transaction-preview.js' => 'f8248bc5', 'rsrc/js/application/owners/OwnersPathEditor.js' => '46efd18e', 'rsrc/js/application/owners/owners-path-editor.js' => '7a68dda3', 'rsrc/js/application/passphrase/phame-credential-control.js' => '1e1c8a59', 'rsrc/js/application/phame/phame-post-preview.js' => '61d927ec', 'rsrc/js/application/pholio/behavior-pholio-mock-edit.js' => '1e1e8bb0', 'rsrc/js/application/pholio/behavior-pholio-mock-view.js' => '28497740', 'rsrc/js/application/phortune/behavior-balanced-payment-form.js' => '3b3e1664', 'rsrc/js/application/phortune/behavior-stripe-payment-form.js' => '1693a296', 'rsrc/js/application/phortune/behavior-test-payment-form.js' => 'b3e5ee60', 'rsrc/js/application/phortune/phortune-credit-card-form.js' => '2290aeef', 'rsrc/js/application/policy/behavior-policy-control.js' => 'f3fef818', 'rsrc/js/application/policy/behavior-policy-rule-editor.js' => '263aeb8c', 'rsrc/js/application/ponder/behavior-votebox.js' => '327dbe61', 'rsrc/js/application/projects/behavior-boards-filter.js' => '22f113af', 'rsrc/js/application/projects/behavior-project-boards.js' => 'd8e135db', 'rsrc/js/application/projects/behavior-project-create.js' => '065227cc', 'rsrc/js/application/releeph/releeph-preview-branch.js' => '9eb2cedb', 'rsrc/js/application/releeph/releeph-request-state-change.js' => 'd259e7c9', 'rsrc/js/application/releeph/releeph-request-typeahead.js' => 'cd9e7094', 'rsrc/js/application/repository/repository-crossreference.js' => '8ab282be', 'rsrc/js/application/search/behavior-reorder-queries.js' => '37871df4', 'rsrc/js/application/slowvote/behavior-slowvote-embed.js' => 'a51fdb2e', 'rsrc/js/application/transactions/behavior-transaction-comment-form.js' => '9084a36f', 'rsrc/js/application/transactions/behavior-transaction-list.js' => 'cf656c84', 'rsrc/js/application/uiexample/JavelinViewExample.js' => 'd4a14807', 'rsrc/js/application/uiexample/ReactorButtonExample.js' => '44524435', 'rsrc/js/application/uiexample/ReactorCheckboxExample.js' => '7ba325ee', 'rsrc/js/application/uiexample/ReactorFocusExample.js' => '82f568cd', 'rsrc/js/application/uiexample/ReactorInputExample.js' => 'd6ca6b1c', 'rsrc/js/application/uiexample/ReactorMouseoverExample.js' => '4e37e4de', 'rsrc/js/application/uiexample/ReactorRadioExample.js' => '858f9728', 'rsrc/js/application/uiexample/ReactorSelectExample.js' => '189e4fe3', 'rsrc/js/application/uiexample/ReactorSendClassExample.js' => 'bf97561d', 'rsrc/js/application/uiexample/ReactorSendPropertiesExample.js' => '551add57', 'rsrc/js/application/uiexample/busy-example.js' => 'fbbce3bf', 'rsrc/js/application/uiexample/gesture-example.js' => 'f42bb8c6', 'rsrc/js/application/uiexample/notification-example.js' => 'c51a6616', 'rsrc/js/core/Busy.js' => '6453c869', 'rsrc/js/core/DragAndDropFileUpload.js' => 'ae6abfba', 'rsrc/js/core/DraggableList.js' => '1681c4d4', 'rsrc/js/core/FileUpload.js' => 'a4ae61bf', 'rsrc/js/core/Hovercard.js' => '4f344388', 'rsrc/js/core/KeyboardShortcut.js' => '1ae869f2', 'rsrc/js/core/KeyboardShortcutManager.js' => 'ad7a69ca', 'rsrc/js/core/MultirowRowManager.js' => '50395a1b', 'rsrc/js/core/Notification.js' => '0c6946e7', 'rsrc/js/core/Prefab.js' => '41ed7994', 'rsrc/js/core/ShapedRequest.js' => '7cbe244b', 'rsrc/js/core/TextAreaUtils.js' => 'b3ec3cfc', 'rsrc/js/core/ToolTip.js' => '3915d490', 'rsrc/js/core/behavior-active-nav.js' => 'c81bc98f', 'rsrc/js/core/behavior-audio-source.js' => '59b251eb', 'rsrc/js/core/behavior-autofocus.js' => '7319e029', 'rsrc/js/core/behavior-crop.js' => 'b98fc918', 'rsrc/js/core/behavior-dark-console.js' => 'e9fdb5e5', 'rsrc/js/core/behavior-device.js' => '03d6ed07', 'rsrc/js/core/behavior-drag-and-drop-textarea.js' => '4a11ea9c', 'rsrc/js/core/behavior-error-log.js' => 'a5d7cf86', 'rsrc/js/core/behavior-fancy-datepicker.js' => '5d584426', 'rsrc/js/core/behavior-file-tree.js' => 'c8728c70', 'rsrc/js/core/behavior-form.js' => 'a9aaba0c', 'rsrc/js/core/behavior-gesture.js' => 'fe2e0ba4', 'rsrc/js/core/behavior-global-drag-and-drop.js' => '8fd76bab', 'rsrc/js/core/behavior-high-security-warning.js' => '8fc1c918', 'rsrc/js/core/behavior-history-install.js' => '7ee2b591', 'rsrc/js/core/behavior-hovercard.js' => '9c808199', 'rsrc/js/core/behavior-keyboard-pager.js' => 'b657bdf8', 'rsrc/js/core/behavior-keyboard-shortcuts.js' => 'd75709e6', 'rsrc/js/core/behavior-konami.js' => '5bc2cb21', 'rsrc/js/core/behavior-lightbox-attachments.js' => '3aa45ad9', 'rsrc/js/core/behavior-line-linker.js' => '0969ff43', 'rsrc/js/core/behavior-more.js' => '9b9197be', 'rsrc/js/core/behavior-object-selector.js' => 'e6f67523', 'rsrc/js/core/behavior-oncopy.js' => 'c3e218fe', 'rsrc/js/core/behavior-phabricator-nav.js' => 'b5842a5e', 'rsrc/js/core/behavior-phabricator-remarkup-assist.js' => 'c021950a', 'rsrc/js/core/behavior-refresh-csrf.js' => '7814b593', 'rsrc/js/core/behavior-remarkup-preview.js' => 'f7379f45', 'rsrc/js/core/behavior-reveal-content.js' => '8f24abfc', 'rsrc/js/core/behavior-search-typeahead.js' => '86549ee3', 'rsrc/js/core/behavior-select-on-click.js' => '0e34ca02', 'rsrc/js/core/behavior-toggle-class.js' => 'a82a7769', 'rsrc/js/core/behavior-tokenizer.js' => 'b3a4b884', 'rsrc/js/core/behavior-tooltip.js' => '48db4145', 'rsrc/js/core/behavior-watch-anchor.js' => '06e05112', 'rsrc/js/core/behavior-workflow.js' => '0a3f3021', 'rsrc/js/core/phtize.js' => 'd254d646', 'rsrc/js/phui/behavior-phui-object-box-tabs.js' => 'a3e2244e', 'rsrc/js/phui/behavior-phui-timeline-dropdown-menu.js' => '4d94d9c3', 'rsrc/js/phuix/PHUIXActionListView.js' => 'b5c256b8', 'rsrc/js/phuix/PHUIXActionView.js' => '6e8cefa4', 'rsrc/js/phuix/PHUIXDropdownMenu.js' => 'bd4c8dca', - 'rsrc/swf/aphlict.swf' => 'abac967d', + 'rsrc/swf/aphlict.swf' => 'f22c1e40', ), 'symbols' => array( 'aphront-bars' => '231ac33c', 'aphront-contextbar-view-css' => '1c3b0529', 'aphront-dark-console-css' => '6378ef3d', 'aphront-dialog-view-css' => 'c01d24b4', 'aphront-error-view-css' => '9f1d5518', 'aphront-list-filter-view-css' => '2ae43867', 'aphront-multi-column-view-css' => '1b95ab2e', 'aphront-pager-view-css' => '2e3539af', 'aphront-panel-view-css' => '5846dfa2', 'aphront-request-failure-view-css' => 'da14df31', 'aphront-table-view-css' => '88e80148', 'aphront-tokenizer-control-css' => '82ce2142', 'aphront-tooltip-css' => '9c90229d', 'aphront-two-column-view-css' => '16ab3ad2', 'aphront-typeahead-control-css' => 'a989b5b3', 'auth-css' => '1e655982', 'config-options-css' => '7fedf08b', 'conpherence-menu-css' => '828ddd3e', 'conpherence-message-pane-css' => '7703a9a9', 'conpherence-notification-css' => '04a6e10a', 'conpherence-update-css' => '1099a660', 'conpherence-widget-pane-css' => 'bf275a6c', 'differential-changeset-view-css' => 'f234b888', 'differential-core-view-css' => '7ac3cabc', 'differential-inline-comment-editor' => 'f2441746', 'differential-results-table-css' => '239924f9', 'differential-revision-add-comment-css' => 'c478bcaa', 'differential-revision-comment-css' => '48186045', 'differential-revision-history-css' => '0e8eb855', 'differential-revision-list-css' => 'f3c47d33', 'differential-table-of-contents-css' => '6bf8e1d2', 'diffusion-commit-view-css' => '92d1e8f9', 'diffusion-icons-css' => '384a0f7d', 'diffusion-source-css' => '66fdf661', 'diviner-shared-css' => '38813222', 'font-fontawesome' => '73d075c3', 'font-source-sans-pro' => '91d53463', 'global-drag-and-drop-css' => '697324ad', 'harbormaster-css' => 'cec833b7', 'herald-css' => 'c544dd1c', 'herald-rule-editor' => '22d2966a', 'herald-test-css' => '778b008e', 'inline-comment-summary-css' => '8cfd34e8', 'javelin-aphlict' => '493665ee', 'javelin-behavior' => '8a3ed18b', 'javelin-behavior-aphlict-dropdown' => '2a2dba85', 'javelin-behavior-aphlict-listen' => '0a6c2de6', 'javelin-behavior-aphront-basic-tokenizer' => 'b3a4b884', 'javelin-behavior-aphront-crop' => 'b98fc918', 'javelin-behavior-aphront-drag-and-drop-textarea' => '4a11ea9c', 'javelin-behavior-aphront-form-disable-on-submit' => 'a9aaba0c', 'javelin-behavior-aphront-more' => '9b9197be', 'javelin-behavior-audio-source' => '59b251eb', 'javelin-behavior-audit-preview' => 'be81801d', 'javelin-behavior-balanced-payment-form' => '3b3e1664', 'javelin-behavior-boards-filter' => '22f113af', 'javelin-behavior-config-reorder-fields' => '938aed89', 'javelin-behavior-conpherence-menu' => '7ee23816', 'javelin-behavior-conpherence-pontificate' => '53f6f2dd', 'javelin-behavior-conpherence-widget-pane' => '40b1ff90', 'javelin-behavior-countdown-timer' => '889c96f3', 'javelin-behavior-dark-console' => 'e9fdb5e5', 'javelin-behavior-dashboard-async-panel' => '469c0d9e', 'javelin-behavior-dashboard-move-panels' => 'fa187a68', 'javelin-behavior-device' => '03d6ed07', 'javelin-behavior-differential-add-reviewers-and-ccs' => '533a187b', 'javelin-behavior-differential-comment-jump' => '71755c79', 'javelin-behavior-differential-diff-radios' => 'e1ff79b1', 'javelin-behavior-differential-dropdown-menus' => '9f0dfafa', 'javelin-behavior-differential-edit-inline-comments' => '00861799', 'javelin-behavior-differential-feedback-preview' => '127f2018', 'javelin-behavior-differential-keyboard-navigation' => '173ce7e7', 'javelin-behavior-differential-populate' => 'dfdf9f34', 'javelin-behavior-differential-show-field-details' => '441f2137', 'javelin-behavior-differential-show-more' => 'dd7e8ef5', 'javelin-behavior-differential-toggle-files' => 'ca3f91eb', 'javelin-behavior-differential-user-select' => 'a8d8459d', 'javelin-behavior-diffusion-commit-branches' => 'bdaf4d04', 'javelin-behavior-diffusion-commit-graph' => 'f7f1289f', 'javelin-behavior-diffusion-jump-to' => '9db3d160', 'javelin-behavior-diffusion-locate-file' => '6d3e1947', 'javelin-behavior-diffusion-pull-lastmodified' => '2b228192', 'javelin-behavior-doorkeeper-tag' => 'e5822781', 'javelin-behavior-error-log' => 'a5d7cf86', 'javelin-behavior-fancy-datepicker' => '5d584426', 'javelin-behavior-global-drag-and-drop' => '8fd76bab', 'javelin-behavior-harbormaster-reorder-steps' => '957a7fde', 'javelin-behavior-herald-rule-editor' => '7ebaeed3', 'javelin-behavior-high-security-warning' => '8fc1c918', 'javelin-behavior-history-install' => '7ee2b591', 'javelin-behavior-icon-composer' => '8ef9ab58', 'javelin-behavior-konami' => '5bc2cb21', 'javelin-behavior-launch-icon-composer' => '48086888', 'javelin-behavior-lightbox-attachments' => '3aa45ad9', 'javelin-behavior-line-chart' => '64ef2fd2', 'javelin-behavior-load-blame' => '42126667', 'javelin-behavior-maniphest-batch-editor' => 'fe80fb6d', 'javelin-behavior-maniphest-batch-selector' => 'ead554ec', 'javelin-behavior-maniphest-list-editor' => 'cf76cfd5', 'javelin-behavior-maniphest-subpriority-editor' => '84845b5b', 'javelin-behavior-maniphest-transaction-controls' => 'dddd43ac', 'javelin-behavior-maniphest-transaction-expand' => '2f2e18aa', 'javelin-behavior-maniphest-transaction-preview' => 'f8248bc5', 'javelin-behavior-owners-path-editor' => '7a68dda3', 'javelin-behavior-passphrase-credential-control' => '1e1c8a59', 'javelin-behavior-persona-login' => '9414ff18', 'javelin-behavior-phabricator-active-nav' => 'c81bc98f', 'javelin-behavior-phabricator-autofocus' => '7319e029', 'javelin-behavior-phabricator-busy-example' => 'fbbce3bf', 'javelin-behavior-phabricator-file-tree' => 'c8728c70', 'javelin-behavior-phabricator-gesture' => 'fe2e0ba4', 'javelin-behavior-phabricator-gesture-example' => 'f42bb8c6', 'javelin-behavior-phabricator-hovercards' => '9c808199', 'javelin-behavior-phabricator-keyboard-pager' => 'b657bdf8', 'javelin-behavior-phabricator-keyboard-shortcuts' => 'd75709e6', 'javelin-behavior-phabricator-line-linker' => '0969ff43', 'javelin-behavior-phabricator-nav' => 'b5842a5e', 'javelin-behavior-phabricator-notification-example' => 'c51a6616', 'javelin-behavior-phabricator-object-selector' => 'e6f67523', 'javelin-behavior-phabricator-oncopy' => 'c3e218fe', 'javelin-behavior-phabricator-remarkup-assist' => 'c021950a', 'javelin-behavior-phabricator-reveal-content' => '8f24abfc', 'javelin-behavior-phabricator-search-typeahead' => '86549ee3', 'javelin-behavior-phabricator-show-all-transactions' => '7c273581', 'javelin-behavior-phabricator-tooltips' => '48db4145', 'javelin-behavior-phabricator-transaction-comment-form' => '9084a36f', 'javelin-behavior-phabricator-transaction-list' => 'cf656c84', 'javelin-behavior-phabricator-watch-anchor' => '06e05112', 'javelin-behavior-phame-post-preview' => '61d927ec', 'javelin-behavior-pholio-mock-edit' => '1e1e8bb0', 'javelin-behavior-pholio-mock-view' => '28497740', 'javelin-behavior-phui-object-box-tabs' => 'a3e2244e', 'javelin-behavior-phui-timeline-dropdown-menu' => '4d94d9c3', 'javelin-behavior-policy-control' => 'f3fef818', 'javelin-behavior-policy-rule-editor' => '263aeb8c', 'javelin-behavior-ponder-votebox' => '327dbe61', 'javelin-behavior-project-boards' => 'd8e135db', 'javelin-behavior-project-create' => '065227cc', 'javelin-behavior-refresh-csrf' => '7814b593', 'javelin-behavior-releeph-preview-branch' => '9eb2cedb', 'javelin-behavior-releeph-request-state-change' => 'd259e7c9', 'javelin-behavior-releeph-request-typeahead' => 'cd9e7094', 'javelin-behavior-remarkup-preview' => 'f7379f45', 'javelin-behavior-repository-crossreference' => '8ab282be', 'javelin-behavior-search-reorder-queries' => '37871df4', 'javelin-behavior-select-on-click' => '0e34ca02', 'javelin-behavior-slowvote-embed' => 'a51fdb2e', 'javelin-behavior-stripe-payment-form' => '1693a296', 'javelin-behavior-test-payment-form' => 'b3e5ee60', 'javelin-behavior-toggle-class' => 'a82a7769', 'javelin-behavior-view-placeholder' => '2fa810fc', 'javelin-behavior-workflow' => '0a3f3021', 'javelin-color' => '7e41274a', 'javelin-cookie' => '6b3dcf44', 'javelin-diffusion-locate-file-source' => '5afdb2f8', 'javelin-dom' => '07d99a3d', 'javelin-dynval' => 'f6555212', 'javelin-event' => '69815cac', 'javelin-fx' => '54b612ba', 'javelin-history' => 'c60f4327', 'javelin-install' => '52a92793', 'javelin-json' => '08e56a4e', 'javelin-magical-init' => 'b88ab49e', 'javelin-mask' => 'b9f26029', 'javelin-reactor' => '77b1cf6f', 'javelin-reactor-dom' => 'b6d401d6', 'javelin-reactor-node-calmer' => '76f4ebed', 'javelin-reactornode' => 'b4c30592', 'javelin-request' => '7bad574b', 'javelin-resource' => '356de121', 'javelin-routable' => 'b3e7d692', 'javelin-router' => '29274e2b', 'javelin-stratcom' => 'c293f7b9', 'javelin-tokenizer' => 'e7c21fb3', 'javelin-typeahead' => 'c54eeefb', 'javelin-typeahead-composite-source' => '84f34ab1', 'javelin-typeahead-normalizer' => '5f850b5c', 'javelin-typeahead-ondemand-source' => 'a79b75a4', 'javelin-typeahead-preloaded-source' => '66815d9c', 'javelin-typeahead-source' => '62e18640', 'javelin-typeahead-static-source' => 'cdde23f1', 'javelin-uri' => 'd9a9b862', 'javelin-util' => '65b0b249', 'javelin-vector' => 'bd0aedcd', 'javelin-view' => '0f764c35', 'javelin-view-html' => 'e5b406f9', 'javelin-view-interpreter' => '0c33c1a0', 'javelin-view-renderer' => '6c2b09a2', 'javelin-view-visitor' => 'efe49472', 'javelin-workflow' => '09b15cf1', 'lightbox-attachment-css' => '7acac05d', 'maniphest-batch-editor' => '8f380ebc', 'maniphest-report-css' => '6fc16517', 'maniphest-task-edit-css' => '8e23031b', 'maniphest-task-summary-css' => '00c3be7a', 'multirow-row-manager' => '50395a1b', 'owners-path-editor' => '46efd18e', 'owners-path-editor-css' => '2f00933b', 'paste-css' => 'aa1767d1', 'path-typeahead' => 'f7fc67ec', 'people-profile-css' => 'ba7b2762', 'phabricator-action-header-view-css' => '33a4590a', 'phabricator-action-list-view-css' => 'dcbfc854', 'phabricator-application-launch-view-css' => '137e794e', 'phabricator-busy' => '6453c869', 'phabricator-chatlog-css' => '852140ff', 'phabricator-content-source-view-css' => '4b8b05d4', 'phabricator-core-css' => '40151074', 'phabricator-countdown-css' => '86b7b0a0', 'phabricator-crumbs-view-css' => '989a48b6', 'phabricator-dashboard-css' => 'f593f8c2', 'phabricator-drag-and-drop-file-upload' => 'ae6abfba', 'phabricator-draggable-list' => '1681c4d4', 'phabricator-fatal-config-template-css' => '25d446d6', 'phabricator-feed-css' => 'dd43ce00', 'phabricator-file-upload' => 'a4ae61bf', 'phabricator-filetree-view-css' => 'a8c86ace', 'phabricator-flag-css' => '5337623f', 'phabricator-hovercard' => '4f344388', 'phabricator-hovercard-view-css' => '46a13cf0', - 'phabricator-jump-nav' => 'f0c5e726', 'phabricator-keyboard-shortcut' => '1ae869f2', 'phabricator-keyboard-shortcut-manager' => 'ad7a69ca', 'phabricator-main-menu-view' => '72d1d2ef', 'phabricator-nav-view-css' => '9283c2df', 'phabricator-notification' => '0c6946e7', 'phabricator-notification-css' => 'ef2c9b34', 'phabricator-notification-menu-css' => '8637a3db', 'phabricator-object-selector-css' => '029a133d', 'phabricator-phtize' => 'd254d646', 'phabricator-prefab' => '41ed7994', 'phabricator-profile-css' => '33e6f703', 'phabricator-project-tag-css' => '095c9404', 'phabricator-remarkup-css' => '80c3a48c', 'phabricator-search-results-css' => 'f240504c', 'phabricator-settings-css' => 'ea8f5915', 'phabricator-shaped-request' => '7cbe244b', 'phabricator-side-menu-view-css' => 'c1986b85', 'phabricator-slowvote-css' => '266df6a1', 'phabricator-source-code-view-css' => '62a99814', 'phabricator-standard-page-view' => '517cdfb1', 'phabricator-textareautils' => 'b3ec3cfc', 'phabricator-tooltip' => '3915d490', 'phabricator-transaction-view-css' => 'ce491938', 'phabricator-ui-example-css' => '528b19de', 'phabricator-uiexample-javelin-view' => 'd4a14807', 'phabricator-uiexample-reactor-button' => '44524435', 'phabricator-uiexample-reactor-checkbox' => '7ba325ee', 'phabricator-uiexample-reactor-focus' => '82f568cd', 'phabricator-uiexample-reactor-input' => 'd6ca6b1c', 'phabricator-uiexample-reactor-mouseover' => '4e37e4de', 'phabricator-uiexample-reactor-radio' => '858f9728', 'phabricator-uiexample-reactor-select' => '189e4fe3', 'phabricator-uiexample-reactor-sendclass' => 'bf97561d', 'phabricator-uiexample-reactor-sendproperties' => '551add57', 'phabricator-zindex-css' => 'efb673ac', 'phame-css' => '19ecc703', 'pholio-css' => 'e059f955', 'pholio-edit-css' => 'b9e59b6d', 'pholio-inline-comments-css' => '52be33f0', 'phortune-credit-card-form' => '2290aeef', 'phortune-credit-card-form-css' => 'b25b4beb', 'phrequent-css' => 'ffc185ad', 'phriction-document-css' => '7d7f0071', 'phui-box-css' => '7b3a2eed', 'phui-button-css' => 'd26cad6e', 'phui-calendar-css' => '5e1ad989', 'phui-calendar-day-css' => 'de035c8a', 'phui-calendar-list-css' => 'c1d0ca59', 'phui-calendar-month-css' => 'a92e47d2', 'phui-document-view-css' => '3b078dc0', 'phui-feed-story-css' => 'e2c9bc83', 'phui-font-icon-base-css' => 'eb84f033', 'phui-fontkit-css' => 'de84aa4a', 'phui-form-css' => 'b78ec020', 'phui-form-view-css' => 'ed856191', 'phui-header-view-css' => '689dbc38', 'phui-icon-view-css' => 'cdcf2aca', 'phui-info-panel-css' => '27ea50a1', 'phui-list-view-css' => '43ed2d93', 'phui-object-box-css' => 'ce92d8ec', - 'phui-object-item-list-view-css' => '16003f41', + 'phui-object-item-list-view-css' => '15c582b1', 'phui-pinboard-view-css' => '874c22f9', 'phui-property-list-view-css' => '2f7199e8', 'phui-remarkup-preview-css' => '19ad512b', 'phui-spacing-css' => '042804d6', 'phui-status-list-view-css' => '2f562399', 'phui-tag-view-css' => '74051f4e', 'phui-text-css' => '23e9b4b7', 'phui-timeline-view-css' => 'bbd990d0', 'phui-workboard-view-css' => '2bf82d00', 'phui-workpanel-view-css' => 'fddd97bf', 'phuix-action-list-view' => 'b5c256b8', 'phuix-action-view' => '6e8cefa4', 'phuix-dropdown-menu' => 'bd4c8dca', 'policy-css' => '957ea14c', 'policy-edit-css' => '05cca26a', 'policy-transaction-detail-css' => '82100a43', 'ponder-comment-table-css' => '6cdccea7', 'ponder-feed-view-css' => 'e62615b6', 'ponder-post-css' => 'ebab8a70', 'ponder-vote-css' => '8ed6ed8b', 'project-icon-css' => 'c2ecb7f1', 'raphael-core' => '51ee6b43', 'raphael-g' => '40dde778', 'raphael-g-line' => '40da039e', 'releeph-core' => '9b3c5733', 'releeph-preview-branch' => 'b7a6f4a5', 'releeph-request-differential-create-dialog' => '8d8b92cd', 'releeph-request-typeahead-css' => '667a48ae', 'setup-issue-css' => '69e640e7', 'sprite-apps-css' => '37ee4f4e', 'sprite-apps-large-css' => '12ea1ced', 'sprite-buttonbar-css' => 'ba1c5738', 'sprite-conpherence-css' => '3b4a0487', 'sprite-docs-css' => '5f65d0da', 'sprite-gradient-css' => '487b5761', 'sprite-login-css' => '67ff30b2', 'sprite-main-header-css' => '92720ee2', 'sprite-menu-css' => '1605a7fb', 'sprite-minicons-css' => 'df4f76fe', 'sprite-payments-css' => 'cc085d44', 'sprite-projects-css' => '7578fa56', 'sprite-remarkup-css' => '5c396a57', 'sprite-tokens-css' => '1706b943', 'subscribers-list-css' => '5bb30c78', 'syntax-highlighting-css' => '3c18c1cb', 'tokens-css' => '3d0f239e', ), 'requires' => array( '00861799' => array( 0 => 'javelin-behavior', 1 => 'javelin-stratcom', 2 => 'javelin-dom', 3 => 'javelin-util', 4 => 'javelin-vector', 5 => 'differential-inline-comment-editor', ), '029a133d' => array( 0 => 'aphront-dialog-view-css', ), '03d6ed07' => array( 0 => 'javelin-behavior', 1 => 'javelin-stratcom', 2 => 'javelin-dom', 3 => 'javelin-vector', 4 => 'javelin-install', ), '065227cc' => array( 0 => 'javelin-behavior', 1 => 'javelin-dom', 2 => 'javelin-stratcom', 3 => 'javelin-workflow', ), '06e05112' => array( 0 => 'javelin-behavior', 1 => 'javelin-stratcom', 2 => 'javelin-dom', 3 => 'javelin-vector', ), '07d99a3d' => array( 0 => 'javelin-magical-init', 1 => 'javelin-install', 2 => 'javelin-util', 3 => 'javelin-vector', 4 => 'javelin-stratcom', ), '08e56a4e' => array( 0 => 'javelin-install', ), '0969ff43' => array( 0 => 'javelin-behavior', 1 => 'javelin-stratcom', 2 => 'javelin-dom', 3 => 'javelin-history', ), '09b15cf1' => array( 0 => 'javelin-stratcom', 1 => 'javelin-request', 2 => 'javelin-dom', 3 => 'javelin-vector', 4 => 'javelin-install', 5 => 'javelin-util', 6 => 'javelin-mask', 7 => 'javelin-uri', 8 => 'javelin-routable', ), '0a3f3021' => array( 0 => 'javelin-behavior', 1 => 'javelin-stratcom', 2 => 'javelin-workflow', 3 => 'javelin-dom', 4 => 'javelin-router', ), '0a6c2de6' => array( 0 => 'javelin-behavior', 1 => 'javelin-aphlict', 2 => 'javelin-stratcom', 3 => 'javelin-request', 4 => 'javelin-uri', 5 => 'javelin-dom', 6 => 'javelin-json', 7 => 'javelin-router', 8 => 'phabricator-notification', ), '0c33c1a0' => array( 0 => 'javelin-view', 1 => 'javelin-install', 2 => 'javelin-dom', ), '0c6946e7' => array( 0 => 'javelin-install', 1 => 'javelin-dom', 2 => 'javelin-stratcom', 3 => 'javelin-util', 4 => 'phabricator-notification-css', ), '0e34ca02' => array( 0 => 'javelin-behavior', 1 => 'javelin-stratcom', 2 => 'javelin-dom', ), '0f764c35' => array( 0 => 'javelin-install', 1 => 'javelin-util', ), '127f2018' => array( 0 => 'javelin-behavior', 1 => 'javelin-stratcom', 2 => 'javelin-dom', 3 => 'javelin-request', 4 => 'javelin-util', 5 => 'phabricator-shaped-request', ), '1681c4d4' => array( 0 => 'javelin-install', 1 => 'javelin-dom', 2 => 'javelin-stratcom', 3 => 'javelin-util', 4 => 'javelin-vector', 5 => 'javelin-magical-init', ), '1693a296' => array( 0 => 'javelin-behavior', 1 => 'javelin-dom', 2 => 'phortune-credit-card-form', ), '173ce7e7' => array( 0 => 'javelin-behavior', 1 => 'javelin-dom', 2 => 'javelin-stratcom', 3 => 'phabricator-keyboard-shortcut', ), '189e4fe3' => array( 0 => 'javelin-install', 1 => 'javelin-dom', 2 => 'javelin-reactor-dom', ), '1ae869f2' => array( 0 => 'javelin-install', 1 => 'javelin-util', 2 => 'phabricator-keyboard-shortcut-manager', ), '1e1c8a59' => array( 0 => 'javelin-behavior', 1 => 'javelin-dom', 2 => 'javelin-stratcom', 3 => 'javelin-workflow', 4 => 'javelin-util', 5 => 'javelin-uri', ), '1e1e8bb0' => array( 0 => 'javelin-behavior', 1 => 'javelin-stratcom', 2 => 'javelin-dom', 3 => 'javelin-workflow', 4 => 'phabricator-phtize', 5 => 'phabricator-drag-and-drop-file-upload', 6 => 'phabricator-draggable-list', ), '2290aeef' => array( 0 => 'javelin-install', 1 => 'javelin-dom', 2 => 'javelin-json', 3 => 'javelin-workflow', 4 => 'javelin-util', ), '22d2966a' => array( 0 => 'multirow-row-manager', 1 => 'javelin-install', 2 => 'javelin-util', 3 => 'javelin-dom', 4 => 'javelin-stratcom', 5 => 'javelin-json', 6 => 'phabricator-prefab', ), '22f113af' => array( 0 => 'javelin-behavior', 1 => 'javelin-dom', 2 => 'javelin-stratcom', 3 => 'phuix-dropdown-menu', ), '263aeb8c' => array( 0 => 'javelin-behavior', 1 => 'multirow-row-manager', 2 => 'javelin-dom', 3 => 'javelin-util', 4 => 'phabricator-prefab', 5 => 'javelin-tokenizer', 6 => 'javelin-typeahead', 7 => 'javelin-typeahead-preloaded-source', 8 => 'javelin-json', ), '29274e2b' => array( 0 => 'javelin-install', 1 => 'javelin-util', ), '2a2dba85' => array( 0 => 'javelin-behavior', 1 => 'javelin-request', 2 => 'javelin-stratcom', 3 => 'javelin-vector', 4 => 'javelin-dom', 5 => 'javelin-uri', 6 => 'javelin-behavior-device', ), '2b228192' => array( 0 => 'javelin-behavior', 1 => 'javelin-dom', 2 => 'javelin-util', 3 => 'javelin-workflow', 4 => 'javelin-json', ), '2f2e18aa' => array( 0 => 'javelin-behavior', 1 => 'javelin-dom', 2 => 'javelin-workflow', 3 => 'javelin-stratcom', ), '2fa810fc' => array( 0 => 'javelin-behavior', 1 => 'javelin-dom', 2 => 'javelin-view-renderer', 3 => 'javelin-install', ), '327dbe61' => array( 0 => 'javelin-behavior', 1 => 'javelin-dom', 2 => 'javelin-util', 3 => 'javelin-stratcom', 4 => 'javelin-request', ), '356de121' => array( 0 => 'javelin-util', 1 => 'javelin-uri', 2 => 'javelin-install', ), '37871df4' => array( 0 => 'javelin-behavior', 1 => 'javelin-stratcom', 2 => 'javelin-workflow', 3 => 'javelin-dom', 4 => 'phabricator-draggable-list', ), '3915d490' => array( 0 => 'javelin-install', 1 => 'javelin-util', 2 => 'javelin-dom', 3 => 'javelin-vector', ), '3aa45ad9' => array( 0 => 'javelin-behavior', 1 => 'javelin-stratcom', 2 => 'javelin-dom', 3 => 'javelin-mask', 4 => 'javelin-util', 5 => 'phabricator-busy', ), '3b3e1664' => array( 0 => 'javelin-behavior', 1 => 'javelin-dom', 2 => 'phortune-credit-card-form', ), '40b1ff90' => array( 0 => 'javelin-behavior', 1 => 'javelin-dom', 2 => 'javelin-stratcom', 3 => 'javelin-workflow', 4 => 'javelin-util', 5 => 'phabricator-notification', 6 => 'javelin-behavior-device', 7 => 'phuix-dropdown-menu', 8 => 'phuix-action-list-view', 9 => 'phuix-action-view', ), '41ed7994' => array( 0 => 'javelin-install', 1 => 'javelin-util', 2 => 'javelin-dom', 3 => 'javelin-typeahead', 4 => 'javelin-tokenizer', 5 => 'javelin-typeahead-preloaded-source', 6 => 'javelin-typeahead-ondemand-source', 7 => 'javelin-dom', 8 => 'javelin-stratcom', 9 => 'javelin-util', ), '441f2137' => array( 0 => 'javelin-behavior', 1 => 'javelin-stratcom', 2 => 'javelin-dom', ), '469c0d9e' => array( 0 => 'javelin-behavior', 1 => 'javelin-dom', 2 => 'javelin-workflow', ), '46efd18e' => array( 0 => 'multirow-row-manager', 1 => 'javelin-install', 2 => 'path-typeahead', 3 => 'javelin-dom', 4 => 'javelin-util', 5 => 'phabricator-prefab', ), '48db4145' => array( 0 => 'javelin-behavior', 1 => 'javelin-behavior-device', 2 => 'javelin-stratcom', 3 => 'phabricator-tooltip', ), '493665ee' => array( 0 => 'javelin-install', 1 => 'javelin-util', ), '4a11ea9c' => array( 0 => 'javelin-behavior', 1 => 'javelin-dom', 2 => 'phabricator-drag-and-drop-file-upload', 3 => 'phabricator-textareautils', ), '4d94d9c3' => array( 0 => 'javelin-behavior', 1 => 'javelin-stratcom', 2 => 'javelin-dom', 3 => 'phuix-dropdown-menu', ), '4e37e4de' => array( 0 => 'javelin-install', 1 => 'javelin-dom', 2 => 'javelin-reactor-dom', ), '4f344388' => array( 0 => 'javelin-install', 1 => 'javelin-dom', 2 => 'javelin-vector', 3 => 'javelin-request', 4 => 'javelin-uri', ), '50395a1b' => array( 0 => 'javelin-install', 1 => 'javelin-stratcom', 2 => 'javelin-dom', 3 => 'javelin-util', ), '52a92793' => array( 0 => 'javelin-util', 1 => 'javelin-magical-init', ), '533a187b' => array( 0 => 'javelin-behavior', 1 => 'javelin-dom', 2 => 'phabricator-prefab', ), '53f6f2dd' => array( 0 => 'javelin-behavior', 1 => 'javelin-dom', 2 => 'javelin-util', 3 => 'javelin-workflow', 4 => 'javelin-stratcom', ), '54b612ba' => array( 0 => 'javelin-color', 1 => 'javelin-install', 2 => 'javelin-util', ), '551add57' => array( 0 => 'javelin-install', 1 => 'javelin-dom', 2 => 'javelin-reactor-dom', ), '59b251eb' => array( 0 => 'javelin-behavior', 1 => 'javelin-stratcom', 2 => 'javelin-vector', 3 => 'javelin-dom', ), '5afdb2f8' => array( 0 => 'javelin-install', 1 => 'javelin-dom', 2 => 'javelin-typeahead-preloaded-source', 3 => 'javelin-util', ), '5bc2cb21' => array( 0 => 'javelin-behavior', 1 => 'javelin-stratcom', ), '5d584426' => array( 0 => 'javelin-behavior', 1 => 'javelin-util', 2 => 'javelin-dom', 3 => 'javelin-stratcom', 4 => 'javelin-vector', ), '5f850b5c' => array( 0 => 'javelin-install', ), '61d927ec' => array( 0 => 'javelin-behavior', 1 => 'javelin-dom', 2 => 'javelin-util', 3 => 'phabricator-shaped-request', ), '62e18640' => array( 0 => 'javelin-install', 1 => 'javelin-util', 2 => 'javelin-dom', 3 => 'javelin-typeahead-normalizer', ), '6453c869' => array( 0 => 'javelin-install', 1 => 'javelin-dom', 2 => 'javelin-fx', ), '64ef2fd2' => array( 0 => 'javelin-behavior', 1 => 'javelin-dom', 2 => 'javelin-vector', ), '66815d9c' => array( 0 => 'javelin-install', 1 => 'javelin-util', 2 => 'javelin-request', 3 => 'javelin-typeahead-source', ), '69815cac' => array( 0 => 'javelin-install', ), '6b3dcf44' => array( 0 => 'javelin-install', 1 => 'javelin-util', ), '6c2b09a2' => array( 0 => 'javelin-install', 1 => 'javelin-util', ), '6d3e1947' => array( 0 => 'javelin-behavior', 1 => 'javelin-diffusion-locate-file-source', 2 => 'javelin-dom', 3 => 'javelin-typeahead', 4 => 'javelin-uri', ), '6e8cefa4' => array( 0 => 'javelin-install', 1 => 'javelin-dom', 2 => 'javelin-util', ), '71755c79' => array( 0 => 'javelin-behavior', 1 => 'javelin-stratcom', 2 => 'javelin-dom', ), '7319e029' => array( 0 => 'javelin-behavior', 1 => 'javelin-dom', ), '76f4ebed' => array( 0 => 'javelin-install', 1 => 'javelin-reactor', 2 => 'javelin-util', ), '77b1cf6f' => array( 0 => 'javelin-install', 1 => 'javelin-util', ), '7814b593' => array( 0 => 'javelin-request', 1 => 'javelin-behavior', 2 => 'javelin-dom', 3 => 'javelin-router', 4 => 'javelin-util', 5 => 'phabricator-busy', ), '7a68dda3' => array( 0 => 'owners-path-editor', 1 => 'javelin-behavior', ), '7ba325ee' => array( 0 => 'javelin-install', 1 => 'javelin-dom', 2 => 'javelin-reactor-dom', ), '7bad574b' => array( 0 => 'javelin-install', 1 => 'javelin-stratcom', 2 => 'javelin-util', 3 => 'javelin-behavior', 4 => 'javelin-json', 5 => 'javelin-dom', 6 => 'javelin-resource', 7 => 'javelin-routable', ), '7c273581' => array( 0 => 'javelin-behavior', 1 => 'javelin-stratcom', 2 => 'javelin-dom', ), '7cbe244b' => array( 0 => 'javelin-install', 1 => 'javelin-util', 2 => 'javelin-request', 3 => 'javelin-router', ), '7e41274a' => array( 0 => 'javelin-install', ), '7ebaeed3' => array( 0 => 'herald-rule-editor', 1 => 'javelin-behavior', ), '7ee23816' => array( 0 => 'javelin-behavior', 1 => 'javelin-dom', 2 => 'javelin-util', 3 => 'javelin-stratcom', 4 => 'javelin-workflow', 5 => 'javelin-behavior-device', 6 => 'javelin-history', 7 => 'javelin-vector', 8 => 'phabricator-shaped-request', ), '7ee2b591' => array( 0 => 'javelin-behavior', 1 => 'javelin-history', ), '82ce2142' => array( 0 => 'aphront-typeahead-control-css', ), '82f568cd' => array( 0 => 'javelin-install', 1 => 'javelin-dom', 2 => 'javelin-reactor-dom', ), '84845b5b' => array( 0 => 'javelin-behavior', 1 => 'javelin-dom', 2 => 'javelin-stratcom', 3 => 'javelin-workflow', 4 => 'phabricator-draggable-list', ), '84f34ab1' => array( 0 => 'javelin-install', 1 => 'javelin-typeahead-source', 2 => 'javelin-util', ), '858f9728' => array( 0 => 'javelin-install', 1 => 'javelin-dom', 2 => 'javelin-reactor-dom', ), '86549ee3' => array( 0 => 'javelin-behavior', 1 => 'javelin-typeahead-ondemand-source', 2 => 'javelin-typeahead', 3 => 'javelin-dom', 4 => 'javelin-uri', 5 => 'javelin-util', 6 => 'javelin-stratcom', ), '889c96f3' => array( 0 => 'javelin-behavior', 1 => 'javelin-dom', ), '8a3ed18b' => array( 0 => 'javelin-magical-init', 1 => 'javelin-util', ), '8ab282be' => array( 0 => 'javelin-behavior', 1 => 'javelin-dom', 2 => 'javelin-stratcom', 3 => 'javelin-uri', ), '8ef9ab58' => array( 0 => 'javelin-behavior', 1 => 'javelin-dom', 2 => 'javelin-stratcom', ), '8f24abfc' => array( 0 => 'javelin-behavior', 1 => 'javelin-stratcom', 2 => 'javelin-dom', ), '8fc1c918' => array( 0 => 'javelin-behavior', 1 => 'javelin-uri', 2 => 'phabricator-notification', ), '8fd76bab' => array( 0 => 'javelin-behavior', 1 => 'javelin-dom', 2 => 'javelin-uri', 3 => 'javelin-mask', 4 => 'phabricator-drag-and-drop-file-upload', ), '9084a36f' => array( 0 => 'javelin-behavior', 1 => 'javelin-dom', 2 => 'javelin-util', 3 => 'javelin-fx', 4 => 'javelin-request', 5 => 'phabricator-shaped-request', ), '938aed89' => array( 0 => 'javelin-behavior', 1 => 'javelin-stratcom', 2 => 'javelin-dom', 3 => 'javelin-json', 4 => 'phabricator-draggable-list', ), '9414ff18' => array( 0 => 'javelin-behavior', 1 => 'javelin-resource', 2 => 'javelin-stratcom', 3 => 'javelin-workflow', 4 => 'javelin-util', ), '957a7fde' => array( 0 => 'javelin-behavior', 1 => 'javelin-stratcom', 2 => 'javelin-workflow', 3 => 'javelin-dom', 4 => 'phabricator-draggable-list', ), '9b9197be' => array( 0 => 'javelin-behavior', 1 => 'javelin-stratcom', 2 => 'javelin-dom', ), '9c808199' => array( 0 => 'javelin-behavior', 1 => 'javelin-behavior-device', 2 => 'javelin-stratcom', 3 => 'javelin-vector', 4 => 'phabricator-hovercard', ), '9db3d160' => array( 0 => 'javelin-behavior', 1 => 'javelin-vector', 2 => 'javelin-dom', ), '9eb2cedb' => array( 0 => 'javelin-behavior', 1 => 'javelin-dom', 2 => 'javelin-uri', 3 => 'javelin-request', ), '9f0dfafa' => array( 0 => 'javelin-behavior', 1 => 'javelin-dom', 2 => 'javelin-util', 3 => 'javelin-stratcom', 4 => 'phuix-dropdown-menu', 5 => 'phuix-action-list-view', 6 => 'phuix-action-view', 7 => 'phabricator-phtize', ), 'a3e2244e' => array( 0 => 'javelin-behavior', 1 => 'javelin-stratcom', 2 => 'javelin-dom', ), 'a4ae61bf' => array( 0 => 'javelin-install', 1 => 'javelin-dom', 2 => 'phabricator-notification', ), 'a51fdb2e' => array( 0 => 'javelin-behavior', 1 => 'javelin-request', 2 => 'javelin-stratcom', 3 => 'javelin-dom', ), 'a5d7cf86' => array( 0 => 'javelin-dom', ), 'a79b75a4' => array( 0 => 'javelin-install', 1 => 'javelin-util', 2 => 'javelin-request', 3 => 'javelin-typeahead-source', ), 'a82a7769' => array( 0 => 'javelin-behavior', 1 => 'javelin-stratcom', 2 => 'javelin-dom', ), 'a8d8459d' => array( 0 => 'javelin-behavior', 1 => 'javelin-dom', 2 => 'javelin-stratcom', ), 'a9aaba0c' => array( 0 => 'javelin-behavior', 1 => 'javelin-stratcom', 2 => 'javelin-dom', ), 'ad7a69ca' => array( 0 => 'javelin-install', 1 => 'javelin-util', 2 => 'javelin-stratcom', 3 => 'javelin-dom', 4 => 'javelin-vector', ), 'ae6abfba' => array( 0 => 'javelin-install', 1 => 'javelin-util', 2 => 'javelin-request', 3 => 'javelin-dom', 4 => 'javelin-uri', 5 => 'phabricator-file-upload', ), 'b3a4b884' => array( 0 => 'javelin-behavior', 1 => 'phabricator-prefab', ), 'b3e5ee60' => array( 0 => 'javelin-behavior', 1 => 'javelin-dom', 2 => 'phortune-credit-card-form', ), 'b3e7d692' => array( 0 => 'javelin-install', ), 'b3ec3cfc' => array( 0 => 'javelin-install', ), 'b4c30592' => array( 0 => 'javelin-install', 1 => 'javelin-reactor', 2 => 'javelin-util', 3 => 'javelin-reactor-node-calmer', ), 'b5842a5e' => array( 0 => 'javelin-behavior', 1 => 'javelin-behavior-device', 2 => 'javelin-stratcom', 3 => 'javelin-dom', 4 => 'javelin-magical-init', 5 => 'javelin-vector', 6 => 'javelin-request', 7 => 'javelin-util', ), 'b5c256b8' => array( 0 => 'javelin-install', 1 => 'javelin-dom', ), 'b657bdf8' => array( 0 => 'javelin-behavior', 1 => 'javelin-uri', 2 => 'phabricator-keyboard-shortcut', ), 'b6d401d6' => array( 0 => 'javelin-dom', 1 => 'javelin-dynval', 2 => 'javelin-reactor', 3 => 'javelin-reactornode', 4 => 'javelin-install', 5 => 'javelin-util', ), 'b98fc918' => array( 0 => 'javelin-behavior', 1 => 'javelin-dom', 2 => 'javelin-vector', 3 => 'javelin-magical-init', ), 'b9f26029' => array( 0 => 'javelin-install', 1 => 'javelin-dom', ), 'bd0aedcd' => array( 0 => 'javelin-install', 1 => 'javelin-event', ), 'bd4c8dca' => array( 0 => 'javelin-install', 1 => 'javelin-util', 2 => 'javelin-dom', 3 => 'javelin-vector', 4 => 'javelin-stratcom', ), 'bdaf4d04' => array( 0 => 'javelin-behavior', 1 => 'javelin-dom', 2 => 'javelin-util', 3 => 'javelin-request', ), 'be81801d' => array( 0 => 'javelin-behavior', 1 => 'javelin-dom', 2 => 'javelin-util', 3 => 'phabricator-shaped-request', ), 'bf97561d' => array( 0 => 'javelin-install', 1 => 'javelin-dom', 2 => 'javelin-reactor-dom', ), 'c021950a' => array( 0 => 'javelin-behavior', 1 => 'javelin-stratcom', 2 => 'javelin-dom', 3 => 'phabricator-phtize', 4 => 'phabricator-textareautils', 5 => 'javelin-workflow', 6 => 'javelin-vector', ), 'c293f7b9' => array( 0 => 'javelin-install', 1 => 'javelin-event', 2 => 'javelin-util', 3 => 'javelin-magical-init', ), 'c3e218fe' => array( 0 => 'javelin-behavior', 1 => 'javelin-dom', ), 'c51a6616' => array( 0 => 'phabricator-notification', 1 => 'javelin-stratcom', 2 => 'javelin-behavior', ), 'c54eeefb' => array( 0 => 'javelin-install', 1 => 'javelin-dom', 2 => 'javelin-vector', 3 => 'javelin-util', ), 'c60f4327' => array( 0 => 'javelin-stratcom', 1 => 'javelin-install', 2 => 'javelin-uri', 3 => 'javelin-util', ), 'c81bc98f' => array( 0 => 'javelin-behavior', 1 => 'javelin-stratcom', 2 => 'javelin-vector', 3 => 'javelin-dom', 4 => 'javelin-uri', ), 'c8728c70' => array( 0 => 'javelin-behavior', 1 => 'phabricator-keyboard-shortcut', 2 => 'javelin-stratcom', ), 'ca3f91eb' => array( 0 => 'javelin-behavior', 1 => 'javelin-dom', 2 => 'javelin-stratcom', 3 => 'phabricator-phtize', ), 'cd9e7094' => array( 0 => 'javelin-behavior', 1 => 'javelin-dom', 2 => 'javelin-typeahead', 3 => 'javelin-typeahead-ondemand-source', 4 => 'javelin-dom', ), 'cdde23f1' => array( 0 => 'javelin-install', 1 => 'javelin-typeahead-source', ), 'cf656c84' => array( 0 => 'javelin-behavior', 1 => 'javelin-stratcom', 2 => 'javelin-workflow', 3 => 'javelin-dom', 4 => 'javelin-fx', 5 => 'javelin-uri', 6 => 'phabricator-textareautils', ), 'cf76cfd5' => array( 0 => 'javelin-behavior', 1 => 'javelin-dom', 2 => 'javelin-stratcom', 3 => 'javelin-workflow', 4 => 'javelin-fx', 5 => 'javelin-util', ), 'd254d646' => array( 0 => 'javelin-util', ), 'd259e7c9' => array( 0 => 'javelin-behavior', 1 => 'javelin-dom', 2 => 'javelin-stratcom', 3 => 'javelin-workflow', 4 => 'javelin-util', 5 => 'phabricator-keyboard-shortcut', ), 'd4a14807' => array( 0 => 'javelin-install', 1 => 'javelin-dom', 2 => 'javelin-view', ), 'd6ca6b1c' => array( 0 => 'javelin-install', 1 => 'javelin-reactor-dom', 2 => 'javelin-view-html', 3 => 'javelin-view-interpreter', 4 => 'javelin-view-renderer', ), 'd75709e6' => array( 0 => 'javelin-behavior', 1 => 'javelin-workflow', 2 => 'javelin-json', 3 => 'javelin-dom', 4 => 'phabricator-keyboard-shortcut', ), 'd8e135db' => array( 0 => 'javelin-behavior', 1 => 'javelin-dom', 2 => 'javelin-util', 3 => 'javelin-stratcom', 4 => 'javelin-workflow', 5 => 'phabricator-draggable-list', ), 'd9a9b862' => array( 0 => 'javelin-install', 1 => 'javelin-util', 2 => 'javelin-stratcom', ), 'dd7e8ef5' => array( 0 => 'javelin-behavior', 1 => 'javelin-dom', 2 => 'javelin-workflow', 3 => 'javelin-util', 4 => 'javelin-stratcom', ), 'dddd43ac' => array( 0 => 'javelin-behavior', 1 => 'javelin-dom', 2 => 'phabricator-prefab', ), 'dfdf9f34' => array( 0 => 'javelin-behavior', 1 => 'javelin-workflow', 2 => 'javelin-util', 3 => 'javelin-dom', 4 => 'javelin-stratcom', 5 => 'javelin-behavior-device', 6 => 'javelin-vector', 7 => 'javelin-router', 8 => 'phabricator-tooltip', ), 'e1ff79b1' => array( 0 => 'javelin-behavior', 1 => 'javelin-stratcom', 2 => 'javelin-dom', ), 'e5822781' => array( 0 => 'javelin-behavior', 1 => 'javelin-dom', 2 => 'javelin-json', 3 => 'javelin-workflow', 4 => 'javelin-magical-init', ), 'e5b406f9' => array( 0 => 'javelin-install', 1 => 'javelin-dom', 2 => 'javelin-view-visitor', 3 => 'javelin-util', ), 'e6f67523' => array( 0 => 'javelin-behavior', 1 => 'javelin-dom', 2 => 'javelin-request', 3 => 'javelin-util', ), 'e7c21fb3' => array( 0 => 'javelin-dom', 1 => 'javelin-util', 2 => 'javelin-stratcom', 3 => 'javelin-install', ), 'e9fdb5e5' => array( 0 => 'javelin-behavior', 1 => 'javelin-stratcom', 2 => 'javelin-util', 3 => 'javelin-dom', 4 => 'javelin-request', 5 => 'phabricator-keyboard-shortcut', ), 'ead554ec' => array( 0 => 'javelin-behavior', 1 => 'javelin-dom', 2 => 'javelin-stratcom', 3 => 'javelin-util', ), 'efe49472' => array( 0 => 'javelin-install', 1 => 'javelin-util', ), 'f2441746' => array( 0 => 'javelin-dom', 1 => 'javelin-util', 2 => 'javelin-stratcom', 3 => 'javelin-install', 4 => 'javelin-request', 5 => 'javelin-workflow', ), 'f3fef818' => array( 0 => 'javelin-behavior', 1 => 'javelin-dom', 2 => 'javelin-util', 3 => 'phuix-dropdown-menu', 4 => 'phuix-action-list-view', 5 => 'phuix-action-view', 6 => 'javelin-workflow', ), 'f42bb8c6' => array( 0 => 'javelin-stratcom', 1 => 'javelin-behavior', 2 => 'javelin-vector', 3 => 'javelin-dom', ), 'f6555212' => array( 0 => 'javelin-install', 1 => 'javelin-reactornode', 2 => 'javelin-util', 3 => 'javelin-reactor', ), 'f7379f45' => array( 0 => 'javelin-behavior', 1 => 'javelin-dom', 2 => 'javelin-util', 3 => 'phabricator-shaped-request', ), 'f7f1289f' => array( 0 => 'javelin-behavior', 1 => 'javelin-dom', 2 => 'javelin-stratcom', ), 'f7fc67ec' => array( 0 => 'javelin-install', 1 => 'javelin-typeahead', 2 => 'javelin-dom', 3 => 'javelin-request', 4 => 'javelin-typeahead-ondemand-source', 5 => 'javelin-util', ), 'f8248bc5' => array( 0 => 'javelin-behavior', 1 => 'javelin-dom', 2 => 'javelin-util', 3 => 'javelin-json', 4 => 'javelin-stratcom', 5 => 'phabricator-shaped-request', ), 'fa187a68' => array( 0 => 'javelin-behavior', 1 => 'javelin-dom', 2 => 'javelin-util', 3 => 'javelin-stratcom', 4 => 'javelin-workflow', 5 => 'phabricator-draggable-list', ), 'fbbce3bf' => array( 0 => 'phabricator-busy', 1 => 'javelin-behavior', ), 'fe2e0ba4' => array( 0 => 'javelin-behavior', 1 => 'javelin-behavior-device', 2 => 'javelin-stratcom', 3 => 'javelin-vector', 4 => 'javelin-dom', 5 => 'javelin-magical-init', ), 'fe80fb6d' => array( 0 => 'javelin-behavior', 1 => 'javelin-dom', 2 => 'javelin-util', 3 => 'phabricator-prefab', 4 => 'multirow-row-manager', 5 => 'javelin-json', ), 28497740 => array( 0 => 'javelin-behavior', 1 => 'javelin-util', 2 => 'javelin-stratcom', 3 => 'javelin-dom', 4 => 'javelin-vector', 5 => 'javelin-magical-init', 6 => 'javelin-request', 7 => 'javelin-history', 8 => 'javelin-workflow', 9 => 'javelin-mask', 10 => 'javelin-behavior-device', 11 => 'phabricator-keyboard-shortcut', ), 42126667 => array( 0 => 'javelin-behavior', 1 => 'javelin-dom', 2 => 'javelin-request', ), 44524435 => array( 0 => 'javelin-install', 1 => 'javelin-dom', 2 => 'javelin-util', 3 => 'javelin-dynval', 4 => 'javelin-reactor-dom', ), 48086888 => array( 0 => 'javelin-behavior', 1 => 'javelin-dom', 2 => 'javelin-workflow', ), ), 'packages' => array( 'core.pkg.css' => array( 0 => 'phabricator-core-css', 1 => 'phabricator-zindex-css', 2 => 'phui-button-css', 3 => 'phabricator-standard-page-view', 4 => 'aphront-dialog-view-css', 5 => 'phui-form-view-css', 6 => 'aphront-panel-view-css', 7 => 'aphront-table-view-css', 8 => 'aphront-tokenizer-control-css', 9 => 'aphront-typeahead-control-css', 10 => 'aphront-list-filter-view-css', - 11 => 'phabricator-jump-nav', - 12 => 'phabricator-remarkup-css', - 13 => 'syntax-highlighting-css', - 14 => 'aphront-pager-view-css', - 15 => 'phabricator-transaction-view-css', - 16 => 'aphront-tooltip-css', - 17 => 'phabricator-flag-css', - 18 => 'aphront-error-view-css', - 19 => 'sprite-remarkup-css', - 20 => 'sprite-gradient-css', - 21 => 'sprite-menu-css', - 22 => 'sprite-apps-css', - 23 => 'sprite-apps-large-css', - 24 => 'phabricator-main-menu-view', - 25 => 'phabricator-notification-css', - 26 => 'phabricator-notification-menu-css', - 27 => 'lightbox-attachment-css', - 28 => 'phui-header-view-css', - 29 => 'phabricator-filetree-view-css', - 30 => 'phabricator-nav-view-css', - 31 => 'phabricator-side-menu-view-css', - 32 => 'phabricator-crumbs-view-css', - 33 => 'phui-object-item-list-view-css', - 34 => 'global-drag-and-drop-css', - 35 => 'phui-spacing-css', - 36 => 'phui-form-css', - 37 => 'phui-icon-view-css', - 38 => 'phabricator-application-launch-view-css', - 39 => 'phabricator-action-list-view-css', - 40 => 'phui-property-list-view-css', - 41 => 'phui-tag-view-css', - 42 => 'phui-list-view-css', - 43 => 'font-fontawesome', - 44 => 'phui-font-icon-base-css', - 45 => 'sprite-main-header-css', - 46 => 'phui-box-css', - 47 => 'phui-object-box-css', - 48 => 'phui-timeline-view-css', - 49 => 'sprite-tokens-css', - 50 => 'tokens-css', - 51 => 'phui-status-list-view-css', + 11 => 'phabricator-remarkup-css', + 12 => 'syntax-highlighting-css', + 13 => 'aphront-pager-view-css', + 14 => 'phabricator-transaction-view-css', + 15 => 'aphront-tooltip-css', + 16 => 'phabricator-flag-css', + 17 => 'aphront-error-view-css', + 18 => 'sprite-remarkup-css', + 19 => 'sprite-gradient-css', + 20 => 'sprite-menu-css', + 21 => 'sprite-apps-css', + 22 => 'sprite-apps-large-css', + 23 => 'phabricator-main-menu-view', + 24 => 'phabricator-notification-css', + 25 => 'phabricator-notification-menu-css', + 26 => 'lightbox-attachment-css', + 27 => 'phui-header-view-css', + 28 => 'phabricator-filetree-view-css', + 29 => 'phabricator-nav-view-css', + 30 => 'phabricator-side-menu-view-css', + 31 => 'phabricator-crumbs-view-css', + 32 => 'phui-object-item-list-view-css', + 33 => 'global-drag-and-drop-css', + 34 => 'phui-spacing-css', + 35 => 'phui-form-css', + 36 => 'phui-icon-view-css', + 37 => 'phabricator-application-launch-view-css', + 38 => 'phabricator-action-list-view-css', + 39 => 'phui-property-list-view-css', + 40 => 'phui-tag-view-css', + 41 => 'phui-list-view-css', + 42 => 'font-fontawesome', + 43 => 'phui-font-icon-base-css', + 44 => 'sprite-main-header-css', + 45 => 'phui-box-css', + 46 => 'phui-object-box-css', + 47 => 'phui-timeline-view-css', + 48 => 'sprite-tokens-css', + 49 => 'tokens-css', + 50 => 'phui-status-list-view-css', ), 'core.pkg.js' => array( 0 => 'javelin-util', 1 => 'javelin-install', 2 => 'javelin-event', 3 => 'javelin-stratcom', 4 => 'javelin-behavior', 5 => 'javelin-resource', 6 => 'javelin-request', 7 => 'javelin-vector', 8 => 'javelin-dom', 9 => 'javelin-json', 10 => 'javelin-uri', 11 => 'javelin-workflow', 12 => 'javelin-mask', 13 => 'javelin-typeahead', 14 => 'javelin-typeahead-normalizer', 15 => 'javelin-typeahead-source', 16 => 'javelin-typeahead-preloaded-source', 17 => 'javelin-typeahead-ondemand-source', 18 => 'javelin-tokenizer', 19 => 'javelin-history', 20 => 'javelin-router', 21 => 'javelin-routable', 22 => 'javelin-behavior-aphront-basic-tokenizer', 23 => 'javelin-behavior-workflow', 24 => 'javelin-behavior-aphront-form-disable-on-submit', 25 => 'phabricator-keyboard-shortcut-manager', 26 => 'phabricator-keyboard-shortcut', 27 => 'javelin-behavior-phabricator-keyboard-shortcuts', 28 => 'javelin-behavior-refresh-csrf', 29 => 'javelin-behavior-phabricator-watch-anchor', 30 => 'javelin-behavior-phabricator-autofocus', 31 => 'phuix-dropdown-menu', 32 => 'phuix-action-list-view', 33 => 'phuix-action-view', 34 => 'phabricator-phtize', 35 => 'javelin-behavior-phabricator-oncopy', 36 => 'phabricator-tooltip', 37 => 'javelin-behavior-phabricator-tooltips', 38 => 'phabricator-prefab', 39 => 'javelin-behavior-device', 40 => 'javelin-behavior-toggle-class', 41 => 'javelin-behavior-lightbox-attachments', 42 => 'phabricator-busy', 43 => 'javelin-aphlict', 44 => 'phabricator-notification', 45 => 'javelin-behavior-aphlict-listen', 46 => 'javelin-behavior-phabricator-search-typeahead', 47 => 'javelin-behavior-konami', 48 => 'javelin-behavior-aphlict-dropdown', 49 => 'javelin-behavior-history-install', 50 => 'javelin-behavior-phabricator-gesture', 51 => 'javelin-behavior-phabricator-active-nav', 52 => 'javelin-behavior-phabricator-nav', 53 => 'javelin-behavior-phabricator-remarkup-assist', 54 => 'phabricator-textareautils', 55 => 'phabricator-file-upload', 56 => 'javelin-behavior-global-drag-and-drop', 57 => 'javelin-behavior-phabricator-reveal-content', 58 => 'phabricator-hovercard', 59 => 'javelin-behavior-phabricator-hovercards', 60 => 'javelin-color', 61 => 'javelin-fx', 62 => 'phabricator-draggable-list', 63 => 'javelin-behavior-phabricator-transaction-list', 64 => 'javelin-behavior-phabricator-show-all-transactions', 65 => 'javelin-behavior-phui-timeline-dropdown-menu', 66 => 'javelin-behavior-doorkeeper-tag', ), 'darkconsole.pkg.js' => array( 0 => 'javelin-behavior-dark-console', 1 => 'javelin-behavior-error-log', ), 'differential.pkg.css' => array( 0 => 'differential-core-view-css', 1 => 'differential-changeset-view-css', 2 => 'differential-results-table-css', 3 => 'differential-revision-history-css', 4 => 'differential-revision-list-css', 5 => 'differential-table-of-contents-css', 6 => 'differential-revision-comment-css', 7 => 'differential-revision-add-comment-css', 8 => 'phabricator-object-selector-css', 9 => 'phabricator-content-source-view-css', 10 => 'inline-comment-summary-css', ), 'differential.pkg.js' => array( 0 => 'phabricator-drag-and-drop-file-upload', 1 => 'phabricator-shaped-request', 2 => 'javelin-behavior-differential-feedback-preview', 3 => 'javelin-behavior-differential-edit-inline-comments', 4 => 'javelin-behavior-differential-populate', 5 => 'javelin-behavior-differential-show-more', 6 => 'javelin-behavior-differential-diff-radios', 7 => 'javelin-behavior-differential-comment-jump', 8 => 'javelin-behavior-differential-add-reviewers-and-ccs', 9 => 'javelin-behavior-differential-keyboard-navigation', 10 => 'javelin-behavior-aphront-drag-and-drop-textarea', 11 => 'javelin-behavior-phabricator-object-selector', 12 => 'javelin-behavior-repository-crossreference', 13 => 'javelin-behavior-load-blame', 14 => 'differential-inline-comment-editor', 15 => 'javelin-behavior-differential-dropdown-menus', 16 => 'javelin-behavior-differential-toggle-files', 17 => 'javelin-behavior-differential-user-select', 18 => 'javelin-behavior-aphront-more', ), 'diffusion.pkg.css' => array( 0 => 'diffusion-commit-view-css', 1 => 'diffusion-icons-css', ), 'diffusion.pkg.js' => array( 0 => 'javelin-behavior-diffusion-pull-lastmodified', 1 => 'javelin-behavior-diffusion-commit-graph', 2 => 'javelin-behavior-audit-preview', ), 'maniphest.pkg.css' => array( 0 => 'maniphest-task-summary-css', 1 => 'phabricator-project-tag-css', ), 'maniphest.pkg.js' => array( 0 => 'javelin-behavior-maniphest-batch-selector', 1 => 'javelin-behavior-maniphest-transaction-controls', 2 => 'javelin-behavior-maniphest-transaction-preview', 3 => 'javelin-behavior-maniphest-transaction-expand', 4 => 'javelin-behavior-maniphest-subpriority-editor', 5 => 'javelin-behavior-maniphest-list-editor', ), ), ); diff --git a/resources/celerity/packages.php b/resources/celerity/packages.php index 2f9522e2d..32c9479f6 100644 --- a/resources/celerity/packages.php +++ b/resources/celerity/packages.php @@ -1,194 +1,192 @@ array( 'javelin-util', 'javelin-install', 'javelin-event', 'javelin-stratcom', 'javelin-behavior', 'javelin-resource', 'javelin-request', 'javelin-vector', 'javelin-dom', 'javelin-json', 'javelin-uri', 'javelin-workflow', 'javelin-mask', 'javelin-typeahead', 'javelin-typeahead-normalizer', 'javelin-typeahead-source', 'javelin-typeahead-preloaded-source', 'javelin-typeahead-ondemand-source', 'javelin-tokenizer', 'javelin-history', 'javelin-router', 'javelin-routable', 'javelin-behavior-aphront-basic-tokenizer', 'javelin-behavior-workflow', 'javelin-behavior-aphront-form-disable-on-submit', 'phabricator-keyboard-shortcut-manager', 'phabricator-keyboard-shortcut', 'javelin-behavior-phabricator-keyboard-shortcuts', 'javelin-behavior-refresh-csrf', 'javelin-behavior-phabricator-watch-anchor', 'javelin-behavior-phabricator-autofocus', 'phuix-dropdown-menu', 'phuix-action-list-view', 'phuix-action-view', 'phabricator-phtize', 'javelin-behavior-phabricator-oncopy', 'phabricator-tooltip', 'javelin-behavior-phabricator-tooltips', 'phabricator-prefab', 'javelin-behavior-device', 'javelin-behavior-toggle-class', 'javelin-behavior-lightbox-attachments', 'phabricator-busy', 'javelin-aphlict', 'phabricator-notification', 'javelin-behavior-aphlict-listen', 'javelin-behavior-phabricator-search-typeahead', 'javelin-behavior-konami', 'javelin-behavior-aphlict-dropdown', 'javelin-behavior-history-install', 'javelin-behavior-phabricator-gesture', 'javelin-behavior-phabricator-active-nav', 'javelin-behavior-phabricator-nav', 'javelin-behavior-phabricator-remarkup-assist', 'phabricator-textareautils', 'phabricator-file-upload', 'javelin-behavior-global-drag-and-drop', 'javelin-behavior-phabricator-reveal-content', 'phabricator-hovercard', 'javelin-behavior-phabricator-hovercards', 'javelin-color', 'javelin-fx', 'phabricator-draggable-list', 'javelin-behavior-phabricator-transaction-list', 'javelin-behavior-phabricator-show-all-transactions', 'javelin-behavior-phui-timeline-dropdown-menu', 'javelin-behavior-doorkeeper-tag', ), 'core.pkg.css' => array( 'phabricator-core-css', 'phabricator-zindex-css', 'phui-button-css', 'phabricator-standard-page-view', 'aphront-dialog-view-css', 'phui-form-view-css', 'aphront-panel-view-css', 'aphront-table-view-css', 'aphront-tokenizer-control-css', 'aphront-typeahead-control-css', 'aphront-list-filter-view-css', - 'phabricator-jump-nav', - 'phabricator-remarkup-css', 'syntax-highlighting-css', 'aphront-pager-view-css', 'phabricator-transaction-view-css', 'aphront-tooltip-css', 'phabricator-flag-css', 'aphront-error-view-css', 'sprite-remarkup-css', 'sprite-gradient-css', 'sprite-menu-css', 'sprite-apps-css', 'sprite-apps-large-css', 'phabricator-main-menu-view', 'phabricator-notification-css', 'phabricator-notification-menu-css', 'lightbox-attachment-css', 'phui-header-view-css', 'phabricator-filetree-view-css', 'phabricator-nav-view-css', 'phabricator-side-menu-view-css', 'phabricator-crumbs-view-css', 'phui-object-item-list-view-css', 'global-drag-and-drop-css', 'phui-spacing-css', 'phui-form-css', 'phui-icon-view-css', 'phabricator-application-launch-view-css', 'phabricator-action-list-view-css', 'phui-property-list-view-css', 'phui-tag-view-css', 'phui-list-view-css', 'font-fontawesome', 'phui-font-icon-base-css', 'sprite-main-header-css', 'phui-box-css', 'phui-object-box-css', 'phui-timeline-view-css', 'sprite-tokens-css', 'tokens-css', 'phui-status-list-view-css', ), 'differential.pkg.css' => array( 'differential-core-view-css', 'differential-changeset-view-css', 'differential-results-table-css', 'differential-revision-history-css', 'differential-revision-list-css', 'differential-table-of-contents-css', 'differential-revision-comment-css', 'differential-revision-add-comment-css', 'phabricator-object-selector-css', 'phabricator-content-source-view-css', 'inline-comment-summary-css', ), 'differential.pkg.js' => array( 'phabricator-drag-and-drop-file-upload', 'phabricator-shaped-request', 'javelin-behavior-differential-feedback-preview', 'javelin-behavior-differential-edit-inline-comments', 'javelin-behavior-differential-populate', 'javelin-behavior-differential-show-more', 'javelin-behavior-differential-diff-radios', 'javelin-behavior-differential-comment-jump', 'javelin-behavior-differential-add-reviewers-and-ccs', 'javelin-behavior-differential-keyboard-navigation', 'javelin-behavior-aphront-drag-and-drop-textarea', 'javelin-behavior-phabricator-object-selector', 'javelin-behavior-repository-crossreference', 'javelin-behavior-load-blame', 'differential-inline-comment-editor', 'javelin-behavior-differential-dropdown-menus', 'javelin-behavior-differential-toggle-files', 'javelin-behavior-differential-user-select', 'javelin-behavior-aphront-more', ), 'diffusion.pkg.css' => array( 'diffusion-commit-view-css', 'diffusion-icons-css', ), 'diffusion.pkg.js' => array( 'javelin-behavior-diffusion-pull-lastmodified', 'javelin-behavior-diffusion-commit-graph', 'javelin-behavior-audit-preview', ), 'maniphest.pkg.css' => array( 'maniphest-task-summary-css', 'phabricator-project-tag-css', ), 'maniphest.pkg.js' => array( 'javelin-behavior-maniphest-batch-selector', 'javelin-behavior-maniphest-transaction-controls', 'javelin-behavior-maniphest-transaction-preview', 'javelin-behavior-maniphest-transaction-expand', 'javelin-behavior-maniphest-subpriority-editor', 'javelin-behavior-maniphest-list-editor', ), 'darkconsole.pkg.js' => array( 'javelin-behavior-dark-console', 'javelin-behavior-error-log', ), ); diff --git a/resources/sql/patches/20131004.dxreviewers.php b/resources/sql/patches/20131004.dxreviewers.php index 3904613eb..8ff3bc32f 100644 --- a/resources/sql/patches/20131004.dxreviewers.php +++ b/resources/sql/patches/20131004.dxreviewers.php @@ -1,53 +1,53 @@ establishConnection('w'); // NOTE: We migrate by revision because the relationship table doesn't have // an "id" column. foreach (new LiskMigrationIterator($table) as $revision) { $revision_id = $revision->getID(); $revision_phid = $revision->getPHID(); echo "Migrating reviewers for D{$revision_id}...\n"; $reviewer_phids = queryfx_all( $conn_w, 'SELECT objectPHID FROM %T WHERE revisionID = %d AND relation = %s ORDER BY sequence', 'differential_relationship', $revision_id, 'revw'); $reviewer_phids = ipull($reviewer_phids, 'objectPHID'); if (!$reviewer_phids) { continue; } $editor = id(new PhabricatorEdgeEditor()) ->setActor(PhabricatorUser::getOmnipotentUser()); foreach ($reviewer_phids as $dst) { if (phid_get_type($dst) == PhabricatorPHIDConstants::PHID_TYPE_UNKNOWN) { // At least one old install ran into some issues here. Skip the row if we // can't figure out what the destination PHID is. See here: - // https://github.com/facebook/phabricator/pull/507 + // https://github.com/phacility/phabricator/pull/507 continue; } $editor->addEdge( $revision_phid, PhabricatorEdgeConfig::TYPE_DREV_HAS_REVIEWER, $dst, array( 'data' => array( 'status' => DifferentialReviewerStatus::STATUS_ADDED, ), )); } $editor->save(); } echo "Done.\n"; diff --git a/scripts/install/install_rhel-derivs.sh b/scripts/install/install_rhel-derivs.sh index a09949f29..58753c038 100755 --- a/scripts/install/install_rhel-derivs.sh +++ b/scripts/install/install_rhel-derivs.sh @@ -1,145 +1,145 @@ #!/bin/bash confirm() { echo "Press RETURN to continue, or ^C to cancel."; read -e ignored } RHEL_VER_FILE="/etc/redhat-release" if [[ ! -f $RHEL_VER_FILE ]] then echo "It looks like you're not running a Red Hat-derived distribution." echo "This script is intended to install Phabricator on RHEL-derived" echo "distributions such as RHEL, Fedora, CentOS, and Scientific Linux." echo "Proceed with caution." confirm fi echo "PHABRICATOR RED HAT DERIVATIVE INSTALLATION SCRIPT"; echo "This script will install Phabricator and all of its core dependencies."; echo "Run it from the directory you want to install into."; echo RHEL_REGEX="release ([0-9]+)\." if [[ $(cat $RHEL_VER_FILE) =~ $RHEL_REGEX ]] then RHEL_MAJOR_VER=${BASH_REMATCH[1]} else echo "Ut oh, we were unable to determine your distribution's major" echo "version number. Please make sure you're running 6.0+ before" echo "proceeding." confirm fi if [[ $RHEL_MAJOR_VER < 6 && $RHEL_MAJOR_VER > 0 ]] then echo "** WARNING **" echo "A major version less than 6 was detected. Because of this," echo "several needed dependencies are not available via default repos." echo "Specifically, RHEL 5 does not have a PEAR package for php53-*." echo "We will attempt to install it manually, for APC. Please be careful." confirm fi echo "Phabricator will be installed to: $(pwd)."; confirm echo "Testing sudo/root..." if [[ $EUID -ne 0 ]] # Check if we're root. If we are, continue. then sudo true SUDO="sudo" if [[ $? -ne 0 ]] then echo "ERROR: You must be able to sudo to run this script, or run it as root."; exit 1 fi fi if [[ $RHEL_MAJOR_VER == 5 ]] then # RHEL 5's "php" package is actually 5.1. The "php53" package won't let us install php-pecl-apc. # (it tries to pull in php 5.1 stuff) ... yum repolist | grep -i epel if [ $? -ne 0 ]; then echo "It doesn't look like you have the EPEL repo enabled. We are to add it" echo "for you, so that we can install git." $SUDO rpm -Uvh http://download.fedoraproject.org/pub/epel/5/i386/epel-release-5-4.noarch.rpm fi YUMCOMMAND="$SUDO yum install httpd git php53 php53-cli php53-mysql php53-process php53-devel php53-gd gcc wget make pcre-devel mysql-server" else # RHEL 6+ defaults with php 5.3 YUMCOMMAND="$SUDO yum install httpd git php php-cli php-mysql php-process php-devel php-gd php-pecl-apc php-pecl-json php-mbstring mysql-server" fi echo "Dropping to yum to install dependencies..." echo "Running: ${YUMCOMMAND}" echo "Yum will prompt you with [Y/n] to continue installing." $YUMCOMMAND if [[ $? -ne 0 ]] then echo "The yum command failed. Please fix the errors and re-run this script." exit 1 fi if [[ $RHEL_MAJOR_VER == 5 ]] then # Now that we've ensured all the devel packages required for pecl/apc are there, let's # set up PEAR, and install apc. echo "Attempting to install PEAR" wget http://pear.php.net/go-pear.phar $SUDO php go-pear.phar && $SUDO pecl install apc fi if [[ $? -ne 0 ]] then echo "The apc install failed. Continuing without APC, performance may be impacted." fi pidof httpd 2>&1 > /dev/null if [[ $? -eq 0 ]] then echo "If php was installed above, please run: /etc/init.d/httpd graceful" else echo "Please remember to start the httpd with: /etc/init.d/httpd start" fi pidof mysqld 2>&1 > /dev/null if [[ $? -ne 0 ]] then echo "Please remember to start the mysql server: /etc/init.d/mysqld start" fi confirm if [[ ! -e libphutil ]] then - git clone git://github.com/facebook/libphutil.git + git clone git://github.com/phacility/libphutil.git else (cd libphutil && git pull --rebase) fi if [[ ! -e arcanist ]] then - git clone git://github.com/facebook/arcanist.git + git clone git://github.com/phacility/arcanist.git else (cd arcanist && git pull --rebase) fi if [[ ! -e phabricator ]] then - git clone git://github.com/facebook/phabricator.git + git clone git://github.com/phacility/phabricator.git else (cd phabricator && git pull --rebase) fi echo echo echo "Install probably worked mostly correctly. Continue with the 'Configuration Guide':"; echo echo " http://www.phabricator.com/docs/phabricator/article/Configuration_Guide.html"; diff --git a/scripts/install/install_ubuntu.sh b/scripts/install/install_ubuntu.sh index e736b7128..265d04488 100755 --- a/scripts/install/install_ubuntu.sh +++ b/scripts/install/install_ubuntu.sh @@ -1,92 +1,92 @@ #!/bin/bash confirm() { echo "Press RETURN to continue, or ^C to cancel."; read -e ignored } GIT='git' LTS="Ubuntu 10.04" ISSUE=`cat /etc/issue` if [[ $ISSUE != Ubuntu* ]] then echo "This script is intended for use on Ubuntu, but this system appears"; echo "to be something else. Your results may vary."; echo confirm elif [[ `expr match "$ISSUE" "$LTS"` -eq ${#LTS} ]] then GIT='git-core' fi echo "PHABRICATOR UBUNTU INSTALL SCRIPT"; echo "This script will install Phabricator and all of its core dependencies."; echo "Run it from the directory you want to install into."; echo ROOT=`pwd` echo "Phabricator will be installed to: ${ROOT}."; confirm echo "Testing sudo..." sudo true if [ $? -ne 0 ] then echo "ERROR: You must be able to sudo to run this script."; exit 1; fi; echo "Installing dependencies: git, apache, mysql, php..."; echo set +x sudo apt-get -qq update sudo apt-get install \ $GIT mysql-server apache2 dpkg-dev \ php5 php5-mysql php5-gd php5-dev php5-curl php-apc php5-cli php5-json # Enable mod_rewrite sudo a2enmod rewrite HAVEPCNTL=`php -r "echo extension_loaded('pcntl');"` if [ $HAVEPCNTL != "1" ] then echo "Installing pcntl..."; echo apt-get source php5 PHP5=`ls -1F | grep '^php5-.*/$'` (cd $PHP5/ext/pcntl && phpize && ./configure && make && sudo make install) else echo "pcntl already installed"; fi if [ ! -e libphutil ] then - git clone git://github.com/facebook/libphutil.git + git clone git://github.com/phacility/libphutil.git else (cd libphutil && git pull --rebase) fi if [ ! -e arcanist ] then - git clone git://github.com/facebook/arcanist.git + git clone git://github.com/phacility/arcanist.git else (cd arcanist && git pull --rebase) fi if [ ! -e phabricator ] then - git clone git://github.com/facebook/phabricator.git + git clone git://github.com/phacility/phabricator.git else (cd phabricator && git pull --rebase) fi echo echo echo "Install probably worked mostly correctly. Continue with the 'Configuration Guide':"; echo echo " http://www.phabricator.com/docs/phabricator/article/Configuration_Guide.html"; echo echo "You can delete any php5-* stuff that's left over in this directory if you want."; diff --git a/src/aphront/console/plugin/DarkConsoleErrorLogPlugin.php b/src/aphront/console/plugin/DarkConsoleErrorLogPlugin.php index 7c96cafe6..cf2d2841f 100644 --- a/src/aphront/console/plugin/DarkConsoleErrorLogPlugin.php +++ b/src/aphront/console/plugin/DarkConsoleErrorLogPlugin.php @@ -1,156 +1,101 @@ getData()); if ($count) { return pht('Error Log (%d)', $count); } return pht('Error Log'); } public function getOrder() { return 0; } public function getColor() { if (count($this->getData())) { return '#ff0000'; } return null; } public function getDescription() { return pht('Shows errors and warnings.'); } public function generateData() { return DarkConsoleErrorLogPluginAPI::getErrors(); } public function renderPanel() { $data = $this->getData(); $rows = array(); $details = array(); foreach ($data as $index => $row) { $file = $row['file']; $line = $row['line']; $tag = phutil_tag( 'a', array( 'onclick' => jsprintf('show_details(%d)', $index), ), $row['str'].' at ['.basename($file).':'.$line.']'); $rows[] = array($tag); $details[] = hsprintf( '
'. "%s\nStack trace:\n", $index, $row['details']); foreach ($row['trace'] as $key => $entry) { $line = ''; if (isset($entry['class'])) { $line .= $entry['class'].'::'; } $line .= idx($entry, 'function', ''); $href = null; if (isset($entry['file'])) { $line .= ' called at ['.$entry['file'].':'.$entry['line'].']'; try { $user = $this->getRequest()->getUser(); $href = $user->loadEditorLink($entry['file'], $entry['line'], ''); } catch (Exception $ex) { // The database can be inaccessible. } } $details[] = phutil_tag( 'a', array( 'href' => $href, ), $line); $details[] = "\n"; } $details[] = hsprintf('
'); } $table = new AphrontTableView($rows); $table->setClassName('error-log'); $table->setHeaders(array('Error')); $table->setNoDataString('No errors.'); return phutil_tag( 'div', array(), array( phutil_tag('div', array(), $table->render()), phutil_tag('pre', array('class' => 'PhabricatorMonospaced'), $details), )); } } - -/* - $data = $this->getData(); - if (!$data) { - return - -
No errors.
-
; - } - - $markup = ; - $alt = false; - foreach ($data as $error) { - $row = ; - - $text = $error['error']; - $text = preg_replace('/\(in .* on line \d+\)$/', '', trim($text)); - - $trace = $error['trace']; - $trace = explode("\n", $trace); - if (!$trace) { - $trace = array('unknown@0@unknown'); - } - - foreach ($trace as $idx => $traceline) { - list($file, $line, $where) = array_merge( - explode('@', $traceline), - array('?', '?', '?')); - if ($where == 'DarkConsole->addError' || - $where == 'debug_rlog') { - unset($trace[$idx]); - } - } - - $row->appendChild(); - - foreach ($trace as $traceline) { - list($file, $line, $where) = array_merge( - explode('@', $traceline), - array('?', '?', '?')); - $row->appendChild(); - $row->appendChild(); - $markup->appendChild($row); - $row = ; - } - - $alt = !$alt; - } - - return - -

Errors

-
{$markup}
-
; -*/ diff --git a/src/applications/audit/application/PhabricatorApplicationAudit.php b/src/applications/audit/application/PhabricatorApplicationAudit.php index 87b6b2cfd..ef85b7c0f 100644 --- a/src/applications/audit/application/PhabricatorApplicationAudit.php +++ b/src/applications/audit/application/PhabricatorApplicationAudit.php @@ -1,80 +1,80 @@ array( '(?:query/(?P[^/]+)/)?' => 'PhabricatorAuditListController', 'addcomment/' => 'PhabricatorAuditAddCommentController', 'preview/(?P[1-9]\d*)/' => 'PhabricatorAuditPreviewController', ), ); } public function getApplicationGroup() { return self::GROUP_CORE; } public function getApplicationOrder() { return 0.130; } public function loadStatus(PhabricatorUser $user) { $status = array(); $phids = PhabricatorAuditCommentEditor::loadAuditPHIDsForUser($user); $query = id(new DiffusionCommitQuery()) ->setViewer($user) ->withAuthorPHIDs(array($user->getPHID())) ->withAuditStatus(DiffusionCommitQuery::AUDIT_STATUS_CONCERN); $commits = $query->execute(); $count = count($commits); $type = PhabricatorApplicationStatusView::TYPE_NEEDS_ATTENTION; $status[] = id(new PhabricatorApplicationStatusView()) ->setType($type) ->setText(pht('%d Problem Commit(s)', $count)) ->setCount($count); $query = id(new DiffusionCommitQuery()) ->setViewer($user) ->withAuditorPHIDs($phids) ->withAuditStatus(DiffusionCommitQuery::AUDIT_STATUS_OPEN) ->withAuditAwaitingUser($user); $commits = $query->execute(); $count = count($commits); $type = PhabricatorApplicationStatusView::TYPE_WARNING; $status[] = id(new PhabricatorApplicationStatusView()) ->setType($type) ->setText(pht('%d Commit(s) Awaiting Audit', $count)) ->setCount($count); return $status; } } diff --git a/src/applications/auth/application/PhabricatorApplicationAuth.php b/src/applications/auth/application/PhabricatorApplicationAuth.php index 0a2581790..3c51aa9bc 100644 --- a/src/applications/auth/application/PhabricatorApplicationAuth.php +++ b/src/applications/auth/application/PhabricatorApplicationAuth.php @@ -1,120 +1,124 @@ isLoggedIn()) { $item = id(new PHUIListItemView()) ->addClass('core-menu-item') ->setName(pht('Log Out')) ->setIcon('logout-sm') ->setWorkflow(true) ->setHref('/logout/') ->setSelected(($controller instanceof PhabricatorLogoutController)) ->setAural(pht('Log Out')) ->setOrder(900); $items[] = $item; } else { if ($controller instanceof PhabricatorAuthController) { // Don't show the "Login" item on auth controllers, since they're // generally all related to logging in anyway. } else { $item = id(new PHUIListItemView()) ->addClass('core-menu-item') ->setName(pht('Log In')) // TODO: Login icon? ->setIcon('power') ->setHref('/auth/start/') ->setAural(pht('Log In')) ->setOrder(900); $items[] = $item; } } return $items; } public function getApplicationGroup() { return self::GROUP_ADMIN; } public function getRoutes() { return array( '/auth/' => array( '' => 'PhabricatorAuthListController', 'config/' => array( 'new/' => 'PhabricatorAuthNewController', 'new/(?P[^/]+)/' => 'PhabricatorAuthEditController', 'edit/(?P\d+)/' => 'PhabricatorAuthEditController', '(?Penable|disable)/(?P\d+)/' => 'PhabricatorAuthDisableController', ), 'login/(?P[^/]+)/(?:(?P[^/]+)/)?' => 'PhabricatorAuthLoginController', 'register/(?:(?P[^/]+)/)?' => 'PhabricatorAuthRegisterController', 'start/' => 'PhabricatorAuthStartController', 'validate/' => 'PhabricatorAuthValidateController', 'finish/' => 'PhabricatorAuthFinishController', 'unlink/(?P[^/]+)/' => 'PhabricatorAuthUnlinkController', '(?Plink|refresh)/(?P[^/]+)/' => 'PhabricatorAuthLinkController', 'confirmlink/(?P[^/]+)/' => 'PhabricatorAuthConfirmLinkController', 'session/terminate/(?P[^/]+)/' => 'PhabricatorAuthTerminateSessionController', 'session/downgrade/' => 'PhabricatorAuthDowngradeSessionController', ), '/oauth/(?P\w+)/login/' => 'PhabricatorAuthOldOAuthRedirectController', '/login/' => array( '' => 'PhabricatorAuthStartController', 'email/' => 'PhabricatorEmailLoginController', 'once/'. '(?P[^/]+)/'. '(?P\d+)/'. '(?P[^/]+)/'. '(?:(?P\d+)/)?' => 'PhabricatorAuthOneTimeLoginController', 'refresh/' => 'PhabricatorRefreshCSRFController', 'mustverify/' => 'PhabricatorMustVerifyEmailController', ), '/emailverify/(?P[^/]+)/' => 'PhabricatorEmailVerificationController', '/logout/' => 'PhabricatorLogoutController', ); } } diff --git a/src/applications/base/PhabricatorApplication.php b/src/applications/base/PhabricatorApplication.php index bed89ed74..91acc55a8 100644 --- a/src/applications/base/PhabricatorApplication.php +++ b/src/applications/base/PhabricatorApplication.php @@ -1,506 +1,504 @@ pht('Core Applications'), self::GROUP_COMMUNICATION => pht('Communication'), self::GROUP_ORGANIZATION => pht('Organization'), self::GROUP_UTILITIES => pht('Utilities'), self::GROUP_ADMIN => pht('Administration'), self::GROUP_DEVELOPER => pht('Developer Tools'), self::GROUP_MISC => pht('Miscellaneous Applications'), ); } public static function getTileDisplayName($constant) { $names = array( self::TILE_INVISIBLE => pht('Invisible'), self::TILE_HIDE => pht('Hidden'), self::TILE_SHOW => pht('Show Small Tile'), self::TILE_FULL => pht('Show Large Tile'), ); return idx($names, $constant); } /* -( Application Information )-------------------------------------------- */ public function getName() { return substr(get_class($this), strlen('PhabricatorApplication')); } public function getShortDescription() { return $this->getName().' Application'; } public function isInstalled() { if (!$this->canUninstall()) { return true; } $beta = PhabricatorEnv::getEnvConfig('phabricator.show-beta-applications'); if (!$beta && $this->isBeta()) { return false; } $uninstalled = PhabricatorEnv::getEnvConfig( 'phabricator.uninstalled-applications'); return empty($uninstalled[get_class($this)]); } public function isBeta() { return false; } /** * Return true if this application should not appear in application lists in * the UI. Primarily intended for unit test applications or other * pseudo-applications. * * @return bool True to remove application from UI lists. */ public function isUnlisted() { return false; } /** * Returns true if an application is first-party (developed by Phacility) * and false otherwise. * * @return bool True if this application is developed by Phacility. */ final public function isFirstParty() { $where = id(new ReflectionClass($this))->getFileName(); $root = phutil_get_library_root('phabricator'); if (!Filesystem::isDescendant($where, $root)) { return false; } if (Filesystem::isDescendant($where, $root.'/extensions')) { return false; } return true; } public function canUninstall() { return true; } public function getPHID() { return 'PHID-APPS-'.get_class($this); } public function getTypeaheadURI() { return $this->getBaseURI(); } public function getBaseURI() { return null; } public function getApplicationURI($path = '') { return $this->getBaseURI().ltrim($path, '/'); } public function getIconURI() { return null; } public function getIconName() { return 'application'; } public function shouldAppearInLaunchView() { return true; } public function getApplicationOrder() { return PHP_INT_MAX; } public function getApplicationGroup() { return self::GROUP_MISC; } public function getTitleGlyph() { return null; } public function getHelpURI() { - // TODO: When these applications get created, link to their docs: - // - // - Drydock - // - OAuth Server - + return null; + } + public function getOverview() { return null; } public function getEventListeners() { return array(); } public function getDefaultTileDisplay(PhabricatorUser $user) { switch ($this->getApplicationGroup()) { case self::GROUP_CORE: return self::TILE_FULL; case self::GROUP_UTILITIES: case self::GROUP_DEVELOPER: return self::TILE_HIDE; case self::GROUP_ADMIN: if ($user->getIsAdmin()) { return self::TILE_SHOW; } else { return self::TILE_INVISIBLE; } break; default: return self::TILE_SHOW; } } public function getRemarkupRules() { return array(); } /* -( URI Routing )-------------------------------------------------------- */ public function getRoutes() { return array(); } /* -( Fact Integration )--------------------------------------------------- */ public function getFactObjectsForAnalysis() { return array(); } /* -( UI Integration )----------------------------------------------------- */ /** * Render status elements (like "3 Waiting Reviews") for application list * views. These provide a way to alert users to new or pending action items * in applications. * * @param PhabricatorUser Viewing user. * @return list Application status elements. * @task ui */ public function loadStatus(PhabricatorUser $user) { return array(); } /** * You can provide an optional piece of flavor text for the application. This * is currently rendered in application launch views if the application has no * status elements. * * @return string|null Flavor text. * @task ui */ public function getFlavorText() { return null; } /** * Build items for the main menu. * * @param PhabricatorUser The viewing user. * @param AphrontController The current controller. May be null for special * pages like 404, exception handlers, etc. * @return list List of menu items. * @task ui */ public function buildMainMenuItems( PhabricatorUser $user, PhabricatorController $controller = null) { return array(); } /** * Build extra items for the main menu. Generally, this is used to render * static dropdowns. * * @param PhabricatorUser The viewing user. * @param AphrontController The current controller. May be null for special * pages like 404, exception handlers, etc. * @return view List of menu items. * @task ui */ public function buildMainMenuExtraNodes( PhabricatorUser $viewer, PhabricatorController $controller = null) { return array(); } /** * Build items for the "quick create" menu. * * @param PhabricatorUser The viewing user. * @return list List of menu items. */ public function getQuickCreateItems(PhabricatorUser $viewer) { return array(); } /* -( Application Management )--------------------------------------------- */ public static function getByClass($class_name) { $selected = null; $applications = PhabricatorApplication::getAllApplications(); foreach ($applications as $application) { if (get_class($application) == $class_name) { $selected = $application; break; } } if (!$selected) { throw new Exception("No application '{$class_name}'!"); } return $selected; } public static function getAllApplications() { static $applications; if ($applications === null) { $apps = id(new PhutilSymbolLoader()) ->setAncestorClass(__CLASS__) ->loadObjects(); // Reorder the applications into "application order". Notably, this // ensures their event handlers register in application order. $apps = msort($apps, 'getApplicationOrder'); $apps = mgroup($apps, 'getApplicationGroup'); $group_order = array_keys(self::getApplicationGroups()); $apps = array_select_keys($apps, $group_order) + $apps; $apps = array_mergev($apps); $applications = $apps; } return $applications; } public static function getAllInstalledApplications() { $all_applications = self::getAllApplications(); $apps = array(); foreach ($all_applications as $app) { if (!$app->isInstalled()) { continue; } $apps[] = $app; } return $apps; } /** * Determine if an application is installed, by application class name. * * To check if an application is installed //and// available to a particular * viewer, user @{method:isClassInstalledForViewer}. * * @param string Application class name. * @return bool True if the class is installed. * @task meta */ public static function isClassInstalled($class) { return self::getByClass($class)->isInstalled(); } /** * Determine if an application is installed and available to a viewer, by * application class name. * * To check if an application is installed at all, use * @{method:isClassInstalled}. * * @param string Application class name. * @param PhabricatorUser Viewing user. * @return bool True if the class is installed for the viewer. * @task meta */ public static function isClassInstalledForViewer( $class, PhabricatorUser $viewer) { if (!self::isClassInstalled($class)) { return false; } return PhabricatorPolicyFilter::hasCapability( $viewer, self::getByClass($class), PhabricatorPolicyCapability::CAN_VIEW); } /* -( PhabricatorPolicyInterface )----------------------------------------- */ public function getCapabilities() { return array_merge( array( PhabricatorPolicyCapability::CAN_VIEW, PhabricatorPolicyCapability::CAN_EDIT, ), array_keys($this->getCustomCapabilities())); } public function getPolicy($capability) { $default = $this->getCustomPolicySetting($capability); if ($default) { return $default; } switch ($capability) { case PhabricatorPolicyCapability::CAN_VIEW: return PhabricatorPolicies::getMostOpenPolicy(); case PhabricatorPolicyCapability::CAN_EDIT: return PhabricatorPolicies::POLICY_ADMIN; default: $spec = $this->getCustomCapabilitySpecification($capability); return idx($spec, 'default', PhabricatorPolicies::POLICY_USER); } } public function hasAutomaticCapability($capability, PhabricatorUser $viewer) { return false; } public function describeAutomaticCapability($capability) { return null; } /* -( Policies )----------------------------------------------------------- */ protected function getCustomCapabilities() { return array(); } private function getCustomPolicySetting($capability) { if (!$this->isCapabilityEditable($capability)) { return null; } $config = PhabricatorEnv::getEnvConfig('phabricator.application-settings'); $app = idx($config, $this->getPHID()); if (!$app) { return null; } $policy = idx($app, 'policy'); if (!$policy) { return null; } return idx($policy, $capability); } private function getCustomCapabilitySpecification($capability) { $custom = $this->getCustomCapabilities(); if (!isset($custom[$capability])) { throw new Exception("Unknown capability '{$capability}'!"); } return $custom[$capability]; } public function getCapabilityLabel($capability) { switch ($capability) { case PhabricatorPolicyCapability::CAN_VIEW: return pht('Can Use Application'); case PhabricatorPolicyCapability::CAN_EDIT: return pht('Can Configure Application'); } $capobj = PhabricatorPolicyCapability::getCapabilityByKey($capability); if ($capobj) { return $capobj->getCapabilityName(); } return null; } public function isCapabilityEditable($capability) { switch ($capability) { case PhabricatorPolicyCapability::CAN_VIEW: return $this->canUninstall(); case PhabricatorPolicyCapability::CAN_EDIT: return false; default: $spec = $this->getCustomCapabilitySpecification($capability); return idx($spec, 'edit', true); } } public function getCapabilityCaption($capability) { switch ($capability) { case PhabricatorPolicyCapability::CAN_VIEW: if (!$this->canUninstall()) { return pht( 'This application is required for Phabricator to operate, so all '. 'users must have access to it.'); } else { return null; } case PhabricatorPolicyCapability::CAN_EDIT: return null; default: $spec = $this->getCustomCapabilitySpecification($capability); return idx($spec, 'caption'); } } } diff --git a/src/applications/calendar/application/PhabricatorApplicationCalendar.php b/src/applications/calendar/application/PhabricatorApplicationCalendar.php index 9554557dc..18c6b12d1 100644 --- a/src/applications/calendar/application/PhabricatorApplicationCalendar.php +++ b/src/applications/calendar/application/PhabricatorApplicationCalendar.php @@ -1,68 +1,68 @@ array( '' => 'PhabricatorCalendarViewController', 'all/' => 'PhabricatorCalendarBrowseController', 'event/' => array( '(?:query/(?P[^/]+)/)?' => 'PhabricatorCalendarEventListController', 'create/' => 'PhabricatorCalendarEventEditController', 'edit/(?P[1-9]\d*)/' => 'PhabricatorCalendarEventEditController', 'delete/(?P[1-9]\d*)/' => 'PhabricatorCalendarEventDeleteController', 'view/(?P[1-9]\d*)/' => 'PhabricatorCalendarEventViewController', ), ), ); } public function getQuickCreateItems(PhabricatorUser $viewer) { $items = array(); $item = id(new PHUIListItemView()) ->setName(pht('Calendar Event')) ->setIcon('fa-calendar') ->setHref($this->getBaseURI().'event/create/'); $items[] = $item; return $items; } } diff --git a/src/applications/chatlog/applications/PhabricatorApplicationChatLog.php b/src/applications/chatlog/applications/PhabricatorApplicationChatLog.php index 707cf4634..018cae36c 100644 --- a/src/applications/chatlog/applications/PhabricatorApplicationChatLog.php +++ b/src/applications/chatlog/applications/PhabricatorApplicationChatLog.php @@ -1,40 +1,40 @@ array( '' => 'PhabricatorChatLogChannelListController', 'channel/(?P[^/]+)/' => 'PhabricatorChatLogChannelLogController', ), ); } } diff --git a/src/applications/conduit/application/PhabricatorApplicationConduit.php b/src/applications/conduit/application/PhabricatorApplicationConduit.php index 248877bb9..e50778e30 100644 --- a/src/applications/conduit/application/PhabricatorApplicationConduit.php +++ b/src/applications/conduit/application/PhabricatorApplicationConduit.php @@ -1,50 +1,50 @@ array( '(?:query/(?P[^/]+)/)?' => 'PhabricatorConduitListController', 'method/(?P[^/]+)/' => 'PhabricatorConduitConsoleController', 'log/' => 'PhabricatorConduitLogController', 'log/view/(?P[^/]+)/' => 'PhabricatorConduitLogController', 'token/' => 'PhabricatorConduitTokenController', ), '/api/(?P[^/]+)' => 'PhabricatorConduitAPIController', ); } } diff --git a/src/applications/config/application/PhabricatorApplicationConfig.php b/src/applications/config/application/PhabricatorApplicationConfig.php index d18b81c2d..aa3756c43 100644 --- a/src/applications/config/application/PhabricatorApplicationConfig.php +++ b/src/applications/config/application/PhabricatorApplicationConfig.php @@ -1,42 +1,46 @@ array( '' => 'PhabricatorConfigListController', 'all/' => 'PhabricatorConfigAllController', 'edit/(?P[\w\.\-]+)/' => 'PhabricatorConfigEditController', 'group/(?P[^/]+)/' => 'PhabricatorConfigGroupController', '(?Pignore|unignore)/(?P[^/]+)/' => 'PhabricatorConfigIgnoreController', 'issue/' => array( '' => 'PhabricatorConfigIssueListController', '(?P[^/]+)/' => 'PhabricatorConfigIssueViewController', ), ), ); } } diff --git a/src/applications/config/option/PhabricatorSyntaxHighlightingConfigOptions.php b/src/applications/config/option/PhabricatorSyntaxHighlightingConfigOptions.php index d0fcc26df..4ce4725fa 100644 --- a/src/applications/config/option/PhabricatorSyntaxHighlightingConfigOptions.php +++ b/src/applications/config/option/PhabricatorSyntaxHighlightingConfigOptions.php @@ -1,128 +1,135 @@ newOption( 'syntax-highlighter.engine', 'class', 'PhutilDefaultSyntaxHighlighterEngine') ->setBaseClass('PhutilSyntaxHighlighterEngine') ->setSummary(pht("Default non-pygments syntax highlighter engine.")) ->setDescription( pht( "Phabricator can highlight PHP by default and use Pygments for ". "other languages if enabled. You can provide a custom ". "highlighter engine by extending class ". "PhutilSyntaxHighlighterEngine.")), $this->newOption('pygments.enabled', 'bool', false) ->setSummary( pht("Should Phabricator shell out to Pygments to highlight code?")) ->setDescription( pht( "If you want syntax highlighting for other languages than PHP ". "then you can install the python package 'Pygments', make sure ". "the 'pygmentize' script is available in the \$PATH of the ". "webserver, and then enable this.")), $this->newOption( 'pygments.dropdown-choices', 'wild', array( 'apacheconf' => 'Apache Configuration', 'bash' => 'Bash Scripting', 'brainfuck' => 'Brainf*ck', 'c' => 'C', + 'coffee-script' => 'CoffeeScript', 'cpp' => 'C++', 'css' => 'CSS', 'd' => 'D', 'diff' => 'Diff', 'django' => 'Django Templating', 'erb' => 'Embedded Ruby/ERB', 'erlang' => 'Erlang', + 'go' => 'Golang', + 'groovy' => 'Groovy', 'haskell' => 'Haskell', 'html' => 'HTML', 'invisible' => 'Invisible', 'java' => 'Java', 'js' => 'Javascript', + 'json' => 'JSON', 'mysql' => 'MySQL', 'objc' => 'Objective-C', 'perl' => 'Perl', 'php' => 'PHP', + 'puppet' => 'Puppet', 'rest' => 'reStructuredText', 'text' => 'Plain Text', 'python' => 'Python', 'rainbow' => 'Rainbow', 'remarkup' => 'Remarkup', 'ruby' => 'Ruby', 'xml' => 'XML', + 'yaml' => 'YAML', )) ->setSummary( pht("Set the language list which appears in dropdowns.")) ->setDescription( pht( "In places that we display a dropdown to syntax-highlight code, ". "this is where that list is defined.")), $this->newOption( 'syntax.filemap', 'wild', array( '@\.arcconfig$@' => 'js', + '@\.arclint$@' => 'js', '@\.divinerconfig$@' => 'js', )) ->setSummary( pht("Override what language files (based on filename) highlight as.")) ->setDescription( pht( "This is an override list of regular expressions which allows ". "you to choose what language files are highlighted as. If your ". "projects have certain rules about filenames or use unusual or ". "ambiguous language extensions, you can create a mapping here. ". "This is an ordered dictionary of regular expressions which will ". "be tested against the filename. They should map to either an ". "explicit language as a string value, or a numeric index into ". "the captured groups as an integer.")) ->addExample('{"@\\.xyz$@": "php"}', pht('Highlight *.xyz as PHP.')) ->addExample( '{"@/httpd\\.conf@": "apacheconf"}', pht('Highlight httpd.conf as "apacheconf".')) ->addExample( '{"@\\.([^.]+)\\.bak$@": 1}', pht( "Treat all '*.x.bak' file as '.x'. NOTE: We map to capturing group ". "1 by specifying the mapping as '1'")), $this->newOption( 'style.monospace', 'string', '10px "Menlo", "Consolas", "Monaco", monospace') ->setLocked(true) ->setSummary( pht("Default monospace font.")) ->setDescription( pht( "Set the default monospaced font style for users who haven't set ". "a custom style.")), $this->newOption( 'style.monospace.windows', 'string', '11px "Menlo", "Consolas", "Monaco", monospace') ->setLocked(true) ->setSummary( pht("Default monospace font for clients on Windows.")) ->setDescription( pht( "Set the default monospaced font style for users who haven't set ". "a custom style and are using Windows.")), ); } } diff --git a/src/applications/conpherence/application/PhabricatorApplicationConpherence.php b/src/applications/conpherence/application/PhabricatorApplicationConpherence.php index 7a3628345..d86cb6772 100644 --- a/src/applications/conpherence/application/PhabricatorApplicationConpherence.php +++ b/src/applications/conpherence/application/PhabricatorApplicationConpherence.php @@ -1,63 +1,63 @@ array( '' => 'ConpherenceListController', 'thread/(?P[1-9]\d*)/' => 'ConpherenceListController', '(?P[1-9]\d*)/' => 'ConpherenceViewController', 'new/' => 'ConpherenceNewController', 'panel/' => 'ConpherenceNotificationPanelController', 'widget/(?P[1-9]\d*)/' => 'ConpherenceWidgetController', 'update/(?P[1-9]\d*)/' => 'ConpherenceUpdateController', ), ); } public function getQuickCreateItems(PhabricatorUser $viewer) { $items = array(); $item = id(new PHUIListItemView()) ->setName(pht('Conpherence Thread')) ->setIcon('fa-comments') ->setWorkflow(true) ->setHref($this->getBaseURI().'new/'); $items[] = $item; return $items; } } diff --git a/src/applications/countdown/application/PhabricatorApplicationCountdown.php b/src/applications/countdown/application/PhabricatorApplicationCountdown.php index ba01b55f5..e50dec772 100644 --- a/src/applications/countdown/application/PhabricatorApplicationCountdown.php +++ b/src/applications/countdown/application/PhabricatorApplicationCountdown.php @@ -1,58 +1,58 @@ array( '(?:query/(?P[^/]+)/)?' => 'PhabricatorCountdownListController', '(?P[1-9]\d*)/' => 'PhabricatorCountdownViewController', 'edit/(?:(?P[1-9]\d*)/)?' => 'PhabricatorCountdownEditController', 'delete/(?P[1-9]\d*)/' => 'PhabricatorCountdownDeleteController' ), ); } public function getCustomCapabilities() { return array( PhabricatorCountdownCapabilityDefaultView::CAPABILITY => array( 'caption' => pht('Default view policy for new countdowns.'), ), ); } } diff --git a/src/applications/daemon/application/PhabricatorApplicationDaemons.php b/src/applications/daemon/application/PhabricatorApplicationDaemons.php index f6582ccfe..4ceec3a4c 100644 --- a/src/applications/daemon/application/PhabricatorApplicationDaemons.php +++ b/src/applications/daemon/application/PhabricatorApplicationDaemons.php @@ -1,56 +1,56 @@ array( '' => 'PhabricatorDaemonConsoleController', 'task/(?P[1-9]\d*)/' => 'PhabricatorWorkerTaskDetailController', 'task/(?P[1-9]\d*)/(?P[^/]+)/' => 'PhabricatorWorkerTaskUpdateController', 'log/' => array( '' => 'PhabricatorDaemonLogListController', 'combined/' => 'PhabricatorDaemonCombinedLogController', '(?P[1-9]\d*)/' => 'PhabricatorDaemonLogViewController', ), 'event/(?P[1-9]\d*)/' => 'PhabricatorDaemonLogEventViewController', ), ); } } diff --git a/src/applications/dashboard/application/PhabricatorApplicationDashboard.php b/src/applications/dashboard/application/PhabricatorApplicationDashboard.php index 4c3f57b5a..65704afd9 100644 --- a/src/applications/dashboard/application/PhabricatorApplicationDashboard.php +++ b/src/applications/dashboard/application/PhabricatorApplicationDashboard.php @@ -1,59 +1,59 @@ \d+)' => 'PhabricatorDashboardPanelViewController', '/dashboard/' => array( '(?:query/(?P[^/]+)/)?' => 'PhabricatorDashboardListController', 'view/(?P\d+)/' => 'PhabricatorDashboardViewController', 'manage/(?P\d+)/' => 'PhabricatorDashboardManageController', 'history/(?P\d+)/' => 'PhabricatorDashboardHistoryController', 'create/' => 'PhabricatorDashboardEditController', 'edit/(?:(?P\d+)/)?' => 'PhabricatorDashboardEditController', 'install/(?P\d+)/' => 'PhabricatorDashboardInstallController', 'uninstall/(?P\d+)/' => 'PhabricatorDashboardUninstallController', 'addpanel/(?P\d+)/' => 'PhabricatorDashboardAddPanelController', 'movepanel/(?P\d+)/' => 'PhabricatorDashboardMovePanelController', 'removepanel/(?P\d+)/' => 'PhabricatorDashboardRemovePanelController', 'panel/' => array( '(?:query/(?P[^/]+)/)?' => 'PhabricatorDashboardPanelListController', 'create/' => 'PhabricatorDashboardPanelCreateController', 'edit/(?:(?P\d+)/)?' => 'PhabricatorDashboardPanelEditController', 'render/(?P\d+)/' => 'PhabricatorDashboardPanelRenderController', ), ), ); } public function getRemarkupRules() { return array( new PhabricatorDashboardRemarkupRule(), ); } public function shouldAppearInLaunchView() { return false; } public function canUninstall() { return false; } } diff --git a/src/applications/dashboard/controller/PhabricatorDashboardViewController.php b/src/applications/dashboard/controller/PhabricatorDashboardViewController.php index 5fded53da..0201a8e2e 100644 --- a/src/applications/dashboard/controller/PhabricatorDashboardViewController.php +++ b/src/applications/dashboard/controller/PhabricatorDashboardViewController.php @@ -1,58 +1,77 @@ id = $data['id']; } public function processRequest() { $request = $this->getRequest(); $viewer = $request->getUser(); $dashboard = id(new PhabricatorDashboardQuery()) ->setViewer($viewer) ->withIDs(array($this->id)) ->needPanels(true) ->executeOne(); if (!$dashboard) { return new Aphront404Response(); } $title = $dashboard->getName(); $crumbs = $this->buildApplicationCrumbs(); $crumbs->addTextCrumb(pht('Dashboard %d', $dashboard->getID())); - $rendered_dashboard = id(new PhabricatorDashboardRenderingEngine()) - ->setViewer($viewer) - ->setDashboard($dashboard) - ->renderDashboard(); + if ($dashboard->getPanelPHIDs()) { + $rendered_dashboard = id(new PhabricatorDashboardRenderingEngine()) + ->setViewer($viewer) + ->setDashboard($dashboard) + ->renderDashboard(); + } else { + $rendered_dashboard = $this->buildEmptyView(); + } return $this->buildApplicationPage( array( $crumbs, $rendered_dashboard, ), array( 'title' => $title, 'device' => true, )); } public function buildApplicationCrumbs() { $crumbs = parent::buildApplicationCrumbs(); $id = $this->id; $crumbs->addAction( id(new PHUIListItemView()) ->setIcon('fa-th') ->setName(pht('Manage Dashboard')) - ->setHref($this->getApplicationURI()."manage/{$id}/")); + ->setHref($this->getApplicationURI("manage/{$id}/"))); return $crumbs; } + public function buildEmptyView() { + $id = $this->id; + $manage_uri = $this->getApplicationURI("manage/{$id}/"); + + return id(new AphrontErrorView()) + ->setSeverity(AphrontErrorView::SEVERITY_NODATA) + ->appendChild( + pht('This dashboard has no panels '. + 'yet. Use %s to add panels.', + phutil_tag( + 'a', + array('href'=>$manage_uri), + pht('Manage Dashboard')))); + } + } diff --git a/src/applications/differential/application/PhabricatorApplicationDifferential.php b/src/applications/differential/application/PhabricatorApplicationDifferential.php index 289a1a4d5..b2816d6ab 100644 --- a/src/applications/differential/application/PhabricatorApplicationDifferential.php +++ b/src/applications/differential/application/PhabricatorApplicationDifferential.php @@ -1,131 +1,139 @@ [1-9]\d*)' => 'DifferentialRevisionViewController', '/differential/' => array( '(?:query/(?P[^/]+)/)?' => 'DifferentialRevisionListController', 'diff/' => array( '(?P[1-9]\d*)/' => 'DifferentialDiffViewController', 'create/' => 'DifferentialDiffCreateController', ), 'changeset/' => 'DifferentialChangesetViewController', 'revision/edit/(?:(?P[1-9]\d*)/)?' => 'DifferentialRevisionEditController', 'revision/land/(?:(?P[1-9]\d*))/(?P[^/]+)/' => 'DifferentialRevisionLandController', 'comment/' => array( 'preview/(?P[1-9]\d*)/' => 'DifferentialCommentPreviewController', 'save/(?P[1-9]\d*)/' => 'DifferentialCommentSaveController', 'inline/' => array( 'preview/(?P[1-9]\d*)/' => 'DifferentialInlineCommentPreviewController', 'edit/(?P[1-9]\d*)/' => 'DifferentialInlineCommentEditController', ), ), 'preview/' => 'PhabricatorMarkupPreviewController', ), ); } public function getApplicationGroup() { return self::GROUP_CORE; } public function getApplicationOrder() { return 0.100; } public function getRemarkupRules() { return array( new DifferentialRemarkupRule(), ); } public function loadStatus(PhabricatorUser $user) { $revisions = id(new DifferentialRevisionQuery()) ->setViewer($user) ->withResponsibleUsers(array($user->getPHID())) ->withStatus(DifferentialRevisionQuery::STATUS_OPEN) ->needRelationships(true) ->execute(); list($blocking, $active, $waiting) = DifferentialRevisionQuery::splitResponsible( $revisions, array($user->getPHID())); $status = array(); $blocking = count($blocking); $type = PhabricatorApplicationStatusView::TYPE_NEEDS_ATTENTION; $status[] = id(new PhabricatorApplicationStatusView()) ->setType($type) ->setText(pht('%d Review(s) Blocking Others', $blocking)) ->setCount($blocking); $active = count($active); $type = PhabricatorApplicationStatusView::TYPE_WARNING; $status[] = id(new PhabricatorApplicationStatusView()) ->setType($type) ->setText(pht('%d Review(s) Need Attention', $active)) ->setCount($active); $waiting = count($waiting); $type = PhabricatorApplicationStatusView::TYPE_INFO; $status[] = id(new PhabricatorApplicationStatusView()) ->setType($type) ->setText(pht('%d Review(s) Waiting on Others', $waiting)) ->setCount($waiting); return $status; } protected function getCustomCapabilities() { return array( DifferentialCapabilityDefaultView::CAPABILITY => array( 'caption' => pht( 'Default view policy for newly created revisions.') ), ); } } diff --git a/src/applications/differential/customfield/DifferentialTestPlanField.php b/src/applications/differential/customfield/DifferentialTestPlanField.php index c4c8cf794..efc3bfc1a 100644 --- a/src/applications/differential/customfield/DifferentialTestPlanField.php +++ b/src/applications/differential/customfield/DifferentialTestPlanField.php @@ -1,202 +1,202 @@ getID()) { return null; } return $revision->getTestPlan(); } protected function writeValueToRevision( DifferentialRevision $revision, $value) { $revision->setTestPlan($value); } protected function isCoreFieldRequired() { return PhabricatorEnv::getEnvConfig('differential.require-test-plan-field'); } public function canDisableField() { return true; } protected function getCoreFieldRequiredErrorString() { return pht( 'You must provide a test plan. Describe the actions you performed '. - 'to verify the behvaior of this change.'); + 'to verify the behavior of this change.'); } public function readValueFromRequest(AphrontRequest $request) { $this->setValue($request->getStr($this->getFieldKey())); } public function renderEditControl(array $handles) { return id(new PhabricatorRemarkupControl()) ->setName($this->getFieldKey()) ->setValue($this->getValue()) ->setError($this->getFieldError()) ->setLabel($this->getFieldName()); } public function getApplicationTransactionTitle( PhabricatorApplicationTransaction $xaction) { $author_phid = $xaction->getAuthorPHID(); $old = $xaction->getOldValue(); $new = $xaction->getNewValue(); return pht( '%s updated the test plan for this revision.', $xaction->renderHandleLink($author_phid)); } public function getApplicationTransactionTitleForFeed( PhabricatorApplicationTransaction $xaction, PhabricatorFeedStory $story) { $object_phid = $xaction->getObjectPHID(); $author_phid = $xaction->getAuthorPHID(); $old = $xaction->getOldValue(); $new = $xaction->getNewValue(); return pht( '%s updated the test plan for %s.', $xaction->renderHandleLink($author_phid), $xaction->renderHandleLink($object_phid)); } public function getApplicationTransactionHasChangeDetails( PhabricatorApplicationTransaction $xaction) { return true; } public function getApplicationTransactionChangeDetails( PhabricatorApplicationTransaction $xaction, PhabricatorUser $viewer) { return $xaction->renderTextCorpusChangeDetails( $viewer, $xaction->getOldValue(), $xaction->getNewValue()); } public function shouldHideInApplicationTransactions( PhabricatorApplicationTransaction $xaction) { return ($xaction->getOldValue() === null); } public function shouldAppearInGlobalSearch() { return true; } public function updateAbstractDocument( PhabricatorSearchAbstractDocument $document) { if (strlen($this->getValue())) { $document->addField('plan', $this->getValue()); } } public function shouldAppearInPropertyView() { return true; } public function renderPropertyViewLabel() { return $this->getFieldName(); } public function getStyleForPropertyView() { return 'block'; } public function getIconForPropertyView() { return PHUIPropertyListView::ICON_TESTPLAN; } public function renderPropertyViewValue(array $handles) { if (!strlen($this->getValue())) { return null; } return PhabricatorMarkupEngine::renderOneObject( id(new PhabricatorMarkupOneOff()) ->setPreserveLinebreaks(true) ->setContent($this->getValue()), 'default', $this->getViewer()); } public function getApplicationTransactionRemarkupBlocks( PhabricatorApplicationTransaction $xaction) { return array($xaction->getNewValue()); } public function shouldAppearInCommitMessage() { return true; } public function shouldAppearInCommitMessageTemplate() { return true; } public function shouldOverwriteWhenCommitMessageIsEdited() { return true; } public function getCommitMessageLabels() { return array( 'Test Plan', 'Testplan', 'Tested', 'Tests', ); } public function validateCommitMessageValue($value) { if (!strlen($value) && $this->isCoreFieldRequired()) { throw new DifferentialFieldValidationException( $this->getCoreFieldRequiredErrorString()); } } public function shouldAppearInTransactionMail() { return true; } public function updateTransactionMailBody( PhabricatorMetaMTAMailBody $body, PhabricatorApplicationTransactionEditor $editor, array $xactions) { if (!$editor->getIsNewObject()) { return; } $test_plan = $this->getValue(); if (!strlen(trim($test_plan))) { return; } $body->addTextSection(pht('TEST PLAN'), $test_plan); } } diff --git a/src/applications/diffusion/application/PhabricatorApplicationDiffusion.php b/src/applications/diffusion/application/PhabricatorApplicationDiffusion.php index e22087af6..15865e5ce 100644 --- a/src/applications/diffusion/application/PhabricatorApplicationDiffusion.php +++ b/src/applications/diffusion/application/PhabricatorApplicationDiffusion.php @@ -1,142 +1,142 @@ [A-Z]+)(?P[a-z0-9]+)' => 'DiffusionCommitController', '/diffusion/' => array( '(?:query/(?P[^/]+)/)?' => 'DiffusionRepositoryListController', 'new/' => 'DiffusionRepositoryNewController', '(?Pcreate)/' => 'DiffusionRepositoryCreateController', '(?Pimport)/' => 'DiffusionRepositoryCreateController', 'pushlog/' => array( '(?:query/(?P[^/]+)/)?' => 'DiffusionPushLogListController', 'view/(?P\d+)/' => 'DiffusionPushEventViewController', ), '(?P[A-Z]+)/' => array( '' => 'DiffusionRepositoryController', 'repository/(?P.*)' => 'DiffusionRepositoryController', 'change/(?P.*)' => 'DiffusionChangeController', 'history/(?P.*)' => 'DiffusionHistoryController', 'browse/(?P.*)' => 'DiffusionBrowseMainController', 'lastmodified/(?P.*)' => 'DiffusionLastModifiedController', 'diff/' => 'DiffusionDiffController', 'tags/(?P.*)' => 'DiffusionTagListController', 'branches/(?P.*)' => 'DiffusionBranchTableController', 'lint/(?P.*)' => 'DiffusionLintController', 'commit/(?P[a-z0-9]+)/branches/' => 'DiffusionCommitBranchesController', 'commit/(?P[a-z0-9]+)/tags/' => 'DiffusionCommitTagsController', 'commit/(?P[a-z0-9]+)/edit/' => 'DiffusionCommitEditController', 'edit/' => array( '' => 'DiffusionRepositoryEditMainController', 'basic/' => 'DiffusionRepositoryEditBasicController', 'encoding/' => 'DiffusionRepositoryEditEncodingController', 'activate/' => 'DiffusionRepositoryEditActivateController', 'dangerous/' => 'DiffusionRepositoryEditDangerousController', 'branches/' => 'DiffusionRepositoryEditBranchesController', 'subversion/' => 'DiffusionRepositoryEditSubversionController', 'actions/' => 'DiffusionRepositoryEditActionsController', '(?Premote)/' => 'DiffusionRepositoryCreateController', '(?Ppolicy)/' => 'DiffusionRepositoryCreateController', 'local/' => 'DiffusionRepositoryEditLocalController', 'delete/' => 'DiffusionRepositoryEditDeleteController', 'hosting/' => 'DiffusionRepositoryEditHostingController', '(?Pserve)/' => 'DiffusionRepositoryEditHostingController', ), 'pathtree/(?P.*)' => 'DiffusionPathTreeController', 'mirror/' => array( 'edit/(?:(?P\d+)/)?' => 'DiffusionMirrorEditController', 'delete/(?P\d+)/' => 'DiffusionMirrorDeleteController', ), ), // NOTE: This must come after the rule above; it just gives us a // catch-all for serving repositories over HTTP. We must accept // requests without the trailing "/" because SVN commands don't // necessarily include it. '(?P[A-Z]+)(/|$).*' => 'DiffusionRepositoryDefaultController', 'inline/' => array( 'edit/(?P[^/]+)/' => 'DiffusionInlineCommentController', 'preview/(?P[^/]+)/' => 'DiffusionInlineCommentPreviewController', ), 'services/' => array( 'path/' => array( 'complete/' => 'DiffusionPathCompleteController', 'validate/' => 'DiffusionPathValidateController', ), ), 'symbol/(?P[^/]+)/' => 'DiffusionSymbolController', 'external/' => 'DiffusionExternalController', 'lint/' => 'DiffusionLintController', ), ); } public function getApplicationGroup() { return self::GROUP_CORE; } public function getApplicationOrder() { return 0.120; } protected function getCustomCapabilities() { return array( DiffusionCapabilityDefaultView::CAPABILITY => array( ), DiffusionCapabilityDefaultEdit::CAPABILITY => array( 'default' => PhabricatorPolicies::POLICY_ADMIN, ), DiffusionCapabilityDefaultPush::CAPABILITY => array( ), DiffusionCapabilityCreateRepositories::CAPABILITY => array( 'default' => PhabricatorPolicies::POLICY_ADMIN, ), ); } } diff --git a/src/applications/diffusion/controller/DiffusionBrowseFileController.php b/src/applications/diffusion/controller/DiffusionBrowseFileController.php index fdeeabdb5..2a926136a 100644 --- a/src/applications/diffusion/controller/DiffusionBrowseFileController.php +++ b/src/applications/diffusion/controller/DiffusionBrowseFileController.php @@ -1,1090 +1,1091 @@ getRequest(); $drequest = $this->getDiffusionRequest(); + $viewer = $request->getUser(); $before = $request->getStr('before'); if ($before) { return $this->buildBeforeResponse($before); } $path = $drequest->getPath(); - $preferences = $request->getUser()->loadPreferences(); + $preferences = $viewer->loadPreferences(); $show_blame = $request->getBool( 'blame', $preferences->getPreference( PhabricatorUserPreferences::PREFERENCE_DIFFUSION_BLAME, false)); $show_color = $request->getBool( 'color', $preferences->getPreference( PhabricatorUserPreferences::PREFERENCE_DIFFUSION_COLOR, true)); $view = $request->getStr('view'); - if ($request->isFormPost() && $view != 'raw') { + if ($request->isFormPost() && $view != 'raw' && $viewer->isLoggedIn()) { $preferences->setPreference( PhabricatorUserPreferences::PREFERENCE_DIFFUSION_BLAME, $show_blame); $preferences->setPreference( PhabricatorUserPreferences::PREFERENCE_DIFFUSION_COLOR, $show_color); $preferences->save(); $uri = $request->getRequestURI() ->alter('blame', null) ->alter('color', null); return id(new AphrontRedirectResponse())->setURI($uri); } // We need the blame information if blame is on and we're building plain // text, or blame is on and this is an Ajax request. If blame is on and // this is a colorized request, we don't show blame at first (we ajax it // in afterward) so we don't need to query for it. $needs_blame = ($show_blame && !$show_color) || ($show_blame && $request->isAjax()); $file_content = DiffusionFileContent::newFromConduit( $this->callConduitWithDiffusionRequest( 'diffusion.filecontentquery', array( 'commit' => $drequest->getCommit(), 'path' => $drequest->getPath(), 'needsBlame' => $needs_blame, ))); $data = $file_content->getCorpus(); if ($view === 'raw') { return $this->buildRawResponse($path, $data); } $this->loadLintMessages(); $this->coverage = $drequest->loadCoverage(); $binary_uri = null; if (ArcanistDiffUtils::isHeuristicBinaryFile($data)) { $file = $this->loadFileForData($path, $data); $file_uri = $file->getBestURI(); if ($file->isViewableImage()) { $corpus = $this->buildImageCorpus($file_uri); } else { $corpus = $this->buildBinaryCorpus($file_uri, $data); $binary_uri = $file_uri; } } else { // Build the content of the file. $corpus = $this->buildCorpus( $show_blame, $show_color, $file_content, $needs_blame, $drequest, $path, $data); } if ($request->isAjax()) { return id(new AphrontAjaxResponse())->setContent($corpus); } require_celerity_resource('diffusion-source-css'); // Render the page. $view = $this->buildActionView($drequest); $action_list = $this->enrichActionView( $view, $drequest, $show_blame, $show_color); $properties = $this->buildPropertyView($drequest, $action_list); $object_box = id(new PHUIObjectBoxView()) ->setHeader($this->buildHeaderView($drequest)) ->addPropertyList($properties); $content = array(); $content[] = $object_box; $follow = $request->getStr('follow'); if ($follow) { $notice = new AphrontErrorView(); $notice->setSeverity(AphrontErrorView::SEVERITY_WARNING); $notice->setTitle(pht('Unable to Continue')); switch ($follow) { case 'first': $notice->appendChild( pht("Unable to continue tracing the history of this file because ". "this commit is the first commit in the repository.")); break; case 'created': $notice->appendChild( pht("Unable to continue tracing the history of this file because ". "this commit created the file.")); break; } $content[] = $notice; } $renamed = $request->getStr('renamed'); if ($renamed) { $notice = new AphrontErrorView(); $notice->setSeverity(AphrontErrorView::SEVERITY_NOTICE); $notice->setTitle(pht('File Renamed')); $notice->appendChild( pht("File history passes through a rename from '%s' to '%s'.", $drequest->getPath(), $renamed)); $content[] = $notice; } $content[] = $corpus; $content[] = $this->buildOpenRevisions(); $crumbs = $this->buildCrumbs( array( 'branch' => true, 'path' => true, 'view' => 'browse', )); $basename = basename($this->getDiffusionRequest()->getPath()); return $this->buildApplicationPage( array( $crumbs, $content, ), array( 'title' => $basename, )); } private function loadLintMessages() { $drequest = $this->getDiffusionRequest(); $branch = $drequest->loadBranch(); if (!$branch || !$branch->getLintCommit()) { return; } $this->lintCommit = $branch->getLintCommit(); $conn = id(new PhabricatorRepository())->establishConnection('r'); $where = ''; if ($drequest->getLint()) { $where = qsprintf( $conn, 'AND code = %s', $drequest->getLint()); } $this->lintMessages = queryfx_all( $conn, 'SELECT * FROM %T WHERE branchID = %d %Q AND path = %s', PhabricatorRepository::TABLE_LINTMESSAGE, $branch->getID(), $where, '/'.$drequest->getPath()); } private function buildCorpus( $show_blame, $show_color, DiffusionFileContent $file_content, $needs_blame, DiffusionRequest $drequest, $path, $data) { if (!$show_color) { $style = "border: none; width: 100%; height: 80em; font-family: monospace"; if (!$show_blame) { $corpus = phutil_tag( 'textarea', array( 'style' => $style, ), $file_content->getCorpus()); } else { $text_list = $file_content->getTextList(); $rev_list = $file_content->getRevList(); $blame_dict = $file_content->getBlameDict(); $rows = array(); foreach ($text_list as $k => $line) { $rev = $rev_list[$k]; $author = $blame_dict[$rev]['author']; $rows[] = sprintf("%-10s %-20s %s", substr($rev, 0, 7), $author, $line); } $corpus = phutil_tag( 'textarea', array( 'style' => $style, ), implode("\n", $rows)); } } else { require_celerity_resource('syntax-highlighting-css'); $text_list = $file_content->getTextList(); $rev_list = $file_content->getRevList(); $blame_dict = $file_content->getBlameDict(); $text_list = implode("\n", $text_list); $text_list = PhabricatorSyntaxHighlighter::highlightWithFilename( $path, $text_list); $text_list = explode("\n", $text_list); $rows = $this->buildDisplayRows($text_list, $rev_list, $blame_dict, $needs_blame, $drequest, $show_blame, $show_color); $corpus_table = javelin_tag( 'table', array( 'class' => "diffusion-source remarkup-code PhabricatorMonospaced", 'sigil' => 'phabricator-source', ), $rows); if ($this->getRequest()->isAjax()) { return $corpus_table; } $id = celerity_generate_unique_node_id(); $projects = $drequest->loadArcanistProjects(); $langs = array(); foreach ($projects as $project) { $ls = $project->getSymbolIndexLanguages(); if (!$ls) { continue; } $dep_projects = $project->getSymbolIndexProjects(); $dep_projects[] = $project->getPHID(); foreach ($ls as $lang) { if (!isset($langs[$lang])) { $langs[$lang] = array(); } $langs[$lang] += $dep_projects + array($project); } } $lang = last(explode('.', $drequest->getPath())); if (isset($langs[$lang])) { Javelin::initBehavior( 'repository-crossreference', array( 'container' => $id, 'lang' => $lang, 'projects' => $langs[$lang], )); } $corpus = phutil_tag( 'div', array( 'id' => $id, ), $corpus_table); Javelin::initBehavior('load-blame', array('id' => $id)); } $edit = $this->renderEditButton(); $file = $this->renderFileButton(); $header = id(new PHUIHeaderView()) ->setHeader(pht('File Contents')) ->addActionLink($edit) ->addActionLink($file); $corpus = id(new PHUIObjectBoxView()) ->setHeader($header) ->appendChild($corpus); return $corpus; } private function enrichActionView( PhabricatorActionListView $view, DiffusionRequest $drequest, $show_blame, $show_color) { $viewer = $this->getRequest()->getUser(); $base_uri = $this->getRequest()->getRequestURI(); $view->addAction( id(new PhabricatorActionView()) ->setName(pht('Show Last Change')) ->setHref( $drequest->generateURI( array( 'action' => 'change', ))) ->setIcon('fa-backward')); if ($show_blame) { $blame_text = pht('Disable Blame'); $blame_icon = 'fa-exclamation-circle lightgreytext'; $blame_value = 0; } else { $blame_text = pht('Enable Blame'); $blame_icon = 'fa-exclamation-circle'; $blame_value = 1; } $view->addAction( id(new PhabricatorActionView()) ->setName($blame_text) ->setHref($base_uri->alter('blame', $blame_value)) ->setIcon($blame_icon) ->setUser($viewer) - ->setRenderAsForm(true)); + ->setRenderAsForm($viewer->isLoggedIn())); if ($show_color) { $highlight_text = pht('Disable Highlighting'); $highlight_icon = 'fa-star-o grey'; $highlight_value = 0; } else { $highlight_text = pht('Enable Highlighting'); $highlight_icon = 'fa-star'; $highlight_value = 1; } $view->addAction( id(new PhabricatorActionView()) ->setName($highlight_text) ->setHref($base_uri->alter('color', $highlight_value)) ->setIcon($highlight_icon) ->setUser($viewer) - ->setRenderAsForm(true)); + ->setRenderAsForm($viewer->isLoggedIn())); $href = null; if ($this->getRequest()->getStr('lint') !== null) { $lint_text = pht('Hide %d Lint Message(s)', count($this->lintMessages)); $href = $base_uri->alter('lint', null); } else if ($this->lintCommit === null) { $lint_text = pht('Lint not Available'); } else { $lint_text = pht( 'Show %d Lint Message(s)', count($this->lintMessages)); $href = $this->getDiffusionRequest()->generateURI(array( 'action' => 'browse', 'commit' => $this->lintCommit, ))->alter('lint', ''); } $view->addAction( id(new PhabricatorActionView()) ->setName($lint_text) ->setHref($href) ->setIcon('fa-exclamation-triangle') ->setDisabled(!$href)); return $view; } private function renderEditButton() { $request = $this->getRequest(); $user = $request->getUser(); $drequest = $this->getDiffusionRequest(); $repository = $drequest->getRepository(); $path = $drequest->getPath(); $line = nonempty((int)$drequest->getLine(), 1); $callsign = $repository->getCallsign(); $editor_link = $user->loadEditorLink($path, $line, $callsign); $template = $user->loadEditorLink($path, '%l', $callsign); $icon_edit = id(new PHUIIconView()) ->setIconFont('fa-pencil'); $button = id(new PHUIButtonView()) ->setTag('a') ->setText(pht('Open in Editor')) ->setHref($editor_link) ->setIcon($icon_edit) ->setID('editor_link') ->setMetadata(array('link_template' => $template)) ->setDisabled(!$editor_link); return $button; } private function renderFileButton($file_uri = null) { $base_uri = $this->getRequest()->getRequestURI(); if ($file_uri) { $text = pht('Download Raw File'); $href = $file_uri; $icon = 'fa-download'; } else { $text = pht('View Raw File'); $href = $base_uri->alter('view', 'raw'); $icon = 'fa-file-text'; } $iconview = id(new PHUIIconView()) ->setIconFont($icon); $button = id(new PHUIButtonView()) ->setTag('a') ->setText($text) ->setHref($href) ->setIcon($iconview); return $button; } private function buildDisplayRows( array $text_list, array $rev_list, array $blame_dict, $needs_blame, DiffusionRequest $drequest, $show_blame, $show_color) { $handles = array(); if ($blame_dict) { $epoch_list = ipull(ifilter($blame_dict, 'epoch'), 'epoch'); $epoch_min = min($epoch_list); $epoch_max = max($epoch_list); $epoch_range = ($epoch_max - $epoch_min) + 1; $author_phids = ipull(ifilter($blame_dict, 'authorPHID'), 'authorPHID'); $handles = $this->loadViewerHandles($author_phids); } $line_arr = array(); $line_str = $drequest->getLine(); $ranges = explode(',', $line_str); foreach ($ranges as $range) { if (strpos($range, '-') !== false) { list($min, $max) = explode('-', $range, 2); $line_arr[] = array( 'min' => min($min, $max), 'max' => max($min, $max), ); } else if (strlen($range)) { $line_arr[] = array( 'min' => $range, 'max' => $range, ); } } $display = array(); $line_number = 1; $last_rev = null; $color = null; foreach ($text_list as $k => $line) { $display_line = array( 'epoch' => null, 'commit' => null, 'author' => null, 'target' => null, 'highlighted' => null, 'line' => $line_number, 'data' => $line, ); if ($show_blame) { // If the line's rev is same as the line above, show empty content // with same color; otherwise generate blame info. The newer a change // is, the more saturated the color. $rev = idx($rev_list, $k, $last_rev); if ($last_rev == $rev) { $display_line['color'] = $color; } else { $blame = $blame_dict[$rev]; if (!isset($blame['epoch'])) { $color = '#ffd'; // Render as warning. } else { $color_ratio = ($blame['epoch'] - $epoch_min) / $epoch_range; $color_value = 0xE6 * (1.0 - $color_ratio); $color = sprintf( '#%02x%02x%02x', $color_value, 0xF6, $color_value); } $display_line['epoch'] = idx($blame, 'epoch'); $display_line['color'] = $color; $display_line['commit'] = $rev; $author_phid = idx($blame, 'authorPHID'); if ($author_phid && $handles[$author_phid]) { $author_link = $handles[$author_phid]->renderLink(); } else { $author_link = $blame['author']; } $display_line['author'] = $author_link; $last_rev = $rev; } } if ($line_arr) { if ($line_number == $line_arr[0]['min']) { $display_line['target'] = true; } foreach ($line_arr as $range) { if ($line_number >= $range['min'] && $line_number <= $range['max']) { $display_line['highlighted'] = true; } } } $display[] = $display_line; ++$line_number; } $request = $this->getRequest(); $viewer = $request->getUser(); $commits = array_filter(ipull($display, 'commit')); if ($commits) { $commits = id(new DiffusionCommitQuery()) ->setViewer($viewer) ->withRepository($drequest->getRepository()) ->withIdentifiers($commits) ->execute(); $commits = mpull($commits, null, 'getCommitIdentifier'); } $revision_ids = id(new DifferentialRevision()) ->loadIDsByCommitPHIDs(mpull($commits, 'getPHID')); $revisions = array(); if ($revision_ids) { $revisions = id(new DifferentialRevisionQuery()) ->setViewer($viewer) ->withIDs($revision_ids) ->execute(); } $phids = array(); foreach ($commits as $commit) { if ($commit->getAuthorPHID()) { $phids[] = $commit->getAuthorPHID(); } } foreach ($revisions as $revision) { if ($revision->getAuthorPHID()) { $phids[] = $revision->getAuthorPHID(); } } $handles = $this->loadViewerHandles($phids); Javelin::initBehavior('phabricator-oncopy', array()); $engine = null; $inlines = array(); if ($this->getRequest()->getStr('lint') !== null && $this->lintMessages) { $engine = new PhabricatorMarkupEngine(); $engine->setViewer($viewer); foreach ($this->lintMessages as $message) { $inline = id(new PhabricatorAuditInlineComment()) ->setID($message['id']) ->setSyntheticAuthor( ArcanistLintSeverity::getStringForSeverity($message['severity']). ' '.$message['code'].' ('.$message['name'].')') ->setLineNumber($message['line']) ->setContent($message['description']); $inlines[$message['line']][] = $inline; $engine->addObject( $inline, PhabricatorInlineCommentInterface::MARKUP_FIELD_BODY); } $engine->process(); require_celerity_resource('differential-changeset-view-css'); } $rows = $this->renderInlines( idx($inlines, 0, array()), $show_blame, (bool)$this->coverage, $engine); foreach ($display as $line) { $line_href = $drequest->generateURI( array( 'action' => 'browse', 'line' => $line['line'], 'stable' => true, )); $blame = array(); $style = null; if (array_key_exists('color', $line)) { if ($line['color']) { $style = 'background: '.$line['color'].';'; } $before_link = null; $commit_link = null; $revision_link = null; if (idx($line, 'commit')) { $commit = $line['commit']; if (idx($commits, $commit)) { $tooltip = $this->renderCommitTooltip( $commits[$commit], $handles, $line['author']); } else { $tooltip = null; } Javelin::initBehavior('phabricator-tooltips', array()); require_celerity_resource('aphront-tooltip-css'); $commit_link = javelin_tag( 'a', array( 'href' => $drequest->generateURI( array( 'action' => 'commit', 'commit' => $line['commit'], )), 'sigil' => 'has-tooltip', 'meta' => array( 'tip' => $tooltip, 'align' => 'E', 'size' => 600, ), ), phutil_utf8_shorten($line['commit'], 9, '')); $revision_id = null; if (idx($commits, $commit)) { $revision_id = idx($revision_ids, $commits[$commit]->getPHID()); } if ($revision_id) { $revision = idx($revisions, $revision_id); if ($revision) { $tooltip = $this->renderRevisionTooltip($revision, $handles); $revision_link = javelin_tag( 'a', array( 'href' => '/D'.$revision->getID(), 'sigil' => 'has-tooltip', 'meta' => array( 'tip' => $tooltip, 'align' => 'E', 'size' => 600, ), ), 'D'.$revision->getID()); } } $uri = $line_href->alter('before', $commit); $before_link = javelin_tag( 'a', array( 'href' => $uri->setQueryParam('view', 'blame'), 'sigil' => 'has-tooltip', 'meta' => array( 'tip' => pht('Skip Past This Commit'), 'align' => 'E', 'size' => 300, ), ), "\xC2\xAB"); } $blame[] = phutil_tag( 'th', array( 'class' => 'diffusion-blame-link', ), $before_link); $object_links = array(); $object_links[] = $commit_link; if ($revision_link) { $object_links[] = phutil_tag('span', array(), '/'); $object_links[] = $revision_link; } $blame[] = phutil_tag( 'th', array( 'class' => 'diffusion-rev-link', ), $object_links); } $line_link = phutil_tag( 'a', array( 'href' => $line_href, 'style' => $style, ), $line['line']); $blame[] = javelin_tag( 'th', array( 'class' => 'diffusion-line-link', 'sigil' => 'phabricator-source-line', 'style' => $style, ), $line_link); Javelin::initBehavior('phabricator-line-linker'); if ($line['target']) { Javelin::initBehavior( 'diffusion-jump-to', array( 'target' => 'scroll_target', )); $anchor_text = phutil_tag( 'a', array( 'id' => 'scroll_target', ), ''); } else { $anchor_text = null; } $blame[] = phutil_tag( 'td', array( ), array( $anchor_text, // NOTE: See phabricator-oncopy behavior. "\xE2\x80\x8B", // TODO: [HTML] Not ideal. phutil_safe_html(str_replace("\t", ' ', $line['data'])), )); if ($this->coverage) { require_celerity_resource('differential-changeset-view-css'); $cov_index = $line['line'] - 1; if (isset($this->coverage[$cov_index])) { $cov_class = $this->coverage[$cov_index]; } else { $cov_class = 'N'; } $blame[] = phutil_tag( 'td', array( 'class' => 'cov cov-'.$cov_class, ), ''); } $rows[] = phutil_tag( 'tr', array( 'class' => ($line['highlighted'] ? 'phabricator-source-highlight' : null), ), $blame); $cur_inlines = $this->renderInlines( idx($inlines, $line['line'], array()), $show_blame, $this->coverage, $engine); foreach ($cur_inlines as $cur_inline) { $rows[] = $cur_inline; } } return $rows; } private function renderInlines( array $inlines, $needs_blame, $has_coverage, $engine) { $rows = array(); foreach ($inlines as $inline) { $inline_view = id(new DifferentialInlineCommentView()) ->setMarkupEngine($engine) ->setInlineComment($inline) ->render(); $row = array_fill(0, ($needs_blame ? 3 : 1), phutil_tag('th')); $row[] = phutil_tag('td', array(), $inline_view); if ($has_coverage) { $row[] = phutil_tag( 'td', array( 'class' => 'cov cov-I', )); } $rows[] = phutil_tag('tr', array('class' => 'inline'), $row); } return $rows; } private function loadFileForData($path, $data) { $file = PhabricatorFile::buildFromFileDataOrHash( $data, array( 'name' => basename($path), 'ttl' => time() + 60 * 60 * 24, 'viewPolicy' => PhabricatorPolicies::POLICY_NOONE, )); $unguarded = AphrontWriteGuard::beginScopedUnguardedWrites(); $file->attachToObject( $this->getRequest()->getUser(), $this->getDiffusionRequest()->getRepository()->getPHID()); unset($unguarded); return $file; } private function buildRawResponse($path, $data) { $file = $this->loadFileForData($path, $data); return id(new AphrontRedirectResponse())->setURI($file->getBestURI()); } private function buildImageCorpus($file_uri) { $properties = new PHUIPropertyListView(); $properties->addImageContent( phutil_tag( 'img', array( 'src' => $file_uri, ))); $file = $this->renderFileButton($file_uri); $header = id(new PHUIHeaderView()) ->setHeader(pht('Image')) ->addActionLink($file); return id(new PHUIObjectBoxView()) ->setHeader($header) ->addPropertyList($properties); } private function buildBinaryCorpus($file_uri, $data) { $size = new PhutilNumber(strlen($data)); $text = pht('This is a binary file. It is %s byte(s) in length.', $size); $text = id(new PHUIBoxView()) ->addPadding(PHUI::PADDING_LARGE) ->appendChild($text); $file = $this->renderFileButton($file_uri); $header = id(new PHUIHeaderView()) ->setHeader(pht('Details')) ->addActionLink($file); $box = id(new PHUIObjectBoxView()) ->setHeader($header) ->appendChild($text); return $box; } private function buildBeforeResponse($before) { $request = $this->getRequest(); $drequest = $this->getDiffusionRequest(); // NOTE: We need to get the grandparent so we can capture filename changes // in the parent. $parent = $this->loadParentCommitOf($before); $old_filename = null; $was_created = false; if ($parent) { $grandparent = $this->loadParentCommitOf($parent); if ($grandparent) { $rename_query = new DiffusionRenameHistoryQuery(); $rename_query->setRequest($drequest); $rename_query->setOldCommit($grandparent); $rename_query->setViewer($request->getUser()); $old_filename = $rename_query->loadOldFilename(); $was_created = $rename_query->getWasCreated(); } } $follow = null; if ($was_created) { // If the file was created in history, that means older commits won't // have it. Since we know it existed at 'before', it must have been // created then; jump there. $target_commit = $before; $follow = 'created'; } else if ($parent) { // If we found a parent, jump to it. This is the normal case. $target_commit = $parent; } else { // If there's no parent, this was probably created in the initial commit? // And the "was_created" check will fail because we can't identify the // grandparent. Keep the user at 'before'. $target_commit = $before; $follow = 'first'; } $path = $drequest->getPath(); $renamed = null; if ($old_filename !== null && $old_filename !== '/'.$path) { $renamed = $path; $path = $old_filename; } $line = null; // If there's a follow error, drop the line so the user sees the message. if (!$follow) { $line = $this->getBeforeLineNumber($target_commit); } $before_uri = $drequest->generateURI( array( 'action' => 'browse', 'commit' => $target_commit, 'line' => $line, 'path' => $path, )); $before_uri->setQueryParams($request->getRequestURI()->getQueryParams()); $before_uri = $before_uri->alter('before', null); $before_uri = $before_uri->alter('renamed', $renamed); $before_uri = $before_uri->alter('follow', $follow); return id(new AphrontRedirectResponse())->setURI($before_uri); } private function getBeforeLineNumber($target_commit) { $drequest = $this->getDiffusionRequest(); $line = $drequest->getLine(); if (!$line) { return null; } $raw_diff = $this->callConduitWithDiffusionRequest( 'diffusion.rawdiffquery', array( 'commit' => $drequest->getCommit(), 'path' => $drequest->getPath(), 'againstCommit' => $target_commit)); $old_line = 0; $new_line = 0; foreach (explode("\n", $raw_diff) as $text) { if ($text[0] == '-' || $text[0] == ' ') { $old_line++; } if ($text[0] == '+' || $text[0] == ' ') { $new_line++; } if ($new_line == $line) { return $old_line; } } // We didn't find the target line. return $line; } private function loadParentCommitOf($commit) { $drequest = $this->getDiffusionRequest(); $user = $this->getRequest()->getUser(); $before_req = DiffusionRequest::newFromDictionary( array( 'user' => $user, 'repository' => $drequest->getRepository(), 'commit' => $commit, )); $parents = DiffusionQuery::callConduitWithDiffusionRequest( $user, $before_req, 'diffusion.commitparentsquery', array( 'commit' => $commit, )); return head($parents); } private function renderRevisionTooltip( DifferentialRevision $revision, array $handles) { $viewer = $this->getRequest()->getUser(); $date = phabricator_date($revision->getDateModified(), $viewer); $id = $revision->getID(); $title = $revision->getTitle(); $header = "D{$id} {$title}"; $author = $handles[$revision->getAuthorPHID()]->getName(); return "{$header}\n{$date} \xC2\xB7 {$author}"; } private function renderCommitTooltip( PhabricatorRepositoryCommit $commit, array $handles, $author) { $viewer = $this->getRequest()->getUser(); $date = phabricator_date($commit->getEpoch(), $viewer); $summary = trim($commit->getSummary()); if ($commit->getAuthorPHID()) { $author = $handles[$commit->getAuthorPHID()]->getName(); } return "{$summary}\n{$date} \xC2\xB7 {$author}"; } } diff --git a/src/applications/diviner/application/PhabricatorApplicationDiviner.php b/src/applications/diviner/application/PhabricatorApplicationDiviner.php index 524786fe9..da2fd4bcb 100644 --- a/src/applications/diviner/application/PhabricatorApplicationDiviner.php +++ b/src/applications/diviner/application/PhabricatorApplicationDiviner.php @@ -1,48 +1,48 @@ array( '' => 'DivinerMainController', 'query/((?[^/]+)/)?' => 'DivinerAtomListController', 'find/' => 'DivinerFindController', ), '/book/(?P[^/]+)/' => 'DivinerBookController', '/book/'. '(?P[^/]+)/'. '(?P[^/]+)/'. '(?:(?P[^/]+)/)?'. '(?P[^/]+)/'. '(?:(?P\d+)/)?' => 'DivinerAtomController', ); } public function getApplicationGroup() { - return self::GROUP_COMMUNICATION; + return self::GROUP_UTILITIES; } public function getRemarkupRules() { return array( new DivinerRemarkupRuleSymbol(), ); } } diff --git a/src/applications/doorkeeper/application/PhabricatorApplicationDoorkeeper.php b/src/applications/doorkeeper/application/PhabricatorApplicationDoorkeeper.php index 24b605c1c..d1e028176 100644 --- a/src/applications/doorkeeper/application/PhabricatorApplicationDoorkeeper.php +++ b/src/applications/doorkeeper/application/PhabricatorApplicationDoorkeeper.php @@ -1,32 +1,32 @@ array( 'tags/' => 'DoorkeeperTagsController', ), ); } } diff --git a/src/applications/drydock/application/PhabricatorApplicationDrydock.php b/src/applications/drydock/application/PhabricatorApplicationDrydock.php index fc2e39c4b..06bd4d264 100644 --- a/src/applications/drydock/application/PhabricatorApplicationDrydock.php +++ b/src/applications/drydock/application/PhabricatorApplicationDrydock.php @@ -1,74 +1,78 @@ array( '' => 'DrydockConsoleController', 'blueprint/' => array( '(?:query/(?P[^/]+)/)?' => 'DrydockBlueprintListController', '(?P[1-9]\d*)/' => 'DrydockBlueprintViewController', 'create/' => 'DrydockBlueprintCreateController', 'edit/(?:(?P[1-9]\d*)/)?' => 'DrydockBlueprintEditController', ), 'resource/' => array( '(?:query/(?P[^/]+)/)?' => 'DrydockResourceListController', '(?P[1-9]\d*)/' => 'DrydockResourceViewController', '(?P[1-9]\d*)/close/' => 'DrydockResourceCloseController', ), 'lease/' => array( '(?:query/(?P[^/]+)/)?' => 'DrydockLeaseListController', '(?P[1-9]\d*)/' => 'DrydockLeaseViewController', '(?P[1-9]\d*)/release/' => 'DrydockLeaseReleaseController', ), 'log/' => array( '(?:query/(?P[^/]+)/)?' => 'DrydockLogListController', ), ), ); } protected function getCustomCapabilities() { return array( DrydockCapabilityDefaultView::CAPABILITY => array( ), DrydockCapabilityDefaultEdit::CAPABILITY => array( 'default' => PhabricatorPolicies::POLICY_ADMIN, ), DrydockCapabilityCreateBlueprints::CAPABILITY => array( 'default' => PhabricatorPolicies::POLICY_ADMIN, ), ); } } diff --git a/src/applications/fact/application/PhabricatorApplicationFact.php b/src/applications/fact/application/PhabricatorApplicationFact.php index 0436594b5..3a3439c7b 100644 --- a/src/applications/fact/application/PhabricatorApplicationFact.php +++ b/src/applications/fact/application/PhabricatorApplicationFact.php @@ -1,38 +1,38 @@ array( '' => 'PhabricatorFactHomeController', 'chart/' => 'PhabricatorFactChartController', ), ); } } diff --git a/src/applications/feed/application/PhabricatorApplicationFeed.php b/src/applications/feed/application/PhabricatorApplicationFeed.php index 21c8092e3..b30da72fe 100644 --- a/src/applications/feed/application/PhabricatorApplicationFeed.php +++ b/src/applications/feed/application/PhabricatorApplicationFeed.php @@ -1,35 +1,35 @@ array( 'public/' => 'PhabricatorFeedPublicStreamController', '(?P\d+)/' => 'PhabricatorFeedDetailController', '(?:query/(?P[^/]+)/)?' => 'PhabricatorFeedListController', ), ); } public function getApplicationGroup() { return self::GROUP_COMMUNICATION; } } diff --git a/src/applications/flag/application/PhabricatorApplicationFlags.php b/src/applications/flag/application/PhabricatorApplicationFlags.php index b7701340b..e9bee801d 100644 --- a/src/applications/flag/application/PhabricatorApplicationFlags.php +++ b/src/applications/flag/application/PhabricatorApplicationFlags.php @@ -1,60 +1,60 @@ setViewer($user) ->withOwnerPHIDs(array($user->getPHID())) ->execute(); $count = count($flags); $type = PhabricatorApplicationStatusView::TYPE_WARNING; $status[] = id(new PhabricatorApplicationStatusView()) ->setType($type) ->setText(pht('%d Flagged Object(s)', $count)) ->setCount($count); return $status; } public function getRoutes() { return array( '/flag/' => array( '(?:query/(?P[^/]+)/)?' => 'PhabricatorFlagListController', 'view/(?P[^/]+)/' => 'PhabricatorFlagListController', 'edit/(?P[^/]+)/' => 'PhabricatorFlagEditController', 'delete/(?P[1-9]\d*)/' => 'PhabricatorFlagDeleteController', ), ); } } diff --git a/src/applications/harbormaster/application/PhabricatorApplicationHarbormaster.php b/src/applications/harbormaster/application/PhabricatorApplicationHarbormaster.php index 4e3c61a64..699f58077 100644 --- a/src/applications/harbormaster/application/PhabricatorApplicationHarbormaster.php +++ b/src/applications/harbormaster/application/PhabricatorApplicationHarbormaster.php @@ -1,89 +1,89 @@ [1-9]\d*)' => 'HarbormasterBuildableViewController', '/harbormaster/' => array( '(?:query/(?P[^/]+)/)?' => 'HarbormasterBuildableListController', 'step/' => array( 'add/(?:(?P\d+)/)?' => 'HarbormasterStepAddController', 'new/(?P\d+)/(?P[^/]+)/' => 'HarbormasterStepEditController', 'edit/(?:(?P\d+)/)?' => 'HarbormasterStepEditController', 'delete/(?:(?P\d+)/)?' => 'HarbormasterStepDeleteController', ), 'buildable/' => array( '(?P\d+)/(?Pstop|resume|restart)/' => 'HarbormasterBuildableActionController', ), 'build/' => array( '(?P\d+)/' => 'HarbormasterBuildViewController', '(?Pstop|resume|restart)/(?P\d+)/(?:(?P[^/]+)/)?' => 'HarbormasterBuildActionController', ), 'plan/' => array( '(?:query/(?P[^/]+)/)?' => 'HarbormasterPlanListController', 'edit/(?:(?P\d+)/)?' => 'HarbormasterPlanEditController', 'order/(?:(?P\d+)/)?' => 'HarbormasterPlanOrderController', 'disable/(?P\d+)/' => 'HarbormasterPlanDisableController', 'run/(?P\d+)/' => 'HarbormasterPlanRunController', '(?P\d+)/' => 'HarbormasterPlanViewController', ), ), ); } public function getCustomCapabilities() { return array( HarbormasterCapabilityManagePlans::CAPABILITY => array( 'caption' => pht('Can create and manage build plans.'), 'default' => PhabricatorPolicies::POLICY_ADMIN, ), ); } } diff --git a/src/applications/home/controller/PhabricatorHomeMainController.php b/src/applications/home/controller/PhabricatorHomeMainController.php index adfad2863..693695e30 100644 --- a/src/applications/home/controller/PhabricatorHomeMainController.php +++ b/src/applications/home/controller/PhabricatorHomeMainController.php @@ -1,505 +1,410 @@ filter = idx($data, 'filter'); } public function processRequest() { $user = $this->getRequest()->getUser(); - - if ($this->filter == 'jump') { - return $this->buildJumpResponse(); - } $nav = $this->buildNav(); $dashboard = PhabricatorDashboardInstall::getDashboard( $user, $user->getPHID(), get_class($this->getCurrentApplication())); if ($dashboard) { $rendered_dashboard = id(new PhabricatorDashboardRenderingEngine()) ->setViewer($user) ->setDashboard($dashboard) ->renderDashboard(); $nav->appendChild($rendered_dashboard); } else { $project_query = new PhabricatorProjectQuery(); $project_query->setViewer($user); $project_query->withMemberPHIDs(array($user->getPHID())); $projects = $project_query->execute(); $nav = $this->buildMainResponse($nav, $projects); } $nav->appendChild(id(new PhabricatorGlobalUploadTargetView()) ->setUser($user)); return $this->buildApplicationPage( $nav, array( 'title' => 'Phabricator', 'device' => true, )); } private function buildMainResponse($nav, array $projects) { assert_instances_of($projects, 'PhabricatorProject'); $viewer = $this->getRequest()->getUser(); $has_maniphest = PhabricatorApplication::isClassInstalledForViewer( 'PhabricatorApplicationManiphest', $viewer); $has_audit = PhabricatorApplication::isClassInstalledForViewer( 'PhabricatorApplicationAudit', $viewer); $has_differential = PhabricatorApplication::isClassInstalledForViewer( 'PhabricatorApplicationDifferential', $viewer); if ($has_maniphest) { $unbreak_panel = $this->buildUnbreakNowPanel(); $triage_panel = $this->buildNeedsTriagePanel($projects); $tasks_panel = $this->buildTasksPanel(); } else { $unbreak_panel = null; $triage_panel = null; $tasks_panel = null; } if ($has_audit) { $audit_panel = $this->buildAuditPanel(); $commit_panel = $this->buildCommitPanel(); } else { $audit_panel = null; $commit_panel = null; } if (PhabricatorEnv::getEnvConfig('welcome.html') !== null) { $welcome_panel = $this->buildWelcomePanel(); } else { $welcome_panel = null; } - $jump_panel = $this->buildJumpPanel(); - if ($has_differential) { $revision_panel = $this->buildRevisionPanel(); } else { $revision_panel = null; } $content = array( - $jump_panel, $welcome_panel, $unbreak_panel, $triage_panel, $revision_panel, $tasks_panel, $audit_panel, $commit_panel, $this->minipanels, ); $nav->appendChild($content); return $nav; } - private function buildJumpResponse() { - $request = $this->getRequest(); - $jump = $request->getStr('jump'); - - $response = PhabricatorJumpNavHandler::getJumpResponse( - $request->getUser(), - $jump); - - if ($response) { - return $response; - } else if ($request->isFormPost()) { - $uri = new PhutilURI('/search/'); - $uri->setQueryParam('query', $jump); - $uri->setQueryParam('search:primary', 'true'); - - return id(new AphrontRedirectResponse())->setURI((string)$uri); - } else { - return id(new AphrontRedirectResponse())->setURI('/'); - } - } - private function buildUnbreakNowPanel() { $unbreak_now = PhabricatorEnv::getEnvConfig( 'maniphest.priorities.unbreak-now'); if (!$unbreak_now) { return null; } $user = $this->getRequest()->getUser(); $task_query = id(new ManiphestTaskQuery()) ->setViewer($user) ->withStatuses(ManiphestTaskStatus::getOpenStatusConstants()) ->withPriorities(array($unbreak_now)) ->setLimit(10); $tasks = $task_query->execute(); if (!$tasks) { return $this->renderMiniPanel( 'No "Unbreak Now!" Tasks', 'Nothing appears to be critically broken right now.'); } $href = urisprintf( '/maniphest/?statuses=%s&priorities=%s#R', implode(',', ManiphestTaskStatus::getOpenStatusConstants()), $unbreak_now); $title = pht('Unbreak Now!'); $panel = new AphrontPanelView(); $panel->setHeader($this->renderSectionHeader($title, $href)); $panel->appendChild($this->buildTaskListView($tasks)); $panel->setNoBackground(); return $panel; } private function buildNeedsTriagePanel(array $projects) { assert_instances_of($projects, 'PhabricatorProject'); $needs_triage = PhabricatorEnv::getEnvConfig( 'maniphest.priorities.needs-triage'); if (!$needs_triage) { return null; } $user = $this->getRequest()->getUser(); if (!$user->isLoggedIn()) { return null; } if ($projects) { $task_query = id(new ManiphestTaskQuery()) ->setViewer($user) ->withStatuses(ManiphestTaskStatus::getOpenStatusConstants()) ->withPriorities(array($needs_triage)) ->withAnyProjects(mpull($projects, 'getPHID')) ->setLimit(10); $tasks = $task_query->execute(); } else { $tasks = array(); } if (!$tasks) { return $this->renderMiniPanel( 'No "Needs Triage" Tasks', hsprintf( 'No tasks in projects you are a member of '. 'need triage.')); } $title = pht('Needs Triage'); $href = urisprintf( '/maniphest/?statuses=%s&priorities=%s&userProjects=%s#R', implode(',', ManiphestTaskStatus::getOpenStatusConstants()), $needs_triage, $user->getPHID()); $panel = new AphrontPanelView(); $panel->setHeader($this->renderSectionHeader($title, $href)); $panel->appendChild($this->buildTaskListView($tasks)); $panel->setNoBackground(); return $panel; } private function buildRevisionPanel() { $user = $this->getRequest()->getUser(); $user_phid = $user->getPHID(); $revision_query = id(new DifferentialRevisionQuery()) ->setViewer($user) ->withStatus(DifferentialRevisionQuery::STATUS_OPEN) ->withResponsibleUsers(array($user_phid)) ->needRelationships(true) ->needFlags(true) ->needDrafts(true); $revisions = $revision_query->execute(); list($blocking, $active, ) = DifferentialRevisionQuery::splitResponsible( $revisions, array($user_phid)); if (!$blocking && !$active) { return $this->renderMiniPanel( 'No Waiting Revisions', 'No revisions are waiting on you.'); } $title = pht('Revisions Waiting on You'); $href = '/differential'; $panel = new AphrontPanelView(); $panel->setHeader($this->renderSectionHeader($title, $href)); $revision_view = id(new DifferentialRevisionListView()) ->setHighlightAge(true) ->setRevisions(array_merge($blocking, $active)) ->setUser($user); $phids = array_merge( array($user_phid), $revision_view->getRequiredHandlePHIDs()); $handles = $this->loadViewerHandles($phids); $revision_view->setHandles($handles); $list_view = $revision_view->render(); $list_view->setFlush(true); $panel->appendChild($list_view); $panel->setNoBackground(); return $panel; } private function buildWelcomePanel() { $panel = new AphrontPanelView(); $panel->appendChild( phutil_safe_html( PhabricatorEnv::getEnvConfig('welcome.html'))); $panel->setNoBackground(); return $panel; } private function buildTasksPanel() { $user = $this->getRequest()->getUser(); $user_phid = $user->getPHID(); $task_query = id(new ManiphestTaskQuery()) ->setViewer($user) ->withStatuses(ManiphestTaskStatus::getOpenStatusConstants()) ->setGroupBy(ManiphestTaskQuery::GROUP_PRIORITY) ->withOwners(array($user_phid)) ->setLimit(10); $tasks = $task_query->execute(); if (!$tasks) { return $this->renderMiniPanel( 'No Assigned Tasks', 'You have no assigned tasks.'); } $title = pht('Assigned Tasks'); $href = '/maniphest'; $panel = new AphrontPanelView(); $panel->setHeader($this->renderSectionHeader($title, $href)); $panel->appendChild($this->buildTaskListView($tasks)); $panel->setNoBackground(); return $panel; } private function buildTaskListView(array $tasks) { assert_instances_of($tasks, 'ManiphestTask'); $user = $this->getRequest()->getUser(); $phids = array_merge( array_filter(mpull($tasks, 'getOwnerPHID')), array_mergev(mpull($tasks, 'getProjectPHIDs'))); $handles = $this->loadViewerHandles($phids); $view = new ManiphestTaskListView(); $view->setTasks($tasks); $view->setUser($user); $view->setHandles($handles); return $view; } - private function buildJumpPanel($query=null) { - $request = $this->getRequest(); - $user = $request->getUser(); - - $uniq_id = celerity_generate_unique_node_id(); - - Javelin::initBehavior( - 'phabricator-autofocus', - array( - 'id' => $uniq_id, - )); - - require_celerity_resource('phabricator-jump-nav'); - - $doc_href = PhabricatorEnv::getDocLink('Jump Nav User Guide'); - $doc_link = phutil_tag( - 'a', - array( - 'href' => $doc_href, - ), - 'Jump Nav User Guide'); - - $jump_input = phutil_tag( - 'input', - array( - 'type' => 'text', - 'class' => 'phabricator-jump-nav', - 'name' => 'jump', - 'id' => $uniq_id, - 'value' => $query, - )); - $jump_caption = phutil_tag( - 'p', - array( - 'class' => 'phabricator-jump-nav-caption', - ), - hsprintf( - 'Enter the name of an object like D123 to quickly jump to '. - 'it. See %s or type help.', - $doc_link)); - - $form = phabricator_form( - $user, - array( - 'action' => '/jump/', - 'method' => 'POST', - 'class' => 'phabricator-jump-nav-form', - ), - array( - $jump_input, - $jump_caption, - )); - - $panel = new AphrontPanelView(); - $panel->setNoBackground(); - // $panel->appendChild(); - - $list_filter = new AphrontListFilterView(); - $list_filter->appendChild($form); - - $container = phutil_tag('div', - array('class' => 'phabricator-jump-nav-container'), - $list_filter); - - return $container; - } - private function renderSectionHeader($title, $href) { $header = phutil_tag( 'a', array( 'href' => $href, ), $title); return $header; } private function renderMiniPanel($title, $body) { $panel = new AphrontMiniPanelView(); $panel->appendChild( phutil_tag( 'p', array( ), array( phutil_tag('strong', array(), $title.': '), $body ))); $this->minipanels[] = $panel; } public function buildAuditPanel() { $request = $this->getRequest(); $user = $request->getUser(); $phids = PhabricatorAuditCommentEditor::loadAuditPHIDsForUser($user); $query = id(new DiffusionCommitQuery()) ->setViewer($user) ->withAuditorPHIDs($phids) ->withAuditStatus(DiffusionCommitQuery::AUDIT_STATUS_OPEN) ->withAuditAwaitingUser($user) ->needAuditRequests(true) ->needCommitData(true) ->setLimit(10); $commits = $query->execute(); if (!$commits) { return $this->renderMinipanel( 'No Audits', 'No commits are waiting for you to audit them.'); } $view = id(new PhabricatorAuditListView()) ->setCommits($commits) ->setUser($user); $phids = $view->getRequiredHandlePHIDs(); $handles = $this->loadViewerHandles($phids); $view->setHandles($handles); $title = pht('Audits'); $href = '/audit/'; $panel = new AphrontPanelView(); $panel->setHeader($this->renderSectionHeader($title, $href)); $panel->appendChild($view); $panel->setNoBackground(); return $panel; } public function buildCommitPanel() { $request = $this->getRequest(); $user = $request->getUser(); $phids = array($user->getPHID()); $query = id(new DiffusionCommitQuery()) ->setViewer($user) ->withAuthorPHIDs($phids) ->withAuditStatus(DiffusionCommitQuery::AUDIT_STATUS_CONCERN) ->needCommitData(true) ->needAuditRequests(true) ->setLimit(10); $commits = $query->execute(); if (!$commits) { return $this->renderMinipanel( 'No Problem Commits', 'No one has raised concerns with your commits.'); } $view = id(new PhabricatorAuditListView()) ->setCommits($commits) ->setUser($user); $phids = $view->getRequiredHandlePHIDs(); $handles = $this->loadViewerHandles($phids); $view->setHandles($handles); $title = pht('Problem Commits'); $href = '/audit/'; $panel = new AphrontPanelView(); $panel->setHeader($this->renderSectionHeader($title, $href)); $panel->appendChild($view); $panel->setNoBackground(); return $panel; } } diff --git a/src/applications/legalpad/application/PhabricatorApplicationLegalpad.php b/src/applications/legalpad/application/PhabricatorApplicationLegalpad.php index 7b9dd4b0b..999e20b64 100644 --- a/src/applications/legalpad/application/PhabricatorApplicationLegalpad.php +++ b/src/applications/legalpad/application/PhabricatorApplicationLegalpad.php @@ -1,66 +1,66 @@ \d+)' => 'LegalpadDocumentSignController', '/legalpad/' => array( '' => 'LegalpadDocumentListController', '(query/(?P[^/]+)/)?' => 'LegalpadDocumentListController', 'create/' => 'LegalpadDocumentEditController', 'edit/(?P\d+)/' => 'LegalpadDocumentEditController', 'comment/(?P\d+)/' => 'LegalpadDocumentCommentController', 'view/(?P\d+)/' => 'LegalpadDocumentViewController', 'verify/(?P[^/]+)/' => 'LegalpadDocumentSignatureVerificationController', 'signatures/(?P\d+)/' => 'LegalpadDocumentSignatureListController', 'document/' => array( 'preview/' => 'PhabricatorMarkupPreviewController'), )); } protected function getCustomCapabilities() { return array( LegalpadCapabilityDefaultView::CAPABILITY => array( ), LegalpadCapabilityDefaultEdit::CAPABILITY => array( ), ); } } diff --git a/src/applications/maniphest/config/PhabricatorManiphestConfigOptions.php b/src/applications/maniphest/config/PhabricatorManiphestConfigOptions.php index aaaa4e79f..6b03cae03 100644 --- a/src/applications/maniphest/config/PhabricatorManiphestConfigOptions.php +++ b/src/applications/maniphest/config/PhabricatorManiphestConfigOptions.php @@ -1,351 +1,351 @@ array( 'name' => pht('Unbreak Now!'), 'short' => pht('Unbreak!'), 'color' => 'indigo', ), 90 => array( 'name' => pht('Needs Triage'), 'short' => pht('Triage'), 'color' => 'violet', ), 80 => array( 'name' => pht('High'), 'short' => pht('High'), 'color' => 'red', ), 50 => array( 'name' => pht('Normal'), 'short' => pht('Normal'), 'color' => 'orange', ), 25 => array( 'name' => pht('Low'), 'short' => pht('Low'), 'color' => 'yellow', ), 0 => array( 'name' => pht('Wishlist'), 'short' => pht('Wish'), 'color' => 'sky', ), ); $status_type = 'custom:ManiphestStatusConfigOptionType'; $status_defaults = array( 'open' => array( 'name' => pht('Open'), 'special' => ManiphestTaskStatus::SPECIAL_DEFAULT, ), 'resolved' => array( 'name' => pht('Resolved'), 'name.full' => pht('Closed, Resolved'), 'closed' => true, 'special' => ManiphestTaskStatus::SPECIAL_CLOSED, 'prefixes' => array( 'closed', 'closes', 'close', 'fix', 'fixes', 'fixed', 'resolve', 'resolves', 'resolved', ), 'suffixes' => array( 'as resolved', 'as fixed', ), ), 'wontfix' => array( 'name' => pht('Wontfix'), 'name.full' => pht('Closed, Wontfix'), 'closed' => true, 'prefixes' => array( 'wontfix', 'wontfixes', 'wontfixed', ), 'suffixes' => array( 'as wontfix', ), ), 'invalid' => array( 'name' => pht('Invalid'), 'name.full' => pht('Closed, Invalid'), 'closed' => true, 'prefixes' => array( 'invalidate', 'invalidates', 'invalidated', ), 'suffixes' => array( 'as invalid', ), ), 'duplicate' => array( 'name' => pht('Duplicate'), 'name.full' => pht('Closed, Duplicate'), 'transaction.icon' => 'fa-times', 'special' => ManiphestTaskStatus::SPECIAL_DUPLICATE, 'closed' => true, ), 'spite' => array( 'name' => pht('Spite'), 'name.full' => pht('Closed, Spite'), 'name.action' => pht('Spited'), 'transaction.icon' => 'fa-thumbs-o-down', 'silly' => true, 'closed' => true, 'prefixes' => array( 'spite', 'spites', 'spited', ), 'suffixes' => array( 'out of spite', 'as spite', ), ), ); $status_description = $this->deformat(pht(<<.// Allows you to specify a list of text prefixes which will trigger a task transition into this status when mentioned in a commit message. For example, providing "closes" here will allow users to move tasks to this status by writing `Closes T123` in commit messages. - `suffixes` //Optional list.// Allows you to specify a list of text suffixes which will trigger a task transition into this status when mentioned in a commit message, after a valid prefix. For example, providing "as invalid" here will allow users to move tasks to this status by writing `Closes T123 as invalid`, even if another status is selected by the "Closes" prefix. Examining the default configuration and examples below will probably be helpful in understanding these options. EOTEXT )); $status_example = array( 'open' => array( 'name' => 'Open', 'special' => 'default', ), 'closed' => array( 'name' => 'Closed', 'special' => 'closed', 'closed' => true, ), 'duplicate' => array( 'name' => 'Duplicate', 'special' => 'duplicate', 'closed' => true, ), ); $json = new PhutilJSON(); $status_example = $json->encodeFormatted($status_example); // This is intentionally blank for now, until we can move more Maniphest // logic to custom fields. $default_fields = array(); foreach ($default_fields as $key => $enabled) { $default_fields[$key] = array( 'disabled' => !$enabled, ); } $custom_field_type = 'custom:PhabricatorCustomFieldConfigOptionType'; return array( $this->newOption('maniphest.custom-field-definitions', 'wild', array()) ->setSummary(pht("Custom Maniphest fields.")) ->setDescription( pht( - "Array of custom fields for Maniphest tasks. For details on ". - "adding custom fields to Maniphest, see 'Maniphest User Guide: ". - "Adding Custom Fields'.")) + 'Array of custom fields for Maniphest tasks. For details on '. + 'adding custom fields to Maniphest, see "Configuring Custom '. + 'Fields" in the documentation.')) ->addExample( '{"mycompany:estimated-hours": {"name": "Estimated Hours", '. '"type": "int", "caption": "Estimated number of hours this will '. 'take."}}', pht('Valid Setting')), $this->newOption('maniphest.fields', $custom_field_type, $default_fields) ->setCustomData(id(new ManiphestTask())->getCustomFieldBaseClass()) ->setDescription(pht("Select and reorder task fields.")), $this->newOption('maniphest.priorities', 'wild', $priority_defaults) ->setSummary(pht("Configure Maniphest priority names.")) ->setDescription( pht( 'Allows you to edit or override the default priorities available '. 'in Maniphest, like "High", "Normal" and "Low". The configuration '. 'should contain a map of priority constants to priority '. 'specifications (see defaults below for examples).'. "\n\n". 'The keys you can define for a priority are:'. "\n\n". ' - `name` Name of the priority.'."\n". ' - `short` Alternate shorter name, used in UIs where there is '. ' not much space available.'."\n". ' - `color` A color for this priority, like "red" or "blue".'. "\n\n". 'You can choose which priority is the default for newly created '. 'tasks with `maniphest.default-priority`.')), $this->newOption('maniphest.statuses', $status_type, $status_defaults) ->setSummary(pht('Configure Maniphest task statuses.')) ->setDescription($status_description) ->addExample($status_example, pht('Minimal Valid Config')), $this->newOption('maniphest.default-priority', 'int', 90) ->setSummary(pht("Default task priority for create flows.")) ->setDescription( pht( "What should the default task priority be in create flows? See ". "the constants in @{class:ManiphestTaskPriority} for valid ". "values. Defaults to 'needs triage'.")), $this->newOption( 'metamta.maniphest.reply-handler-domain', 'string', null) ->setSummary(pht('Enable replying to tasks via email.')) ->setDescription( pht( 'You can configure a reply handler domain so that email sent from '. 'Maniphest will have a special "Reply To" address like '. '"T123+82+af19f@example.com" that allows recipients to reply by '. 'email and interact with tasks. For instructions on configurating '. 'reply handlers, see the article "Configuring Inbound Email" in '. 'the Phabricator documentation. By default, this is set to `null` '. 'and Phabricator will use a generic `noreply@` address or the '. 'address of the acting user instead of a special reply handler '. 'address (see `metamta.default-address`). If you set a domain '. 'here, Phabricator will begin generating private reply handler '. 'addresses. See also `metamta.maniphest.reply-handler` to further '. 'configure behavior. This key should be set to the domain part '. 'after the @, like "example.com".')), $this->newOption( 'metamta.maniphest.reply-handler', 'class', 'ManiphestReplyHandler') ->setBaseClass('PhabricatorMailReplyHandler') ->setDescription(pht('Override reply handler class.')), $this->newOption( 'metamta.maniphest.subject-prefix', 'string', '[Maniphest]') ->setDescription(pht('Subject prefix for Maniphest mail.')), $this->newOption( 'metamta.maniphest.public-create-email', 'string', null) ->setSummary(pht('Allow filing bugs via email.')) ->setDescription( pht( 'You can configure an email address like '. '"bugs@phabricator.example.com" which will automatically create '. 'Maniphest tasks when users send email to it. This relies on the '. '"From" address to authenticate users, so it is is not completely '. 'secure. To set this up, enter a complete email address like '. '"bugs@phabricator.example.com" and then configure mail to that '. 'address so it routed to Phabricator (if you\'ve already '. 'configured reply handlers, you\'re probably already done). See '. '"Configuring Inbound Email" in the documentation for more '. 'information.')), $this->newOption( 'metamta.maniphest.default-public-author', 'string', null) ->setSummary(pht('Username anonymous bugs are filed under.')) ->setDescription( pht( 'If you enable `metamta.maniphest.public-create-email` and create '. 'an email address like "bugs@phabricator.example.com", it will '. 'default to rejecting mail which doesn\'t come from a known user. '. 'However, you might want to let anyone send email to this '. 'address; to do so, set a default author here (a Phabricator '. 'username). A typical use of this might be to create a "System '. 'Agent" user called "bugs" and use that name here. If you specify '. 'a valid username, mail will always be accepted and used to '. 'create a task, even if the sender is not a system user. The '. 'original email address will be stored in an `From Email` field '. 'on the task.')), $this->newOption( 'maniphest.priorities.unbreak-now', 'int', 100) ->setSummary(pht('Priority used to populate "Unbreak Now" on home.')) ->setDescription( pht( 'Temporary setting. If set, this priority is used to populate the '. '"Unbreak Now" panel on the home page. You should adjust this if '. 'you adjust priorities using `maniphest.priorities`.')), $this->newOption( 'maniphest.priorities.needs-triage', 'int', 90) ->setSummary(pht('Priority used to populate "Needs Triage" on home.')) ->setDescription( pht( 'Temporary setting. If set, this priority is used to populate the '. '"Needs Triage" panel on the home page. You should adjust this if '. 'you adjust priorities using `maniphest.priorities`.')), ); } } diff --git a/src/applications/meta/application/PhabricatorApplicationApplications.php b/src/applications/meta/application/PhabricatorApplicationApplications.php index 66c611406..847e0f2c2 100644 --- a/src/applications/meta/application/PhabricatorApplicationApplications.php +++ b/src/applications/meta/application/PhabricatorApplicationApplications.php @@ -1,45 +1,44 @@ array( '(?:query/(?P[^/]+)/)?' => 'PhabricatorApplicationsListController', 'view/(?P\w+)/' => 'PhabricatorApplicationDetailViewController', 'edit/(?P\w+)/' => 'PhabricatorApplicationEditController', '(?P\w+)/(?Pinstall|uninstall)/' => 'PhabricatorApplicationUninstallController', ), - ); } } diff --git a/src/applications/meta/controller/PhabricatorApplicationDetailViewController.php b/src/applications/meta/controller/PhabricatorApplicationDetailViewController.php index b2cc71318..1dacee676 100644 --- a/src/applications/meta/controller/PhabricatorApplicationDetailViewController.php +++ b/src/applications/meta/controller/PhabricatorApplicationDetailViewController.php @@ -1,153 +1,186 @@ application = $data['application']; } public function processRequest() { $request = $this->getRequest(); $user = $request->getUser(); $selected = id(new PhabricatorApplicationQuery()) ->setViewer($user) ->withClasses(array($this->application)) ->executeOne(); if (!$selected) { return new Aphront404Response(); } $title = $selected->getName(); $crumbs = $this->buildApplicationCrumbs(); $crumbs->addTextCrumb($selected->getName()); $header = id(new PHUIHeaderView()) ->setHeader($title) ->setUser($user) ->setPolicyObject($selected); if ($selected->isInstalled()) { $header->setStatus('fa-check', 'bluegrey', pht('Installed')); } else { $header->setStatus('fa-ban', 'dark', pht('Uninstalled')); } $actions = $this->buildActionView($user, $selected); $properties = $this->buildPropertyView($selected, $actions); $object_box = id(new PHUIObjectBoxView()) ->setHeader($header) ->addPropertyList($properties); return $this->buildApplicationPage( array( $crumbs, $object_box, ), array( 'title' => $title, 'device' => true, )); } private function buildPropertyView( PhabricatorApplication $application, PhabricatorActionListView $actions) { $viewer = $this->getRequest()->getUser(); - $properties = id(new PHUIPropertyListView()) - ->addProperty(pht('Description'), $application->getShortDescription()); + $properties = id(new PHUIPropertyListView()); $properties->setActionList($actions); + $properties->addProperty( + pht('Description'), + $application->getShortDescription()); + + if ($application->getFlavorText()) { + $properties->addProperty( + null, + phutil_tag('em', array(), $application->getFlavorText())); + } + if ($application->isBeta()) { $properties->addProperty( pht('Release'), pht('Beta')); } + $overview = $application->getOverview(); + if ($overview) { + $properties->addSectionHeader( + pht('Overview'), + PHUIPropertyListView::ICON_SUMMARY); + $properties->addTextContent( + PhabricatorMarkupEngine::renderOneObject( + id(new PhabricatorMarkupOneOff())->setContent($overview), + 'default', + $viewer)); + } + $descriptions = PhabricatorPolicyQuery::renderPolicyDescriptions( $viewer, $application); $properties->addSectionHeader(pht('Policies')); foreach ($application->getCapabilities() as $capability) { $properties->addProperty( $application->getCapabilityLabel($capability), idx($descriptions, $capability)); } return $properties; } private function buildActionView( PhabricatorUser $user, PhabricatorApplication $selected) { $view = id(new PhabricatorActionListView()) ->setUser($user) ->setObjectURI($this->getRequest()->getRequestURI()); + if ($selected->getHelpURI()) { + $view->addAction( + id(new PhabricatorActionView()) + ->setName(pht('Help / Documentation')) + ->setIcon('fa-life-ring') + ->setHref($selected->getHelpURI())); + } + $can_edit = PhabricatorPolicyFilter::hasCapability( $user, $selected, PhabricatorPolicyCapability::CAN_EDIT); $edit_uri = $this->getApplicationURI('edit/'.get_class($selected).'/'); $view->addAction( id(new PhabricatorActionView()) ->setName(pht('Edit Policies')) ->setIcon('fa-pencil') ->setDisabled(!$can_edit) ->setWorkflow(!$can_edit) ->setHref($edit_uri)); if ($selected->canUninstall()) { if ($selected->isInstalled()) { $view->addAction( id(new PhabricatorActionView()) ->setName(pht('Uninstall')) ->setIcon('fa-times') ->setDisabled(!$can_edit) ->setWorkflow(true) ->setHref( $this->getApplicationURI(get_class($selected).'/uninstall/'))); } else { $action = id(new PhabricatorActionView()) ->setName(pht('Install')) ->setIcon('fa-plus') ->setDisabled(!$can_edit) ->setWorkflow(true) ->setHref( $this->getApplicationURI(get_class($selected).'/install/')); $beta_enabled = PhabricatorEnv::getEnvConfig( 'phabricator.show-beta-applications'); if ($selected->isBeta() && !$beta_enabled) { $action->setDisabled(true); } $view->addAction($action); } } else { $view->addAction( id(new PhabricatorActionView()) ->setName(pht('Uninstall')) ->setIcon('fa-times') ->setWorkflow(true) ->setDisabled(true) ->setHref( $this->getApplicationURI(get_class($selected).'/uninstall/'))); } return $view; } } diff --git a/src/applications/meta/controller/PhabricatorApplicationEditController.php b/src/applications/meta/controller/PhabricatorApplicationEditController.php index aa4952205..5d1ac1e18 100644 --- a/src/applications/meta/controller/PhabricatorApplicationEditController.php +++ b/src/applications/meta/controller/PhabricatorApplicationEditController.php @@ -1,170 +1,174 @@ application = $data['application']; } public function processRequest() { $request = $this->getRequest(); $user = $request->getUser(); $application = id(new PhabricatorApplicationQuery()) ->setViewer($user) ->withClasses(array($this->application)) ->requireCapabilities( array( PhabricatorPolicyCapability::CAN_VIEW, PhabricatorPolicyCapability::CAN_EDIT, )) ->executeOne(); if (!$application) { return new Aphront404Response(); } $title = $application->getName(); $view_uri = $this->getApplicationURI('view/'.get_class($application).'/'); $policies = id(new PhabricatorPolicyQuery()) ->setViewer($user) ->setObject($application) ->execute(); if ($request->isFormPost()) { $result = array(); foreach ($application->getCapabilities() as $capability) { $old = $application->getPolicy($capability); $new = $request->getStr('policy:'.$capability); if ($old == $new) { // No change to the setting. continue; } if (empty($policies[$new])) { // Not a standard policy, check for a custom policy. $policy = id(new PhabricatorPolicyQuery()) ->setViewer($user) ->withPHIDs(array($new)) ->executeOne(); if (!$policy) { // Not a custom policy either. Can't set the policy to something // invalid, so skip this. continue; } } if ($new == PhabricatorPolicies::POLICY_PUBLIC) { $capobj = PhabricatorPolicyCapability::getCapabilityByKey( $capability); if (!$capobj || !$capobj->shouldAllowPublicPolicySetting()) { // Can't set non-public policies to public. continue; } } $result[$capability] = $new; } if ($result) { $key = 'phabricator.application-settings'; $config_entry = PhabricatorConfigEntry::loadConfigEntry($key); $value = $config_entry->getValue(); $phid = $application->getPHID(); if (empty($value[$phid])) { $value[$application->getPHID()] = array(); } if (empty($value[$phid]['policy'])) { $value[$phid]['policy'] = array(); } $value[$phid]['policy'] = $result + $value[$phid]['policy']; // Don't allow users to make policy edits which would lock them out of // applications, since they would be unable to undo those actions. PhabricatorEnv::overrideConfig($key, $value); PhabricatorPolicyFilter::mustRetainCapability( $user, $application, PhabricatorPolicyCapability::CAN_VIEW); PhabricatorPolicyFilter::mustRetainCapability( $user, $application, PhabricatorPolicyCapability::CAN_EDIT); PhabricatorConfigEditor::storeNewValue( $config_entry, $value, $this->getRequest()); } return id(new AphrontRedirectResponse())->setURI($view_uri); } $descriptions = PhabricatorPolicyQuery::renderPolicyDescriptions( $user, $application); $form = id(new AphrontFormView()) ->setUser($user); foreach ($application->getCapabilities() as $capability) { $label = $application->getCapabilityLabel($capability); $can_edit = $application->isCapabilityEditable($capability); $caption = $application->getCapabilityCaption($capability); if (!$can_edit) { $form->appendChild( id(new AphrontFormStaticControl()) ->setLabel($label) ->setValue(idx($descriptions, $capability)) ->setCaption($caption)); } else { $form->appendChild( id(new AphrontFormPolicyControl()) ->setUser($user) ->setCapability($capability) ->setPolicyObject($application) ->setPolicies($policies) ->setLabel($label) ->setName('policy:'.$capability) ->setCaption($caption)); } } $form->appendChild( id(new AphrontFormSubmitControl()) ->setValue(pht('Save Policies')) ->addCancelButton($view_uri)); $crumbs = $this->buildApplicationCrumbs(); $crumbs->addTextCrumb($application->getName(), $view_uri); $crumbs->addTextCrumb(pht('Edit Policies')); $header = id(new PHUIHeaderView()) ->setHeader(pht('Edit Policies: %s', $application->getName())); $object_box = id(new PHUIObjectBoxView()) ->setHeader($header) ->setForm($form); return $this->buildApplicationPage( array( $crumbs, $object_box, ), array( 'title' => $title, 'device' => true, )); } } diff --git a/src/applications/meta/controller/PhabricatorApplicationUninstallController.php b/src/applications/meta/controller/PhabricatorApplicationUninstallController.php index 8445526a0..052ef3022 100644 --- a/src/applications/meta/controller/PhabricatorApplicationUninstallController.php +++ b/src/applications/meta/controller/PhabricatorApplicationUninstallController.php @@ -1,92 +1,96 @@ application = $data['application']; $this->action = $data['action']; } public function processRequest() { $request = $this->getRequest(); $user = $request->getUser(); $selected = PhabricatorApplication::getByClass($this->application); if (!$selected) { return new Aphront404Response(); } $view_uri = $this->getApplicationURI('view/'.$this->application); $beta_enabled = PhabricatorEnv::getEnvConfig( 'phabricator.show-beta-applications'); $dialog = id(new AphrontDialogView()) ->setUser($user) ->addCancelButton($view_uri); if ($selected->isBeta() && !$beta_enabled) { $dialog ->setTitle(pht('Beta Applications Not Enabled')) ->appendChild( pht( 'To manage beta applications, enable them by setting %s in your '. 'Phabricator configuration.', phutil_tag('tt', array(), 'phabricator.show-beta-applications'))); return id(new AphrontDialogResponse())->setDialog($dialog); } if ($request->isDialogFormPost()) { $this->manageApplication(); return id(new AphrontRedirectResponse())->setURI($view_uri); } if ($this->action == 'install') { if ($selected->canUninstall()) { $dialog->setTitle('Confirmation') ->appendChild( 'Install '. $selected->getName(). ' application?') ->addSubmitButton('Install'); } else { $dialog->setTitle('Information') ->appendChild('You cannot install an installed application.'); } } else { if ($selected->canUninstall()) { $dialog->setTitle('Confirmation') ->appendChild( 'Really Uninstall '. $selected->getName(). ' application?') ->addSubmitButton('Uninstall'); } else { $dialog->setTitle('Information') ->appendChild( 'This application cannot be uninstalled, because it is required for Phabricator to work.'); } } return id(new AphrontDialogResponse())->setDialog($dialog); } public function manageApplication() { $key = 'phabricator.uninstalled-applications'; $config_entry = PhabricatorConfigEntry::loadConfigEntry($key); $list = $config_entry->getValue(); $uninstalled = PhabricatorEnv::getEnvConfig($key); if (isset($uninstalled[$this->application])) { unset($list[$this->application]); } else { $list[$this->application] = true; } PhabricatorConfigEditor::storeNewValue( $config_entry, $list, $this->getRequest()); } } diff --git a/src/applications/meta/controller/PhabricatorApplicationsController.php b/src/applications/meta/controller/PhabricatorApplicationsController.php index 69f5bd03d..e5b035bab 100644 --- a/src/applications/meta/controller/PhabricatorApplicationsController.php +++ b/src/applications/meta/controller/PhabricatorApplicationsController.php @@ -1,28 +1,24 @@ getRequest()->getUser(); $nav = new AphrontSideNavFilterView(); $nav->setBaseURI(new PhutilURI($this->getApplicationURI())); id(new PhabricatorAppSearchEngine()) ->setViewer($user) ->addNavigationItems($nav->getMenu()); $nav->selectFilter(null); return $nav; } public function buildApplicationMenu() { return $this->buildSideNavView(true)->getMenu(); } } diff --git a/src/applications/meta/controller/PhabricatorApplicationsListController.php b/src/applications/meta/controller/PhabricatorApplicationsListController.php index 5815f7cff..9bcb84576 100644 --- a/src/applications/meta/controller/PhabricatorApplicationsListController.php +++ b/src/applications/meta/controller/PhabricatorApplicationsListController.php @@ -1,22 +1,26 @@ queryKey = idx($data, 'queryKey'); } public function processRequest() { $request = $this->getRequest(); $controller = id(new PhabricatorApplicationSearchController($request)) ->setQueryKey($this->queryKey) ->setSearchEngine(new PhabricatorAppSearchEngine()) ->setNavigation($this->buildSideNavView()); return $this->delegateToController($controller); } } diff --git a/src/applications/meta/query/PhabricatorAppSearchEngine.php b/src/applications/meta/query/PhabricatorAppSearchEngine.php index a83a84e56..4f26a8085 100644 --- a/src/applications/meta/query/PhabricatorAppSearchEngine.php +++ b/src/applications/meta/query/PhabricatorAppSearchEngine.php @@ -1,161 +1,245 @@ setParameter('name', $request->getStr('name')); $saved->setParameter( 'installed', $this->readBoolFromRequest($request, 'installed')); $saved->setParameter( 'beta', $this->readBoolFromRequest($request, 'beta')); $saved->setParameter( 'firstParty', $this->readBoolFromRequest($request, 'firstParty')); + $saved->setParameter( + 'launchable', + $this->readBoolFromRequest($request, 'launchable')); return $saved; } public function buildQueryFromSavedQuery(PhabricatorSavedQuery $saved) { $query = id(new PhabricatorApplicationQuery()) ->setOrder(PhabricatorApplicationQuery::ORDER_NAME) ->withUnlisted(false); $name = $saved->getParameter('name'); if (strlen($name)) { $query->withNameContains($name); } $installed = $saved->getParameter('installed'); if ($installed !== null) { $query->withInstalled($installed); } $beta = $saved->getParameter('beta'); if ($beta !== null) { $query->withBeta($beta); } $first_party = $saved->getParameter('firstParty'); if ($first_party !== null) { $query->withFirstParty($first_party); } + $launchable = $saved->getParameter('launchable'); + if ($launchable !== null) { + $query->withLaunchable($launchable); + } + return $query; } public function buildSearchForm( AphrontFormView $form, PhabricatorSavedQuery $saved) { $form ->appendChild( id(new AphrontFormTextControl()) ->setLabel(pht('Name Contains')) ->setName('name') ->setValue($saved->getParameter('name'))) ->appendChild( id(new AphrontFormSelectControl()) ->setLabel(pht('Installed')) ->setName('installed') ->setValue($this->getBoolFromQuery($saved, 'installed')) ->setOptions( array( '' => pht('Show All Applications'), 'true' => pht('Show Installed Applications'), 'false' => pht('Show Uninstalled Applications'), ))) ->appendChild( id(new AphrontFormSelectControl()) ->setLabel(pht('Beta')) ->setName('beta') ->setValue($this->getBoolFromQuery($saved, 'beta')) ->setOptions( array( '' => pht('Show All Applications'), 'true' => pht('Show Beta Applications'), 'false' => pht('Show Released Applications'), ))) ->appendChild( id(new AphrontFormSelectControl()) ->setLabel(pht('Provenance')) ->setName('firstParty') ->setValue($this->getBoolFromQuery($saved, 'firstParty')) ->setOptions( array( '' => pht('Show All Applications'), 'true' => pht('Show First-Party Applications'), 'false' => pht('Show Third-Party Applications'), + ))) + ->appendChild( + id(new AphrontFormSelectControl()) + ->setLabel(pht('Launchable')) + ->setName('launchable') + ->setValue($this->getBoolFromQuery($saved, 'launchable')) + ->setOptions( + array( + '' => pht('Show All Applications'), + 'true' => pht('Show Launchable Applications'), + 'false' => pht('Show Non-Launchable Applications'), ))); } protected function getURI($path) { return '/applications/'.$path; } public function getBuiltinQueryNames() { $names = array( + 'launcher' => pht('Launcher'), 'all' => pht('All Applications'), ); return $names; } public function buildSavedQueryFromBuiltin($query_key) { $query = $this->newSavedQuery(); $query->setQueryKey($query_key); switch ($query_key) { + case 'launcher': + return $query + ->setParameter('installed', true) + ->setParameter('launchable', true); case 'all': return $query; } return parent::buildSavedQueryFromBuiltin($query_key); } protected function renderResultList( - array $applications, + array $all_applications, PhabricatorSavedQuery $query, array $handle) { - assert_instances_of($applications, 'PhabricatorApplication'); + assert_instances_of($all_applications, 'PhabricatorApplication'); - $list = new PHUIObjectItemListView(); + $all_applications = msort($all_applications, 'getName'); - $applications = msort($applications, 'getName'); - - foreach ($applications as $application) { - $item = id(new PHUIObjectItemView()) - ->setHeader($application->getName()) - ->setHref('/applications/view/'.get_class($application).'/') - ->addAttribute($application->getShortDescription()); + if ($query->getQueryKey() == 'launcher') { + $groups = mgroup($all_applications, 'getApplicationGroup'); + } else { + $groups = array($all_applications); + } - if (!$application->isInstalled()) { - $item->addIcon('delete', pht('Uninstalled')); + $group_names = PhabricatorApplication::getApplicationGroups(); + $groups = array_select_keys($groups, array_keys($group_names)) + $groups; + + $results = array(); + foreach ($groups as $group => $applications) { + if (count($groups) > 1) { + $results[] = phutil_tag( + 'h1', + array( + 'class' => 'launcher-header', + ), + idx($group_names, $group, $group)); } - if ($application->isBeta()) { - $item->addIcon('lint-warning', pht('Beta')); + $list = new PHUIObjectItemListView(); + $list->addClass('phui-object-item-launcher-list'); + + foreach ($applications as $application) { + $icon = $application->getIconName(); + if (!$icon) { + $icon = 'application'; + } + + // TODO: This sheet doesn't work the same way other sheets do so it + // ends up with the wrong classes if we try to use PHUIIconView. This + // is probably all changing in the redesign anyway. + + $icon_view = javelin_tag( + 'span', + array( + 'class' => 'phui-icon-view '. + 'sprite-apps-large apps-'.$icon.'-dark-large', + 'aural' => false, + ), + ''); + + $description = phutil_tag( + 'div', + array( + 'style' => 'white-space: nowrap; '. + 'overflow: hidden; '. + 'text-overflow: ellipsis;', + ), + $application->getShortDescription()); + + $item = id(new PHUIObjectItemView()) + ->setHeader($application->getName()) + ->setImageIcon($icon_view) + ->addAttribute($description) + ->addAction( + id(new PHUIListItemView()) + ->setName(pht('Help/Options')) + ->setIcon('fa-cog') + ->setHref('/applications/view/'.get_class($application).'/')); + + if ($application->getBaseURI()) { + $item->setHref($application->getBaseURI()); + } + + if (!$application->isInstalled()) { + $item->addIcon('delete', pht('Uninstalled')); + } + + if ($application->isBeta()) { + $item->addIcon('fa-star-half-o grey', pht('Beta')); + } + + $list->addItem($item); } - $list->addItem($item); + $results[] = $list; } - return $list; + return $results; } } diff --git a/src/applications/meta/query/PhabricatorApplicationQuery.php b/src/applications/meta/query/PhabricatorApplicationQuery.php index ca14d6954..366a29d42 100644 --- a/src/applications/meta/query/PhabricatorApplicationQuery.php +++ b/src/applications/meta/query/PhabricatorApplicationQuery.php @@ -1,144 +1,159 @@ nameContains = $name_contains; return $this; } public function withInstalled($installed) { $this->installed = $installed; return $this; } public function withBeta($beta) { $this->beta = $beta; return $this; } public function withFirstParty($first_party) { $this->firstParty = $first_party; return $this; } public function withUnlisted($unlisted) { $this->unlisted = $unlisted; return $this; } + public function withLaunchable($launchable) { + $this->launchable = $launchable; + return $this; + } + public function withClasses(array $classes) { $this->classes = $classes; return $this; } public function withPHIDs(array $phids) { $this->phids = $phids; return $this; } public function setOrder($order) { $this->order = $order; return $this; } public function loadPage() { $apps = PhabricatorApplication::getAllApplications(); if ($this->classes) { $classes = array_fuse($this->classes); foreach ($apps as $key => $app) { if (empty($classes[get_class($app)])) { unset($apps[$key]); } } } if ($this->phids) { $phids = array_fuse($this->phids); foreach ($apps as $key => $app) { if (empty($phids[$app->getPHID()])) { unset($apps[$key]); } } } if (strlen($this->nameContains)) { foreach ($apps as $key => $app) { if (stripos($app->getName(), $this->nameContains) === false) { unset($apps[$key]); } } } if ($this->installed !== null) { foreach ($apps as $key => $app) { if ($app->isInstalled() != $this->installed) { unset($apps[$key]); } } } if ($this->beta !== null) { foreach ($apps as $key => $app) { if ($app->isBeta() != $this->beta) { unset($apps[$key]); } } } if ($this->firstParty !== null) { foreach ($apps as $key => $app) { if ($app->isFirstParty() != $this->firstParty) { unset($apps[$key]); } } } if ($this->unlisted !== null) { foreach ($apps as $key => $app) { if ($app->isUnlisted() != $this->unlisted) { unset($apps[$key]); } } } + if ($this->launchable !== null) { + foreach ($apps as $key => $app) { + if ($app->shouldAppearInLaunchView() != $this->launchable) { + unset($apps[$key]); + } + } + } + + switch ($this->order) { case self::ORDER_NAME: $apps = msort($apps, 'getName'); break; case self::ORDER_APPLICATION: $apps = $apps; break; default: throw new Exception( pht('Unknown order "%s"!', $this->order)); } return $apps; } public function getQueryApplicationClass() { // NOTE: Although this belongs to the "Applications" application, trying // to filter its results just leaves us recursing indefinitely. Users // always have access to applications regardless of other policy settings // anyway. return null; } } diff --git a/src/applications/metamta/application/PhabricatorApplicationMetaMTA.php b/src/applications/metamta/application/PhabricatorApplicationMetaMTA.php index 844eca2ec..70605eb1a 100644 --- a/src/applications/metamta/application/PhabricatorApplicationMetaMTA.php +++ b/src/applications/metamta/application/PhabricatorApplicationMetaMTA.php @@ -1,46 +1,46 @@ getBaseURI() => array( 'sendgrid/' => 'PhabricatorMetaMTASendGridReceiveController', 'mailgun/' => 'PhabricatorMetaMTAMailgunReceiveController', ), ); } public function getTitleGlyph() { return '@'; } } diff --git a/src/applications/notification/application/PhabricatorApplicationNotifications.php b/src/applications/notification/application/PhabricatorApplicationNotifications.php index a4925c107..1de16b76c 100644 --- a/src/applications/notification/application/PhabricatorApplicationNotifications.php +++ b/src/applications/notification/application/PhabricatorApplicationNotifications.php @@ -1,31 +1,31 @@ array( '(?:(?Pall|unread)/)?' => 'PhabricatorNotificationListController', 'panel/' => 'PhabricatorNotificationPanelController', 'individual/' => 'PhabricatorNotificationIndividualController', 'status/' => 'PhabricatorNotificationStatusController', 'clear/' => 'PhabricatorNotificationClearController', 'test/' => 'PhabricatorNotificationTestController', ), ); } public function shouldAppearInLaunchView() { return false; } } diff --git a/src/applications/nuance/application/PhabricatorApplicationNuance.php b/src/applications/nuance/application/PhabricatorApplicationNuance.php index ac962b198..5bf137b81 100644 --- a/src/applications/nuance/application/PhabricatorApplicationNuance.php +++ b/src/applications/nuance/application/PhabricatorApplicationNuance.php @@ -1,72 +1,76 @@ array( 'item/' => array( 'view/(?P[1-9]\d*)/' => 'NuanceItemViewController', 'edit/(?P[1-9]\d*)/' => 'NuanceItemEditController', 'new/' => 'NuanceItemEditController', ), 'source/' => array( 'view/(?P[1-9]\d*)/' => 'NuanceSourceViewController', 'edit/(?P[1-9]\d*)/' => 'NuanceSourceEditController', 'new/' => 'NuanceSourceEditController', ), 'queue/' => array( 'view/(?P[1-9]\d*)/' => 'NuanceQueueViewController', 'edit/(?P[1-9]\d*)/' => 'NuanceQueueEditController', 'new/' => 'NuanceQueueEditController', ), 'requestor/' => array( 'view/(?P[1-9]\d*)/' => 'NuanceRequestorViewController', 'edit/(?P[1-9]\d*)/' => 'NuanceRequestorEditController', 'new/' => 'NuanceRequestorEditController', ), ), ); } protected function getCustomCapabilities() { return array( NuanceCapabilitySourceDefaultView::CAPABILITY => array( 'caption' => pht( 'Default view policy for newly created sources.'), ), NuanceCapabilitySourceDefaultEdit::CAPABILITY => array( 'caption' => pht( 'Default edit policy for newly created sources.'), ), NuanceCapabilitySourceManage::CAPABILITY => array( ), ); } } diff --git a/src/applications/oauthserver/application/PhabricatorApplicationOAuthServer.php b/src/applications/oauthserver/application/PhabricatorApplicationOAuthServer.php index 8510f8c5f..399367436 100644 --- a/src/applications/oauthserver/application/PhabricatorApplicationOAuthServer.php +++ b/src/applications/oauthserver/application/PhabricatorApplicationOAuthServer.php @@ -1,59 +1,63 @@ array( '(?:query/(?P[^/]+)/)?' => 'PhabricatorOAuthClientListController', 'auth/' => 'PhabricatorOAuthServerAuthController', 'test/(?P\d+)/' => 'PhabricatorOAuthServerTestController', 'token/' => 'PhabricatorOAuthServerTokenController', 'client/' => array( 'create/' => 'PhabricatorOAuthClientEditController', 'delete/(?P[^/]+)/' => 'PhabricatorOAuthClientDeleteController', 'edit/(?P[^/]+)/' => 'PhabricatorOAuthClientEditController', 'view/(?P[^/]+)/' => 'PhabricatorOAuthClientViewController', ), ), ); } protected function getCustomCapabilities() { return array( PhabricatorOAuthServerCapabilityCreateClients::CAPABILITY => array( 'default' => PhabricatorPolicies::POLICY_ADMIN, ), ); } } diff --git a/src/applications/owners/application/PhabricatorApplicationOwners.php b/src/applications/owners/application/PhabricatorApplicationOwners.php index ed80bf0b4..641277223 100644 --- a/src/applications/owners/application/PhabricatorApplicationOwners.php +++ b/src/applications/owners/application/PhabricatorApplicationOwners.php @@ -1,46 +1,46 @@ array( '' => 'PhabricatorOwnersListController', 'view/(?P[^/]+)/' => 'PhabricatorOwnersListController', 'edit/(?P[1-9]\d*)/' => 'PhabricatorOwnersEditController', 'new/' => 'PhabricatorOwnersEditController', 'package/(?P[1-9]\d*)/' => 'PhabricatorOwnersDetailController', 'delete/(?P[1-9]\d*)/' => 'PhabricatorOwnersDeleteController', ), ); } } diff --git a/src/applications/passphrase/application/PhabricatorApplicationPassphrase.php b/src/applications/passphrase/application/PhabricatorApplicationPassphrase.php index 263210604..dedeb505b 100644 --- a/src/applications/passphrase/application/PhabricatorApplicationPassphrase.php +++ b/src/applications/passphrase/application/PhabricatorApplicationPassphrase.php @@ -1,48 +1,48 @@ \d+)' => 'PassphraseCredentialViewController', '/passphrase/' => array( '(?:query/(?P[^/]+)/)?' => 'PassphraseCredentialListController', 'create/' => 'PassphraseCredentialCreateController', 'edit/(?:(?P\d+)/)?' => 'PassphraseCredentialEditController', 'destroy/(?P\d+)/' => 'PassphraseCredentialDestroyController', 'reveal/(?P\d+)/' => 'PassphraseCredentialRevealController', 'public/(?P\d+)/' => 'PassphraseCredentialPublicController', 'lock/(?P\d+)/' => 'PassphraseCredentialLockController', )); } } diff --git a/src/applications/passphrase/controller/PassphraseCredentialEditController.php b/src/applications/passphrase/controller/PassphraseCredentialEditController.php index 063e8eca2..b8126cf47 100644 --- a/src/applications/passphrase/controller/PassphraseCredentialEditController.php +++ b/src/applications/passphrase/controller/PassphraseCredentialEditController.php @@ -1,361 +1,367 @@ id = idx($data, 'id'); } public function processRequest() { $request = $this->getRequest(); $viewer = $request->getUser(); if ($this->id) { $credential = id(new PassphraseCredentialQuery()) ->setViewer($viewer) ->withIDs(array($this->id)) ->requireCapabilities( array( PhabricatorPolicyCapability::CAN_VIEW, PhabricatorPolicyCapability::CAN_EDIT, )) ->executeOne(); if (!$credential) { return new Aphront404Response(); } - $type = PassphraseCredentialType::getTypeByConstant( - $credential->getCredentialType()); - if (!$type) { - throw new Exception(pht('Credential has invalid type "%s"!', $type)); - } - - if (!$type->isCreateable()) { - throw new Exception( - pht('Credential has noncreateable type "%s"!', $type)); - } + $type = $this->getCredentialType($credential->getCredentialType()); $is_new = false; } else { $type_const = $request->getStr('type'); - $type = PassphraseCredentialType::getTypeByConstant($type_const); - if (!$type) { - return new Aphront404Response(); + $type = $this->getCredentialType($type_const); + + if (!$type->isCreateable()) { + throw new Exception( + pht( + 'Credential has noncreateable type "%s"!', + $credential->getCredentialType())); } $credential = PassphraseCredential::initializeNewCredential($viewer) ->setCredentialType($type->getCredentialType()) ->setProvidesType($type->getProvidesType()); $is_new = true; // Prefill username if provided. $credential->setUsername($request->getStr('username')); if (!$request->getStr('isInitialized')) { $type->didInitializeNewCredential($viewer, $credential); } } $errors = array(); $v_name = $credential->getName(); $e_name = true; $v_desc = $credential->getDescription(); $v_username = $credential->getUsername(); $e_username = true; $v_is_locked = false; $bullet = "\xE2\x80\xA2"; $v_secret = $credential->getSecretID() ? str_repeat($bullet, 32) : null; if ($is_new && ($v_secret === null)) { // If we're creating a new credential, the credential type may have // populated the secret for us (for example, generated an SSH key). In // this case, try { $v_secret = $credential->getSecret()->openEnvelope(); } catch (Exception $ex) { // Ignore this. } } $validation_exception = null; $errors = array(); $e_password = null; if ($request->isFormPost()) { $v_name = $request->getStr('name'); $v_desc = $request->getStr('description'); $v_username = $request->getStr('username'); $v_view_policy = $request->getStr('viewPolicy'); $v_edit_policy = $request->getStr('editPolicy'); $v_is_locked = $request->getStr('lock'); $v_secret = $request->getStr('secret'); $v_password = $request->getStr('password'); $v_decrypt = $v_secret; $env_secret = new PhutilOpaqueEnvelope($v_secret); $env_password = new PhutilOpaqueEnvelope($v_password); if ($type->requiresPassword($env_secret)) { if (strlen($v_password)) { $v_decrypt = $type->decryptSecret($env_secret, $env_password); if ($v_decrypt === null) { $e_password = pht('Incorrect'); $errors[] = pht( 'This key requires a password, but the password you provided '. 'is incorrect.'); } else { $v_decrypt = $v_decrypt->openEnvelope(); } } else { $e_password = pht('Required'); $errors[] = pht( 'This key requires a password. You must provide the password '. 'for the key.'); } } if (!$errors) { $type_name = PassphraseCredentialTransaction::TYPE_NAME; $type_desc = PassphraseCredentialTransaction::TYPE_DESCRIPTION; $type_username = PassphraseCredentialTransaction::TYPE_USERNAME; $type_destroy = PassphraseCredentialTransaction::TYPE_DESTROY; $type_secret_id = PassphraseCredentialTransaction::TYPE_SECRET_ID; $type_is_locked = PassphraseCredentialTransaction::TYPE_LOCK; $type_view_policy = PhabricatorTransactions::TYPE_VIEW_POLICY; $type_edit_policy = PhabricatorTransactions::TYPE_EDIT_POLICY; $xactions = array(); $xactions[] = id(new PassphraseCredentialTransaction()) ->setTransactionType($type_name) ->setNewValue($v_name); $xactions[] = id(new PassphraseCredentialTransaction()) ->setTransactionType($type_desc) ->setNewValue($v_desc); $xactions[] = id(new PassphraseCredentialTransaction()) ->setTransactionType($type_view_policy) ->setNewValue($v_view_policy); $xactions[] = id(new PassphraseCredentialTransaction()) ->setTransactionType($type_edit_policy) ->setNewValue($v_edit_policy); // Open a transaction in case we're writing a new secret; this limits // the amount of code which handles secret plaintexts. $credential->openTransaction(); if (!$credential->getIsLocked()) { $xactions[] = id(new PassphraseCredentialTransaction()) ->setTransactionType($type_username) ->setNewValue($v_username); $min_secret = str_replace($bullet, '', trim($v_decrypt)); if (strlen($min_secret)) { // If the credential was previously destroyed, restore it when it is // edited if a secret is provided. $xactions[] = id(new PassphraseCredentialTransaction()) ->setTransactionType($type_destroy) ->setNewValue(0); $new_secret = id(new PassphraseSecret()) ->setSecretData($v_decrypt) ->save(); $xactions[] = id(new PassphraseCredentialTransaction()) ->setTransactionType($type_secret_id) ->setNewValue($new_secret->getID()); } $xactions[] = id(new PassphraseCredentialTransaction()) ->setTransactionType($type_is_locked) ->setNewValue($v_is_locked); } try { $editor = id(new PassphraseCredentialTransactionEditor()) ->setActor($viewer) ->setContinueOnNoEffect(true) ->setContentSourceFromRequest($request) ->applyTransactions($credential, $xactions); $credential->saveTransaction(); if ($request->isAjax()) { return id(new AphrontAjaxResponse())->setContent( array( 'phid' => $credential->getPHID(), 'name' => 'K'.$credential->getID().' '.$credential->getName(), )); } else { return id(new AphrontRedirectResponse()) ->setURI('/K'.$credential->getID()); } } catch (PhabricatorApplicationTransactionValidationException $ex) { $credential->killTransaction(); $validation_exception = $ex; $e_name = $ex->getShortMessage($type_name); $e_username = $ex->getShortMessage($type_username); $credential->setViewPolicy($v_view_policy); $credential->setEditPolicy($v_edit_policy); } } } $policies = id(new PhabricatorPolicyQuery()) ->setViewer($viewer) ->setObject($credential) ->execute(); $secret_control = $type->newSecretControl(); $credential_is_locked = $credential->getIsLocked(); $form = id(new AphrontFormView()) ->setUser($viewer) ->addHiddenInput('isInitialized', true) ->appendChild( id(new AphrontFormTextControl()) ->setName('name') ->setLabel(pht('Name')) ->setValue($v_name) ->setError($e_name)) ->appendChild( id(new AphrontFormTextAreaControl()) ->setHeight(AphrontFormTextAreaControl::HEIGHT_VERY_SHORT) ->setName('description') ->setLabel(pht('Description')) ->setValue($v_desc)) ->appendChild( id(new AphrontFormMarkupControl()) ->setLabel(pht('Credential Type')) ->setValue($type->getCredentialTypeName())) ->appendChild( id(new AphrontFormDividerControl())) ->appendChild( id(new AphrontFormPolicyControl()) ->setName('viewPolicy') ->setPolicyObject($credential) ->setCapability(PhabricatorPolicyCapability::CAN_VIEW) ->setPolicies($policies)) ->appendChild( id(new AphrontFormPolicyControl()) ->setName('editPolicy') ->setPolicyObject($credential) ->setCapability(PhabricatorPolicyCapability::CAN_EDIT) ->setPolicies($policies)) ->appendChild( id(new AphrontFormDividerControl())); if ($credential_is_locked) { $form->appendRemarkupInstructions( pht('This credential is permanently locked and can not be edited.')); } $form ->appendChild( id(new AphrontFormTextControl()) ->setName('username') ->setLabel(pht('Login/Username')) ->setValue($v_username) ->setDisabled($credential_is_locked) ->setError($e_username)) ->appendChild( $secret_control ->setName('secret') ->setLabel($type->getSecretLabel()) ->setDisabled($credential_is_locked) ->setValue($v_secret)); if ($type->shouldShowPasswordField()) { $form->appendChild( id(new AphrontFormPasswordControl()) ->setName('password') ->setLabel($type->getPasswordLabel()) ->setDisabled($credential_is_locked) ->setError($e_password)); } if ($is_new) { $form->appendChild( id(new AphrontFormCheckboxControl()) ->addCheckbox( 'lock', 1, array( phutil_tag('strong', array(), pht('Lock Permanently:')), ' ', pht('Prevent the secret from being revealed or changed.'), ), $v_is_locked) ->setDisabled($credential_is_locked)); } $crumbs = $this->buildApplicationCrumbs(); if ($is_new) { $title = pht('Create Credential'); $header = pht('Create New Credential'); $crumbs->addTextCrumb(pht('Create')); $cancel_uri = $this->getApplicationURI(); } else { $title = pht('Edit Credential'); $header = pht('Edit Credential %s', 'K'.$credential->getID()); $crumbs->addTextCrumb( 'K'.$credential->getID(), '/K'.$credential->getID()); $crumbs->addTextCrumb(pht('Edit')); $cancel_uri = '/K'.$credential->getID(); } if ($request->isAjax()) { if ($errors) { $errors = id(new AphrontErrorView())->setErrors($errors); } $dialog = id(new AphrontDialogView()) ->setUser($viewer) ->setWidth(AphrontDialogView::WIDTH_FORM) ->setTitle($title) ->appendChild($errors) ->appendChild($form->buildLayoutView()) ->addSubmitButton(pht('Create Credential')) ->addCancelButton($cancel_uri); return id(new AphrontDialogResponse())->setDialog($dialog); } $form->appendChild( id(new AphrontFormSubmitControl()) ->setValue(pht('Save')) ->addCancelButton($cancel_uri)); $box = id(new PHUIObjectBoxView()) ->setHeaderText($header) ->setFormErrors($errors) ->setValidationException($validation_exception) ->setForm($form); return $this->buildApplicationPage( array( $crumbs, $box, ), array( 'title' => $title, 'device' => true, )); } + private function getCredentialType($type_const) { + $type = PassphraseCredentialType::getTypeByConstant($type_const); + + if (!$type) { + throw new Exception( + pht('Credential has invalid type "%s"!', $type_const)); + } + + return $type; + } + } diff --git a/src/applications/paste/application/PhabricatorApplicationPaste.php b/src/applications/paste/application/PhabricatorApplicationPaste.php index 0e99441e5..5ba1f211f 100644 --- a/src/applications/paste/application/PhabricatorApplicationPaste.php +++ b/src/applications/paste/application/PhabricatorApplicationPaste.php @@ -1,61 +1,65 @@ [1-9]\d*)(?:\$(?P\d+(?:-\d+)?))?' => 'PhabricatorPasteViewController', '/paste/' => array( '(query/(?P[^/]+)/)?' => 'PhabricatorPasteListController', 'create/' => 'PhabricatorPasteEditController', 'edit/(?P[1-9]\d*)/' => 'PhabricatorPasteEditController', 'comment/(?P[1-9]\d*)/' => 'PhabricatorPasteCommentController', ), ); } protected function getCustomCapabilities() { return array( PasteCapabilityDefaultView::CAPABILITY => array( 'caption' => pht( 'Default view policy for newly created pastes.') ), ); } public function getQuickCreateItems(PhabricatorUser $viewer) { $items = array(); $item = id(new PHUIListItemView()) ->setName(pht('Paste')) ->setIcon('fa-clipboard') ->setHref($this->getBaseURI().'create/'); $items[] = $item; return $items; } } diff --git a/src/applications/people/application/PhabricatorApplicationPeople.php b/src/applications/people/application/PhabricatorApplicationPeople.php index 2b89dc4d2..d387b0f88 100644 --- a/src/applications/people/application/PhabricatorApplicationPeople.php +++ b/src/applications/people/application/PhabricatorApplicationPeople.php @@ -1,162 +1,162 @@ array( '(query/(?P[^/]+)/)?' => 'PhabricatorPeopleListController', 'logs/(?:query/(?P[^/]+)/)?' => 'PhabricatorPeopleLogsController', 'approve/(?P[1-9]\d*)/' => 'PhabricatorPeopleApproveController', '(?Pdisapprove)/(?P[1-9]\d*)/' => 'PhabricatorPeopleDisableController', '(?Pdisable)/(?P[1-9]\d*)/' => 'PhabricatorPeopleDisableController', 'empower/(?P[1-9]\d*)/' => 'PhabricatorPeopleEmpowerController', 'delete/(?P[1-9]\d*)/' => 'PhabricatorPeopleDeleteController', 'rename/(?P[1-9]\d*)/' => 'PhabricatorPeopleRenameController', 'welcome/(?P[1-9]\d*)/' => 'PhabricatorPeopleWelcomeController', 'create/' => 'PhabricatorPeopleCreateController', 'new/(?P[^/]+)/' => 'PhabricatorPeopleNewController', 'ldap/' => 'PhabricatorPeopleLdapController', 'editprofile/(?P[1-9]\d*)/' => 'PhabricatorPeopleProfileEditController', 'picture/(?P[1-9]\d*)/' => 'PhabricatorPeopleProfilePictureController', ), '/p/(?P[\w._-]+)/' => 'PhabricatorPeopleProfileController', '/p/(?P[\w._-]+)/calendar/' => 'PhabricatorPeopleCalendarController', ); } public function getRemarkupRules() { return array( new PhabricatorRemarkupRuleMention(), ); } protected function getCustomCapabilities() { return array( PeopleCapabilityBrowseUserDirectory::CAPABILITY => array( ), ); } public function loadStatus(PhabricatorUser $user) { if (!$user->getIsAdmin()) { return array(); } $need_approval = id(new PhabricatorPeopleQuery()) ->setViewer($user) ->withIsApproved(false) ->withIsDisabled(false) ->execute(); if (!$need_approval) { return array(); } $status = array(); $count = count($need_approval); $type = PhabricatorApplicationStatusView::TYPE_NEEDS_ATTENTION; $status[] = id(new PhabricatorApplicationStatusView()) ->setType($type) ->setText(pht('%d User(s) Need Approval', $count)) ->setCount($count); return $status; } public function buildMainMenuItems( PhabricatorUser $user, PhabricatorController $controller = null) { $items = array(); if ($user->isLoggedIn() && $user->isUserActivated()) { $image = $user->loadProfileImageURI(); $item = id(new PHUIListItemView()) ->setName($user->getUsername()) ->setHref('/p/'.$user->getUsername().'/') ->addClass('core-menu-item') ->setAural(pht('Profile')) ->setOrder(100); $classes = array( 'phabricator-core-menu-icon', 'phabricator-core-menu-profile-image', ); $item->appendChild( phutil_tag( 'span', array( 'class' => implode(' ', $classes), 'style' => 'background-image: url('.$image.')', ), '')); $items[] = $item; } return $items; } public function getQuickCreateItems(PhabricatorUser $viewer) { $items = array(); if ($viewer->getIsAdmin()) { $item = id(new PHUIListItemView()) ->setName(pht('User Account')) ->setIcon('fa-users') ->setHref($this->getBaseURI().'create/'); $items[] = $item; } return $items; } } diff --git a/src/applications/phlux/application/PhabricatorApplicationPhlux.php b/src/applications/phlux/application/PhabricatorApplicationPhlux.php index 85c1699cb..c6e42598c 100644 --- a/src/applications/phlux/application/PhabricatorApplicationPhlux.php +++ b/src/applications/phlux/application/PhabricatorApplicationPhlux.php @@ -1,39 +1,39 @@ array( '' => 'PhluxListController', 'view/(?P[^/]+)/' => 'PhluxViewController', 'edit/(?:(?P[^/]+)/)?' => 'PhluxEditController', ), ); } } diff --git a/src/applications/pholio/application/PhabricatorApplicationPholio.php b/src/applications/pholio/application/PhabricatorApplicationPholio.php index 96f113cba..500ee5098 100644 --- a/src/applications/pholio/application/PhabricatorApplicationPholio.php +++ b/src/applications/pholio/application/PhabricatorApplicationPholio.php @@ -1,89 +1,89 @@ [1-9]\d*)(?:/(?P\d+)/)?' => 'PholioMockViewController', '/pholio/' => array( '(?:query/(?P[^/]+)/)?' => 'PholioMockListController', 'new/' => 'PholioMockEditController', 'edit/(?P\d+)/' => 'PholioMockEditController', 'comment/(?P\d+)/' => 'PholioMockCommentController', 'inline/' => array( '(?P\d+)/' => 'PholioInlineController', 'save/' => 'PholioInlineSaveController', 'delete/(?P\d+)/' => 'PholioInlineDeleteController', 'view/(?P\d+)/' => 'PholioInlineViewController', 'edit/(?P\d+)/' => 'PholioInlineEditController', 'thumb/(?P\d+)/' => 'PholioInlineThumbController' ), 'image/' => array( 'upload/' => 'PholioImageUploadController', 'history/(?P\d+)/' => 'PholioImageHistoryController', ), ), ); } public function getQuickCreateItems(PhabricatorUser $viewer) { $items = array(); $item = id(new PHUIListItemView()) ->setName(pht('Pholio Mock')) ->setIcon('fa-picture-o') ->setHref($this->getBaseURI().'new/'); $items[] = $item; return $items; } protected function getCustomCapabilities() { return array( PholioCapabilityDefaultView::CAPABILITY => array( ), ); } } diff --git a/src/applications/phortune/application/PhabricatorApplicationPhortune.php b/src/applications/phortune/application/PhabricatorApplicationPhortune.php index fe08ef448..ab22fbde8 100644 --- a/src/applications/phortune/application/PhabricatorApplicationPhortune.php +++ b/src/applications/phortune/application/PhabricatorApplicationPhortune.php @@ -1,58 +1,58 @@ array( '' => 'PhortuneLandingController', '(?P\d+)/' => array( '' => 'PhortuneAccountViewController', 'paymentmethod/' => array( 'edit/' => 'PhortunePaymentMethodEditController', ), 'buy/(?P\d+)/' => 'PhortuneAccountBuyController', ), 'account/' => array( '' => 'PhortuneAccountListController', 'edit/(?:(?P\d+)/)?' => 'PhortuneAccountEditController', ), 'stripe/' => array( 'testpaymentform/' => 'PhortuneStripeTestPaymentFormController', ), 'product/' => array( '' => 'PhortuneProductListController', 'view/(?P\d+)/' => 'PhortuneProductViewController', 'edit/(?:(?P\d+)/)?' => 'PhortuneProductEditController', ), 'provider/(?P[^/]+)/(?P[^/]+)/' => 'PhortuneProviderController', ), ); } } diff --git a/src/applications/phrequent/application/PhabricatorApplicationPhrequent.php b/src/applications/phrequent/application/PhabricatorApplicationPhrequent.php index 35f2dd9fd..4bcccef60 100644 --- a/src/applications/phrequent/application/PhabricatorApplicationPhrequent.php +++ b/src/applications/phrequent/application/PhabricatorApplicationPhrequent.php @@ -1,61 +1,61 @@ array( '(?:query/(?P[^/]+)/)?' => 'PhrequentListController', 'track/(?P[a-z]+)/(?P[^/]+)/' => 'PhrequentTrackController' ), ); } public function loadStatus(PhabricatorUser $user) { $status = array(); // Show number of objects that are currently // being tracked for a user. $count = PhrequentUserTimeQuery::getUserTotalObjectsTracked($user); $type = PhabricatorApplicationStatusView::TYPE_NEEDS_ATTENTION; $status[] = id(new PhabricatorApplicationStatusView()) ->setType($type) ->setText(pht('%d Object(s) Tracked', $count)) ->setCount($count); return $status; } } diff --git a/src/applications/phrequent/storage/PhrequentTimeBlock.php b/src/applications/phrequent/storage/PhrequentTimeBlock.php index 60721e45c..45b3555fd 100644 --- a/src/applications/phrequent/storage/PhrequentTimeBlock.php +++ b/src/applications/phrequent/storage/PhrequentTimeBlock.php @@ -1,268 +1,273 @@ events = $events; } public function getTimeSpentOnObject($phid, $now) { $ranges = idx($this->getObjectTimeRanges($now), $phid, array()); $sum = 0; foreach ($ranges as $range) { $sum += ($range[1] - $range[0]); } return $sum; } public function getObjectTimeRanges($now) { $ranges = array(); + $range_start = time(); + foreach ($this->events as $event) { + $range_start = min($range_start, $event->getDateStarted()); + } + $object_ranges = array(); foreach ($this->events as $event) { // First, convert each event's preempting stack into a linear timeline // of events. $timeline = array(); $timeline[] = array( 'event' => $event, 'at' => $event->getDateStarted(), 'type' => 'start', ); $timeline[] = array( 'event' => $event, 'at' => nonempty($event->getDateEnded(), $now), 'type' => 'end', ); $base_phid = $event->getObjectPHID(); $preempts = $event->getPreemptingEvents(); foreach ($preempts as $preempt) { $same_object = ($preempt->getObjectPHID() == $base_phid); $timeline[] = array( 'event' => $preempt, 'at' => $preempt->getDateStarted(), 'type' => $same_object ? 'start' : 'push', ); $timeline[] = array( 'event' => $preempt, 'at' => nonempty($preempt->getDateEnded(), $now), 'type' => $same_object ? 'end' : 'pop', ); } // Now, figure out how much time was actually spent working on the // object. usort($timeline, array(__CLASS__, 'sortTimeline')); $stack = array(); $depth = null; // NOTE: "Strata" track the separate layers between each event tracking // the object we care about. Events might look like this: // // |xxxxxxxxxxxxxxxxx| // |yyyyyyy| // |xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx| // 9AM 5PM // // ...where we care about event "x". When "y" is popped, that shouldn't // pop the top stack -- we need to pop the stack a level down. Each // event tracking "x" creates a new stratum, and we keep track of where // timeline events are among the strata in order to keep stack depths // straight. $stratum = null; $strata = array(); $ranges = array(); foreach ($timeline as $timeline_event) { $id = $timeline_event['event']->getID(); $type = $timeline_event['type']; switch ($type) { case 'start': $stack[] = $depth; $depth = 0; $stratum = count($stack); $strata[$id] = $stratum; $range_start = $timeline_event['at']; break; case 'end': if ($strata[$id] == $stratum) { if ($depth == 0) { $ranges[] = array($range_start, $timeline_event['at']); $depth = array_pop($stack); } else { // Here, we've prematurely ended the current stratum. Merge all // the higher strata into it. This looks like this: // // V // V // |zzzzzzzz| // |xxxxx| // |yyyyyyyyyyyyy| // |xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx| $depth = array_pop($stack) + $depth; } } else { // Here, we've prematurely ended a deeper stratum. Merge higher // stata. This looks like this: // // V // V // |aaaaaaa| // |xxxxxxxxxxxxxxxxxxx| // |zzzzzzzzzzzzz| // |xxxxxxx| // |yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy| // |xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx| $extra = $stack[$strata[$id]]; unset($stack[$strata[$id] - 1]); $stack = array_values($stack); $stack[$strata[$id] - 1] += $extra; } // Regardless of how we got here, we need to merge down any higher // strata. $target = $strata[$id]; foreach ($strata as $strata_id => $id_stratum) { if ($id_stratum >= $target) { $strata[$strata_id]--; } } $stratum = count($stack); unset($strata[$id]); break; case 'push': $strata[$id] = $stratum; if ($depth == 0) { $ranges[] = array($range_start, $timeline_event['at']); } $depth++; break; case 'pop': if ($strata[$id] == $stratum) { $depth--; if ($depth == 0) { $range_start = $timeline_event['at']; } } else { $stack[$strata[$id]]--; } unset($strata[$id]); break; } } $object_ranges[$base_phid][] = $ranges; } // Finally, collapse all the ranges so we don't double-count time. foreach ($object_ranges as $phid => $ranges) { $object_ranges[$phid] = self::mergeTimeRanges(array_mergev($ranges)); } return $object_ranges; } /** * Merge a list of time ranges (pairs of `` epochs) so that no * elements overlap. For example, the ranges: * * array( * array(50, 150), * array(100, 175), * ); * * ...are merged to: * * array( * array(50, 175), * ); * * This is used to avoid double-counting time on objects which had timers * started multiple times. * * @param list> List of possibly overlapping time ranges. * @return list> Nonoverlapping time ranges. */ public static function mergeTimeRanges(array $ranges) { $ranges = isort($ranges, 0); $result = array(); $current = null; foreach ($ranges as $key => $range) { if ($current === null) { $current = $range; continue; } if ($range[0] <= $current[1]) { $current[1] = max($range[1], $current[1]); continue; } $result[] = $current; $current = $range; } $result[] = $current; return $result; } /** * Sort events in timeline order. Notably, for events which occur on the same * second, we want to process end events after start events. */ public static function sortTimeline(array $u, array $v) { // If these events occur at different times, ordering is obvious. if ($u['at'] != $v['at']) { return ($u['at'] < $v['at']) ? -1 : 1; } $u_end = ($u['type'] == 'end' || $u['type'] == 'pop'); $v_end = ($v['type'] == 'end' || $v['type'] == 'pop'); $u_id = $u['event']->getID(); $v_id = $v['event']->getID(); if ($u_end == $v_end) { // These are both start events or both end events. Sort them by ID. if (!$u_end) { return ($u_id < $v_id) ? -1 : 1; } else { return ($u_id < $v_id) ? 1 : -1; } } else { // Sort them (start, end) if they're the same event, and (end, start) // otherwise. if ($u_id == $v_id) { return $v_end ? -1 : 1; } else { return $v_end ? 1 : -1; } } return 0; } } diff --git a/src/applications/policy/__tests__/PhabricatorPolicyDataTestCase.php b/src/applications/policy/__tests__/PhabricatorPolicyDataTestCase.php index 89b303b0d..e0f66c156 100644 --- a/src/applications/policy/__tests__/PhabricatorPolicyDataTestCase.php +++ b/src/applications/policy/__tests__/PhabricatorPolicyDataTestCase.php @@ -1,147 +1,149 @@ true, ); } public function testProjectPolicyMembership() { $author = $this->generateNewTestUser(); $proj_a = id(new PhabricatorProject()) ->setName('A') ->setAuthorPHID($author->getPHID()) + ->setIcon('fa-briefcase') ->save(); $proj_b = id(new PhabricatorProject()) ->setName('B') ->setAuthorPHID($author->getPHID()) + ->setIcon('fa-briefcase') ->save(); $proj_a->setViewPolicy($proj_b->getPHID())->save(); $proj_b->setViewPolicy($proj_a->getPHID())->save(); $user = new PhabricatorUser(); $results = id(new PhabricatorProjectQuery()) ->setViewer($user) ->execute(); $this->assertEqual(0, count($results)); } public function testCustomPolicyRuleUser() { $user_a = $this->generateNewTestUser(); $user_b = $this->generateNewTestUser(); $author = $this->generateNewTestUser(); $policy = id(new PhabricatorPolicy()) ->setRules( array( array( 'action' => PhabricatorPolicy::ACTION_ALLOW, 'rule' => 'PhabricatorPolicyRuleUsers', 'value' => array($user_a->getPHID()), ), )) ->save(); $task = ManiphestTask::initializeNewTask($author); $task->setViewPolicy($policy->getPHID()); $task->save(); $can_a_view = PhabricatorPolicyFilter::hasCapability( $user_a, $task, PhabricatorPolicyCapability::CAN_VIEW); $this->assertTrue($can_a_view); $can_b_view = PhabricatorPolicyFilter::hasCapability( $user_b, $task, PhabricatorPolicyCapability::CAN_VIEW); $this->assertFalse($can_b_view); } public function testCustomPolicyRuleAdministrators() { $user_a = $this->generateNewTestUser(); $user_a->setIsAdmin(true)->save(); $user_b = $this->generateNewTestUser(); $author = $this->generateNewTestUser(); $policy = id(new PhabricatorPolicy()) ->setRules( array( array( 'action' => PhabricatorPolicy::ACTION_ALLOW, 'rule' => 'PhabricatorPolicyRuleAdministrators', 'value' => null, ), )) ->save(); $task = ManiphestTask::initializeNewTask($author); $task->setViewPolicy($policy->getPHID()); $task->save(); $can_a_view = PhabricatorPolicyFilter::hasCapability( $user_a, $task, PhabricatorPolicyCapability::CAN_VIEW); $this->assertTrue($can_a_view); $can_b_view = PhabricatorPolicyFilter::hasCapability( $user_b, $task, PhabricatorPolicyCapability::CAN_VIEW); $this->assertFalse($can_b_view); } public function testCustomPolicyRuleLunarPhase() { $user_a = $this->generateNewTestUser(); $author = $this->generateNewTestUser(); $policy = id(new PhabricatorPolicy()) ->setRules( array( array( 'action' => PhabricatorPolicy::ACTION_ALLOW, 'rule' => 'PhabricatorPolicyRuleLunarPhase', 'value' => 'new', ), )) ->save(); $task = ManiphestTask::initializeNewTask($author); $task->setViewPolicy($policy->getPHID()); $task->save(); $time_a = PhabricatorTime::pushTime(934354800, 'UTC'); $can_a_view = PhabricatorPolicyFilter::hasCapability( $user_a, $task, PhabricatorPolicyCapability::CAN_VIEW); $this->assertTrue($can_a_view); unset($time_a); $time_b = PhabricatorTime::pushTime(1116745200, 'UTC'); $can_a_view = PhabricatorPolicyFilter::hasCapability( $user_a, $task, PhabricatorPolicyCapability::CAN_VIEW); $this->assertFalse($can_a_view); unset($time_b); } } diff --git a/src/applications/ponder/application/PhabricatorApplicationPonder.php b/src/applications/ponder/application/PhabricatorApplicationPonder.php index 220750b55..082153346 100644 --- a/src/applications/ponder/application/PhabricatorApplicationPonder.php +++ b/src/applications/ponder/application/PhabricatorApplicationPonder.php @@ -1,68 +1,68 @@ [1-9]\d*)' => 'PonderQuestionViewController', '/ponder/' => array( '(?:query/(?P[^/]+)/)?' => 'PonderQuestionListController', 'answer/add/' => 'PonderAnswerSaveController', 'answer/edit/(?P\d+)/' => 'PonderAnswerEditController', 'answer/comment/(?P\d+)/' => 'PonderAnswerCommentController', 'answer/history/(?P\d+)/' => 'PonderAnswerHistoryController', 'question/edit/(?:(?P\d+)/)?' => 'PonderQuestionEditController', 'question/comment/(?P\d+)/' => 'PonderQuestionCommentController', 'question/history/(?P\d+)/' => 'PonderQuestionHistoryController', 'preview/' => 'PhabricatorMarkupPreviewController', 'question/(?Popen|close)/(?P[1-9]\d*)/' => 'PonderQuestionStatusController', 'vote/' => 'PonderVoteSaveController', ), ); } } diff --git a/src/applications/project/application/PhabricatorApplicationProject.php b/src/applications/project/application/PhabricatorApplicationProject.php index 22e016c28..60c13fb62 100644 --- a/src/applications/project/application/PhabricatorApplicationProject.php +++ b/src/applications/project/application/PhabricatorApplicationProject.php @@ -1,87 +1,87 @@ array( '(?:query/(?P[^/]+)/)?' => 'PhabricatorProjectListController', 'filter/(?P[^/]+)/' => 'PhabricatorProjectListController', 'edit/(?P[1-9]\d*)/' => 'PhabricatorProjectEditMainController', 'details/(?P[1-9]\d*)/' => 'PhabricatorProjectEditDetailsController', 'archive/(?P[1-9]\d*)/' => 'PhabricatorProjectArchiveController', 'members/(?P[1-9]\d*)/' => 'PhabricatorProjectMembersEditController', 'members/(?P[1-9]\d*)/remove/' => 'PhabricatorProjectMembersRemoveController', 'view/(?P[1-9]\d*)/' => 'PhabricatorProjectProfileController', 'picture/(?P[1-9]\d*)/' => 'PhabricatorProjectEditPictureController', 'icon/(?P[1-9]\d*)/' => 'PhabricatorProjectEditIconController', 'create/' => 'PhabricatorProjectCreateController', 'board/(?P[1-9]\d*)/'. '(?Pfilter/)?'. '(?:query/(?P[^/]+)/)?' => 'PhabricatorProjectBoardViewController', 'move/(?P[1-9]\d*)/' => 'PhabricatorProjectMoveController', 'board/(?P[1-9]\d*)/edit/(?:(?P\d+)/)?' => 'PhabricatorProjectBoardEditController', 'board/(?P[1-9]\d*)/delete/(?:(?P\d+)/)?' => 'PhabricatorProjectBoardDeleteController', 'board/(?P[1-9]\d*)/column/(?:(?P\d+)/)?' => 'PhabricatorProjectColumnDetailController', 'update/(?P[1-9]\d*)/(?P[^/]+)/' => 'PhabricatorProjectUpdateController', 'history/(?P[1-9]\d*)/' => 'PhabricatorProjectHistoryController', '(?Pwatch|unwatch)/(?P[1-9]\d*)/' => 'PhabricatorProjectWatchController', ), '/tag/' => array( '(?P[^/]+)/' => 'PhabricatorProjectProfileController', ), ); } protected function getCustomCapabilities() { return array( ProjectCapabilityCreateProjects::CAPABILITY => array( ), ); } } diff --git a/src/applications/project/editor/PhabricatorProjectTransactionEditor.php b/src/applications/project/editor/PhabricatorProjectTransactionEditor.php index 27b0c4a13..a1312bac2 100644 --- a/src/applications/project/editor/PhabricatorProjectTransactionEditor.php +++ b/src/applications/project/editor/PhabricatorProjectTransactionEditor.php @@ -1,416 +1,423 @@ getTransactionType()) { case PhabricatorProjectTransaction::TYPE_NAME: return $object->getName(); case PhabricatorProjectTransaction::TYPE_SLUGS: $slugs = $object->getSlugs(); $slugs = mpull($slugs, 'getSlug', 'getSlug'); unset($slugs[$object->getPrimarySlug()]); return $slugs; case PhabricatorProjectTransaction::TYPE_STATUS: return $object->getStatus(); case PhabricatorProjectTransaction::TYPE_IMAGE: return $object->getProfileImagePHID(); case PhabricatorProjectTransaction::TYPE_ICON: return $object->getIcon(); } return parent::getCustomTransactionOldValue($object, $xaction); } protected function getCustomTransactionNewValue( PhabricatorLiskDAO $object, PhabricatorApplicationTransaction $xaction) { switch ($xaction->getTransactionType()) { case PhabricatorProjectTransaction::TYPE_NAME: case PhabricatorProjectTransaction::TYPE_SLUGS: case PhabricatorProjectTransaction::TYPE_STATUS: case PhabricatorProjectTransaction::TYPE_IMAGE: case PhabricatorProjectTransaction::TYPE_ICON: return $xaction->getNewValue(); } return parent::getCustomTransactionNewValue($object, $xaction); } protected function applyCustomInternalTransaction( PhabricatorLiskDAO $object, PhabricatorApplicationTransaction $xaction) { switch ($xaction->getTransactionType()) { case PhabricatorProjectTransaction::TYPE_NAME: $object->setName($xaction->getNewValue()); $object->setPhrictionSlug($xaction->getNewValue()); return; case PhabricatorProjectTransaction::TYPE_SLUGS: return; case PhabricatorProjectTransaction::TYPE_STATUS: $object->setStatus($xaction->getNewValue()); return; case PhabricatorProjectTransaction::TYPE_IMAGE: $object->setProfileImagePHID($xaction->getNewValue()); return; case PhabricatorProjectTransaction::TYPE_ICON: $object->setIcon($xaction->getNewValue()); return; case PhabricatorTransactions::TYPE_EDGE: return; case PhabricatorTransactions::TYPE_VIEW_POLICY: $object->setViewPolicy($xaction->getNewValue()); return; case PhabricatorTransactions::TYPE_EDIT_POLICY: $object->setEditPolicy($xaction->getNewValue()); return; case PhabricatorTransactions::TYPE_JOIN_POLICY: $object->setJoinPolicy($xaction->getNewValue()); return; } return parent::applyCustomInternalTransaction($object, $xaction); } protected function applyCustomExternalTransaction( PhabricatorLiskDAO $object, PhabricatorApplicationTransaction $xaction) { switch ($xaction->getTransactionType()) { case PhabricatorProjectTransaction::TYPE_NAME: $new_slug = id(new PhabricatorProjectSlug()) ->setSlug($object->getPrimarySlug()) ->setProjectPHID($object->getPHID()) ->save(); if ($xaction->getOldValue() !== null) { $clone_object = clone $object; $clone_object->setPhrictionSlug($xaction->getOldValue()); $old_slug = $clone_object->getPrimarySlug(); $old_slug = id(new PhabricatorProjectSlug()) ->loadOneWhere('slug = %s', $old_slug); if ($old_slug) { $old_slug->delete(); } } // TODO -- delete all of the below once we sever automagical project // to phriction stuff if ($xaction->getOldValue() === null) { // Project was just created, we don't need to move anything. return; } $clone_object = clone $object; $clone_object->setPhrictionSlug($xaction->getOldValue()); $old_slug = $clone_object->getFullPhrictionSlug(); $old_document = id(new PhrictionDocument()) ->loadOneWhere('slug = %s', $old_slug); if ($old_document && $old_document->getStatus() == PhrictionDocumentStatus::STATUS_EXISTS) { $content = id(new PhrictionContent()) ->load($old_document->getContentID()); $from_editor = id(PhrictionDocumentEditor::newForSlug($old_slug)) ->setActor($this->getActor()) ->setTitle($content->getTitle()) ->setContent($content->getContent()) ->setDescription($content->getDescription()); $target_editor = id(PhrictionDocumentEditor::newForSlug( $object->getFullPhrictionSlug())) ->setActor($this->getActor()) ->setTitle($content->getTitle()) ->setContent($content->getContent()) ->setDescription($content->getDescription()) ->moveHere($old_document->getID(), $old_document->getPHID()); $target_document = $target_editor->getDocument(); $from_editor->moveAway($target_document->getID()); } return; case PhabricatorProjectTransaction::TYPE_SLUGS: $old = $xaction->getOldValue(); $new = $xaction->getNewValue(); $add = array_diff($new, $old); $rem = array_diff($old, $new); if ($add) { $add_slug_template = id(new PhabricatorProjectSlug()) ->setProjectPHID($object->getPHID()); foreach ($add as $add_slug_str) { $add_slug = id(clone $add_slug_template) ->setSlug($add_slug_str) ->save(); } } if ($rem) { $rem_slugs = id(new PhabricatorProjectSlug()) ->loadAllWhere('slug IN (%Ls)', $rem); foreach ($rem_slugs as $rem_slug) { $rem_slug->delete(); } } return; case PhabricatorTransactions::TYPE_VIEW_POLICY: case PhabricatorTransactions::TYPE_EDIT_POLICY: case PhabricatorTransactions::TYPE_JOIN_POLICY: case PhabricatorProjectTransaction::TYPE_STATUS: case PhabricatorProjectTransaction::TYPE_IMAGE: case PhabricatorProjectTransaction::TYPE_ICON: return; case PhabricatorTransactions::TYPE_EDGE: $edge_type = $xaction->getMetadataValue('edge:type'); switch ($edge_type) { case PhabricatorEdgeConfig::TYPE_PROJ_MEMBER: case PhabricatorEdgeConfig::TYPE_OBJECT_HAS_WATCHER: $old = $xaction->getOldValue(); $new = $xaction->getNewValue(); // When adding members or watchers, we add subscriptions. $add = array_keys(array_diff_key($new, $old)); // When removing members, we remove their subscription too. // When unwatching, we leave subscriptions, since it's fine to be // subscribed to a project but not be a member of it. if ($edge_type == PhabricatorEdgeConfig::TYPE_PROJ_MEMBER) { $rem = array_keys(array_diff_key($old, $new)); } else { $rem = array(); } // NOTE: The subscribe is "explicit" because there's no implicit // unsubscribe, so Join -> Leave -> Join doesn't resubscribe you // if we use an implicit subscribe, even though you never willfully // unsubscribed. Not sure if adding implicit unsubscribe (which // would not write the unsubscribe row) is justified to deal with // this, which is a fairly weird edge case and pretty arguable both // ways. // Subscriptions caused by watches should also clearly be explicit, // and that case is unambiguous. id(new PhabricatorSubscriptionsEditor()) ->setActor($this->requireActor()) ->setObject($object) ->subscribeExplicit($add) ->unsubscribe($rem) ->save(); if ($rem) { // When removing members, also remove any watches on the project. $edge_editor = id(new PhabricatorEdgeEditor()) ->setSuppressEvents(true); foreach ($rem as $rem_phid) { $edge_editor->removeEdge( $object->getPHID(), PhabricatorEdgeConfig::TYPE_OBJECT_HAS_WATCHER, $rem_phid); } $edge_editor->save(); } break; } return; } return parent::applyCustomExternalTransaction($object, $xaction); } protected function validateTransaction( PhabricatorLiskDAO $object, $type, array $xactions) { $errors = parent::validateTransaction($object, $type, $xactions); switch ($type) { case PhabricatorProjectTransaction::TYPE_NAME: $missing = $this->validateIsEmptyTextField( $object->getName(), $xactions); if ($missing) { $error = new PhabricatorApplicationTransactionValidationError( $type, pht('Required'), pht('Project name is required.'), nonempty(last($xactions), null)); $error->setIsMissingFieldError(true); $errors[] = $error; } if (!$xactions) { break; } $name = last($xactions)->getNewValue(); $name_used_already = id(new PhabricatorProjectQuery()) ->setViewer($this->getActor()) ->withNames(array($name)) ->executeOne(); if ($name_used_already && ($name_used_already->getPHID() != $object->getPHID())) { $error = new PhabricatorApplicationTransactionValidationError( $type, pht('Duplicate'), pht('Project name is already used.'), nonempty(last($xactions), null)); $errors[] = $error; } $slug_builder = clone $object; $slug_builder->setPhrictionSlug($name); $slug = $slug_builder->getPrimarySlug(); $slug_used_already = id(new PhabricatorProjectSlug()) ->loadOneWhere('slug = %s', $slug); if ($slug_used_already && $slug_used_already->getProjectPHID() != $object->getPHID()) { $error = new PhabricatorApplicationTransactionValidationError( $type, pht('Duplicate'), pht('Project name can not be used due to hashtag collision.'), nonempty(last($xactions), null)); $errors[] = $error; } break; case PhabricatorProjectTransaction::TYPE_SLUGS: if (!$xactions) { break; } $slug_xaction = last($xactions); $new = $slug_xaction->getNewValue(); - $slugs_used_already = id(new PhabricatorProjectSlug()) - ->loadAllWhere('slug IN (%Ls)', $new); + + if ($new) { + $slugs_used_already = id(new PhabricatorProjectSlug()) + ->loadAllWhere('slug IN (%Ls)', $new); + } else { + // The project doesn't have any extra slugs. + $slugs_used_already = array(); + } + $slugs_used_already = mgroup($slugs_used_already, 'getProjectPHID'); foreach ($slugs_used_already as $project_phid => $used_slugs) { $used_slug_strs = mpull($used_slugs, 'getSlug'); if ($project_phid == $object->getPHID()) { if (in_array($object->getPrimarySlug(), $used_slug_strs)) { $error = new PhabricatorApplicationTransactionValidationError( $type, pht('Invalid'), pht( 'Project hashtag %s is already the primary hashtag.', $object->getPrimarySlug()), $slug_xaction); $errors[] = $error; } continue; } $error = new PhabricatorApplicationTransactionValidationError( $type, pht('Invalid'), pht( '%d project hashtag(s) are already used: %s', count($used_slug_strs), implode(', ', $used_slug_strs)), $slug_xaction); $errors[] = $error; } break; } return $errors; } protected function requireCapabilities( PhabricatorLiskDAO $object, PhabricatorApplicationTransaction $xaction) { switch ($xaction->getTransactionType()) { case PhabricatorProjectTransaction::TYPE_NAME: case PhabricatorProjectTransaction::TYPE_STATUS: case PhabricatorProjectTransaction::TYPE_IMAGE: case PhabricatorProjectTransaction::TYPE_ICON: PhabricatorPolicyFilter::requireCapability( $this->requireActor(), $object, PhabricatorPolicyCapability::CAN_EDIT); return; case PhabricatorTransactions::TYPE_EDGE: switch ($xaction->getMetadataValue('edge:type')) { case PhabricatorEdgeConfig::TYPE_PROJ_MEMBER: $old = $xaction->getOldValue(); $new = $xaction->getNewValue(); $add = array_keys(array_diff_key($new, $old)); $rem = array_keys(array_diff_key($old, $new)); $actor_phid = $this->requireActor()->getPHID(); $is_join = (($add === array($actor_phid)) && !$rem); $is_leave = (($rem === array($actor_phid)) && !$add); if ($is_join) { // You need CAN_JOIN to join a project. PhabricatorPolicyFilter::requireCapability( $this->requireActor(), $object, PhabricatorPolicyCapability::CAN_JOIN); } else if ($is_leave) { // You don't need any capabilities to leave a project. } else { // You need CAN_EDIT to change members other than yourself. PhabricatorPolicyFilter::requireCapability( $this->requireActor(), $object, PhabricatorPolicyCapability::CAN_EDIT); } return; } break; } return parent::requireCapabilities($object, $xaction); } protected function supportsSearch() { return true; } protected function extractFilePHIDsFromCustomTransaction( PhabricatorLiskDAO $object, PhabricatorApplicationTransaction $xaction) { switch ($xaction->getTransactionType()) { case PhabricatorProjectTransaction::TYPE_IMAGE: $new = $xaction->getNewValue(); if ($new) { return array($new); } break; } return parent::extractFilePHIDsFromCustomTransaction($object, $xaction); } } diff --git a/src/applications/releeph/application/PhabricatorApplicationReleeph.php b/src/applications/releeph/application/PhabricatorApplicationReleeph.php index 3876470ff..79f7a2496 100644 --- a/src/applications/releeph/application/PhabricatorApplicationReleeph.php +++ b/src/applications/releeph/application/PhabricatorApplicationReleeph.php @@ -1,88 +1,88 @@ [1-9]\d*)' => 'ReleephRequestViewController', // TODO: Remove these older routes eventually. '/RQ(?P[1-9]\d*)' => 'ReleephRequestViewController', '/releeph/request/(?P[1-9]\d*)/' => 'ReleephRequestViewController', '/releeph/' => array( '' => 'ReleephProductListController', '(?:product|project)/' => array( '(?:query/(?P[^/]+)/)?' => 'ReleephProductListController', 'create/' => 'ReleephProductCreateController', '(?P[1-9]\d*)/' => array( '(?:query/(?P[^/]+)/)?' => 'ReleephProductViewController', 'edit/' => 'ReleephProductEditController', 'cutbranch/' => 'ReleephBranchCreateController', 'action/(?P.+)/' => 'ReleephProductActionController', 'history/' => 'ReleephProductHistoryController', ), ), 'branch/' => array( 'edit/(?P[1-9]\d*)/' => 'ReleephBranchEditController', '(?Pclose|re-open)/(?P[1-9]\d*)/' => 'ReleephBranchAccessController', 'preview/' => 'ReleephBranchNamePreviewController', '(?P[1-9]\d*)/' => array( 'history/' => 'ReleephBranchHistoryController', '(?:query/(?P[^/]+)/)?' => 'ReleephBranchViewController', ), 'pull/(?P[1-9]\d*)/' => 'ReleephRequestEditController', ), 'request/' => array( 'create/' => 'ReleephRequestEditController', 'differentialcreate/' => array( 'D(?P[1-9]\d*)' => 'ReleephRequestDifferentialCreateController', ), 'edit/(?P[1-9]\d*)/' => 'ReleephRequestEditController', 'action/(?P.+)/(?P[1-9]\d*)/' => 'ReleephRequestActionController', 'typeahead/' => 'ReleephRequestTypeaheadController', 'comment/(?P[1-9]\d*)/' => 'ReleephRequestCommentController', ), ) ); } } diff --git a/src/applications/repository/application/PhabricatorApplicationRepositories.php b/src/applications/repository/application/PhabricatorApplicationRepositories.php index 0afce533b..e2caea352 100644 --- a/src/applications/repository/application/PhabricatorApplicationRepositories.php +++ b/src/applications/repository/application/PhabricatorApplicationRepositories.php @@ -1,40 +1,40 @@ array( '' => 'PhabricatorRepositoryListController', 'project/edit/(?P[1-9]\d*)/' => 'PhabricatorRepositoryArcanistProjectEditController', 'project/delete/(?P[1-9]\d*)/' => 'PhabricatorRepositoryArcanistProjectDeleteController', ), ); } } diff --git a/src/applications/repository/engine/PhabricatorRepositoryPullEngine.php b/src/applications/repository/engine/PhabricatorRepositoryPullEngine.php index e93574074..5ce19f875 100644 --- a/src/applications/repository/engine/PhabricatorRepositoryPullEngine.php +++ b/src/applications/repository/engine/PhabricatorRepositoryPullEngine.php @@ -1,470 +1,470 @@ getRepository(); $is_hg = false; $is_git = false; $is_svn = false; $vcs = $repository->getVersionControlSystem(); $callsign = $repository->getCallsign(); switch ($vcs) { case PhabricatorRepositoryType::REPOSITORY_TYPE_SVN: // We never pull a local copy of non-hosted Subversion repositories. if (!$repository->isHosted()) { $this->skipPull( pht( "Repository '%s' is a non-hosted Subversion repository, which ". "does not require a local working copy to be pulled.", $callsign)); return; } $is_svn = true; break; case PhabricatorRepositoryType::REPOSITORY_TYPE_GIT: $is_git = true; break; case PhabricatorRepositoryType::REPOSITORY_TYPE_MERCURIAL: $is_hg = true; break; default: $this->abortPull(pht('Unknown VCS "%s"!', $vcs)); } $callsign = $repository->getCallsign(); $local_path = $repository->getLocalPath(); if ($local_path === null) { $this->abortPull( pht( "No local path is configured for repository '%s'.", $callsign)); } try { $dirname = dirname($local_path); if (!Filesystem::pathExists($dirname)) { Filesystem::createDirectory($dirname, 0755, $recursive = true); } if (!Filesystem::pathExists($local_path)) { $this->logPull( pht( "Creating a new working copy for repository '%s'.", $callsign)); if ($is_git) { $this->executeGitCreate(); } else if ($is_hg) { $this->executeMercurialCreate(); } else { $this->executeSubversionCreate(); } } else { if (!$repository->isHosted()) { $this->logPull( pht( "Updating the working copy for repository '%s'.", $callsign)); if ($is_git) { $this->executeGitUpdate(); } else if ($is_hg) { $this->executeMercurialUpdate(); } } } if ($repository->isHosted()) { if ($is_git) { $this->installGitHook(); } else if ($is_svn) { $this->installSubversionHook(); } else if ($is_hg) { $this->installMercurialHook(); } foreach ($repository->getHookDirectories() as $directory) { $this->installHookDirectory($directory); } } } catch (Exception $ex) { $this->abortPull( pht('Pull of "%s" failed: %s', $callsign, $ex->getMessage()), $ex); } $this->donePull(); return $this; } private function skipPull($message) { $this->log('%s', $message); $this->donePull(); } private function abortPull($message, Exception $ex = null) { $code_error = PhabricatorRepositoryStatusMessage::CODE_ERROR; $this->updateRepositoryInitStatus($code_error, $message); if ($ex) { throw $ex; } else { throw new Exception($message); } } private function logPull($message) { $code_working = PhabricatorRepositoryStatusMessage::CODE_WORKING; $this->updateRepositoryInitStatus($code_working, $message); $this->log('%s', $message); } private function donePull() { $code_okay = PhabricatorRepositoryStatusMessage::CODE_OKAY; $this->updateRepositoryInitStatus($code_okay); } private function updateRepositoryInitStatus($code, $message = null) { $this->getRepository()->writeStatusMessage( PhabricatorRepositoryStatusMessage::TYPE_INIT, $code, array( 'message' => $message )); } private function installHook($path) { $this->log('%s', pht('Installing commit hook to "%s"...', $path)); $repository = $this->getRepository(); $callsign = $repository->getCallsign(); $root = dirname(phutil_get_library_root('phabricator')); $bin = $root.'/bin/commit-hook'; $full_php_path = Filesystem::resolveBinary('php'); $cmd = csprintf( 'exec %s -f %s -- %s "$@"', $full_php_path, $bin, $callsign); $hook = "#!/bin/sh\n{$cmd}\n"; Filesystem::writeFile($path, $hook); Filesystem::changePermissions($path, 0755); } private function installHookDirectory($path) { $readme = pht( "To add custom hook scripts to this repository, add them to this ". "directory.\n\nPhabricator will run any executables in this directory ". "after running its own checks, as though they were normal hook ". "scripts."); Filesystem::createDirectory($path, 0755); Filesystem::writeFile($path.'/README', $readme); } /* -( Pulling Git Working Copies )----------------------------------------- */ /** * @task git */ private function executeGitCreate() { $repository = $this->getRepository(); $path = rtrim($repository->getLocalPath(), '/'); if ($repository->isHosted()) { $repository->execxRemoteCommand( 'init --bare -- %s', $path); } else { $repository->execxRemoteCommand( 'clone --bare -- %P %s', $repository->getRemoteURIEnvelope(), $path); } } /** * @task git */ private function executeGitUpdate() { $repository = $this->getRepository(); list($err, $stdout) = $repository->execLocalCommand( 'rev-parse --show-toplevel'); $message = null; $path = $repository->getLocalPath(); if ($err) { // Try to raise a more tailored error message in the more common case // of the user creating an empty directory. (We could try to remove it, // but might not be able to, and it's much simpler to raise a good // message than try to navigate those waters.) if (is_dir($path)) { $files = Filesystem::listDirectory($path, $include_hidden = true); if (!$files) { $message = "Expected to find a git repository at '{$path}', but there ". "is an empty directory there. Remove the directory: the daemon ". "will run 'git clone' for you."; } else { $message = "Expected to find a git repository at '{$path}', but there is ". "a non-repository directory (with other stuff in it) there. Move ". "or remove this directory (or reconfigure the repository to use a ". "different directory), and then either clone a repository ". "yourself or let the daemon do it."; } } else if (is_file($path)) { $message = "Expected to find a git repository at '{$path}', but there is a ". "file there instead. Remove it and let the daemon clone a ". "repository for you."; } else { $message = "Expected to find a git repository at '{$path}', but did not."; } } else { $repo_path = rtrim($stdout, "\n"); if (empty($repo_path)) { // This can mean one of two things: we're in a bare repository, or // we're inside a git repository inside another git repository. Since // the first is dramatically more likely now that we perform bare // clones and I don't have a great way to test for the latter, assume // we're OK. } else if (!Filesystem::pathsAreEquivalent($repo_path, $path)) { $err = true; $message = "Expected to find repo at '{$path}', but the actual ". "git repository root for this directory is '{$repo_path}'. ". "Something is misconfigured. The repository's 'Local Path' should ". "be set to some place where the daemon can check out a working ". "copy, and should not be inside another git repository."; } } if ($err && $repository->canDestroyWorkingCopy()) { phlog("Repository working copy at '{$path}' failed sanity check; ". "destroying and re-cloning. {$message}"); Filesystem::remove($path); $this->executeGitCreate(); } else if ($err) { throw new Exception($message); } $retry = false; do { // This is a local command, but needs credentials. if ($repository->isWorkingCopyBare()) { // For bare working copies, we need this magic incantation. $future = $repository->getRemoteCommandFuture( 'fetch origin %s --prune', '+refs/heads/*:refs/heads/*'); } else { $future = $repository->getRemoteCommandFuture( 'fetch --all --prune'); } $future->setCWD($path); list($err, $stdout, $stderr) = $future->resolve(); if ($err && !$retry && $repository->canDestroyWorkingCopy()) { $retry = true; // Fix remote origin url if it doesn't match our configuration $origin_url = $repository->execLocalCommand( 'config --get remote.origin.url'); $remote_uri = $repository->getRemoteURIEnvelope(); if ($origin_url != $remote_uri->openEnvelope()) { $repository->execLocalCommand( 'remote set-url origin %P', $remote_uri); } } else if ($err) { throw new Exception( "git fetch failed with error #{$err}:\n". "stdout:{$stdout}\n\n". "stderr:{$stderr}\n"); } else { $retry = false; } } while ($retry); } /** * @task git */ private function installGitHook() { $repository = $this->getRepository(); $root = $repository->getLocalPath(); if ($repository->isWorkingCopyBare()) { $path = '/hooks/pre-receive'; } else { $path = '/.git/hooks/pre-receive'; } $this->installHook($root.$path); } /* -( Pulling Mercurial Working Copies )----------------------------------- */ /** * @task hg */ private function executeMercurialCreate() { $repository = $this->getRepository(); $path = rtrim($repository->getLocalPath(), '/'); if ($repository->isHosted()) { $repository->execxRemoteCommand( 'init -- %s', $path); } else { $repository->execxRemoteCommand( 'clone --noupdate -- %P %s', $repository->getRemoteURIEnvelope(), $path); } } /** * @task hg */ private function executeMercurialUpdate() { $repository = $this->getRepository(); $path = $repository->getLocalPath(); // This is a local command, but needs credentials. $future = $repository->getRemoteCommandFuture('pull -u'); $future->setCWD($path); try { $future->resolvex(); } catch (CommandException $ex) { $err = $ex->getError(); $stdout = $ex->getStdOut(); // NOTE: Between versions 2.1 and 2.1.1, Mercurial changed the behavior // of "hg pull" to return 1 in case of a successful pull with no changes. // This behavior has been reverted, but users who updated between Feb 1, // 2012 and Mar 1, 2012 will have the erroring version. Do a dumb test // against stdout to check for this possibility. - // See: https://github.com/facebook/phabricator/issues/101/ + // See: https://github.com/phacility/phabricator/issues/101/ // NOTE: Mercurial has translated versions, which translate this error // string. In a translated version, the string will be something else, // like "aucun changement trouve". There didn't seem to be an easy way // to handle this (there are hard ways but this is not a common problem // and only creates log spam, not application failures). Assume English. // TODO: Remove this once we're far enough in the future that deployment // of 2.1 is exceedingly rare? if ($err == 1 && preg_match('/no changes found/', $stdout)) { return; } else { throw $ex; } } } /** * @task hg */ private function installMercurialHook() { $repository = $this->getRepository(); $path = $repository->getLocalPath().'/.hg/hgrc'; $root = dirname(phutil_get_library_root('phabricator')); $bin = $root.'/bin/commit-hook'; $data = array(); $data[] = '[hooks]'; // This hook handles normal pushes. $data[] = csprintf( 'pretxnchangegroup.phabricator = %s %s %s', $bin, $repository->getCallsign(), 'pretxnchangegroup'); // This one handles creating bookmarks. $data[] = csprintf( 'prepushkey.phabricator = %s %s %s', $bin, $repository->getCallsign(), 'prepushkey'); $data[] = null; $data = implode("\n", $data); $this->log('%s', pht('Installing commit hook config to "%s"...', $path)); Filesystem::writeFile($path, $data); } /* -( Pulling Subversion Working Copies )---------------------------------- */ /** * @task svn */ private function executeSubversionCreate() { $repository = $this->getRepository(); $path = rtrim($repository->getLocalPath(), '/'); execx('svnadmin create -- %s', $path); } /** * @task svn */ private function installSubversionHook() { $repository = $this->getRepository(); $root = $repository->getLocalPath(); $path = '/hooks/pre-commit'; $this->installHook($root.$path); } } diff --git a/src/applications/repository/management/PhabricatorRepositoryManagementParentsWorkflow.php b/src/applications/repository/management/PhabricatorRepositoryManagementParentsWorkflow.php index fd49df59a..86fb07c7b 100644 --- a/src/applications/repository/management/PhabricatorRepositoryManagementParentsWorkflow.php +++ b/src/applications/repository/management/PhabricatorRepositoryManagementParentsWorkflow.php @@ -1,157 +1,161 @@ setName('parents') ->setExamples('**parents** [options] [__repository__] ...') ->setSynopsis( pht( 'Build parent caches in repositories that are missing the data, '. 'or rebuild them in a specific __repository__.')) ->setArguments( array( array( 'name' => 'repos', 'wildcard' => true, ), )); } public function execute(PhutilArgumentParser $args) { $repos = $this->loadRepositories($args, 'repos'); if (!$repos) { $repos = id(new PhabricatorRepositoryQuery()) ->setViewer($this->getViewer()) ->execute(); } $console = PhutilConsole::getConsole(); foreach ($repos as $repo) { $monogram = $repo->getMonogram(); if ($repo->isSVN()) { $console->writeOut( "%s\n", pht( 'Skipping "%s": Subversion repositories do not require this '. 'cache to be built.', $monogram)); continue; } $this->rebuildRepository($repo); } return 0; } private function rebuildRepository(PhabricatorRepository $repo) { $console = PhutilConsole::getConsole(); $console->writeOut("%s\n", pht('Rebuilding "%s"...', $repo->getMonogram())); $refs = id(new PhabricatorRepositoryRefCursorQuery()) ->setViewer($this->getViewer()) ->withRefTypes(array(PhabricatorRepositoryRefCursor::TYPE_BRANCH)) ->withRepositoryPHIDs(array($repo->getPHID())) ->execute(); $graph = array(); foreach ($refs as $ref) { + if (!$repo->shouldTrackBranch($ref->getRefName())) { + continue; + } + $console->writeOut( "%s\n", pht('Rebuilding branch "%s"...', $ref->getRefName())); $commit = $ref->getCommitIdentifier(); if ($repo->isGit()) { $stream = new PhabricatorGitGraphStream($repo, $commit); } else { $stream = new PhabricatorMercurialGraphStream($repo, $commit); } $discover = array($commit); while ($discover) { $target = array_pop($discover); if (isset($graph[$target])) { continue; } $graph[$target] = $stream->getParents($target); foreach ($graph[$target] as $parent) { $discover[] = $parent; } } } $console->writeOut( "%s\n", pht( 'Found %s total commit(s); updating...', new PhutilNumber(count($graph)))); $commit_table = id(new PhabricatorRepositoryCommit()); $commit_table_name = $commit_table->getTableName(); $conn_w = $commit_table->establishConnection('w'); $bar = id(new PhutilConsoleProgressBar()) ->setTotal(count($graph)); foreach ($graph as $child => $parents) { $names = $parents; $names[] = $child; $rows = queryfx_all( $conn_w, 'SELECT id, commitIdentifier FROM %T WHERE commitIdentifier IN (%Ls) AND repositoryID = %d', $commit_table_name, $names, $repo->getID()); $map = ipull($rows, 'id', 'commitIdentifier'); foreach ($names as $name) { if (empty($map[$name])) { throw new Exception(pht('Unknown commit "%s"!', $name)); } } $sql = array(); if (!$parents) { // Write an explicit 0 to indicate "no parents" instead of "no data". $sql[] = qsprintf( $conn_w, '(%d, 0)', $map[$child]); } else { foreach ($parents as $parent) { $sql[] = qsprintf( $conn_w, '(%d, %d)', $map[$child], $map[$parent]); } } $commit_table->openTransaction(); queryfx( $conn_w, 'DELETE FROM %T WHERE childCommitID = %d', PhabricatorRepository::TABLE_PARENTS, $map[$child]); if ($sql) { queryfx( $conn_w, 'INSERT INTO %T (childCommitID, parentCommitID) VALUES %Q', PhabricatorRepository::TABLE_PARENTS, implode(', ', $sql)); } $commit_table->saveTransaction(); $bar->update(1); } $bar->done(); } } diff --git a/src/applications/search/application/PhabricatorApplicationSearch.php b/src/applications/search/application/PhabricatorApplicationSearch.php index eb2199947..c1b73f744 100644 --- a/src/applications/search/application/PhabricatorApplicationSearch.php +++ b/src/applications/search/application/PhabricatorApplicationSearch.php @@ -1,48 +1,48 @@ array( '(?:query/(?P[^/]+)/)?' => 'PhabricatorSearchController', 'attach/(?P[^/]+)/(?P\w+)/(?:(?P\w+)/)?' => 'PhabricatorSearchAttachController', 'select/(?P\w+)/' => 'PhabricatorSearchSelectController', 'index/(?P[^/]+)/' => 'PhabricatorSearchIndexController', 'hovercard/(?Pretrieve|test)/' => 'PhabricatorSearchHovercardController', 'edit/(?P[^/]+)/' => 'PhabricatorSearchEditController', 'delete/(?P[^/]+)/(?P[^/]+)/' => 'PhabricatorSearchDeleteController', 'order/(?P[^/]+)/' => 'PhabricatorSearchOrderController', ), ); } } diff --git a/src/applications/search/engine/PhabricatorSearchEngineElastic.php b/src/applications/search/engine/PhabricatorSearchEngineElastic.php index 120460a6c..82e03cf35 100644 --- a/src/applications/search/engine/PhabricatorSearchEngineElastic.php +++ b/src/applications/search/engine/PhabricatorSearchEngineElastic.php @@ -1,263 +1,263 @@ uri = $uri; } public function setTimeout($timeout) { $this->timeout = $timeout; return $this; } public function getTimeout() { return $this->timeout; } public function reindexAbstractDocument( PhabricatorSearchAbstractDocument $doc) { $type = $doc->getDocumentType(); $phid = $doc->getPHID(); $handle = id(new PhabricatorHandleQuery()) ->setViewer(PhabricatorUser::getOmnipotentUser()) ->withPHIDs(array($phid)) ->executeOne(); // URL is not used internally but it can be useful externally. $spec = array( 'title' => $doc->getDocumentTitle(), 'url' => PhabricatorEnv::getProductionURI($handle->getURI()), 'dateCreated' => $doc->getDocumentCreated(), '_timestamp' => $doc->getDocumentModified(), 'field' => array(), 'relationship' => array(), ); foreach ($doc->getFieldData() as $field) { $spec['field'][] = array_combine(array('type', 'corpus', 'aux'), $field); } foreach ($doc->getRelationshipData() as $relationship) { list($rtype, $to_phid, $to_type, $time) = $relationship; $spec['relationship'][$rtype][] = array( 'phid' => $to_phid, 'phidType' => $to_type, 'when' => $time, ); } $this->executeRequest( "/phabricator/{$type}/{$phid}/", $spec, $is_write = true); } public function reconstructDocument($phid) { $type = phid_get_type($phid); $response = $this->executeRequest("/phabricator/{$type}/{$phid}", array()); if (empty($response['exists'])) { return null; } $hit = $response['_source']; $doc = new PhabricatorSearchAbstractDocument(); $doc->setPHID($phid); $doc->setDocumentType($response['_type']); $doc->setDocumentTitle($hit['title']); $doc->setDocumentCreated($hit['dateCreated']); $doc->setDocumentModified($hit['_timestamp']); foreach ($hit['field'] as $fdef) { $doc->addField($fdef['type'], $fdef['corpus'], $fdef['aux']); } foreach ($hit['relationship'] as $rtype => $rships) { foreach ($rships as $rship) { $doc->addRelationship( $rtype, $rship['phid'], $rship['phidType'], $rship['when']); } } return $doc; } private function buildSpec(PhabricatorSavedQuery $query) { $spec = array(); $filter = array(); if (strlen($query->getParameter('query'))) { $spec[] = array( - 'field' => array( + 'term' => array( 'field.corpus' => $query->getParameter('query'), ), ); } $exclude = $query->getParameter('exclude'); if ($exclude) { $filter[] = array( 'not' => array( 'ids' => array( 'values' => array($exclude), ), ), ); } $relationship_map = array( PhabricatorSearchRelationship::RELATIONSHIP_AUTHOR => $query->getParameter('authorPHIDs', array()), PhabricatorSearchRelationship::RELATIONSHIP_OWNER => $query->getParameter('ownerPHIDs', array()), PhabricatorSearchRelationship::RELATIONSHIP_SUBSCRIBER => $query->getParameter('subscriberPHIDs', array()), PhabricatorSearchRelationship::RELATIONSHIP_PROJECT => $query->getParameter('projectPHIDs', array()), PhabricatorSearchRelationship::RELATIONSHIP_REPOSITORY => $query->getParameter('repositoryPHIDs', array()), ); $statuses = $query->getParameter('statuses', array()); $statuses = array_fuse($statuses); $rel_open = PhabricatorSearchRelationship::RELATIONSHIP_OPEN; $rel_closed = PhabricatorSearchRelationship::RELATIONSHIP_CLOSED; $rel_unowned = PhabricatorSearchRelationship::RELATIONSHIP_UNOWNED; $include_open = !empty($statuses[$rel_open]); $include_closed = !empty($statuses[$rel_closed]); if ($include_open && !$include_closed) { $relationship_map[$rel_open] = true; } else if (!$include_open && $include_closed) { $relationship_map[$rel_closed] = true; } if ($query->getParameter('withUnowned')) { $relationship_map[$rel_unowned] = true; } foreach ($relationship_map as $field => $param) { if (is_array($param) && $param) { $should = array(); foreach ($param as $val) { $should[] = array( 'text' => array( "relationship.{$field}.phid" => array( 'query' => $val, 'type' => 'phrase', ), ), ); } // We couldn't solve it by minimum_number_should_match because it can // match multiple owners without matching author. $spec[] = array('bool' => array('should' => $should)); } else if ($param) { $filter[] = array( 'exists' => array( 'field' => "relationship.{$field}.phid", ), ); } } if ($spec) { $spec = array('query' => array('bool' => array('must' => $spec))); } if ($filter) { $filter = array('filter' => array('and' => $filter)); if (!$spec) { $spec = array('query' => array('match_all' => new stdClass())); } $spec = array( 'query' => array( 'filtered' => $spec + $filter, ), ); } if (!$query->getParameter('query')) { $spec['sort'] = array( array('dateCreated' => 'desc'), ); } $spec['from'] = (int)$query->getParameter('offset', 0); $spec['size'] = (int)$query->getParameter('limit', 25); return $spec; } public function executeSearch(PhabricatorSavedQuery $query) { $types = $query->getParameter('types'); if (!$types) { $types = array_keys( PhabricatorSearchApplicationSearchEngine::getIndexableDocumentTypes()); } // Don't use '/phabricator/_search' for the case that there is something // else in the index (for example if 'phabricator' is only an alias to // some bigger index). $uri = '/phabricator/'.implode(',', $types).'/_search'; try { $response = $this->executeRequest($uri, $this->buildSpec($query)); } catch (HTTPFutureResponseStatusHTTP $ex) { // elasticsearch probably uses Lucene query syntax: // http://lucene.apache.org/core/3_6_1/queryparsersyntax.html // Try literal search if operator search fails. if (!strlen($query->getParameter('query'))) { throw $ex; } $query = clone $query; $query->setParameter( 'query', addcslashes( $query->getParameter('query'), '+-&|!(){}[]^"~*?:\\')); $response = $this->executeRequest($uri, $this->buildSpec($query)); } $phids = ipull($response['hits']['hits'], '_id'); return $phids; } private function executeRequest($path, array $data, $is_write = false) { $uri = new PhutilURI($this->uri); $data = json_encode($data); $uri->setPath($path); $future = new HTTPSFuture($uri, $data); if ($is_write) { $future->setMethod('PUT'); } if ($this->getTimeout()) { $future->setTimeout($this->getTimeout()); } list($body) = $future->resolvex(); if ($is_write) { return null; } $body = json_decode($body, true); if (!is_array($body)) { throw new Exception("elasticsearch server returned invalid JSON!"); } return $body; } } diff --git a/src/applications/settings/application/PhabricatorApplicationSettings.php b/src/applications/settings/application/PhabricatorApplicationSettings.php index 65ad9505a..a4028f6b9 100644 --- a/src/applications/settings/application/PhabricatorApplicationSettings.php +++ b/src/applications/settings/application/PhabricatorApplicationSettings.php @@ -1,57 +1,61 @@ array( '(?:(?P\d+)/)?(?:panel/(?P[^/]+)/)?' => 'PhabricatorSettingsMainController', 'adjust/' => 'PhabricatorSettingsAdjustController', ), ); } public function getApplicationGroup() { return self::GROUP_UTILITIES; } public function buildMainMenuItems( PhabricatorUser $user, PhabricatorController $controller = null) { $items = array(); if ($user->isLoggedIn() && $user->isUserActivated()) { $selected = ($controller instanceof PhabricatorSettingsMainController); $item = id(new PHUIListItemView()) ->setName(pht('Settings')) ->setIcon('settings-sm') ->addClass('core-menu-item') ->setSelected($selected) ->setHref('/settings/') ->setAural(pht('Settings')) ->setOrder(400); $items[] = $item; } return $items; } } diff --git a/src/applications/tokens/application/PhabricatorApplicationTokens.php b/src/applications/tokens/application/PhabricatorApplicationTokens.php index 39d665e7b..dd1a77d2d 100644 --- a/src/applications/tokens/application/PhabricatorApplicationTokens.php +++ b/src/applications/tokens/application/PhabricatorApplicationTokens.php @@ -1,46 +1,46 @@ array( '' => 'PhabricatorTokenGivenController', 'given/' => 'PhabricatorTokenGivenController', 'give/(?[^/]+)/' => 'PhabricatorTokenGiveController', 'leaders/' => 'PhabricatorTokenLeaderController', ), ); } public function getEventListeners() { return array( new PhabricatorTokenUIEventListener(), ); } } diff --git a/src/applications/uiexample/application/PhabricatorApplicationUIExamples.php b/src/applications/uiexample/application/PhabricatorApplicationUIExamples.php index e74c1138a..e7774432a 100644 --- a/src/applications/uiexample/application/PhabricatorApplicationUIExamples.php +++ b/src/applications/uiexample/application/PhabricatorApplicationUIExamples.php @@ -1,42 +1,42 @@ array( '' => 'PhabricatorUIExampleRenderController', 'view/(?P[^/]+)/' => 'PhabricatorUIExampleRenderController', ), ); } } diff --git a/src/docs/user/feedback.diviner b/src/docs/user/feedback.diviner index 216482030..bfb395be2 100644 --- a/src/docs/user/feedback.diviner +++ b/src/docs/user/feedback.diviner @@ -1,149 +1,149 @@ @title Give Feedback! Get Support! @short Feedback/Support @group intro How to give us feedback, report bugs, and request features, and get support for problems with Phabricator. = Overview = We'd love to hear your feedback about Phabricator, whether it's good or bad. We stay on top of bug reports and fix many of them within a day or two (and sometimes within hours). The Phabricator roadmap is determined in large part by user feedback and feature requests. Your feedback matters, will often have an immediate short-term impact, and the project leads are actively listening to it. We also try to provide a very high level of free support. If you have trouble with anything or just don't understand how something works, ask us! We're happy to help, and it's usually valuable for us because we can prevent the problem in the code (or document it better) so future users don't hit it. Some day we will no doubt grow callous and distant, but for now the community is small enough that we can provide a high level of service and support to everyone and still have plenty of time to write code. If you're in the SF bay area, we're also happy to come onsite and help you set things up, answer any questions you might have, or just hang out and tell Facebook war stories. The best ways to provide feedback are: = Maniphest = The best way to report bugs and request features is through [[http://secure.phabricator.com/maniphest/task/create/ | Maniphest]]. Just file the bug/request and we'll handle everything else. (If it's time-sensitive or blocking you, feel free to assign it to `epriestley`.) Feel free to file support requests, general questions, or random feedback this way, too. = GitHub Issues = You can also use -[[https://github.com/facebook/phabricator/issues/new | GitHub Issues]] if you +[[https://github.com/phacility/phabricator/issues/new | GitHub Issues]] if you prefer. = IRC = We're active in #phabricator on FreeNode, and it's the best place to ask questions and get support. = Email = You can email us at `btrahan@phacility.com` and `epriestley@phacility.com`. = Filing Good Feature Requests = When filing a feature request, please provide as much information as possible, especially about what your use case is and why you want the feature. Explaining what your larger goals are is very helpful, and lets us design better features. Tell us what your problem is before you tell us your idea to solve it: sometimes we can come up with a better approach to the problem, a slightly different approach that solves more problems or helps other users, or a way to make the problem go away entirely. = Filing Good Bug Reports = When filing a bug report, please provide as much information as possible. In particular: - If you received an error message, please please please provide it! It is often incredibly useful. See @{article:Please Please Please}. - The second most useful thing to us is reproduction steps. If at all possible, provide a brief list of steps required to reproduce the problem. - If something you didn't expect (other than a crash or obvious error) happened, tell us what you expected and what actually happened. We can fix clearly-described bugs with reproduction steps and error messages much more quickly than vague bugs we can't reproduce. In particular, if you don't include error messages, we need to ask you for error messages. We will nearly always need to do this, and nearly always be unable to continue until you provide them. You'll save everyone time if you spend a few extra seconds copy/pasting them in the first place. Generally, err on the side of giving us too much information. It's much quicker and easier for us to filter out information that isn't relevant than it is to go back and forth asking you to provide more details. Tips: - For issues with `arc`, you can get more information by running the command with the `--trace` flag. For instance, run `arc diff --trace` instead of `arc diff`. Including this output in your bug report is often helpful. - For issues with Phabricator, check your webserver error logs for more information. For Apache, this is usually `/var/log/httpd/error.log` or `/var/log/apache2/error.log`. - For issues with the UI, check your Javascript error console in your browser. - Some other things, like daemons, have debug flags or troubleshooting steps covered in their documentation. Enabling these flags may give you more information about the problem. = Unreproducible Problems = Before we can fix a bug, we need to reproduce it. If we can't reproduce a problem, we can't tell if we've fixed it and often won't be able to figure out why it is occurring. Most problems reproduce easily, but some are more difficult to reproduce. We will generally make a reasonable effort to reproduce problems, but sometimes we will be unable to reproduce an issue. Many of these unreproducible issues turn out to be bizarre environmental problems that are unique to one user's install, and figuring out what is wrong takes a very long time with a lot of back and forth as we ask questions to narrow down the cause of the problem. When we eventually figure it out and fix it, few others benefit (in some cases, no one else). This sort of fishing expedition is not a good use of anyone's time, and it's very hard for us to prioritize solving these problems because they represent a huge effort for very little benefit. These problems are a tiny fraction of requests (maybe 1-2%) but take up the vast majority of our support effort (maybe 80%). **If you want us to fix a bug we can't reproduce, you need to build us a working reproduction case.** Generally, this means a `root` login on a machine where the issue occurs. For example: - Bring up a new machine (e.g., in EC2), install Phabricator on it, configure it so the problem is reproducible, and then give us access to it. - If the machine Phabricator is running on is nonessential/nonsensitive and you're comfortable with us having root on it, give us credentials. - If the issue is with Git, SVN or Mercurial, create a new empty repository, add a commit or series of commits which replicate the issue, and give us a zip/tarball of the repository as a reproduction case. - If you're in the San Francisco bay area, we can probably swing by and fix the issue onsite. These are all fairly heavyweight and will take some time, but often //less// of your time than a fishing expedition. If you can build a reproduction case, there is a very high chance we can resolve your problem quickly. Alternatively, you can pay us an enormous pile of money for some kind of enterprise support thing and we'd be thrilled to go fishing with you for as long as you remain solvent. Email us (see above) for specifics. = Next Steps = Continue by: - Filing a bug of feature request in [[http://secure.phabricator.com/maniphest/task/create/ | Maniphest]]; or - contributing to Phabricator with @{article:Contributor Introduction}. diff --git a/src/docs/user/installation_guide.diviner b/src/docs/user/installation_guide.diviner index ece216513..b829c5aa1 100644 --- a/src/docs/user/installation_guide.diviner +++ b/src/docs/user/installation_guide.diviner @@ -1,173 +1,173 @@ @title Installation Guide @group intro This document contains basic install instructions to get Phabricator up and running. = Installation Requirements = You will need **a computer**. Options include: - **A Normal Computer**: This is strongly recommended. Many installs use a VM in EC2. Phabricator installs properly and works well on a normal computer. - **A Shared Host**: This may work, but is not recommended. Many shared hosting environments have restrictions which prevent some of Phabricator's features from working. Consider using a normal computer instead. - **A SAN Appliance, Network Router, Gaming Console, Raspberry Pi, etc.**: Although you may be able to install Phabricator on specialized hardware, it is unlikely to work well and will be difficult for us to support. Strongly consider using a normal computer instead. - **A Toaster, Car, Firearm, Thermostat, etc.**: Yes, many modern devices now have embedded computing capability. We live in interesting times. However, you should not install Phabricator on these devices. Instead, install it on a normal computer. To install the Phabricator server software, you will need an **operating system** on your normal computer which is **not Windows**. Note that **the command line interface //does// work on Windows**, and **you can //use// Phabricator from any operating system with a web browser**. However, the server software does not run on Windows. It does run on most other operating systems, so choose one of these instead: - **Linux**: Most installs use Linux. - **Mac OS X**: Mac OS X is an acceptable flavor of Linux. - **FreeBSD**: While FreeBSD is certainly not a flavor of Linux, it is a fine operating system possessed of many desirable qualities, and Phabricator will install and run properly on FreeBSD. - **Solaris, etc.**: Other systems which look like Linux and quack like Linux will generally work fine, although we may suffer a reduced ability to support and resolve issues on unusual operating systems. Beyond an operating system, you will need **a webserver**. - **Apache**: Many installs use Apache + `mod_php`. - **nginx**: Many installs use nginx + `php-fpm`. - **lighttpd**: `lighttpd` is less popular than Apache or nginx, but it works fine. - **Other**: Other webservers which can run PHP are also likely to work fine, although these installation instructions will not cover how to set them up. - **PHP Builtin Server**: You can use the builtin PHP webserver for development or testing, although it should not be used in production. You will also need: - **MySQL**: You need MySQL. - **PHP**: You need PHP 5.2 or newer. You'll probably also need a **domain name**. In particular, you should read this note: NOTE: Phabricator must be installed on an entire domain. You can not install it to a path on an existing domain, like `example.com/phabricator/`. Instead, install it to an entire domain or subdomain, like `phabricator.example.com`. = Installing Required Components = If you are installing on Ubuntu or an RedHat derivative, there are install scripts available which should handle most of the things discussed in this document for you: - **RedHat Derivatives**: - **Ubuntu**: If those work for you, you can skip directly to the @{article:Configuration Guide}. These scripts are also available in the ##scripts/install## directory in the project itself. Otherwise, here's a general description of what you need to install: - git (usually called "git" in package management systems) - Apache (usually "httpd" or "apache2") (or nginx) - MySQL Server (usually "mysqld" or "mysql-server") - PHP (usually "php") - Required PHP extensions: mbstring, iconv, mysql (or mysqli), curl, pcntl (these might be something like "php-mysql" or "php5-mysql") - Optional PHP extensions: gd, apc (special instructions for APC are available below if you have difficulty installing it), xhprof (instructions below, you only need this if you are developing Phabricator) If you already have LAMP setup, you've probably already got everything you need. It may also be helpful to refer to the install scripts above, even if they don't work for your system. Now that you have all that stuff installed, grab Phabricator and its dependencies: $ cd somewhere/ # pick some install directory - somewhere/ $ git clone git://github.com/facebook/libphutil.git - somewhere/ $ git clone git://github.com/facebook/arcanist.git - somewhere/ $ git clone git://github.com/facebook/phabricator.git + somewhere/ $ git clone git://github.com/phacility/libphutil.git + somewhere/ $ git clone git://github.com/phacility/arcanist.git + somewhere/ $ git clone git://github.com/phacility/phabricator.git = Installing APC (Optional) = Like everything else written in PHP, Phabricator will run much faster with APC installed. You likely need to install "pcre-devel" first: sudo yum install pcre-devel Then you have two options. Either install via PECL (try this first): sudo yum install php-pear sudo pecl install apc **If that doesn't work**, grab the package from PECL directly and follow the build instructions there: http://pecl.php.net/package/APC Installing APC is optional but **strongly recommended**, especially on production hosts. Once APC is installed, test that it is available by running: php -i | grep apc If it doesn't show up, add: extension=apc.so ..to "/etc/php.d/apc.ini" or the "php.ini" file indicated by "php -i". = Installing XHProf (Optional) = XHProf is a PHP profiling tool. You don't need to install it unless you are developing Phabricator and making performance changes. You can install xhprof with: $ pecl install xhprof If you have a PEAR version prior to 1.9.3, you may run into a `phpize` failure. If so, you can download the source and build it with: $ cd extension/ $ phpize $ ./configure $ make $ sudo make install You may also need to add "##extension=xhprof.so##" to your php.ini. See for more information. = Updating Phabricator = Since Phabricator is under active development, you should update frequently. To update Phabricator: - Stop the webserver (including `php-fpm`, if you use it). - Run `git pull` in `libphutil/`, `arcanist/` and `phabricator/`. - Run `phabricator/bin/storage upgrade`. - Restart the webserver (and `php-fpm`, if you stopped it earlier). For more details, see @{article:Configuration Guide}. You can use a script similar to this one to automate the process: http://www.phabricator.com/rsrc/install/update_phabricator.sh = Next Steps = Continue by: - configuring Phabricator with the @{article:Configuration Guide}. diff --git a/src/docs/user/userguide/arcanist.diviner b/src/docs/user/userguide/arcanist.diviner index 7f5d44952..5211e173d 100644 --- a/src/docs/user/userguide/arcanist.diviner +++ b/src/docs/user/userguide/arcanist.diviner @@ -1,185 +1,185 @@ @title Arcanist User Guide @group userguide Guide to Arcanist, a command-line interface to Phabricator. Arcanists provides command-line access to many Phabricator tools (like Differential, Files, and Paste), integrates with static analysis ("lint") and unit tests, and manages common workflows like getting changes into Differential for review. A detailed command reference is available by running ##arc help##. This document provides an overview of common workflows and installation. Arcanist has technical, contributor-focused documentation here: = Quick Start = A quick start guide is available at @{article:Arcanist Quick Start}. It provides a much more compact summary of how to get `arc` set up and running for a new project. You may want to start there, and return here if you need more information. = Overview = Arcanist is a wrapper script that sits on top of other tools (e.g., Differential, linters, unit test frameworks, git, Mercurial, and SVN) and provides a simple command-line API to manage code review and some related revision control operations. For a detailed list of all available commands, run: $ arc help For detailed information about a specific command, run: $ arc help Arcanist allows you to do things like: - get detailed help about available commands with ##arc help## - send your code to Differential for review with ##arc diff## (for detailed instructions, see @{article:Arcanist User Guide: arc diff}) - show pending revision information with ##arc list## - find likely reviewers for a change with ##arc cover## - apply changes in a revision to the working copy with ##arc patch## - download a patch from Differential with ##arc export## - update Git commit messages after review with ##arc amend## - commit SVN changes with ##arc commit## - push Git and Mercurial changes with ##arc land## - view enhanced information about Git branches with ##arc branch## Once you've configured lint and unit test integration, you can also: - check your code for syntax and style errors with ##arc lint## (see @{article:Arcanist User Guide: Lint}) - run unit tests that cover your changes with ##arc unit## Arcanist integrates with other tools: - upload and download files with ##arc upload## and ##arc download## - create and view pastes with ##arc paste## Arcanist has some advanced features as well, you can: - execute Conduit method calls with ##arc call-conduit## - create or update libphutil libraries with ##arc liberate## - activate tab completion with ##arc shell-complete## - install arc as a pre-commit hook with ##arc svn-hook-pre-commit## or ##arc git-hook-pre-receive## - ...or extend Arcanist and add new commands. Except where otherwise noted, these workflows are generally agnostic to the underlying version control system and will work properly in git, Mercurial, or SVN repositories. = Installing Arcanist = Arcanist is meant to be installed on your local machine or development server -- whatever machine you're editing code on. It runs on: - Linux; - Other operating systems which are pretty similar to Linux, or which Linux is pretty similar to; - FreeBSD, a fine operating system held in great esteem by many; - Mac OS X (see @{article:Arcanist User Guide: Mac OS X}); and - Windows (see @{article:Arcanist User Guide: Windows}). Arcanist is written in PHP, so you need to install the PHP CLI first if you don't already have it. Arcanist should run on PHP 5.2 and newer. If you don't have PHP installed, you can download it from . To install Arcanist, pick an install directory and clone the code from GitHub: - some_install_path/ $ git clone git://github.com/facebook/libphutil.git - some_install_path/ $ git clone git://github.com/facebook/arcanist.git + some_install_path/ $ git clone git://github.com/phacility/libphutil.git + some_install_path/ $ git clone git://github.com/phacility/arcanist.git This should leave you with a directory structure like this some_install_path/ # Wherever you chose to install it. arcanist/ # Arcanist-specific code and libraries. libphutil/ # A shared library Arcanist depends upon. Now add ##some_install_path/arcanist/bin/## to your PATH environment variable. When you type "arc", you should see something like this: Usage Exception: No command provided. Try 'arc help'. If you get that far, you've done things correctly. If you get an error or have trouble getting this far, see these detailed guides: - On Windows: @{article:Arcanist User Guide: Windows} - On Mac OS X: @{article:Arcanist User Guide: Mac OS X} You can later upgrade Arcanist and libphutil to the latest versions with `arc upgrade`: $ arc upgrade == Installing Arcanist for a Team == Arcanist changes quickly, so it can be something of a headache to get it installed and keep people up to date. Here are some approaches you might be able to use: - Facebook does most development on development servers, which have a standard environment and NFS mounts. Arcanist and libphutil themselves live on an NFS mount, and the default `.bashrc` adds them to the PATH. Updating the mount source updates everyone's versions, and new employees have a working `arc` when they first log in. - Another common approach is to write an install script as an action into existing build scripts, so users can run `make install-arc` or `ant install-arc` or similar. - In general, if this sucks and is causing you pain, let us know (see @{article:Give Feedback! Get Support!}). We're planning to improve this at some point, but it's somewhat complicated to get right. While it can take a little time to set up, we aren't getting feedback that it's a persistent pain point, so it hasn't been a priority. == Installing Tab Completion == If you use ##bash##, you can set up tab completion by adding something like this to your ##.bashrc##, ##.profile## or similar: source /path/to/arcanist/resources/shell/bash-completion == Configuration == Some Arcanist commands can be configured. This configuration is read from several sources: # A customization can force any setting with @{method@arcanist:ArcanistWorkingCopyIdentity::setRuntimeConfig}. # User can specify settings for working copy in `arc/config` file located in VCS directory (e.g. `.git/arc/config`) in JSON format. This file can also be modified by running `arc set-config --local`. # Repository can specify its config in `.arcconfig` in JSON format. # User can specify the settings in `~/.arcrc` (JSON) through the `config` key. This file can be modified also by `arc set-config --global`. # Machine can specify the settings with `/etc/arcconfig` (JSON). On Windows, the file path is 'C:\ProgramData\Phabricator\Arcanist\config`. The first place where the setting is defined wins. -Existing settings can be printed with `arc set-config --show`. +Existing settings can be printed with `arc get-config`. == Next Steps == Continue by: - setting up a new project for use with `arc`, with @{article:Arcanist User Guide: Configuring a New Project}; or - learning how to use `arc` to send changes for review with @{article:Arcanist User Guide: arc diff}. Advanced topics are also available. These are detailed guides to configuring technical features of `arc` that refine its behavior. You do not need to read them to get it working. - @{article:Arcanist User Guide: Commit Ranges} - @{article:Arcanist User Guide: Lint} - @{article:Arcanist User Guide: Customizing Existing Linters} - @{article:Arcanist User Guide: Customizing Lint, Unit Tests and Workflows} - @{article:Arcanist User Guide: Code Coverage} - @{article:Arcanist User Guide: Repository Hooks} diff --git a/src/docs/user/userguide/arcanist_new_project.diviner b/src/docs/user/userguide/arcanist_new_project.diviner index c8f938957..c29a53dfb 100644 --- a/src/docs/user/userguide/arcanist_new_project.diviner +++ b/src/docs/user/userguide/arcanist_new_project.diviner @@ -1,227 +1,227 @@ @title Arcanist User Guide: Configuring a New Project @group userguide Explains how to configure Arcanist projects with `.arcconfig` files. = Overview = In most cases, you should be able to use `arc` without specifically configuring your project for it. If you want to adjust `arc` behaviors, you can create a `.arcconfig` file in your project to provide project-specific settings. = .arcconfig Basics = An `.arcconfig` file is a JSON file which you check into your project's root. Arcanist uses `.arcconfig` files to customize a number of things about its behavior. The first thing you're likely to want to configure is the URI for your Phabricator install. A simple, valid file looks something like this: name=.arcconfig { "phabricator.uri" : "https://phabricator.example.com/" } For details on available options, see below. NOTE: You should commit your `.arcconfig` file! It contains project configuration, not user configuration. = Advanced .arcconfig = Common options are: - **phabricator.uri**: the URI for the Phabricator install that `arc` should connect to when run in this project. This option was previously called `conduit_uri`. - **repository.callsign**: The callsign of this repository in Diffusion. Normally, `arc` can detect this automatically, but if it can't figure it out you can specify it explicitly. Use `arc which` to understand the detection process. - **history.immutable**: Configures `arc` to use workflows which never rewrite history in the working copy. By default, `arc` will perform some rewriting of unpublished history (amending commit messages, squash merging) on some workflows in Git. The distinctions are covered in detail below. Other options include: - **load**: list of additional Phutil libraries to load at startup. See below for details about path resolution, or see @{article:libphutil Libraries User Guide} for a general introduction to libphutil libraries. - **project.name**: name an "Arcanist Project" to associate this working copy (Git, Mercurial) or directory (SVN) with. Previously, this was a required option, but `arc` can now usually operate without it in Git and Mercurial. This option was previously called `project_id`. - **https.cabundle**: specifies the path to an alternate certificate bundle for use when making HTTPS connections. - **lint.engine**: the name of a subclass of @{class@arcanist:ArcanistLintEngine}, which should be used to apply lint rules to this project. See @{article:Arcanist User Guide: Lint}. - **unit.engine**: the name of a subclass of @{class@arcanist:ArcanistBaseUnitTestEngine}, which should be used to apply unit test rules to this project. See @{article:Arcanist User Guide: Customizing Lint, Unit Tests and Workflows}. These options are supported, but their use is discouraged: - **http.basicauth.user**: specify an HTTP basic auth username for use when connecting to Phabricator. - **http.basicauth.pass**: specify an HTTP basic auth password for use when connecting to Phabricator. - **https.blindly-trust-domains**: a list of domains to trust blindly over HTTPS, even if their certificates are invalid. This is a brute force solution to certificate validity problems, and is discouraged. Instead, use valid certificates. -For a complete list of options, run `arc set-config --show`. Although all +For a complete list of options, run `arc get-config`. Although all options can be set in `.arcconfig`, some options (like `editor`) usually do not make sense to set here because they're likely to vary from user to user. = History Mutability = Arcanist workflows run in two broad modes: either history is //mutable// or //immutable//. Under a //mutable// history, `arc` commands may rewrite the working copy history; under an //immutable// history, they may not. You control history mutability by setting `history.immutable` to `true` or `false` in your configuration. By default, it is `false` in Git (i.e., //mutable//) and `true` in Mercurial (i.e., //immutable//). The sections below explain how these settings affect workflows. == History Mutability: Git == In a workflow with //mutable// history, you rewrite local history. You develop in feature branches, but squash or amend before pushing by using ##git commit --amend##, ##git rebase -i##, or `git merge --squash`. Generally, one idea in the remote is represented by one commit. In a workflow with //immutable// history, you do not rewrite local history. You develop in feature branches and push them without squashing commits. You do not use ##git commit --amend## or ##git rebase -i##. Generally, one idea in the remote is represented by many commits. Practically, these are the differences you'll see based on your setting: - **Mutable** - `arc diff` will prompt you to amend lint changes into HEAD. - `arc diff` will amend the commit message in HEAD after creating a revision. - `arc land` will default to the `--squash` strategy. - `arc amend` will amend the commit message in HEAD with information from the corresponding or specified Differential revision. - **Immutable** - `arc diff` will abort if it makes lint changes. - `arc diff` will not amend the commit message in HEAD after creating a revision. - `arc land` will default to the `--merge` strategy. - `arc amend` will exit with an error message. == History Mutability: Mercurial == Before version 2.2, stock Mercurial has no history mutation commands, so this setting has no effect. With Mercurial 2.2. or newer, making history //mutable// means: - **Mutable** (versions 2.2 and newer) - `arc diff` will amend the commit message in `.` after creating a revision. - `arc amend` will amend the commit message in `.` with information from the corresponding or specified Differential revision. - **Immutable** (or versions prior to 2.2) - `arc diff` will not amend the commit message in `.` after creating a revision. - `arc amend` will exit with an error message. = How Libraries Are Located = If you specify an external library to load, like 'examplelib', and use a relative path like this: { ... "load": [ "examplelib/src" ], ... } ...arc looks for it by trying these paths: - `path/to/root/examplelib/src/` First, arc looks in the project's root directory (where the .arcconfig lives) to see if the library is part of the project. This makes it easy to just put project-specific code in a project. - `path/to/root/../examplelib/src/` Next, arc looks //next to// the project's root directory to see if the library is in a sibling directory. If you work with several repositories, this makes it easy to put all the `arc` code in one repository and just check it out in the same directory as everything else. - `php/include/path/examplelib/src` Finally, arc falls back to PHP, which will look in paths described in the `include_path` php.ini setting. This allows you to install libraries in some global location if you prefer. You can alternately supply an absolute path, like `/var/arc/examplelib/src`, but then everyone will need to install the library at that exact location. NOTE: Specify the path to the directory which includes `__phutil_library_init__.php`. For example, if your init file is in `examplelib/src/__phutil_library_init__.php`, specify `examplelib/src`, not just `examplelib/`. The general intent here is: - Put project-specific code in some directory in the project, like `support/arc/src/`. - Put shared code (e.g., which enforces general coding standards or hooks up to unit tests or whatever) in a separate repository and check it out next to other repositories. - Or put everything in some standard location and add it to `include_path`. = Running Without .arcconfig = Although you don't need to set up `.arcconfig`, and you can run `arc` command that require a working copy in any Git, Subversion or Mercurial working copy, some features won't work unless you set up an `.arcconfig` file. Without `.arcconfig`: - You will need to set a default Phabricator URI with `arc set-config default `, or specify an explicit URI with `--conduit-uri` each time you run a command. - You will not be able to run linters through arc unless you pass `--engine` explicitly. - You will not be able to customize certain linter parameters even with `--engine`. - You will not be able to run unit tests through arc unless you pass `--engine` explicitly. - You will not be able to trigger lint and unit integration through `arc diff`. - You will not be able to put Git working copies into immutable history mode (see below). - You will not be able to specify a repository encoding. UTF-8 will be assumed if you do not pass `--encoding`. - You will not be able to add plugins to arc to modify existing workflows or add new ones. - You will not be able to load additional libraries unless you specify them explicitly with `--load-phutil-library`. - Symbol index integration, which allows users to click function or class names in Differential and jump to their definitions, will not work. - `arc patch` will be unable to detect that you are applying changes to the wrong project. - In Subversion, `arc` will be unable to determine the canonical root of a project, and will assume it is the working directory (in Subversion prior to 1.7) or the root of the checkout (in Subversion after 1.7). This means the paths of files in diffs won't be anchored to the same place, and will have different amounts of path context, which may be confusing for reviewers and will sometimes prevent patches from applying properly if they are applied against a different directory than they were generated from. - In Subversion, `arc` will be unable to guess that you intend to update an existing revision; you must use `--update` explicitly or `--preview` and attach diffs via the web interface. = Next Steps = Continue by: - returning to @{article:Arcanist User Guide}. diff --git a/src/docs/user/userguide/arcanist_quick_start.diviner b/src/docs/user/userguide/arcanist_quick_start.diviner index 141416d30..3d4407f42 100644 --- a/src/docs/user/userguide/arcanist_quick_start.diviner +++ b/src/docs/user/userguide/arcanist_quick_start.diviner @@ -1,86 +1,86 @@ @title Arcanist Quick Start @group userguide Quick guide to getting Arcanist working for a new project. This is a summary of steps to install Arcanist, configure a project for use with it, and run `arc` to send changes for review. = Install Arcanist = For detailed instructions on installing Arcanist, see @{article:Arcanist User Guide}. - For Mac OS X, see @{article:Arcanist User Guide: Mac OS X}. - For Windows, see @{article:Arcanist User Guide: Windows}. First, install dependencies: - Install PHP. - Install Git. Then install Arcanist itself: $ mkdir somewhere/ $ cd somewhere/ - somewhere/ $ git clone git://github.com/facebook/libphutil.git - somewhere/ $ git clone git://github.com/facebook/arcanist.git + somewhere/ $ git clone git://github.com/phacility/libphutil.git + somewhere/ $ git clone git://github.com/phacility/arcanist.git Add `arc` to your path: $ export PATH="$PATH:/somewhere/arcanist/bin/" This won't work for Windows, see @{article:Arcanist User Guide: Windows} for instructions. = Configure Your Project = For detailed instructions on project configuration, see @{article:Arcanist User Guide: Configuring a New Project}. Create a `.arcconfig` file in your project's working copy: $ cd yourproject/ yourproject/ $ $EDITOR .arcconfig yourproject/ $ cat .arcconfig { "project_id" : "yourprojectname", "conduit_uri" : "https://phabricator.example.com/" } Set `project_id` to a string that identifies the project. Set `conduit_uri` to the URI for your Phabricator install (where `arc` should send changes to). NOTE: You should **commit this file** to the repository. = Install Arcanist Credentials = Credentials allow you to authenticate. You must have an account on Phabricator before you can perform this step. $ cd yourproject/ yourproject/ $ arc install-certificate ... Follow the instructions. This will link your user account on your local machine to your Phabricator account. = Send Changes For Review = For detailed instructions on using `arc diff`, see @{article:Arcanist User Guide: arc diff}. $ $EDITOR file.c $ arc diff = Next Steps = Continue by: - learning more about project configuration with @{article:Arcanist User Guide: Configuring a New Project}; or - learning more about `arc diff` with @{article:Arcanist User Guide: arc diff}; or - returning to @{article:Arcanist User Guide}. diff --git a/src/view/phui/PHUIFeedStoryView.php b/src/view/phui/PHUIFeedStoryView.php index c1b4df9cb..5fc874547 100644 --- a/src/view/phui/PHUIFeedStoryView.php +++ b/src/view/phui/PHUIFeedStoryView.php @@ -1,287 +1,275 @@ chronologicalKey = $chronological_key; return $this; } public function getChronologicalKey() { return $this->chronologicalKey; } public function setTitle($title) { $this->title = $title; return $this; } public function getTitle() { return $this->title; } public function setEpoch($epoch) { $this->epoch = $epoch; return $this; } public function setImage($image) { $this->image = $image; return $this; } public function setImageHref($image_href) { $this->imageHref = $image_href; return $this; } public function setAppIcon($icon) { $this->appIcon = $icon; return $this; } public function setViewed($viewed) { $this->viewed = $viewed; return $this; } public function getViewed() { return $this->viewed; } public function setHref($href) { $this->href = $href; return $this; } public function setTokenBar(array $tokens) { $this->tokenBar = $tokens; return $this; } public function addProject($project) { $this->projects[] = $project; return $this; } public function addAction(PHUIIconView $action) { $this->actions[] = $action; return $this; } public function setPontification($text, $title = null) { if ($title) { $title = phutil_tag('h3', array(), $title); } $copy = phutil_tag( 'div', array( 'class' => 'phui-feed-story-bigtext-post', ), array( $title, $text)); $this->appendChild($copy); return $this; } public function getHref() { return $this->href; } public function renderNotification($user) { $classes = array( 'phabricator-notification', ); if (!$this->viewed) { $classes[] = 'phabricator-notification-unread'; } if ($this->epoch) { if ($user) { $foot = phabricator_datetime($this->epoch, $user); $foot = phutil_tag( 'span', array( 'class' => 'phabricator-notification-date'), $foot); } else { $foot = null; } } else { $foot = pht('No time specified.'); } return javelin_tag( 'div', array( 'class' => implode(' ', $classes), 'sigil' => 'notification', 'meta' => array( 'href' => $this->getHref(), ), ), array($this->title, $foot)); } public function render() { require_celerity_resource('phui-feed-story-css'); Javelin::initBehavior('phabricator-hovercards'); - $oneline = !$this->hasChildren(); $body = null; $foot = null; $image_style = null; $actor = ''; if ($this->image) { $actor = new PHUIIconView(); $actor->setImage($this->image); $actor->addClass('phui-feed-story-actor-image'); if ($this->imageHref) { $actor->setHref($this->imageHref); } } if ($this->epoch) { // TODO: This is really bad; when rendering through Conduit and via // renderText() we don't have a user. if ($this->user) { $foot = phabricator_datetime($this->epoch, $this->user); } else { $foot = null; } } else { $foot = pht('No time specified.'); } if ($this->chronologicalKey) { $foot = phutil_tag( 'a', array( 'href' => '/feed/'.$this->chronologicalKey.'/', ), $foot); } $icon = null; if ($this->appIcon) { $icon = new PHUIIconView(); $icon->setSpriteIcon($this->appIcon); $icon->setSpriteSheet(PHUIIconView::SPRITE_APPS); } - $ol_foot = null; - if ($oneline) { - $ol_foot = phutil_tag( - 'div', - array( - 'class' => 'phui-feed-story-oneline-foot' - ), - array( - $icon, - $foot)); - } - $action_list = array(); $icons = null; foreach ($this->actions as $action) { $action_list[] = phutil_tag( 'li', array( 'class' => 'phui-feed-story-action-item' ), $action); } if (!empty($action_list)) { $icons = phutil_tag( 'ul', array( 'class' => 'phui-feed-story-action-list' ), $action_list); } $head = phutil_tag( 'div', array( 'class' => 'phui-feed-story-head', ), array( - (!$oneline ? $actor : null), + $actor, nonempty($this->title, pht('Untitled Story')), $icons, - $ol_foot )); if (!empty($this->tokenBar)) { $tokenview = phutil_tag( 'div', array( 'class' => 'phui-feed-token-bar' ), $this->tokenBar); $this->appendChild($tokenview); } $body_content = $this->renderChildren(); if ($body_content) { $body = phutil_tag( 'div', array( 'class' => 'phui-feed-story-body', ), $body_content); } - if ($oneline) { - $foot = null; - } else { - $foot = phutil_tag( - 'div', - array( - 'class' => 'phui-feed-story-foot', - ), - array( - $icon, - $foot)); - } + $foot = phutil_tag( + 'div', + array( + 'class' => 'phui-feed-story-foot', + ), + array( + $icon, + $foot)); $classes = array('phui-feed-story'); - if ($oneline) { - $classes[] = 'phui-feed-story-oneline'; - } return id(new PHUIBoxView()) ->addClass(implode(' ', $classes)) ->setBorder(true) ->addMargin(PHUI::MARGIN_MEDIUM_BOTTOM) ->appendChild(array($head, $body, $foot)); } public function setAppIconFromPHID($phid) { switch (phid_get_type($phid)) { case PholioPHIDTypeMock::TYPECONST: $this->setAppIcon("pholio-dark"); break; case PhabricatorMacroPHIDTypeMacro::TYPECONST: $this->setAppIcon("macro-dark"); break; + case ManiphestPHIDTypeTask::TYPECONST: + $this->setAppIcon('maniphest-dark'); + break; + case DifferentialPHIDTypeRevision::TYPECONST: + $this->setAppIcon('differential-dark'); + break; + case PhabricatorCalendarPHIDTypeEvent::TYPECONST: + $this->setAppIcon('calendar-dark'); + break; } } } diff --git a/src/view/phui/PHUIObjectItemView.php b/src/view/phui/PHUIObjectItemView.php index f88820745..80592fe5f 100644 --- a/src/view/phui/PHUIObjectItemView.php +++ b/src/view/phui/PHUIObjectItemView.php @@ -1,617 +1,647 @@ disabled = $disabled; return $this; } public function getDisabled() { return $this->disabled; } public function addHeadIcon($icon) { $this->headIcons[] = $icon; return $this; } public function setObjectName($name) { $this->objectName = $name; return $this; } public function setGrippable($grippable) { $this->grippable = $grippable; return $this; } public function getGrippable() { return $this->grippable; } public function setEffect($effect) { $this->effect = $effect; return $this; } public function getEffect() { return $this->effect; } public function setObject($object) { $this->object = $object; return $this; } public function getObject() { return $this->object; } public function setHref($href) { $this->href = $href; return $this; } public function getHref() { return $this->href; } public function setHeader($header) { $this->header = $header; return $this; } public function setSubHead($subhead) { $this->subhead = $subhead; return $this; } public function getHeader() { return $this->header; } public function addByline($byline) { $this->bylines[] = $byline; return $this; } public function setImageURI($image_uri) { $this->imageURI = $image_uri; return $this; } public function getImageURI() { return $this->imageURI; } + public function setImageIcon($image_icon) { + $this->imageIcon = $image_icon; + return $this; + } + + public function getImageIcon() { + return $this->imageIcon; + } + public function setState($state) { $this->state = $state; switch ($state) { case self::STATE_SUCCESS: $fi = 'fa-check-circle green'; break; case self::STATE_FAIL: $fi = 'fa-times-circle red'; break; case self::STATE_WARN: $fi = 'fa-exclamation-circle yellow'; break; case self::STATE_NOTE: $fi = 'fa-info-circle blue'; break; case self::STATE_BUILD: $fi = 'fa-refresh ph-spin sky'; break; } $this->fontIcon = id(new PHUIIconView()) ->setIconFont($fi.' fa-2x'); return $this; } public function setEpoch($epoch, $age = self::AGE_FRESH) { $date = phabricator_datetime($epoch, $this->getUser()); $days = floor((time() - $epoch) / 60 / 60 / 24); switch ($age) { case self::AGE_FRESH: $this->addIcon('none', $date); break; case self::AGE_STALE: $attr = array( 'tip' => pht('Stale (%s day(s))', new PhutilNumber($days)), 'class' => 'icon-age-stale', ); $this->addIcon('fa-clock-o yellow', $date, $attr); break; case self::AGE_OLD: $attr = array( 'tip' => pht('Old (%s day(s))', new PhutilNumber($days)), 'class' => 'icon-age-old', ); $this->addIcon('fa-clock-o red', $date, $attr); break; default: throw new Exception("Unknown age '{$age}'!"); } return $this; } public function addAction(PHUIListItemView $action) { if (count($this->actions) >= 3) { throw new Exception("Limit 3 actions per item."); } $this->actions[] = $action; return $this; } public function addIcon($icon, $label = null, $attributes = array()) { $this->icons[] = array( 'icon' => $icon, 'label' => $label, 'attributes' => $attributes, ); return $this; } public function addFootIcon($icon, $label = null) { $this->footIcons[] = array( 'icon' => $icon, 'label' => $label, ); return $this; } public function addHandleIcon( PhabricatorObjectHandle $handle, $label = null) { $this->handleIcons[] = array( 'icon' => $handle, 'label' => $label, ); return $this; } public function setBarColor($bar_color) { $this->barColor = $bar_color; return $this; } public function getBarColor() { return $this->barColor; } public function addAttribute($attribute) { if (!empty($attribute)) { $this->attributes[] = $attribute; } return $this; } protected function getTagName() { return 'li'; } protected function getTagAttributes() { $item_classes = array(); $item_classes[] = 'phui-object-item'; if ($this->icons) { $item_classes[] = 'phui-object-item-with-icons'; } if ($this->attributes) { $item_classes[] = 'phui-object-item-with-attrs'; } if ($this->handleIcons) { $item_classes[] = 'phui-object-item-with-handle-icons'; } if ($this->barColor) { $item_classes[] = 'phui-object-item-bar-color-'.$this->barColor; } if ($this->footIcons) { $item_classes[] = 'phui-object-item-with-foot-icons'; } if ($this->bylines) { $item_classes[] = 'phui-object-item-with-bylines'; } if ($this->actions) { $n = count($this->actions); $item_classes[] = 'phui-object-item-with-actions'; $item_classes[] = 'phui-object-item-with-'.$n.'-actions'; } if ($this->disabled) { $item_classes[] = 'phui-object-item-disabled'; } if ($this->state) { $item_classes[] = 'phui-object-item-state-'.$this->state; } switch ($this->effect) { case 'highlighted': $item_classes[] = 'phui-object-item-highlighted'; break; case 'selected': $item_classes[] = 'phui-object-item-selected'; break; case null: break; default: throw new Exception(pht("Invalid effect!")); } if ($this->getGrippable()) { $item_classes[] = 'phui-object-item-grippable'; } if ($this->getImageURI()) { $item_classes[] = 'phui-object-item-with-image'; } + if ($this->getImageIcon()) { + $item_classes[] = 'phui-object-item-with-image-icon'; + } + if ($this->fontIcon) { $item_classes[] = 'phui-object-item-with-ficon'; } return array( 'class' => $item_classes, ); } public function getTagContent() { $content_classes = array(); $content_classes[] = 'phui-object-item-content'; $header_name = null; if ($this->objectName) { $header_name = array( phutil_tag( 'span', array( 'class' => 'phui-object-item-objname', ), $this->objectName), ' ', ); } $header_link = phutil_tag( $this->href ? 'a' : 'div', array( 'href' => $this->href, 'class' => 'phui-object-item-link', ), $this->header); $header = javelin_tag( 'div', array( 'class' => 'phui-object-item-name', 'sigil' => 'slippery', ), array( $this->headIcons, $header_name, $header_link, )); $icons = array(); if ($this->icons) { $icon_list = array(); foreach ($this->icons as $spec) { $icon = $spec['icon']; $icon = id(new PHUIIconView()) ->setIconFont($icon) ->addClass('phui-object-item-icon-image'); if (isset($spec['attributes']['tip'])) { $sigil = 'has-tooltip'; $meta = array( 'tip' => $spec['attributes']['tip'], 'align' => 'W', ); $icon->addSigil($sigil); $icon->setMetadata($meta); } $label = phutil_tag( 'span', array( 'class' => 'phui-object-item-icon-label', ), $spec['label']); if (isset($spec['attributes']['href'])) { $icon_href = phutil_tag( 'a', array('href' => $spec['attributes']['href']), array($label, $icon)); } else { $icon_href = array($label, $icon); } $classes = array(); $classes[] = 'phui-object-item-icon'; if ($spec['icon'] == 'none') { $classes[] = 'phui-object-item-icon-none'; } if (isset($spec['attributes']['class'])) { $classes[] = $spec['attributes']['class']; } $icon_list[] = javelin_tag( 'li', array( 'class' => implode(' ', $classes), ), $icon_href); } $icons[] = phutil_tag( 'ul', array( 'class' => 'phui-object-item-icons', ), $icon_list); } if ($this->handleIcons) { $handle_bar = array(); foreach ($this->handleIcons as $icon) { $handle_bar[] = $this->renderHandleIcon($icon['icon'], $icon['label']); } $icons[] = phutil_tag( 'div', array( 'class' => 'phui-object-item-handle-icons', ), $handle_bar); } $bylines = array(); if ($this->bylines) { foreach ($this->bylines as $byline) { $bylines[] = phutil_tag( 'div', array( 'class' => 'phui-object-item-byline', ), $byline); } $bylines = phutil_tag( 'div', array( 'class' => 'phui-object-item-bylines', ), $bylines); } $subhead = null; if ($this->subhead) { $subhead = phutil_tag( 'div', array( 'class' => 'phui-object-item-subhead', ), $this->subhead); } if ($icons) { $icons = phutil_tag( 'div', array( 'class' => 'phui-object-icon-pane', ), $icons); } $attrs = null; if ($this->attributes) { $attrs = array(); $spacer = phutil_tag( 'span', array( 'class' => 'phui-object-item-attribute-spacer', ), "\xC2\xB7"); $first = true; foreach ($this->attributes as $attribute) { $attrs[] = phutil_tag( 'li', array( 'class' => 'phui-object-item-attribute', ), array( ($first ? null : $spacer), $attribute, )); $first = false; } $attrs = phutil_tag( 'ul', array( 'class' => 'phui-object-item-attributes', ), $attrs); } $foot = null; if ($this->footIcons) { $foot_bar = array(); foreach ($this->footIcons as $icon) { $foot_bar[] = $this->renderFootIcon($icon['icon'], $icon['label']); } $foot = phutil_tag( 'div', array( 'class' => 'phui-object-item-foot-icons', ), $foot_bar); } $grippable = null; if ($this->getGrippable()) { $grippable = phutil_tag( 'div', array( 'class' => 'phui-object-item-grip', ), ''); } $content = phutil_tag( 'div', array( 'class' => implode(' ', $content_classes), ), array( $subhead, $attrs, $this->renderChildren(), $foot, )); $image = null; if ($this->getImageURI()) { $image = phutil_tag( 'div', array( 'class' => 'phui-object-item-image', 'style' => 'background-image: url('.$this->getImageURI().')', ), ''); + } else if ($this->getImageIcon()) { + $image = phutil_tag( + 'div', + array( + 'class' => 'phui-object-item-image-icon', + ), + $this->getImageIcon()); + } + + if ($image && $this->href) { + $image = phutil_tag( + 'a', + array( + 'href' => $this->href, + ), + $image); } $ficon = null; if ($this->fontIcon) { $image = phutil_tag( 'div', array( 'class' => 'phui-object-item-ficon', ), $this->fontIcon); } $box = phutil_tag( 'div', array( 'class' => 'phui-object-item-content-box', ), array( $grippable, $header, $icons, $bylines, $content, )); $actions = array(); if ($this->actions) { Javelin::initBehavior('phabricator-tooltips'); foreach (array_reverse($this->actions) as $action) { $action->setRenderNameAsTooltip(true); $actions[] = $action; } $actions = phutil_tag( 'ul', array( 'class' => 'phui-object-item-actions', ), $actions); } return phutil_tag( 'div', array( 'class' => 'phui-object-item-frame', ), array( $actions, $image, $box, )); } private function renderFootIcon($icon, $label) { $icon = id(new PHUIIconView()) ->setIconFont($icon); $label = phutil_tag( 'span', array( ), $label); return phutil_tag( 'span', array( 'class' => 'phui-object-item-foot-icon', ), array($icon, $label)); } private function renderHandleIcon(PhabricatorObjectHandle $handle, $label) { Javelin::initBehavior('phabricator-tooltips'); $options = array( 'class' => 'phui-object-item-handle-icon', 'style' => 'background-image: url('.$handle->getImageURI().')', ); if (strlen($label)) { $options['sigil'] = 'has-tooltip'; $options['meta'] = array('tip' => $label); } return javelin_tag( 'span', $options, ''); } } diff --git a/support/aphlict/client/build_aphlict_client.sh b/support/aphlict/client/build_aphlict_client.sh index b328873d1..0d2e4ae12 100755 --- a/support/aphlict/client/build_aphlict_client.sh +++ b/support/aphlict/client/build_aphlict_client.sh @@ -1,28 +1,20 @@ #!/bin/sh BASEDIR=`dirname $0` ROOT=`cd $BASEDIR/../../../ && pwd`; if [ -z "$MXMLC" ]; then echo "ERROR: Define environmental variable MXMLC to point to 'mxmlc' binary."; exit 1; fi; set -e -set -x -# cp -R $ROOT/externals/vegas/src $BASEDIR/src/vegas - -(cd $BASEDIR && $MXMLC \ - -output aphlict.swf \ +$MXMLC \ + -output=$ROOT/webroot/rsrc/swf/aphlict.swf \ -default-background-color=0x444444 \ -default-size=500,500 \ -warnings=true \ - -debug=true \ -source-path=$ROOT/externals/vegas/src \ -static-link-runtime-shared-libraries=true \ - src/Aphlict.as) - -mv $BASEDIR/aphlict.swf $ROOT/webroot/rsrc/swf/aphlict.swf - -# -target-player=10.2.0 \ + $BASEDIR/src/AphlictClient.as diff --git a/support/aphlict/client/src/Aphlict.as b/support/aphlict/client/src/Aphlict.as index 14eb9548d..d61ad04b0 100644 --- a/support/aphlict/client/src/Aphlict.as +++ b/support/aphlict/client/src/Aphlict.as @@ -1,119 +1,40 @@ package { - import flash.net.*; - import flash.utils.*; - import flash.media.*; - import flash.display.*; - import flash.events.*; + import flash.display.Sprite; import flash.external.ExternalInterface; + import flash.net.LocalConnection; - import vegas.strings.JSON; public class Aphlict extends Sprite { - private var client:String; + /** + * A transport channel used to receive data. + */ + protected var recv:LocalConnection; - private var socket:Socket; - private var readBuffer:ByteArray; + /** + * A transport channel used to send data. + */ + protected var send:LocalConnection; - private var remoteServer:String; - private var remotePort:Number; public function Aphlict() { super(); - ExternalInterface.addCallback('connect', this.externalConnect); - ExternalInterface.call( - 'JX.Stratcom.invoke', - 'aphlict-component-ready', - null, - {}); - } - - public function externalConnect(server:String, port:Number):void { - this.externalInvoke('connect'); - - this.remoteServer = server; - this.remotePort = port; - - this.connectToServer(); - } - - - public function connectToServer():void { - var socket:Socket = new Socket(); - - socket.addEventListener(Event.CONNECT, didConnectSocket); - socket.addEventListener(Event.CLOSE, didCloseSocket); - socket.addEventListener(ProgressEvent.SOCKET_DATA, didReceiveSocket); - - socket.addEventListener(IOErrorEvent.IO_ERROR, didIOErrorSocket); - socket.addEventListener( - SecurityErrorEvent.SECURITY_ERROR, - didSecurityErrorSocket); - - socket.connect(this.remoteServer, this.remotePort); - - this.readBuffer = new ByteArray(); - this.socket = socket; - } - - private function didConnectSocket(event:Event):void { - this.externalInvoke('connected'); - } - - private function didCloseSocket(event:Event):void { - this.externalInvoke('close'); - } - - private function didIOErrorSocket(event:IOErrorEvent):void { - this.externalInvoke('error', event.text); - } - - private function didSecurityErrorSocket(event:SecurityErrorEvent):void { - this.externalInvoke('error', event.text); - } - - private function didReceiveSocket(event:Event):void { - var b:ByteArray = this.readBuffer; - this.socket.readBytes(b, b.length); - - do { - b = this.readBuffer; - b.position = 0; - - if (b.length <= 8) { - break; - } - - var msg_len:Number = parseInt(b.readUTFBytes(8), 10); - if (b.length >= msg_len + 8) { - var bytes:String = b.readUTFBytes(msg_len); - var data:Object = vegas.strings.JSON.deserialize(bytes); - var t:ByteArray = new ByteArray(); - t.writeBytes(b, msg_len + 8); - this.readBuffer = t; - - this.receiveMessage(data); - } else { - break; - } - } while (true); - - } + this.recv = new LocalConnection(); + this.recv.client = this; - public function receiveMessage(msg:Object):void { - this.externalInvoke('receive', msg); + this.send = new LocalConnection(); } - public function externalInvoke(type:String, object:Object = null):void { + protected function externalInvoke(type:String, object:Object = null):void { ExternalInterface.call('JX.Aphlict.didReceiveEvent', type, object); } - public function log(message:String):void { - ExternalInterface.call('console.log', message); + protected function log(message:String):void { + this.externalInvoke('log', message); } } } diff --git a/support/aphlict/client/src/AphlictClient.as b/support/aphlict/client/src/AphlictClient.as new file mode 100644 index 000000000..31105c67d --- /dev/null +++ b/support/aphlict/client/src/AphlictClient.as @@ -0,0 +1,129 @@ +package { + + import flash.events.TimerEvent; + import flash.external.ExternalInterface; + import flash.utils.Timer; + + + public class AphlictClient extends Aphlict { + + /** + * The connection name for this client. This will be used for the + * @{class:LocalConnection} object. + */ + private var client:String; + + /** + * The expiry timestamp for the @{class:AphlictMaster}. If this time is + * elapsed then the master will be assumed to be dead and another + * @{class:AphlictClient} will create a master. + */ + private var expiry:Number = 0; + + /** + * The interval at which to ping the @{class:AphlictMaster}. + */ + public static const INTERVAL:Number = 3000; + + private var master:AphlictMaster; + private var timer:Timer; + + private var remoteServer:String; + private var remotePort:Number; + + + public function AphlictClient() { + super(); + + ExternalInterface.addCallback('connect', this.externalConnect); + ExternalInterface.call( + 'JX.Stratcom.invoke', + 'aphlict-component-ready', + null, + {}); + } + + public function externalConnect(server:String, port:Number):void { + this.externalInvoke('connect'); + + this.remoteServer = server; + this.remotePort = port; + + this.client = AphlictClient.generateClientId(); + this.recv.connect(this.client); + + this.timer = new Timer(AphlictClient.INTERVAL); + this.timer.addEventListener(TimerEvent.TIMER, this.keepalive); + + this.connectToMaster(); + } + + /** + * Generate a unique identifier that will be used to communicate with the + * @{class:AphlictMaster}. + */ + private static function generateClientId():String { + return 'aphlict_client_' + Math.round(Math.random() * 100000); + } + + /** + * Create a new connection to the @{class:AphlictMaster}. + * + * If there is no current @{class:AphlictMaster} instance, then a new master + * will be created. + */ + private function connectToMaster():void { + this.timer.stop(); + + // Try to become the master. + try { + this.log('Attempting to become the master...'); + this.master = new AphlictMaster(this.remoteServer, this.remotePort); + this.log('I am the master.'); + } catch (x:Error) { + // Couldn't become the master + this.log('Cannot become the master... probably one already exists'); + } + + this.send.send('aphlict_master', 'register', this.client); + this.expiry = new Date().getTime() + (5 * AphlictClient.INTERVAL); + this.log('Registered client ' + this.client); + + this.timer.start(); + } + + /** + * Send a keepalive signal to the @{class:AphlictMaster}. + * + * If the connection to the master has expired (because the master has not + * sent a heartbeat signal), then a new connection to master will be + * created. + */ + private function keepalive(event:TimerEvent):void { + if (new Date().getTime() > this.expiry) { + this.connectToMaster(); + } + + this.send.send('aphlict_master', 'ping', this.client); + } + + /** + * This function is used to receive the heartbeat signal from the + * @{class:AphlictMaster}. + */ + public function pong():void { + this.expiry = new Date().getTime() + (2 * AphlictClient.INTERVAL); + } + + /** + * Receive a message from the Aphlict Server, via the + * @{class:AphlictMaster}. + */ + public function receiveMessage(msg:Object):void { + this.log('Received message.'); + this.externalInvoke('receive', msg); + } + + } + +} diff --git a/support/aphlict/client/src/AphlictMaster.as b/support/aphlict/client/src/AphlictMaster.as new file mode 100644 index 000000000..1991ab48b --- /dev/null +++ b/support/aphlict/client/src/AphlictMaster.as @@ -0,0 +1,166 @@ +package { + + import flash.events.Event; + import flash.events.IOErrorEvent; + import flash.events.ProgressEvent; + import flash.events.SecurityErrorEvent; + import flash.events.TimerEvent; + import flash.net.Socket; + import flash.utils.ByteArray; + import flash.utils.Dictionary; + import flash.utils.Timer; + import vegas.strings.JSON; + + + public class AphlictMaster extends Aphlict { + + /** + * The pool of connected clients. + */ + private var clients:Dictionary; + + /** + * A timer used to trigger periodic events. + */ + private var timer:Timer; + + /** + * The interval after which clients will be considered dead and removed + * from the pool. + */ + public static const PURGE_INTERVAL:Number = 3 * AphlictClient.INTERVAL; + + /** + * The hostname for the Aphlict Server. + */ + private var remoteServer:String; + + /** + * The port number for the Aphlict Server. + */ + private var remotePort:Number; + + private var socket:Socket; + private var readBuffer:ByteArray; + + + public function AphlictMaster(server:String, port:Number) { + super(); + + this.remoteServer = server; + this.remotePort = port; + + // Connect to the Aphlict Server. + this.recv.connect('aphlict_master'); + this.connectToServer(); + + this.clients = new Dictionary(); + + // Start a timer and regularly purge dead clients. + this.timer = new Timer(AphlictMaster.PURGE_INTERVAL); + this.timer.addEventListener(TimerEvent.TIMER, this.purgeClients); + this.timer.start(); + } + + /** + * Register a @{class:AphlictClient}. + */ + public function register(client:String):void { + if (!this.clients[client]) { + this.log('Registering client: ' + client); + this.clients[client] = new Date().getTime(); + } + } + + /** + * Purge stale client connections from the client pool. + */ + private function purgeClients(event:TimerEvent):void { + for (var client:String in this.clients) { + var checkin:Number = this.clients[client]; + + if (new Date().getTime() - checkin > AphlictMaster.PURGE_INTERVAL) { + this.log('Purging client: ' + client); + delete this.clients[client]; + } + } + } + + /** + * Clients will regularly "ping" the master to let us know that they are + * still alive. We will "pong" them back to let the client know that the + * master is still alive. + */ + public function ping(client:String):void { + this.clients[client] = new Date().getTime(); + this.send.send(client, 'pong'); + } + + private function connectToServer():void { + var socket:Socket = new Socket(); + + socket.addEventListener(Event.CONNECT, didConnectSocket); + socket.addEventListener(Event.CLOSE, didCloseSocket); + socket.addEventListener(ProgressEvent.SOCKET_DATA, didReceiveSocket); + + socket.addEventListener(IOErrorEvent.IO_ERROR, didIOErrorSocket); + socket.addEventListener( + SecurityErrorEvent.SECURITY_ERROR, + didSecurityErrorSocket); + + socket.connect(this.remoteServer, this.remotePort); + + this.readBuffer = new ByteArray(); + this.socket = socket; + } + + private function didConnectSocket(event:Event):void { + this.externalInvoke('connected'); + } + + private function didCloseSocket(event:Event):void { + this.externalInvoke('close'); + } + + private function didIOErrorSocket(event:IOErrorEvent):void { + this.externalInvoke('error', event.text); + } + + private function didSecurityErrorSocket(event:SecurityErrorEvent):void { + this.externalInvoke('error', event.text); + } + + private function didReceiveSocket(event:Event):void { + var b:ByteArray = this.readBuffer; + this.socket.readBytes(b, b.length); + + do { + b = this.readBuffer; + b.position = 0; + + if (b.length <= 8) { + break; + } + + var msg_len:Number = parseInt(b.readUTFBytes(8), 10); + if (b.length >= msg_len + 8) { + var bytes:String = b.readUTFBytes(msg_len); + var data:Object = vegas.strings.JSON.deserialize(bytes); + var t:ByteArray = new ByteArray(); + t.writeBytes(b, msg_len + 8); + this.readBuffer = t; + + // Send the message to all clients. + for (var client:String in this.clients) { + this.log('Sending message to client: ' + client); + this.send.send(client, 'receiveMessage', data); + } + } else { + break; + } + } while (true); + } + + } + +} diff --git a/webroot/rsrc/css/application/directory/phabricator-jump-nav.css b/webroot/rsrc/css/application/directory/phabricator-jump-nav.css deleted file mode 100644 index fe06c4c97..000000000 --- a/webroot/rsrc/css/application/directory/phabricator-jump-nav.css +++ /dev/null @@ -1,25 +0,0 @@ -/** - * @provides phabricator-jump-nav - */ - -.phabricator-jump-nav-form { - text-align: center; - padding: 0px; - margin: 0; -} - -input.phabricator-jump-nav[type='text'] { - font-size: 16px; - width: 100%; -} - -.phabricator-jump-nav-caption { - margin-top: 4px; - font-size: 11px; - color: {$greytext}; - text-align: left; -} - -.phabricator-jump-nav-container form { - padding: 12px 16px; -} diff --git a/webroot/rsrc/css/phui/phui-object-item-list-view.css b/webroot/rsrc/css/phui/phui-object-item-list-view.css index e4055541e..d9787f2c5 100644 --- a/webroot/rsrc/css/phui/phui-object-item-list-view.css +++ b/webroot/rsrc/css/phui/phui-object-item-list-view.css @@ -1,673 +1,724 @@ /** * @provides phui-object-item-list-view-css */ .phui-object-item-list-view { padding: 8px 6px; } .device-desktop .phui-object-item-list-view { padding: 16px; } .phui-object-item-list-view + .phui-object-item-list-view { padding-top: 0; } .phui-object-item-list-view.phui-object-list-flush { padding: 0; } .phui-object-box .phui-object-item-list-view.phui-object-list-flush { padding: 8px 12px 4px 12px; background-color: #E5E8EE; } .device-phone .phui-object-box .phui-object-item-list-view.phui-object-list-flush { padding: 4px; } .phui-object-item-list-view .aphront-error-view { margin: 4px 0 8px 0; color: {$bluetext}; } .phui-object-item { background: #fff; border-style: solid; border-color: {$lightgreyborder}; border-width: 0 0 0 4px; margin: 5px 0; overflow: hidden; } .phui-object-item .phui-icon-view { display: inline-block; } .phui-object-item-frame { border-style: solid; border-color: {$lightgreyborder}; border-bottom-color: {$greyborder}; border-width: 1px 1px 1px 0; position: relative; min-height: 29px; overflow: hidden; } .phui-object-list-cards .phui-object-item-frame { border-bottom-right-radius: 3px; border-top-right-radius: 3px; } .device-desktop .phui-object-item { margin: 0 0 5px 0; } .phui-object-item-name { display: inline-block; font-weight: bold; padding: 6px 8px 0; white-space: nowrap; } .phui-object-item-link { display: inline-block; } .phui-object-item-objname { color: {$darkgreytext}; cursor: text; } .phui-object-item-content { margin-top: 4px; overflow: hidden; } .phui-object-item-grippable { cursor: move; } .phui-object-item-grip { position: absolute; top: 0; bottom: 0; left: 0; width: 17px; background: url('/rsrc/image/texture/grip.png') center center no-repeat; } .phui-object-item-grippable .phui-object-item-frame { padding-left: 11px; } .phui-object-item-list-header { padding: 0 0 8px 0; color: {$darkgreytext}; } /* - Item Actions -------------------------------------------------------------- Action buttons, like "Edit" and "Delete". */ .phui-object-item-actions { position: absolute; right: 0; top: 0; bottom: 0; vertical-align: middle; text-align: right; border-left: 1px solid {$lightgreyborder}; } .phui-object-item-actions .phui-list-item-view { float: right; height: 100%; width: 24px; display: inline-block; position: relative; } .phui-object-item-actions .phui-list-item-view + .phui-list-item-view { border-right: 1px solid #d6d6e9; } .phui-object-item-actions .phui-list-item-href { display: inline-block; position: relative; width: 24px; height: 100%; } .phui-object-item-actions .phui-list-item-href:hover { background: #e9e9f9; } .phui-object-item-actions .phui-list-item-icon { width: 14px; height: 14px; position: absolute; display: block; top: 50%; margin-top: -7px; left: 3px; } .phui-object-item-actions .phui-list-item-name { display: none; } .phui-object-item-with-1-actions .phui-object-item-content-box { margin-right: 24px; overflow: hidden; } .phui-object-item-with-2-actions .phui-object-item-content-box { margin-right: 48px; overflow: hidden; } .phui-object-item-with-3-actions .phui-object-item-content-box { margin-right: 72px; overflow: hidden; } /* - Stackable List ------------------------------------------------------------ Tighter, stacking list. */ .phui-object-item-list-view.phui-object-list-stackable .phui-object-item { margin: -1px 0 0 0; } .phui-object-list-stackable .phui-object-item { border-left-width: 1px; } .device-desktop .phui-object-list-stackable .phui-object-item:hover { background: #e9ecf5; } /* - Subhead ------------------------------------------------------------------- Descriptive Text or Links under the main header, before attributes. */ .phui-object-item-subhead { color: {$greytext}; padding: 0 8px 6px; } /* - Attribute List ------------------------------------------------------------ Object attributes, commonly used to render created date, etc. */ .phui-object-item-attributes { padding: 0 8px 6px; } .phui-object-item-attribute { display: inline; color: {$greytext}; } .phui-object-item-attribute-spacer { padding: 0 4px; } /* - Icons --------------------------------------------------------------------- Icons, which show object state. On mobile, they are rendered without labels to save space. */ .phui-object-icon-pane { float: right; margin-top: 6px; } .device .phui-object-item-no-icon-images .phui-object-icon-pane { display: none; } .phui-object-item-with-handle-icons .phui-object-item-icons { padding-bottom: 30px; } .phui-object-item-icons { float: right; padding: 0 10px; } /* NOTE: The main content is an "overflow: hidden" div which we give a right margin so it doesn't overlap the icons. The margin is slightly larger than the width + padding of the icon div, so the icons have some space even if the content is wider than available space. */ .device-desktop .phui-object-icon-pane { width: 120px; } .device-phone .phui-object-icon-pane { position: absolute; top: 0; right: 0; background: #fff; } .device-phone .phui-object-item-with-1-actions .phui-object-icon-pane { right: 25px; } .device-desktop .phui-object-item-with-icons .phui-object-item-content, .device-desktop .phui-object-item-with-handle-icons .phui-object-item-content { margin-right: 132px; } .device .phui-object-item-icons { width: 18px; } .device .phui-object-item-with-icons .phui-object-item-content, .device .phui-object-item-with-handle-icons .phui-object-item-content { margin-right: 30px; } .device .phui-object-item-icon-label, .device .phui-object-item-icon-none { display: none; } .phui-object-item-icon { vertical-align: middle; position: relative; font-size: 12px; color: {$greytext}; text-align: right; white-space: nowrap; overflow: hidden; min-height: 18px; line-height: 18px; } /* * Items with icon 'none' still have on mobile, thus creating a weird vertical * margin for elements which follow */ .device-phone .phui-object-item-icon-none { display: none; } .device-desktop .phui-object-item-icon { padding-right: 18px; } .device-desktop .phui-object-item-icon-none { padding-right: 0; } .phui-object-item-icon-image { position: absolute; right: 0; top: 2px; width: 14px; height: 14px; font-size: 13px; } /* - Bar Colors ---------------------------------------------------------------- Colors for the left-hand border bars, used to indicate object status or other attributes. */ .phui-object-item-bar-color-red { border-left-color: {$red}; } .phui-object-item-bar-color-orange { border-left-color: {$orange}; } .phui-object-item-bar-color-yellow { border-left-color: {$yellow}; } .phui-object-item-bar-color-green { border-left-color: {$green}; } .phui-object-item-bar-color-sky { border-left-color: {$sky}; } .phui-object-item-bar-color-blue { border-left-color: {$blue}; } .phui-object-item-bar-color-indigo { border-left-color: {$indigo}; } .phui-object-item-bar-color-violet { border-left-color: {$violet}; } .phui-object-item-bar-color-grey { border-left-color: #bdc3c7; } .phui-object-item-bar-color-black { border-left-color: #333333; } /* - Disabled ------------------------------------------------------------------ Disabled/inactive objects. */ .phui-object-item-disabled { border-left-color: #d7d7d7; } .phui-object-item-disabled .phui-object-item-link, .phui-object-item-disabled .phui-object-item-link a { color: {$lightgreytext}; } .phui-object-item-disabled .phui-object-item-frame { border-color: #d7d7d7; } .phui-object-item-disabled .phui-object-item-objname { color: {$greytext}; text-decoration: line-through; } /* - Effects ------------------------------------------------------------------- Effects like highlighted items. */ .phui-object-item-highlighted { background: {$lightyellow}; } .phui-object-list-cards .phui-object-item.phui-object-item-highlighted { background-image: linear-gradient(to bottom, rgb(253, 255, 221), rgb(243, 245, 206)); background-image: -webkit-linear-gradient(top, rgb(253, 255, 221), rgb(243, 245, 206)); } .phui-object-item-selected { background: {$lightblue}; } /* - Foot Icons ---------------------------------------------------------------- Object counts shown in the footer. */ .phui-object-item-foot-icons { margin-left: 10px; bottom: 0; position: absolute; } .phui-object-item-with-foot-icons .phui-object-item-content { padding-bottom: 22px; } .phui-object-item-foot-icon { display: inline-block; background: #909090; color: #ffffff; font-weight: bold; margin-right: 3px; padding: 3px 6px 0; height: 17px; vertical-align: middle; position: relative; font-size: 12px; -webkit-font-smoothing: antialiased; } .phui-object-item-foot-icon .phui-icon-view { margin-right: 4px; } /* - Handle Icons -------------------------------------------------------------- Shows owners, reviewers, etc., using profile picture icons. */ .phui-object-item-handle-icons { height: 28px; margin-right: 10px; bottom: 0; right: 0; text-align: right; position: absolute; } .phui-object-item-handle-icon { margin: 1px; width: 28px; height: 28px; display: inline-block; background-size: 28px 28px; background-repeat: no-repeat; } /* - Bylines ------------------------------------------------------------------- Shows owners, authors, reviewers, etc., in text. */ .phui-object-item-bylines { float: right; clear: right; padding: 0 10px; margin: 4px 0; font-size: 12px; color: {$greytext}; white-space: nowrap; overflow: hidden; text-align: right; } .device-phone .phui-object-item-bylines { float: none; text-align: left; padding: 0 8px; font-size: 13px; } /* - Card List ----------------------------------------------------------------- Rounded card list. */ /* Hard to sprite since we can't have other sprites appearing in tall cells */ .phui-object-list-cards .phui-object-item { border-radius: 3px; border-left-width: 6px; background: #f0f0f0 url('/rsrc/image/texture/card-gradient.png') repeat-x; margin-bottom: 4px; } .phui-object-list-cards .phui-object-item-frame { min-height: 50px; } .phui-object-list-cards .phui-object-item-selected { background: #bfdcff; } .phui-object-list-cards .phui-object-item-selected .phui-object-item-frame { border-color: #99ccff; } /* - Draggable List ------------------------------------------------------------ These classes are applied by and/or provided for use with JX.DraggableList. */ .drag-ghost { position: relative; border: 1px dashed #fff; background: rgba(255,255,255,.5); margin-bottom: 4px; border-radius: 3px; } .drag-dragging { position: relative; opacity: 0.9; } .drag-sending { opacity: 0.75; } /* - Plain --------------------------------------------------------------------- Remove all border styles, just a list of objects */ .phui-object-list-plain .phui-object-item { background: transparent; } .phui-object-list-plain .phui-object-item, .phui-object-list-plain .phui-object-item-frame { border: none; } .phui-object-list-plain .phui-object-item-attributes, .phui-object-list-plain .phui-object-item-name { padding-left: 0; padding-top: 0; } .phui-object-item-image { width: 50px; height: 50px; margin: 4px 4px 4px 4px; position: absolute; background-color: {$lightbluebackground}; } .phui-object-item-with-image .phui-object-item-frame { min-height: 58px; } .phui-object-item-with-image .phui-object-item-content-box { margin-left: 54px; } /* - State --------------------------------------------------------------------- Provides a list of object status or states, success or fail, etc */ .phui-object-item-ficon { width: 26px; height: 26px; margin: 11px 9px 7px 12px; position: absolute; } .device-desktop .phui-object-list-states .phui-object-item { margin: 0; } .phui-object-item-with-ficon .phui-object-item-content-box { margin-left: 38px; } .phui-object-box .phui-object-list-states { padding: 0; } .phui-object-list-states .aphront-error-view { margin: 0; border: none; } .phui-object-box .phui-object-list-states li:last-child .phui-object-item-frame { border: none; padding-bottom: 0; } .phui-object-list-states .phui-object-item-frame { border: none; border-bottom: 1px solid {$thinblueborder}; } .phui-object-list-states .phui-object-item { border: none; } .phui-object-list-states .phui-object-item-frame { min-height: 44px; } /* - Dashboards ------------------------------------------------------------ */ .dashboard-panel .phui-object-item-list-view { padding: 0; border-right: 1px solid {$lightblueborder}; border-bottom: 1px solid {$blueborder}; margin-bottom: -1px; } .dashboard-panel .phui-object-list-cards .phui-object-item { border-radius: 0; margin-bottom: 0; background-image: none; background-color: #fff; border-left-width: 4px; } .dashboard-panel .phui-object-item-frame { border: none; border-bottom: 1px solid {$thinblueborder}; } + + +/* - Launcher List ---------------------------------------------------------- */ + +.launcher-header { + margin: 8px 16px -4px; + clear: both; + color: {$darkbluetext}; +} + +.launcher-header:nth-of-type(1) { + margin-top: 24px; +} + +.phui-object-item-launcher-list { + overflow: hidden; +} + +.device-desktop .phui-object-item-launcher-list .phui-object-item { + width: 32.333%; + float: left; + margin-right: 1%; + box-sizing: border-box; +} + +.phui-object-item-image-icon { + background: none; +} + +.phui-object-item-image-icon { + width: 30px; + height: 30px; + margin: 4px 4px 4px 4px; + position: absolute; +} + +.phui-object-item-image-icon .phui-icon-view { + position: absolute; + width: 28px; + height: 28px; + left: 6px; + top: 6px; +} + +.phui-object-item-with-image-icon .phui-object-item-frame { + min-height: 48px; +} + +.phui-object-item-with-image-icon .phui-object-item-content-box { + margin-left: 44px; +} diff --git a/webroot/rsrc/swf/aphlict.swf b/webroot/rsrc/swf/aphlict.swf index 4ac315b9d..cee18c7dc 100644 Binary files a/webroot/rsrc/swf/aphlict.swf and b/webroot/rsrc/swf/aphlict.swf differ
{$text}{$file}:{$line}{$where}()