public function willLintPaths(array $paths) { // Load all file path data into $this->data. array_map(array($this, 'getData'), $paths); $conduit = new ConduitClient($this->conduitURI); $this->lintByPath = $conduit->callMethodSynchronous(self::CONDUIT_METHOD, array('file_contents' => $this->data)); }
public function run() { $argv = $this->getArgv(); if (count($argv) !== 1) { throw new Exception("usage: PhabricatorIRCBot <json_config_file>"); } $json_raw = Filesystem::readFile($argv[0]); $config = json_decode($json_raw, true); if (!is_array($config)) { throw new Exception("File '{$argv[0]}' is not valid JSON!"); } $server = idx($config, 'server'); $port = idx($config, 'port', 6667); $handlers = idx($config, 'handlers', array()); $pass = idx($config, 'pass'); $nick = idx($config, 'nick', 'phabot'); $user = idx($config, 'user', $nick); $ssl = idx($config, 'ssl', false); $nickpass = idx($config, 'nickpass'); $this->config = $config; if (!preg_match('/^[A-Za-z0-9_`[{}^|\\]\\-]+$/', $nick)) { throw new Exception("Nickname '{$nick}' is invalid!"); } foreach ($handlers as $handler) { $obj = newv($handler, array($this)); $this->handlers[] = $obj; } $conduit_uri = idx($config, 'conduit.uri'); if ($conduit_uri) { $conduit_user = idx($config, 'conduit.user'); $conduit_cert = idx($config, 'conduit.cert'); $conduit = new ConduitClient($conduit_uri); $response = $conduit->callMethodSynchronous('conduit.connect', array('client' => 'PhabricatorIRCBot', 'clientVersion' => '1.0', 'clientDescription' => php_uname('n') . ':' . $nick, 'user' => $conduit_user, 'certificate' => $conduit_cert)); $this->conduit = $conduit; } $errno = null; $error = null; if (!$ssl) { $socket = fsockopen($server, $port, $errno, $error); } else { $socket = fsockopen('ssl://' . $server, $port, $errno, $error); } if (!$socket) { throw new Exception("Failed to connect, #{$errno}: {$error}"); } $ok = stream_set_blocking($socket, false); if (!$ok) { throw new Exception("Failed to set stream nonblocking."); } $this->socket = $socket; $this->writeCommand('USER', "{$user} 0 * :{$user}"); if ($pass) { $this->writeCommand('PASS', "{$pass}"); } if ($nickpass) { $this->writeCommand("NickServ IDENTIFY ", "{$nickpass}"); } $this->writeCommand('NICK', "{$nick}"); $this->runSelectLoop(); }
public function pullDataFromConduit(ConduitClient $conduit, $partial = false) { $result = $conduit->callMethodSynchronous('differential.parsecommitmessage', array('corpus' => $this->rawCorpus, 'partial' => $partial)); $this->fields = $result['fields']; if (!empty($result['errors'])) { throw new ArcanistDifferentialCommitMessageParserException($result['errors']); } return $this; }
protected function run() { $argv = $this->getArgv(); if (count($argv) !== 1) { throw new Exception(pht('Usage: %s %s', __CLASS__, '<json_config_file>')); } $json_raw = Filesystem::readFile($argv[0]); try { $config = phutil_json_decode($json_raw); } catch (PhutilJSONParserException $ex) { throw new PhutilProxyException(pht("File '%s' is not valid JSON!", $argv[0]), $ex); } $nick = idx($config, 'nick', 'phabot'); $handlers = idx($config, 'handlers', array()); $protocol_adapter_class = idx($config, 'protocol-adapter', 'PhabricatorIRCProtocolAdapter'); $this->pollFrequency = idx($config, 'poll-frequency', 1); $this->config = $config; foreach ($handlers as $handler) { $obj = newv($handler, array($this)); $this->handlers[] = $obj; } $ca_bundle = idx($config, 'https.cabundle'); if ($ca_bundle) { HTTPSFuture::setGlobalCABundleFromPath($ca_bundle); } $conduit_uri = idx($config, 'conduit.uri'); if ($conduit_uri) { $conduit_token = idx($config, 'conduit.token'); // Normalize the path component of the URI so users can enter the // domain without the "/api/" part. $conduit_uri = new PhutilURI($conduit_uri); $conduit_host = (string) $conduit_uri->setPath('/'); $conduit_uri = (string) $conduit_uri->setPath('/api/'); $conduit = new ConduitClient($conduit_uri); if ($conduit_token) { $conduit->setConduitToken($conduit_token); } else { $conduit_user = idx($config, 'conduit.user'); $conduit_cert = idx($config, 'conduit.cert'); $response = $conduit->callMethodSynchronous('conduit.connect', array('client' => __CLASS__, 'clientVersion' => '1.0', 'clientDescription' => php_uname('n') . ':' . $nick, 'host' => $conduit_host, 'user' => $conduit_user, 'certificate' => $conduit_cert)); } $this->conduit = $conduit; } // Instantiate Protocol Adapter, for now follow same technique as // handler instantiation $this->protocolAdapter = newv($protocol_adapter_class, array()); $this->protocolAdapter->setConfig($this->config)->connect(); $this->runLoop(); $this->protocolAdapter->disconnect(); }
public function run() { $uri = $this->determineConduitURI(); echo "Installing certificate for '{$uri}'...\n"; $config = self::readUserConfigurationFile(); echo "Trying to connect to server...\n"; $conduit = new ConduitClient($uri); try { $conduit->callMethodSynchronous('conduit.ping', array()); } catch (Exception $ex) { throw new ArcanistUsageException("Failed to connect to server: " . $ex->getMessage()); } echo "Connection OK!\n"; $token_uri = new PhutilURI($uri); $token_uri->setPath('/conduit/token/'); echo "\n"; echo phutil_console_format("**LOGIN TO PHABRICATOR**\n"); echo "Open this page in your browser and login to Phabricator if " . "necessary:\n"; echo "\n"; echo " {$token_uri}\n"; echo "\n"; echo "Then paste the token on that page below."; do { $token = phutil_console_prompt('Paste token from that page:'); $token = trim($token); if (strlen($token)) { break; } } while (true); echo "\n"; echo "Downloading authentication certificate...\n"; $info = $conduit->callMethodSynchronous('conduit.getcertificate', array('token' => $token, 'host' => $uri)); $user = $info['username']; echo "Installing certificate for '{$user}'...\n"; $config['hosts'][$uri] = array('user' => $user, 'cert' => $info['certificate']); echo "Writing ~/.arcrc...\n"; self::writeUserConfigurationFile($config); echo phutil_console_format("<bg:green>** SUCCESS! **</bg> Certificate installed.\n"); return 0; }
private function loadBundleFromConduit(ConduitClient $conduit, $params) { $future = $conduit->callMethod('differential.getdiff', $params); $diff = $future->resolve(); $changes = array(); foreach ($diff['changes'] as $changedict) { $changes[] = ArcanistDiffChange::newFromDictionary($changedict); } $bundle = ArcanistBundle::newFromChanges($changes); $bundle->setConduit($conduit); // since the conduit method has changes, assume that these fields // could be unset $bundle->setProjectID(idx($diff, 'projectName')); $bundle->setBaseRevision(idx($diff, 'sourceControlBaseRevision')); $bundle->setRevisionID(idx($diff, 'revisionID')); $bundle->setAuthorName(idx($diff, 'authorName')); $bundle->setAuthorEmail(idx($diff, 'authorEmail')); return $bundle; }
private function loadBundleFromConduit(ConduitClient $conduit, $params) { $future = $conduit->callMethod('differential.getdiff', $params); $diff = $future->resolve(); $changes = array(); foreach ($diff['changes'] as $changedict) { $changes[] = ArcanistDiffChange::newFromDictionary($changedict); } $bundle = ArcanistBundle::newFromChanges($changes); $bundle->setConduit($conduit); $bundle->setProjectID($diff['projectName']); $bundle->setBaseRevision($diff['sourceControlBaseRevision']); $bundle->setRevisionID($diff['revisionID']); return $bundle; }
private function executeMethod() { $user = $this->getUser(); if (!$user) { $user = new PhabricatorUser(); } $this->request->setUser($user); if (!$this->shouldRequireAuthentication()) { // No auth requirement here. } else { $allow_public = $this->handler->shouldAllowPublic() && PhabricatorEnv::getEnvConfig('policy.allow-public'); if (!$allow_public) { if (!$user->isLoggedIn() && !$user->isOmnipotent()) { // TODO: As per below, this should get centralized and cleaned up. throw new ConduitException('ERR-INVALID-AUTH'); } } // TODO: This would be slightly cleaner by just using a Query, but the // Conduit auth workflow requires the Call and User be built separately. // Just do it this way for the moment. $application = $this->handler->getApplication(); if ($application) { $can_view = PhabricatorPolicyFilter::hasCapability($user, $application, PhabricatorPolicyCapability::CAN_VIEW); if (!$can_view) { throw new ConduitException(pht('You do not have access to the application which provides this ' . 'API method.')); } } } if (!$this->shouldForceLocal() && $this->servers) { $server = $this->pickRandomServer($this->servers); $client = new ConduitClient($server); $params = $this->request->getAllParameters(); $params['__conduit__']['isProxied'] = true; if ($this->handler->shouldRequireAuthentication()) { $client->callMethodSynchronous('conduit.connect', array('client' => 'PhabricatorConduit', 'clientVersion' => '1.0', 'user' => $this->getUser()->getUserName(), 'certificate' => $this->getUser()->getConduitCertificate(), '__conduit__' => $params['__conduit__'])); } return $client->callMethodSynchronous($this->method, $params); } else { return $this->handler->executeMethod($this->request); } }
/** * Authenticate the client making the request to a Phabricator user account. * * @param ConduitAPIRequest Request being executed. * @param dict Request metadata. * @return null|pair Null to indicate successful authentication, or * an error code and error message pair. */ private function authenticateUser(ConduitAPIRequest $api_request, array $metadata) { $request = $this->getRequest(); if ($request->getUser()->getPHID()) { $request->validateCSRF(); return $this->validateAuthenticatedUser($api_request, $request->getUser()); } $auth_type = idx($metadata, 'auth.type'); if ($auth_type === ConduitClient::AUTH_ASYMMETRIC) { $host = idx($metadata, 'auth.host'); if (!$host) { return array('ERR-INVALID-AUTH', pht('Request is missing required "%s" parameter.', 'auth.host')); } // TODO: Validate that we are the host! $raw_key = idx($metadata, 'auth.key'); $public_key = PhabricatorAuthSSHPublicKey::newFromRawKey($raw_key); $ssl_public_key = $public_key->toPKCS8(); // First, verify the signature. try { $protocol_data = $metadata; // TODO: We should stop writing this into the protocol data when // processing a request. unset($protocol_data['scope']); ConduitClient::verifySignature($this->method, $api_request->getAllParameters(), $protocol_data, $ssl_public_key); } catch (Exception $ex) { return array('ERR-INVALID-AUTH', pht('Signature verification failure. %s', $ex->getMessage())); } // If the signature is valid, find the user or device which is // associated with this public key. $stored_key = id(new PhabricatorAuthSSHKeyQuery())->setViewer(PhabricatorUser::getOmnipotentUser())->withKeys(array($public_key))->executeOne(); if (!$stored_key) { return array('ERR-INVALID-AUTH', pht('No user or device is associated with that public key.')); } $object = $stored_key->getObject(); if ($object instanceof PhabricatorUser) { $user = $object; } else { if (!$stored_key->getIsTrusted()) { return array('ERR-INVALID-AUTH', pht('The key which signed this request is not trusted. Only ' . 'trusted keys can be used to sign API calls.')); } if (!PhabricatorEnv::isClusterRemoteAddress()) { return array('ERR-INVALID-AUTH', pht('This request originates from outside of the Phabricator ' . 'cluster address range. Requests signed with trusted ' . 'device keys must originate from within the cluster.')); } $user = PhabricatorUser::getOmnipotentUser(); // Flag this as an intracluster request. $api_request->setIsClusterRequest(true); } return $this->validateAuthenticatedUser($api_request, $user); } else { if ($auth_type === null) { // No specified authentication type, continue with other authentication // methods below. } else { return array('ERR-INVALID-AUTH', pht('Provided "%s" ("%s") is not recognized.', 'auth.type', $auth_type)); } } $token_string = idx($metadata, 'token'); if (strlen($token_string)) { if (strlen($token_string) != 32) { return array('ERR-INVALID-AUTH', pht('API token "%s" has the wrong length. API tokens should be ' . '32 characters long.', $token_string)); } $type = head(explode('-', $token_string)); $valid_types = PhabricatorConduitToken::getAllTokenTypes(); $valid_types = array_fuse($valid_types); if (empty($valid_types[$type])) { return array('ERR-INVALID-AUTH', pht('API token "%s" has the wrong format. API tokens should be ' . '32 characters long and begin with one of these prefixes: %s.', $token_string, implode(', ', $valid_types))); } $token = id(new PhabricatorConduitTokenQuery())->setViewer(PhabricatorUser::getOmnipotentUser())->withTokens(array($token_string))->withExpired(false)->executeOne(); if (!$token) { $token = id(new PhabricatorConduitTokenQuery())->setViewer(PhabricatorUser::getOmnipotentUser())->withTokens(array($token_string))->withExpired(true)->executeOne(); if ($token) { return array('ERR-INVALID-AUTH', pht('API token "%s" was previously valid, but has expired.', $token_string)); } else { return array('ERR-INVALID-AUTH', pht('API token "%s" is not valid.', $token_string)); } } // If this is a "cli-" token, it expires shortly after it is generated // by default. Once it is actually used, we extend its lifetime and make // it permanent. This allows stray tokens to get cleaned up automatically // if they aren't being used. if ($token->getTokenType() == PhabricatorConduitToken::TYPE_COMMANDLINE) { if ($token->getExpires()) { $unguarded = AphrontWriteGuard::beginScopedUnguardedWrites(); $token->setExpires(null); $token->save(); unset($unguarded); } } // If this is a "clr-" token, Phabricator must be configured in cluster // mode and the remote address must be a cluster node. if ($token->getTokenType() == PhabricatorConduitToken::TYPE_CLUSTER) { if (!PhabricatorEnv::isClusterRemoteAddress()) { return array('ERR-INVALID-AUTH', pht('This request originates from outside of the Phabricator ' . 'cluster address range. Requests signed with cluster API ' . 'tokens must originate from within the cluster.')); } // Flag this as an intracluster request. $api_request->setIsClusterRequest(true); } $user = $token->getObject(); if (!$user instanceof PhabricatorUser) { return array('ERR-INVALID-AUTH', pht('API token is not associated with a valid user.')); } return $this->validateAuthenticatedUser($api_request, $user); } // handle oauth $access_token = idx($metadata, 'access_token'); $method_scope = idx($metadata, 'scope'); if ($access_token && $method_scope != PhabricatorOAuthServerScope::SCOPE_NOT_ACCESSIBLE) { $token = id(new PhabricatorOAuthServerAccessToken())->loadOneWhere('token = %s', $access_token); if (!$token) { return array('ERR-INVALID-AUTH', pht('Access token does not exist.')); } $oauth_server = new PhabricatorOAuthServer(); $valid = $oauth_server->validateAccessToken($token, $method_scope); if (!$valid) { return array('ERR-INVALID-AUTH', pht('Access token is invalid.')); } // valid token, so let's log in the user! $user_phid = $token->getUserPHID(); $user = id(new PhabricatorUser())->loadOneWhere('phid = %s', $user_phid); if (!$user) { return array('ERR-INVALID-AUTH', pht('Access token is for invalid user.')); } return $this->validateAuthenticatedUser($api_request, $user); } // Handle sessionless auth. // TODO: This is super messy. // TODO: Remove this in favor of token-based auth. if (isset($metadata['authUser'])) { $user = id(new PhabricatorUser())->loadOneWhere('userName = %s', $metadata['authUser']); if (!$user) { return array('ERR-INVALID-AUTH', pht('Authentication is invalid.')); } $token = idx($metadata, 'authToken'); $signature = idx($metadata, 'authSignature'); $certificate = $user->getConduitCertificate(); $hash = sha1($token . $certificate); if (!phutil_hashes_are_identical($hash, $signature)) { return array('ERR-INVALID-AUTH', pht('Authentication is invalid.')); } return $this->validateAuthenticatedUser($api_request, $user); } // Handle session-based auth. // TODO: Remove this in favor of token-based auth. $session_key = idx($metadata, 'sessionKey'); if (!$session_key) { return array('ERR-INVALID-SESSION', pht('Session key is not present.')); } $user = id(new PhabricatorAuthSessionEngine())->loadUserForSession(PhabricatorAuthSession::TYPE_CONDUIT, $session_key); if (!$user) { return array('ERR-INVALID-SESSION', pht('Session key is invalid.')); } return $this->validateAuthenticatedUser($api_request, $user); }
public function pingConduit() { // It's fairly common to have issues here, e.g. because Phabricator isn't // running, isn't accessible, you put the domain in your hostsfile but it // isn't available on the production host, etc. If any of this doesn't work, // conduit will throw. // We do this here rather than in the daemon since there's an HTTPS + curl // + fork issue of some kind that makes $conduit = new ConduitClient(PhabricatorEnv::getURI('/api/')); $conduit->setTimeout(5); $conduit->callMethodSynchronous('conduit.ping', array()); }
public function loadWorkingCopyDifferentialRevisions(ConduitClient $conduit, array $query) { // We don't have much to go on in SVN, look for revisions that came from // this directory and belong to the same project. $project = $this->getWorkingCopyIdentity()->getProjectID(); if (!$project) { return array(); } $results = $conduit->callMethodSynchronous('differential.query', $query + array('arcanistProjects' => array($project))); foreach ($results as $key => $result) { if ($result['sourcePath'] != $this->getPath()) { unset($results[$key]); } } foreach ($results as $key => $result) { $results[$key]['why'] = "Matching arcanist project name and working copy directory path."; } return $results; }
public function testConduitRequestEncoding() { $input = array('z' => array('nothing' => null, 'emptystring' => ''), 'empty' => array(), 'list' => array(15, 'quack', true, false), 'a' => array('key' => 'value', 'key2' => 'value2')); $expect = 'O4:S1:aO2:S3:keyS5:valueS4:key2S6:value2S5:emptyA0:S4:listA4:I2:15' . 'S5:quackB1:B0:S1:zO2:S11:emptystringS0:S7:nothingN:'; $this->assertEqual($expect, ConduitClient::encodeRawDataForSignature($input)); }
<?php require_once __DIR__ . '/vendor/libphutil/src/__phutil_library_init__.php'; require_once __DIR__ . '/vendor/autoload.php'; if (count($argv) < 3) { echo "Usage: php maniphest_bulk_add.php project_id /path/to/tasks_file\n"; exit(1); } $project = $argv[1]; $tasksFile = $argv[2]; $config = Symfony\Component\Yaml\Yaml::parse(file_get_contents(__DIR__ . '/config.yml')); $conduit = new ConduitClient($config['PHABRICATOR_URL']); $conduit->callMethodSynchronous('conduit.connect', ['client' => 'Maniphest Bulk Add', 'clientVersion' => 1, 'user' => $config['USER'], 'certificate' => $config['CONDUIT_CERTIFICATE']]); $projectQuery = $conduit->callMethodSynchronous('project.query', ['ids' => [$project]]); if (empty($projectQuery['data'])) { echo "Could not find project {$project}"; exit(1); } $targetProject = reset($projectQuery['data']); $taskIDs = array_filter(array_map(function ($line) { preg_match("/\\d+\$/", $line, $matches); if (!empty($matches)) { return $matches[0]; } }, explode("\n", file_get_contents($tasksFile)))); $tasks = $conduit->callMethodSynchronous('maniphest.query', ['ids' => $taskIDs]); if (count($tasks) !== count($taskIDs)) { echo "Couldn't find all the tasks. Aborting :(\n"; exit(1); } foreach ($tasks as $task) {
public function loadWorkingCopyDifferentialRevisions(ConduitClient $conduit, array $query) { $results = $conduit->callMethodSynchronous('differential.query', $query); foreach ($results as $key => $result) { if ($result['sourcePath'] != $this->getPath()) { unset($results[$key]); } } foreach ($results as $key => $result) { $results[$key]['why'] = pht('Matching working copy directory path.'); } return $results; }
public function loadWorkingCopyDifferentialRevisions(ConduitClient $conduit, array $query) { $messages = $this->getGitCommitLog(); if (!strlen($messages)) { return array(); } $parser = new ArcanistDiffParser(); $messages = $parser->parseDiff($messages); // First, try to find revisions by explicit revision IDs in commit messages. $revision_ids = array(); foreach ($messages as $message) { $object = ArcanistDifferentialCommitMessage::newFromRawCorpus($message->getMetadata('message')); if ($object->getRevisionID()) { $revision_ids[] = $object->getRevisionID(); } } if ($revision_ids) { $results = $conduit->callMethodSynchronous('differential.query', $query + array('ids' => $revision_ids)); return $results; } // If we didn't succeed, try to find revisions by hash. $hashes = array(); foreach ($this->getLocalCommitInformation() as $commit) { $hashes[] = array('gtcm', $commit['commit']); $hashes[] = array('gttr', $commit['tree']); } $results = $conduit->callMethodSynchronous('differential.query', $query + array('commitHashes' => $hashes)); return $results; }
public function pingConduit() { // It's fairly common to have issues here, e.g. because Phabricator isn't // running, isn't accessible, you put the domain in your hostsfile but it // isn't available on the production host, etc. If any of this doesn't work, // conduit will throw. $conduit = new ConduitClient(PhabricatorEnv::getURI('/api/')); $conduit->callMethodSynchronous('conduit.ping', array()); }
public function loadWorkingCopyDifferentialRevisions(ConduitClient $conduit, array $query) { // Try to find revisions by hash. $hashes = array(); foreach ($this->getLocalCommitInformation() as $commit) { $hashes[] = array('hgcm', $commit['rev']); } $results = $conduit->callMethodSynchronous('differential.query', $query + array('commitHashes' => $hashes)); return $results; }
public function loadWorkingCopyDifferentialRevisions(ConduitClient $conduit, array $query) { $messages = $this->getCommitMessageLog(); $parser = new ArcanistDiffParser(); // First, try to find revisions by explicit revision IDs in commit messages. $reason_map = array(); $revision_ids = array(); foreach ($messages as $node_id => $message) { $object = ArcanistDifferentialCommitMessage::newFromRawCorpus($message); if ($object->getRevisionID()) { $revision_ids[] = $object->getRevisionID(); $reason_map[$object->getRevisionID()] = $node_id; } } if ($revision_ids) { $results = $conduit->callMethodSynchronous('differential.query', $query + array('ids' => $revision_ids)); foreach ($results as $key => $result) { $hash = substr($reason_map[$result['id']], 0, 16); $results[$key]['why'] = pht("Commit message for '%s' has explicit 'Differential Revision'.", $hash); } return $results; } // Try to find revisions by hash. $hashes = array(); foreach ($this->getLocalCommitInformation() as $commit) { $hashes[] = array('hgcm', $commit['commit']); } if ($hashes) { // NOTE: In the case of "arc diff . --uncommitted" in a Mercurial working // copy with dirty changes, there may be no local commits. $results = $conduit->callMethodSynchronous('differential.query', $query + array('commitHashes' => $hashes)); foreach ($results as $key => $hash) { $results[$key]['why'] = pht('A mercurial commit hash in the commit range is already attached ' . 'to the Differential revision.'); } return $results; } return array(); }
public function loadWorkingCopyDifferentialRevisions(ConduitClient $conduit, array $query) { $messages = $this->getGitCommitLog(); if (!strlen($messages)) { return array(); } $parser = new ArcanistDiffParser(); $messages = $parser->parseDiff($messages); // First, try to find revisions by explicit revision IDs in commit messages. $reason_map = array(); $revision_ids = array(); foreach ($messages as $message) { $object = ArcanistDifferentialCommitMessage::newFromRawCorpus($message->getMetadata('message')); if ($object->getRevisionID()) { $revision_ids[] = $object->getRevisionID(); $reason_map[$object->getRevisionID()] = $message->getCommitHash(); } } if ($revision_ids) { $results = $conduit->callMethodSynchronous('differential.query', $query + array('ids' => $revision_ids)); foreach ($results as $key => $result) { $hash = substr($reason_map[$result['id']], 0, 16); $results[$key]['why'] = "Commit message for '{$hash}' has explicit 'Differential Revision'."; } return $results; } // If we didn't succeed, try to find revisions by hash. $hashes = array(); foreach ($this->getLocalCommitInformation() as $commit) { $hashes[] = array('gtcm', $commit['commit']); $hashes[] = array('gttr', $commit['tree']); } $results = $conduit->callMethodSynchronous('differential.query', $query + array('commitHashes' => $hashes)); foreach ($results as $key => $result) { $results[$key]['why'] = 'A git commit or tree hash in the commit range is already attached ' . 'to the Differential revision.'; } return $results; }
<?php use Rych\Silex\Provider\PlatesServiceProvider; require_once __DIR__ . '/vendor/autoload.php'; $app = new Silex\Application(require_once __DIR__ . '/config.php'); require_once $app['libphutil_path'] . '/libphutil/src/__phutil_library_init__.php'; $_client = new ConduitClient($app['phabricator_url']); $_client->setConduitToken($app['api_token']); $_escaper = new Escaper(); $_template = new Template(__DIR__ . '/_tpl', $_escaper); /* HOME redirect to /{year}/{date} */ $app->get('/', function () use($app) { $url = $app['request']->getBaseUrl() . '/' . date('Y') . '/' . date('n'); return $app->redirect($url); }); /* MAIN */ $app->get('/{year}/{month}', function ($year, $month) use($app, $_client, $_template) { $data['base_path'] = $app['request']->getBaseUrl(); $data['year'] = $year; $data['month'] = $month; $data['users'] = $_client->callMethodSynchronous('user.query', []); $data['days'] = cal_days_in_month(CAL_GREGORIAN, $month, $year); return $_template->render('index', $data); }); /* API to get json data */ $app->get('/api', function () use($app, $_client) { $users = $_client->callMethodSynchronous('user.query', []); $colors = ['#80E680', '#4D4DFF', '#E066A3', '#DB4D4D', '#FF944D', '#A38566', '#FFFF66']; $projectColors = []; foreach ($users as &$user) { $open_tasks = $_client->callMethodSynchronous('maniphest.query', ['status' => 'status-open', 'ownerPHIDs' => [$user['phid']]]);