public static function loadAndApplyRules(HeraldAdapter $adapter) { $engine = new HeraldEngine(); $rules = $engine->loadRulesForAdapter($adapter); $effects = $engine->applyRules($rules, $adapter); $engine->applyEffects($effects, $adapter, $rules); return $engine->getTranscript(); }
public static function loadAndApplyRules(HeraldObjectAdapter $object) { $content_type = $object->getHeraldTypeName(); $rules = HeraldRule::loadAllByContentTypeWithFullData($content_type, $object->getPHID()); $engine = new HeraldEngine(); $effects = $engine->applyRules($rules, $object); $engine->applyEffects($effects, $object, $rules); return $engine->getTranscript(); }
public function saveDiff(DifferentialDiff $diff) { $actor = $this->requireActor(); // Generate a PHID first, so the transcript will point at the object if // we deicde to preserve it. $phid = $diff->generatePHID(); $diff->setPHID($phid); $adapter = id(new HeraldDifferentialDiffAdapter())->setDiff($diff); $adapter->setContentSource($this->getContentSource()); $adapter->setIsNewObject(true); $engine = new HeraldEngine(); $rules = $engine->loadRulesForAdapter($adapter); $rules = mpull($rules, null, 'getID'); $effects = $engine->applyRules($rules, $adapter); $blocking_effect = null; foreach ($effects as $effect) { if ($effect->getAction() == HeraldAdapter::ACTION_BLOCK) { $blocking_effect = $effect; break; } } if ($blocking_effect) { $rule = idx($rules, $effect->getRuleID()); if ($rule && strlen($rule->getName())) { $rule_name = $rule->getName(); } else { $rule_name = pht('Unnamed Herald Rule'); } $message = $effect->getTarget(); if (!strlen($message)) { $message = pht('(None.)'); } throw new DifferentialDiffCreationRejectException(pht("Creation of this diff was rejected by Herald rule %s.\n" . " Rule: %s\n" . "Reason: %s", 'H' . $effect->getRuleID(), $rule_name, $message)); } $diff->save(); // NOTE: We only save the transcript if we didn't block the diff. // Otherwise, we might save some of the diff's content in the transcript // table, which would defeat the purpose of allowing rules to block // storage of key material. $engine->applyEffects($effects, $adapter, $rules); $xscript = $engine->getTranscript(); }
private function applyHeraldRules(array $updates, HeraldAdapter $adapter_template, array $all_updates) { if (!$updates) { return; } $adapter_template->setHookEngine($this); $engine = new HeraldEngine(); $rules = null; $blocking_effect = null; $blocked_update = null; $blocking_xscript = null; foreach ($updates as $update) { $adapter = id(clone $adapter_template)->setPushLog($update); if ($rules === null) { $rules = $engine->loadRulesForAdapter($adapter); } $effects = $engine->applyRules($rules, $adapter); $engine->applyEffects($effects, $adapter, $rules); $xscript = $engine->getTranscript(); // Store any PHIDs we want to send email to for later. foreach ($adapter->getEmailPHIDs() as $email_phid) { $this->emailPHIDs[$email_phid] = $email_phid; } $block_action = DiffusionBlockHeraldAction::ACTIONCONST; if ($blocking_effect === null) { foreach ($effects as $effect) { if ($effect->getAction() == $block_action) { $blocking_effect = $effect; $blocked_update = $update; $blocking_xscript = $xscript; break; } } } } if ($blocking_effect) { $rule = $blocking_effect->getRule(); $this->rejectCode = PhabricatorRepositoryPushLog::REJECT_HERALD; $this->rejectDetails = $rule->getPHID(); $message = $blocking_effect->getTarget(); if (!strlen($message)) { $message = pht('(None.)'); } $blocked_ref_name = coalesce($blocked_update->getRefName(), $blocked_update->getRefNewShort()); $blocked_name = $blocked_update->getRefType() . '/' . $blocked_ref_name; throw new DiffusionCommitHookRejectException(pht("This push was rejected by Herald push rule %s.\n" . " Change: %s\n" . " Rule: %s\n" . " Reason: %s\n" . "Transcript: %s", $rule->getMonogram(), $blocked_name, $rule->getName(), $message, PhabricatorEnv::getProductionURI('/herald/transcript/' . $blocking_xscript->getID() . '/'))); } }
public function processRequest() { $request = $this->getRequest(); $user = $request->getUser(); $request = $this->getRequest(); $object_name = trim($request->getStr('object_name')); $e_name = true; $errors = array(); if ($request->isFormPost()) { if (!$object_name) { $e_name = 'Required'; $errors[] = 'An object name is required.'; } if (!$errors) { $matches = null; $object = null; if (preg_match('/^D(\\d+)$/', $object_name, $matches)) { $object = id(new DifferentialRevision())->load($matches[1]); if (!$object) { $e_name = 'Invalid'; $errors[] = 'No Differential Revision with that ID exists.'; } } else { if (preg_match('/^r([A-Z]+)(\\w+)$/', $object_name, $matches)) { $repo = id(new PhabricatorRepository())->loadOneWhere('callsign = %s', $matches[1]); if (!$repo) { $e_name = 'Invalid'; $errors[] = 'There is no repository with the callsign ' . $matches[1] . '.'; } $commit = id(new PhabricatorRepositoryCommit())->loadOneWhere('repositoryID = %d AND commitIdentifier = %s', $repo->getID(), $matches[2]); if (!$commit) { $e_name = 'Invalid'; $errors[] = 'There is no commit with that identifier.'; } $object = $commit; } else { $e_name = 'Invalid'; $errors[] = 'This object name is not recognized.'; } } if (!$errors) { if ($object instanceof DifferentialRevision) { $adapter = new HeraldDifferentialRevisionAdapter($object, $object->loadActiveDiff()); } else { if ($object instanceof PhabricatorRepositoryCommit) { $data = id(new PhabricatorRepositoryCommitData())->loadOneWhere('commitID = %d', $object->getID()); $adapter = new HeraldCommitAdapter($repo, $object, $data); } else { throw new Exception("Can not build adapter for object!"); } } $rules = HeraldRule::loadAllByContentTypeWithFullData($adapter->getHeraldTypeName()); $engine = new HeraldEngine(); $effects = $engine->applyRules($rules, $adapter); $dry_run = new HeraldDryRunAdapter(); $engine->applyEffects($effects, $dry_run); $xscript = $engine->getTranscript(); return id(new AphrontRedirectResponse())->setURI('/herald/transcript/' . $xscript->getID() . '/'); } } } if ($errors) { $error_view = new AphrontErrorView(); $error_view->setTitle('Form Errors'); $error_view->setErrors($errors); } else { $error_view = null; } $form = id(new AphrontFormView())->setUser($user)->appendChild('<p class="aphront-form-instructions">Enter an object to test rules ' . 'for, like a Diffusion commit (e.g., <tt>rX123</tt>) or a ' . 'Differential revision (e.g., <tt>D123</tt>). You will be shown the ' . 'results of a dry run on the object.</p>')->appendChild(id(new AphrontFormTextControl())->setLabel('Object Name')->setName('object_name')->setError($e_name)->setValue($object_name))->appendChild(id(new AphrontFormSubmitControl())->setValue('Test Rules')); $panel = new AphrontPanelView(); $panel->setHeader('Test Herald Rules'); $panel->setWidth(AphrontPanelView::WIDTH_FULL); $panel->appendChild($form); return $this->buildStandardPageResponse(array($error_view, $panel), array('title' => 'Test Console', 'tab' => 'test')); }
public function parseCommit(PhabricatorRepository $repository, PhabricatorRepositoryCommit $commit) { if ($repository->getDetail('herald-disabled')) { return; } $data = id(new PhabricatorRepositoryCommitData())->loadOneWhere('commitID = %d', $commit->getID()); $rules = HeraldRule::loadAllByContentTypeWithFullData(HeraldContentTypeConfig::CONTENT_TYPE_COMMIT); $adapter = new HeraldCommitAdapter($repository, $commit, $data); $engine = new HeraldEngine(); $effects = $engine->applyRules($rules, $adapter); $engine->applyEffects($effects, $adapter); $email_phids = $adapter->getEmailPHIDs(); if (!$email_phids) { return; } $xscript = $engine->getTranscript(); $commit_name = $adapter->getHeraldName(); $revision = $adapter->loadDifferentialRevision(); $name = null; if ($revision) { $name = ' ' . $revision->getTitle(); } $author_phid = $data->getCommitDetail('authorPHID'); $reviewer_phid = $data->getCommitDetail('reviewerPHID'); $phids = array_filter(array($author_phid, $reviewer_phid)); $handles = array(); if ($phids) { $handles = id(new PhabricatorObjectHandleData($phids))->loadHandles(); } if ($author_phid) { $author_name = $handles[$author_phid]->getName(); } else { $author_name = $data->getAuthorName(); } if ($reviewer_phid) { $reviewer_name = $handles[$reviewer_phid]->getName(); } else { $reviewer_name = null; } $who = implode(', ', array_filter(array($author_name, $reviewer_name))); $description = $data->getCommitMessage(); $details = PhabricatorEnv::getProductionURI('/' . $commit_name); $differential = $revision ? PhabricatorEnv::getProductionURI('/D' . $revision->getID()) : 'No revision.'; $files = $adapter->loadAffectedPaths(); sort($files); $files = implode("\n ", $files); $xscript_id = $xscript->getID(); $manage_uri = PhabricatorEnv::getProductionURI('/herald/view/commits/'); $why_uri = PhabricatorEnv::getProductionURI('/herald/transcript/' . $xscript_id . '/'); $body = <<<EOBODY DESCRIPTION {$description} DETAILS {$details} DIFFERENTIAL REVISION {$differential} AFFECTED FILES {$files} MANAGE HERALD COMMIT RULES {$manage_uri} WHY DID I GET THIS EMAIL? {$why_uri} EOBODY; $subject = "[Herald/Commit] {$commit_name} ({$who}){$name}"; $mailer = new PhabricatorMetaMTAMail(); $mailer->setRelatedPHID($commit->getPHID()); $mailer->addTos($email_phids); $mailer->setSubject($subject); $mailer->setBody($body); $mailer->addHeader('X-Herald-Rules', $xscript->getXHeraldRulesHeader()); if ($author_phid) { $mailer->setFrom($author_phid); } $mailer->saveAndSend(); }
/** * @task mail */ private function runHeraldMailRules(array $messages) { foreach ($messages as $message) { $engine = new HeraldEngine(); $adapter = id(new PhabricatorMailOutboundMailHeraldAdapter())->setObject($message); $rules = $engine->loadRulesForAdapter($adapter); $effects = $engine->applyRules($rules, $adapter); $engine->applyEffects($effects, $adapter, $rules); } }
private function applyHeraldRules(PhabricatorRepository $repository, PhabricatorRepositoryCommit $commit) { $commit->attachRepository($repository); // Don't take any actions on an importing repository. Principally, this // avoids generating thousands of audits or emails when you import an // established repository on an existing install. if ($repository->isImporting()) { return; } if ($repository->getDetail('herald-disabled')) { return; } $data = id(new PhabricatorRepositoryCommitData())->loadOneWhere('commitID = %d', $commit->getID()); if (!$data) { throw new PhabricatorWorkerPermanentFailureException(pht('Unable to load commit data. The data for this task is invalid ' . 'or no longer exists.')); } $adapter = id(new HeraldCommitAdapter())->setCommit($commit); $rules = id(new HeraldRuleQuery())->setViewer(PhabricatorUser::getOmnipotentUser())->withContentTypes(array($adapter->getAdapterContentType()))->withDisabled(false)->needConditionsAndActions(true)->needAppliedToPHIDs(array($adapter->getPHID()))->needValidateAuthors(true)->execute(); $engine = new HeraldEngine(); $effects = $engine->applyRules($rules, $adapter); $engine->applyEffects($effects, $adapter, $rules); $xscript = $engine->getTranscript(); $audit_phids = $adapter->getAuditMap(); $cc_phids = $adapter->getAddCCMap(); if ($audit_phids || $cc_phids) { $this->createAudits($commit, $audit_phids, $cc_phids, $rules); } HarbormasterBuildable::applyBuildPlans($commit->getPHID(), $repository->getPHID(), $adapter->getBuildPlans()); $explicit_auditors = $this->createAuditsFromCommitMessage($commit, $data); $this->publishFeedStory($repository, $commit, $data); $herald_targets = $adapter->getEmailPHIDs(); $email_phids = array_unique(array_merge($explicit_auditors, array_keys($cc_phids), $herald_targets)); if (!$email_phids) { return; } $revision = $adapter->loadDifferentialRevision(); if ($revision) { $name = $revision->getTitle(); } else { $name = $data->getSummary(); } $author_phid = $data->getCommitDetail('authorPHID'); $reviewer_phid = $data->getCommitDetail('reviewerPHID'); $phids = array_filter(array($author_phid, $reviewer_phid, $commit->getPHID())); $handles = id(new PhabricatorHandleQuery())->setViewer(PhabricatorUser::getOmnipotentUser())->withPHIDs($phids)->execute(); $commit_handle = $handles[$commit->getPHID()]; $commit_name = $commit_handle->getName(); if ($author_phid) { $author_name = $handles[$author_phid]->getName(); } else { $author_name = $data->getAuthorName(); } if ($reviewer_phid) { $reviewer_name = $handles[$reviewer_phid]->getName(); } else { $reviewer_name = null; } $who = implode(', ', array_filter(array($author_name, $reviewer_name))); $description = $data->getCommitMessage(); $commit_uri = PhabricatorEnv::getProductionURI($commit_handle->getURI()); $differential = $revision ? PhabricatorEnv::getProductionURI('/D' . $revision->getID()) : 'No revision.'; $limit = self::MAX_FILES_SHOWN_IN_EMAIL; $files = $adapter->loadAffectedPaths(); sort($files); if (count($files) > $limit) { array_splice($files, $limit); $files[] = '(This commit affected more than ' . $limit . ' files. ' . 'Only ' . $limit . ' are shown here and additional ones are truncated.)'; } $files = implode("\n", $files); $xscript_id = $xscript->getID(); $why_uri = '/herald/transcript/' . $xscript_id . '/'; $reply_handler = PhabricatorAuditCommentEditor::newReplyHandlerForCommit($commit); $template = new PhabricatorMetaMTAMail(); $inline_patch_text = $this->buildPatch($template, $repository, $commit); $body = new PhabricatorMetaMTAMailBody(); $body->addRawSection($description); $body->addTextSection(pht('DETAILS'), $commit_uri); // TODO: This should be integrated properly once we move to // ApplicationTransactions. $field_list = PhabricatorCustomField::getObjectFields($commit, PhabricatorCustomField::ROLE_APPLICATIONTRANSACTIONS); $field_list->setViewer(PhabricatorUser::getOmnipotentUser())->readFieldsFromStorage($commit); foreach ($field_list->getFields() as $field) { try { $field->buildApplicationTransactionMailBody(new DifferentialTransaction(), $body); } catch (Exception $ex) { // Log the exception and continue. phlog($ex); } } $body->addTextSection(pht('DIFFERENTIAL REVISION'), $differential); $body->addTextSection(pht('AFFECTED FILES'), $files); $body->addReplySection($reply_handler->getReplyHandlerInstructions()); $body->addHeraldSection($why_uri); $body->addRawSection($inline_patch_text); $body = $body->render(); $prefix = PhabricatorEnv::getEnvConfig('metamta.diffusion.subject-prefix'); $threading = PhabricatorAuditCommentEditor::getMailThreading($repository, $commit); list($thread_id, $thread_topic) = $threading; $template->setRelatedPHID($commit->getPHID()); $template->setSubject("{$commit_name}: {$name}"); $template->setSubjectPrefix($prefix); $template->setVarySubjectPrefix('[Commit]'); $template->setBody($body); $template->setThreadID($thread_id, $is_new = true); $template->addHeader('Thread-Topic', $thread_topic); $template->setIsBulk(true); $template->addHeader('X-Herald-Rules', $xscript->getXHeraldRulesHeader()); if ($author_phid) { $template->setFrom($author_phid); } // TODO: We should verify that each recipient can actually see the // commit before sending them email (T603). $mails = $reply_handler->multiplexMail($template, id(new PhabricatorHandleQuery())->setViewer(PhabricatorUser::getOmnipotentUser())->withPHIDs($email_phids)->execute(), array()); foreach ($mails as $mail) { $mail->saveAndSend(); } }
public function parseCommit(PhabricatorRepository $repository, PhabricatorRepositoryCommit $commit) { $data = id(new PhabricatorRepositoryCommitData())->loadOneWhere('commitID = %d', $commit->getID()); if (!$data) { // TODO: Permanent failure. return; } $rules = HeraldRule::loadAllByContentTypeWithFullData(HeraldContentTypeConfig::CONTENT_TYPE_COMMIT, $commit->getPHID()); $adapter = new HeraldCommitAdapter($repository, $commit, $data); $engine = new HeraldEngine(); $effects = $engine->applyRules($rules, $adapter); $engine->applyEffects($effects, $adapter, $rules); $audit_phids = $adapter->getAuditMap(); if ($audit_phids) { $this->createAudits($commit, $audit_phids, $rules); } $explicit_auditors = $this->createAuditsFromCommitMessage($commit, $data); if ($repository->getDetail('herald-disabled')) { // This just means "disable email"; audits are (mostly) idempotent. return; } $this->publishFeedStory($repository, $commit, $data); $herald_targets = $adapter->getEmailPHIDs(); $email_phids = array_unique(array_merge($explicit_auditors, $herald_targets)); if (!$email_phids) { return; } $xscript = $engine->getTranscript(); $revision = $adapter->loadDifferentialRevision(); if ($revision) { $name = $revision->getTitle(); } else { $name = $data->getSummary(); } $author_phid = $data->getCommitDetail('authorPHID'); $reviewer_phid = $data->getCommitDetail('reviewerPHID'); $phids = array_filter(array($author_phid, $reviewer_phid, $commit->getPHID())); $handles = id(new PhabricatorObjectHandleData($phids))->loadHandles(); $commit_handle = $handles[$commit->getPHID()]; $commit_name = $commit_handle->getName(); if ($author_phid) { $author_name = $handles[$author_phid]->getName(); } else { $author_name = $data->getAuthorName(); } if ($reviewer_phid) { $reviewer_name = $handles[$reviewer_phid]->getName(); } else { $reviewer_name = null; } $who = implode(', ', array_filter(array($author_name, $reviewer_name))); $description = $data->getCommitMessage(); $commit_uri = PhabricatorEnv::getProductionURI($commit_handle->getURI()); $differential = $revision ? PhabricatorEnv::getProductionURI('/D' . $revision->getID()) : 'No revision.'; $files = $adapter->loadAffectedPaths(); sort($files); $files = implode("\n", $files); $xscript_id = $xscript->getID(); $manage_uri = '/herald/view/commits/'; $why_uri = '/herald/transcript/' . $xscript_id . '/'; $reply_handler = PhabricatorAuditCommentEditor::newReplyHandlerForCommit($commit); $template = new PhabricatorMetaMTAMail(); $inline_patch_text = $this->buildPatch($template, $repository, $commit); $body = new PhabricatorMetaMTAMailBody(); $body->addRawSection($description); $body->addTextSection(pht('DETAILS'), $commit_uri); $body->addTextSection(pht('DIFFERENTIAL REVISION'), $differential); $body->addTextSection(pht('AFFECTED FILES'), $files); $body->addReplySection($reply_handler->getReplyHandlerInstructions()); $body->addHeraldSection($manage_uri, $why_uri); $body->addRawSection($inline_patch_text); $body = $body->render(); $prefix = PhabricatorEnv::getEnvConfig('metamta.diffusion.subject-prefix'); $threading = PhabricatorAuditCommentEditor::getMailThreading($repository, $commit); list($thread_id, $thread_topic) = $threading; $template->setRelatedPHID($commit->getPHID()); $template->setSubject("{$commit_name}: {$name}"); $template->setSubjectPrefix($prefix); $template->setVarySubjectPrefix("[Commit]"); $template->setBody($body); $template->setThreadID($thread_id, $is_new = true); $template->addHeader('Thread-Topic', $thread_topic); $template->setIsBulk(true); $template->addHeader('X-Herald-Rules', $xscript->getXHeraldRulesHeader()); if ($author_phid) { $template->setFrom($author_phid); } $mails = $reply_handler->multiplexMail($template, id(new PhabricatorObjectHandleData($email_phids))->loadHandles(), array()); foreach ($mails as $mail) { $mail->saveAndSend(); } }
public function parseCommit(PhabricatorRepository $repository, PhabricatorRepositoryCommit $commit) { $data = id(new PhabricatorRepositoryCommitData())->loadOneWhere('commitID = %d', $commit->getID()); if (!$data) { // TODO: Permanent failure. return; } $rules = HeraldRule::loadAllByContentTypeWithFullData(HeraldContentTypeConfig::CONTENT_TYPE_COMMIT, $commit->getPHID()); $adapter = new HeraldCommitAdapter($repository, $commit, $data); $engine = new HeraldEngine(); $effects = $engine->applyRules($rules, $adapter); $engine->applyEffects($effects, $adapter, $rules); $audit_phids = $adapter->getAuditMap(); if ($audit_phids) { $this->createAudits($commit, $audit_phids, $rules); } $this->createAuditsFromCommitMessage($commit, $data); $email_phids = $adapter->getEmailPHIDs(); if (!$email_phids) { return; } if ($repository->getDetail('herald-disabled')) { // This just means "disable email"; audits are (mostly) idempotent. return; } $xscript = $engine->getTranscript(); $revision = $adapter->loadDifferentialRevision(); if ($revision) { $name = $revision->getTitle(); } else { $name = $data->getSummary(); } $author_phid = $data->getCommitDetail('authorPHID'); $reviewer_phid = $data->getCommitDetail('reviewerPHID'); $phids = array_filter(array($author_phid, $reviewer_phid, $commit->getPHID())); $handles = id(new PhabricatorObjectHandleData($phids))->loadHandles(); $commit_handle = $handles[$commit->getPHID()]; $commit_name = $commit_handle->getName(); if ($author_phid) { $author_name = $handles[$author_phid]->getName(); } else { $author_name = $data->getAuthorName(); } if ($reviewer_phid) { $reviewer_name = $handles[$reviewer_phid]->getName(); } else { $reviewer_name = null; } $who = implode(', ', array_filter(array($author_name, $reviewer_name))); $description = $data->getCommitMessage(); $commit_uri = PhabricatorEnv::getProductionURI($commit_handle->getURI()); $differential = $revision ? PhabricatorEnv::getProductionURI('/D' . $revision->getID()) : 'No revision.'; $files = $adapter->loadAffectedPaths(); sort($files); $files = implode("\n ", $files); $xscript_id = $xscript->getID(); $manage_uri = PhabricatorEnv::getProductionURI('/herald/view/commits/'); $why_uri = PhabricatorEnv::getProductionURI('/herald/transcript/' . $xscript_id . '/'); $reply_handler = PhabricatorAuditCommentEditor::newReplyHandlerForCommit($commit); $reply_instructions = $reply_handler->getReplyHandlerInstructions(); if ($reply_instructions) { $reply_instructions = "\n" . "REPLY HANDLER ACTIONS\n" . " " . $reply_instructions . "\n"; } $body = <<<EOBODY DESCRIPTION {$description} DETAILS {$commit_uri} DIFFERENTIAL REVISION {$differential} AFFECTED FILES {$files} {$reply_instructions} MANAGE HERALD COMMIT RULES {$manage_uri} WHY DID I GET THIS EMAIL? {$why_uri} EOBODY; $prefix = PhabricatorEnv::getEnvConfig('metamta.diffusion.subject-prefix'); $subject = trim("{$prefix} {$commit_name}: {$name}"); $vary_subject = trim("{$prefix} [Commit] {$commit_name}: {$name}"); $threading = PhabricatorAuditCommentEditor::getMailThreading($commit->getPHID()); list($thread_id, $thread_topic) = $threading; $template = new PhabricatorMetaMTAMail(); $template->setRelatedPHID($commit->getPHID()); $template->setSubject($subject); $template->setVarySubject($subject); $template->setBody($body); $template->setThreadID($thread_id, $is_new = true); $template->addHeader('Thread-Topic', $thread_topic); $template->setIsBulk(true); $template->addHeader('X-Herald-Rules', $xscript->getXHeraldRulesHeader()); if ($author_phid) { $template->setFrom($author_phid); } $mails = $reply_handler->multiplexMail($template, id(new PhabricatorObjectHandleData($email_phids))->loadHandles(), array()); foreach ($mails as $mail) { $mail->saveAndSend(); } }