protected function applyAuditors(array $phids, HeraldRule $rule) { $adapter = $this->getAdapter(); $object = $adapter->getObject(); $auditors = $object->getAudits(); $current = array(); foreach ($auditors as $auditor) { if ($auditor->isInteresting()) { $current[] = $auditor->getAuditorPHID(); } } $allowed_types = array(PhabricatorPeopleUserPHIDType::TYPECONST, PhabricatorProjectProjectPHIDType::TYPECONST, PhabricatorOwnersPackagePHIDType::TYPECONST); $targets = $this->loadStandardTargets($phids, $allowed_types, $current); if (!$targets) { return; } $phids = array_fuse(array_keys($targets)); // TODO: Convert this to be translatable, structured data eventually. $reason_map = array(); foreach ($phids as $phid) { $reason_map[$phid][] = pht('%s Triggered Audit', $rule->getMonogram()); } $xaction = $adapter->newTransaction()->setTransactionType(PhabricatorAuditActionConstants::ADD_AUDITORS)->setNewValue($phids)->setMetadataValue('auditStatus', PhabricatorAuditStatusConstants::AUDIT_REQUIRED)->setMetadataValue('auditReasonMap', $reason_map); $adapter->queueTransaction($xaction); $this->logEffect(self::DO_ADD_AUDITORS, $phids); }
protected function applyRouting(HeraldRule $rule, $route, $phids) { $adapter = $this->getAdapter(); $mail = $adapter->getObject(); $mail->addRoutingRule($route, $phids, $rule->getPHID()); $this->logEffect(self::DO_ROUTE, array('route' => $route, 'phids' => $phids)); }
protected function loadPage() { $table = new HeraldRule(); $conn_r = $table->establishConnection('r'); $data = queryfx_all($conn_r, 'SELECT rule.* FROM %T rule %Q %Q %Q', $table->getTableName(), $this->buildWhereClause($conn_r), $this->buildOrderClause($conn_r), $this->buildLimitClause($conn_r)); return $table->loadAllFromArray($data); }
public function execute() { $table = new HeraldRule(); $conn_r = $table->establishConnection('r'); $where = $this->buildWhereClause($conn_r); $order = $this->buildOrderClause($conn_r); $limit = $this->buildLimitClause($conn_r); $data = queryfx_all($conn_r, 'SELECT rule.* FROM %T rule %Q %Q %Q', $table->getTableName(), $where, $order, $limit); return $table->loadAllFromArray($data); }
private function buildDescriptionView(HeraldRule $rule) { $viewer = $this->getRequest()->getUser(); $view = id(new PHUIPropertyListView())->setUser($viewer); $adapter = HeraldAdapter::getAdapterForContentType($rule->getContentType()); if ($adapter) { $handles = $viewer->loadHandles(HeraldAdapter::getHandlePHIDs($rule)); $rule_text = $adapter->renderRuleAsText($rule, $handles, $viewer); $view->addTextContent($rule_text); return $view; } return null; }
private function buildTimeline(HeraldRule $rule) { $viewer = $this->getRequest()->getUser(); $xactions = id(new HeraldTransactionQuery())->setViewer($viewer)->withObjectPHIDs(array($rule->getPHID()))->needComments(true)->execute(); $engine = id(new PhabricatorMarkupEngine())->setViewer($viewer); foreach ($xactions as $xaction) { if ($xaction->getComment()) { $engine->addObject($xaction->getComment(), PhabricatorApplicationTransactionComment::MARKUP_FIELD_COMMENT); } } $engine->process(); return id(new PhabricatorApplicationTransactionView())->setUser($viewer)->setObjectPHID($rule->getPHID())->setTransactions($xactions)->setMarkupEngine($engine); }
protected function applyBuilds(array $phids, HeraldRule $rule) { $adapter = $this->getAdapter(); $allowed_types = array(HarbormasterBuildPlanPHIDType::TYPECONST); $targets = $this->loadStandardTargets($phids, $allowed_types, array()); if (!$targets) { return; } $phids = array_fuse(array_keys($targets)); foreach ($phids as $phid) { $request = id(new HarbormasterBuildRequest())->setBuildPlanPHID($phid)->setInitiatorPHID($rule->getPHID()); $adapter->queueHarbormasterBuildRequest($request); } $this->logEffect(self::DO_BUILD, $phids); }
private function buildActionView(HeraldRule $rule) { $viewer = $this->getRequest()->getUser(); $id = $rule->getID(); $view = id(new PhabricatorActionListView())->setUser($viewer)->setObject($rule)->setObjectURI($this->getApplicationURI("rule/{$id}/")); $can_edit = PhabricatorPolicyFilter::hasCapability($viewer, $rule, PhabricatorPolicyCapability::CAN_EDIT); $view->addAction(id(new PhabricatorActionView())->setName(pht('Edit Rule'))->setHref($this->getApplicationURI("edit/{$id}/"))->setIcon('fa-pencil')->setDisabled(!$can_edit)->setWorkflow(!$can_edit)); if ($rule->getIsDisabled()) { $disable_uri = "disable/{$id}/enable/"; $disable_icon = 'fa-check'; $disable_name = pht('Activate Rule'); } else { $disable_uri = "disable/{$id}/disable/"; $disable_icon = 'fa-ban'; $disable_name = pht('Archive Rule'); } $view->addAction(id(new PhabricatorActionView())->setName(pht('Disable Rule'))->setHref($this->getApplicationURI($disable_uri))->setIcon($disable_icon)->setName($disable_name)->setDisabled(!$can_edit)->setWorkflow(true)); return $view; }
<?php echo pht("Checking for rules that can be converted to 'personal'.") . "\n"; $table = new HeraldRule(); $table->openTransaction(); $table->beginReadLocking(); $rules = $table->loadAll(); foreach ($rules as $rule) { if ($rule->getRuleType() !== HeraldRuleTypeConfig::RULE_TYPE_PERSONAL) { $actions = $rule->loadActions(); $can_be_personal = true; foreach ($actions as $action) { $target = $action->getTarget(); if (is_array($target)) { if (count($target) > 1) { $can_be_personal = false; break; } else { $targetPHID = head($target); if ($targetPHID !== $rule->getAuthorPHID()) { $can_be_personal = false; break; } } } else { if ($target) { if ($target !== $rule->getAuthorPHID()) { $can_be_personal = false; break; } }
protected function getRuleEffects(HeraldRule $rule, HeraldObjectAdapter $object) { $effects = array(); foreach ($rule->getActions() as $action) { $effect = new HeraldEffect(); $effect->setObjectPHID($object->getPHID()); $effect->setAction($action->getAction()); $effect->setTarget($action->getTarget()); $effect->setRuleID($rule->getID()); $name = $rule->getName(); $id = $rule->getID(); $effect->setReason('Conditions were met for Herald rule "' . $name . '" (#' . $id . ').'); $effects[] = $effect; } return $effects; }
/** * Load rules for the "Another Herald rule..." condition dropdown, which * allows one rule to depend upon the success or failure of another rule. */ private function loadRulesThisRuleMayDependUpon(HeraldRule $rule) { $viewer = $this->getRequest()->getUser(); // Any rule can depend on a global rule. $all_rules = id(new HeraldRuleQuery())->setViewer($viewer)->withRuleTypes(array(HeraldRuleTypeConfig::RULE_TYPE_GLOBAL))->withContentTypes(array($rule->getContentType()))->execute(); if ($rule->isObjectRule()) { // Object rules may depend on other rules for the same object. $all_rules += id(new HeraldRuleQuery())->setViewer($viewer)->withRuleTypes(array(HeraldRuleTypeConfig::RULE_TYPE_OBJECT))->withContentTypes(array($rule->getContentType()))->withTriggerObjectPHIDs(array($rule->getTriggerObjectPHID()))->execute(); } if ($rule->isPersonalRule()) { // Personal rules may depend upon your other personal rules. $all_rules += id(new HeraldRuleQuery())->setViewer($viewer)->withRuleTypes(array(HeraldRuleTypeConfig::RULE_TYPE_PERSONAL))->withContentTypes(array($rule->getContentType()))->withAuthorPHIDs(array($rule->getAuthorPHID()))->execute(); } // mark disabled rules as disabled since they are not useful as such; // don't filter though to keep edit cases sane / expected foreach ($all_rules as $current_rule) { if ($current_rule->getIsDisabled()) { $current_rule->makeEphemeral(); $current_rule->setName($rule->getName() . ' ' . pht('(Disabled)')); } } // A rule can not depend upon itself. unset($all_rules[$rule->getID()]); return $all_rules; }
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(); }
private function queryRules(AphrontPagerView $pager) { $rule = new HeraldRule(); $conn_r = $rule->establishConnection('r'); $where_clause = qsprintf($conn_r, 'WHERE contentType = %s', $this->view); if ($this->viewPHID) { $where_clause .= qsprintf($conn_r, ' AND authorPHID = %s', $this->viewPHID); } $data = queryfx_all($conn_r, 'SELECT * FROM %T %Q ORDER BY id DESC LIMIT %d, %d', $rule->getTableName(), $where_clause, $pager->getOffset(), $pager->getPageSize() + 1); $data = $pager->sliceResults($data); $rules = $rule->loadAllFromArray($data); $need_phids = mpull($rules, 'getAuthorPHID'); if ($this->viewPHID) { $need_phids[] = $this->viewPHID; } $handles = id(new PhabricatorObjectHandleData($need_phids))->loadHandles(); return array($rules, $handles); }
<?php echo pht('Cleaning up old Herald rule applied rows...') . "\n"; $table = new HeraldRule(); $table->openTransaction(); $table->beginReadLocking(); $rules = $table->loadAll(); foreach ($rules as $key => $rule) { $first_policy = HeraldRepetitionPolicyConfig::toInt(HeraldRepetitionPolicyConfig::FIRST); if ($rule->getRepetitionPolicy() != $first_policy) { unset($rules[$key]); } } $conn_w = $table->establishConnection('w'); $clause = ''; if ($rules) { $clause = qsprintf($conn_w, 'WHERE ruleID NOT IN (%Ld)', mpull($rules, 'getID')); } echo pht('This may take a moment') . "\n"; do { queryfx($conn_w, 'DELETE FROM %T %Q LIMIT 1000', HeraldRule::TABLE_RULE_APPLIED, $clause); echo '.'; } while ($conn_w->getAffectedRows()); $table->endReadLocking(); $table->saveTransaction(); echo "\n" . pht('Done.') . "\n";
/** * Given a @{class:HeraldRule}, this function extracts all the phids that * we'll want to load as handles later. * * This function performs a somewhat hacky approach to figuring out what * is and is not a phid - try to get the phid type and if the type is * *not* unknown assume its a valid phid. * * Don't try this at home. Use more strongly typed data at home. * * Think of the children. */ public static function getHandlePHIDs(HeraldRule $rule) { $phids = array($rule->getAuthorPHID()); foreach ($rule->getConditions() as $condition) { $value = $condition->getValue(); if (!is_array($value)) { $value = array($value); } foreach ($value as $val) { if (phid_get_type($val) != PhabricatorPHIDConstants::PHID_TYPE_UNKNOWN) { $phids[] = $val; } } } foreach ($rule->getActions() as $action) { $target = $action->getTarget(); if (!is_array($target)) { $target = array($target); } foreach ($target as $val) { if (phid_get_type($val) != PhabricatorPHIDConstants::PHID_TYPE_UNKNOWN) { $phids[] = $val; } } } if ($rule->isObjectRule()) { $phids[] = $rule->getTriggerObjectPHID(); } return $phids; }
public function serializeRule(HeraldRule $rule) { return $this->serializeRuleComponents((bool) $rule->getMustMatchAll(), $rule->getConditions(), $rule->getActions(), HeraldRepetitionPolicyConfig::toString($rule->getRepetitionPolicy())); }
public function processRequest() { $request = $this->getRequest(); $user = $request->getUser(); $content_type_map = HeraldContentTypeConfig::getContentTypeMap(); if ($this->id) { $rule = id(new HeraldRule())->load($this->id); if (!$rule) { return new Aphront404Response(); } if ($rule->getAuthorPHID() != $user->getPHID() && !$user->getIsAdmin()) { throw new Exception("You don't own this rule and can't edit it."); } } else { $rule = new HeraldRule(); $rule->setAuthorPHID($user->getPHID()); $rule->setMustMatchAll(true); $type = $request->getStr('type'); if (!isset($content_type_map[$type])) { $type = HeraldContentTypeConfig::CONTENT_TYPE_DIFFERENTIAL; } $rule->setContentType($type); } $this->setFilter($rule->getContentType()); $local_version = id(new HeraldRule())->getConfigVersion(); if ($rule->getConfigVersion() > $local_version) { throw new Exception("This rule was created with a newer version of Herald. You can not " . "view or edit it in this older version. Try dev or wait for a push."); } // Upgrade rule version to our version, since we might add newly-defined // conditions, etc. $rule->setConfigVersion($local_version); $rule_conditions = $rule->loadConditions(); $rule_actions = $rule->loadActions(); $rule->attachConditions($rule_conditions); $rule->attachActions($rule_actions); $e_name = true; $errors = array(); if ($request->isFormPost() && $request->getStr('save')) { list($e_name, $errors) = $this->saveRule($rule, $request); if (!$errors) { $uri = '/herald/view/' . $rule->getContentType() . '/'; return id(new AphrontRedirectResponse())->setURI($uri); } } if ($errors) { $error_view = new AphrontErrorView(); $error_view->setTitle('Form Errors'); $error_view->setErrors($errors); } else { $error_view = null; } $must_match_selector = $this->getMustMatchSelector($rule); $repetition_selector = $this->getRepetitionSelector($rule); $handles = $this->loadHandles($rule); require_celerity_resource('herald-css'); $type_name = $content_type_map[$rule->getContentType()]; $form = id(new AphrontFormView())->setUser($user)->setID('herald-rule-edit-form')->addHiddenInput('type', $rule->getContentType())->addHiddenInput('save', 1)->appendChild(javelin_render_tag('input', array('type' => 'hidden', 'name' => 'rule', 'sigil' => 'rule')))->appendChild(id(new AphrontFormTextControl())->setLabel('Rule Name')->setName('name')->setError($e_name)->setValue($rule->getName()))->appendChild(id(new AphrontFormStaticControl())->setLabel('Author')->setValue($handles[$rule->getAuthorPHID()]->getName()))->appendChild(id(new AphrontFormMarkupControl())->setValue("This rule triggers for <strong>{$type_name}</strong>."))->appendChild('<h1>Conditions</h1>' . '<div class="aphront-form-inset">' . '<div style="float: right;">' . javelin_render_tag('a', array('href' => '#', 'class' => 'button green', 'sigil' => 'create-condition', 'mustcapture' => true), 'Create New Condition') . '</div>' . '<p>When ' . $must_match_selector . ' these conditions are met:</p>' . '<div style="clear: both;"></div>' . javelin_render_tag('table', array('sigil' => 'rule-conditions', 'class' => 'herald-condition-table'), '') . '</div>')->appendChild('<h1>Action</h1>' . '<div class="aphront-form-inset">' . '<div style="float: right;">' . javelin_render_tag('a', array('href' => '#', 'class' => 'button green', 'sigil' => 'create-action', 'mustcapture' => true), 'Create New Action') . '</div>' . '<p>' . 'Take these actions ' . $repetition_selector . ' this rule matches:' . '</p>' . '<div style="clear: both;"></div>' . javelin_render_tag('table', array('sigil' => 'rule-actions', 'class' => 'herald-action-table'), '') . '</div>')->appendChild(id(new AphrontFormSubmitControl())->setValue('Save Rule')->addCancelButton('/herald/view/' . $rule->getContentType() . '/')); $this->setupEditorBehavior($rule, $handles); $panel = new AphrontPanelView(); $panel->setHeader('Edit Herald Rule'); $panel->setWidth(AphrontPanelView::WIDTH_WIDE); $panel->appendChild($form); return $this->buildStandardPageResponse(array($error_view, $panel), array('title' => 'Edit Rule')); }
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')); }
/** * Load rules for the "Another Herald rule..." condition dropdown, which * allows one rule to depend upon the success or failure of another rule. */ private function loadRulesThisRuleMayDependUpon(HeraldRule $rule) { // Any rule can depend on a global rule. $all_rules = id(new HeraldRuleQuery())->withRuleTypes(array(HeraldRuleTypeConfig::RULE_TYPE_GLOBAL))->withContentTypes(array($rule->getContentType()))->execute(); if ($rule->getRuleType() == HeraldRuleTypeConfig::RULE_TYPE_PERSONAL) { // Personal rules may depend upon your other personal rules. $all_rules += id(new HeraldRuleQuery())->withRuleTypes(array(HeraldRuleTypeConfig::RULE_TYPE_PERSONAL))->withContentTypes(array($rule->getContentType()))->withAuthorPHIDs(array($rule->getAuthorPHID()))->execute(); } // A rule can not depend upon itself. unset($all_rules[$rule->getID()]); return $all_rules; }
<?php $table = new HeraldRule(); $conn_w = $table->establishConnection('w'); echo "Assigning PHIDs to Herald Rules...\n"; foreach (new LiskMigrationIterator(new HeraldRule()) as $rule) { $id = $rule->getID(); echo "Rule {$id}.\n"; if ($rule->getPHID()) { continue; } queryfx($conn_w, 'UPDATE %T SET phid = %s WHERE id = %d', $table->getTableName(), PhabricatorPHID::generateNewPHID(HeraldRulePHIDType::TYPECONST), $rule->getID()); } echo "Done.\n";
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 processRequest() { $request = $this->getRequest(); $user = $request->getUser(); $content_type_map = HeraldContentTypeConfig::getContentTypeMap(); if ($this->id) { $rule = id(new HeraldRule())->load($this->id); if (!$rule) { return new Aphront404Response(); } if ($rule->getAuthorPHID() != $user->getPHID()) { throw new Exception("You don't own this rule and can't edit it."); } } else { $rule = new HeraldRule(); $rule->setAuthorPHID($user->getPHID()); $rule->setMustMatchAll(true); $type = $request->getStr('type'); if (!isset($content_type_map[$type])) { $type = HeraldContentTypeConfig::CONTENT_TYPE_DIFFERENTIAL; } $rule->setContentType($type); } $local_version = id(new HeraldRule())->getConfigVersion(); if ($rule->getConfigVersion() > $local_version) { throw new Exception("This rule was created with a newer version of Herald. You can not " . "view or edit it in this older version. Try dev or wait for a push."); } // Upgrade rule version to our version, since we might add newly-defined // conditions, etc. $rule->setConfigVersion($local_version); $rule_conditions = $rule->loadConditions(); $rule_actions = $rule->loadActions(); $rule->attachConditions($rule_conditions); $rule->attachActions($rule_actions); $e_name = true; $errors = array(); if ($request->isFormPost() && $request->getStr('save')) { $rule->setName($request->getStr('name')); $rule->setMustMatchAll($request->getStr('must_match') == 'all'); $repetition_policy_param = $request->getStr('repetition_policy'); $rule->setRepetitionPolicy(HeraldRepetitionPolicyConfig::toInt($repetition_policy_param)); if (!strlen($rule->getName())) { $e_name = "Required"; $errors[] = "Rule must have a name."; } $data = json_decode($request->getStr('rule'), true); if (!is_array($data) || !$data['conditions'] || !$data['actions']) { throw new Exception("Failed to decode rule data."); } $conditions = array(); foreach ($data['conditions'] as $condition) { if ($condition === null) { // We manage this as a sparse array on the client, so may receive // NULL if conditions have been removed. continue; } $obj = new HeraldCondition(); $obj->setFieldName($condition[0]); $obj->setFieldCondition($condition[1]); if (is_array($condition[2])) { $obj->setValue(array_keys($condition[2])); } else { $obj->setValue($condition[2]); } $cond_type = $obj->getFieldCondition(); if ($cond_type == HeraldConditionConfig::CONDITION_REGEXP) { if (@preg_match($obj->getValue(), '') === false) { $errors[] = 'The regular expression "' . $obj->getValue() . '" is not valid. ' . 'Regular expressions must have enclosing characters (e.g. ' . '"@/path/to/file@", not "/path/to/file") and be syntactically ' . 'correct.'; } } if ($cond_type == HeraldConditionConfig::CONDITION_REGEXP_PAIR) { $json = json_decode($obj->getValue(), true); if (!is_array($json)) { $errors[] = 'The regular expression pair "' . $obj->getValue() . '" is not ' . 'valid JSON. Enter a valid JSON array with two elements.'; } else { if (count($json) != 2) { $errors[] = 'The regular expression pair "' . $obj->getValue() . '" must have ' . 'exactly two elements.'; } else { $key_regexp = array_shift($json); $val_regexp = array_shift($json); if (@preg_match($key_regexp, '') === false) { $errors[] = 'The first regexp, "' . $key_regexp . '" in the regexp pair ' . 'is not a valid regexp.'; } if (@preg_match($val_regexp, '') === false) { $errors[] = 'The second regexp, "' . $val_regexp . '" in the regexp pair ' . 'is not a valid regexp.'; } } } } $conditions[] = $obj; } $actions = array(); foreach ($data['actions'] as $action) { if ($action === null) { // Sparse on the client; removals can give us NULLs. continue; } $obj = new HeraldAction(); $obj->setAction($action[0]); if (!isset($action[1])) { // Legitimate for any action which doesn't need a target, like // "Do nothing". $action[1] = null; } if (is_array($action[1])) { $obj->setTarget(array_keys($action[1])); } else { $obj->setTarget($action[1]); } $actions[] = $obj; } $rule->attachConditions($conditions); $rule->attachActions($actions); if (!$errors) { try { // TODO // $rule->openTransaction(); $rule->save(); $rule->saveConditions($conditions); $rule->saveActions($actions); // $rule->saveTransaction(); $uri = '/herald/view/' . $rule->getContentType() . '/'; return id(new AphrontRedirectResponse())->setURI($uri); } catch (AphrontQueryDuplicateKeyException $ex) { $e_name = "Not Unique"; $errors[] = "Rule name is not unique. Choose a unique name."; } } } $phids = array(); $phids[] = $rule->getAuthorPHID(); foreach ($rule->getActions() as $action) { if (!is_array($action->getTarget())) { continue; } foreach ($action->getTarget() as $target) { $target = (array) $target; foreach ($target as $phid) { $phids[] = $phid; } } } foreach ($rule->getConditions() as $condition) { $value = $condition->getValue(); if (is_array($value)) { foreach ($value as $phid) { $phids[] = $phid; } } } $handles = id(new PhabricatorObjectHandleData($phids))->loadHandles(); if ($errors) { $error_view = new AphrontErrorView(); $error_view->setTitle('Form Errors'); $error_view->setErrors($errors); } else { $error_view = null; } $options = array('all' => 'all of', 'any' => 'any of'); $selected = $rule->getMustMatchAll() ? 'all' : 'any'; $must_match = array(); foreach ($options as $key => $option) { $must_match[] = phutil_render_tag('option', array('selected' => $selected == $key ? 'selected' : null, 'value' => $key), phutil_escape_html($option)); } $must_match = '<select name="must_match">' . implode("\n", $must_match) . '</select>'; if ($rule->getID()) { $action = '/herald/rule/' . $rule->getID() . '/'; } else { $action = '/herald/rule/' . $rule->getID() . '/'; } // Make the selector for choosing how often this rule should be repeated $repetition_selector = ""; $repetition_policy = HeraldRepetitionPolicyConfig::toString($rule->getRepetitionPolicy()); $repetition_options = HeraldRepetitionPolicyConfig::getMapForContentType($rule->getContentType()); if (empty($repetition_options)) { // default option is 'every time' $repetition_selector = idx(HeraldRepetitionPolicyConfig::getMap(), HeraldRepetitionPolicyConfig::EVERY); } else { if (count($repetition_options) == 1) { // if there's only 1 option, just pick it for the user $repetition_selector = reset($repetition_options); } else { // give the user all the options for this rule type $tags = array(); foreach ($repetition_options as $name => $option) { $tags[] = phutil_render_tag('option', array('selected' => $repetition_policy == $name ? 'selected' : null, 'value' => $name), phutil_escape_html($option)); } $repetition_selector = '<select name="repetition_policy">' . implode("\n", $tags) . '</select>'; } } require_celerity_resource('herald-css'); $type_name = $content_type_map[$rule->getContentType()]; $form = id(new AphrontFormView())->setUser($user)->setID('herald-rule-edit-form')->addHiddenInput('type', $rule->getContentType())->addHiddenInput('save', 1)->appendChild(javelin_render_tag('input', array('type' => 'hidden', 'name' => 'rule', 'sigil' => 'rule')))->appendChild(id(new AphrontFormTextControl())->setLabel('Rule Name')->setName('name')->setError($e_name)->setValue($rule->getName()))->appendChild(id(new AphrontFormStaticControl())->setLabel('Author')->setValue($handles[$rule->getAuthorPHID()]->getName()))->appendChild(id(new AphrontFormMarkupControl())->setValue("This rule triggers for <strong>{$type_name}</strong>."))->appendChild('<h1>Conditions</h1>' . '<div class="aphront-form-inset">' . '<div style="float: right;">' . javelin_render_tag('a', array('href' => '#', 'class' => 'button green', 'sigil' => 'create-condition', 'mustcapture' => true), 'Create New Condition') . '</div>' . '<p>When ' . $must_match . ' these conditions are met:</p>' . '<div style="clear: both;"></div>' . javelin_render_tag('table', array('sigil' => 'rule-conditions', 'class' => 'herald-condition-table'), '') . '</div>')->appendChild('<h1>Action</h1>' . '<div class="aphront-form-inset">' . '<div style="float: right;">' . javelin_render_tag('a', array('href' => '#', 'class' => 'button green', 'sigil' => 'create-action', 'mustcapture' => true), 'Create New Action') . '</div>' . '<p>' . 'Take these actions ' . $repetition_selector . ' this rule matches:' . '</p>' . '<div style="clear: both;"></div>' . javelin_render_tag('table', array('sigil' => 'rule-actions', 'class' => 'herald-action-table'), '') . '</div>')->appendChild(id(new AphrontFormSubmitControl())->setValue('Save Rule')->addCancelButton('/herald/view/' . $rule->getContentType() . '/')); $serial_conditions = array(array('default', 'default', '')); if ($rule->getConditions()) { $serial_conditions = array(); foreach ($rule->getConditions() as $condition) { $value = $condition->getValue(); if (is_array($value)) { $value_map = array(); foreach ($value as $k => $fbid) { $value_map[$fbid] = $handles[$fbid]->getName(); } $value = $value_map; } $serial_conditions[] = array($condition->getFieldName(), $condition->getFieldCondition(), $value); } } $serial_actions = array(array('default', '')); if ($rule->getActions()) { $serial_actions = array(); foreach ($rule->getActions() as $action) { $target_map = array(); foreach ((array) $action->getTarget() as $fbid) { $target_map[$fbid] = $handles[$fbid]->getName(); } $serial_actions[] = array($action->getAction(), $target_map); } } $all_rules = id(new HeraldRule())->loadAllWhere('authorPHID = %d AND contentType = %s', $rule->getAuthorPHID(), $rule->getContentType()); $all_rules = mpull($all_rules, 'getName', 'getID'); asort($all_rules); unset($all_rules[$rule->getID()]); $config_info = array(); $config_info['fields'] = HeraldFieldConfig::getFieldMapForContentType($rule->getContentType()); $config_info['conditions'] = HeraldConditionConfig::getConditionMap(); foreach ($config_info['fields'] as $field => $name) { $config_info['conditionMap'][$field] = array_keys(HeraldConditionConfig::getConditionMapForField($field)); } foreach ($config_info['fields'] as $field => $fname) { foreach ($config_info['conditions'] as $condition => $cname) { $config_info['values'][$field][$condition] = HeraldValueTypeConfig::getValueTypeForFieldAndCondition($field, $condition); } } $config_info['actions'] = HeraldActionConfig::getActionMapForContentType($rule->getContentType()); foreach ($config_info['actions'] as $action => $name) { $config_info['targets'][$action] = HeraldValueTypeConfig::getValueTypeForAction($action); } Javelin::initBehavior('herald-rule-editor', array('root' => 'herald-rule-edit-form', 'conditions' => (object) $serial_conditions, 'actions' => (object) $serial_actions, 'template' => $this->buildTokenizerTemplates() + array('rules' => $all_rules), 'info' => $config_info)); $panel = new AphrontPanelView(); $panel->setHeader('Edit Herald Rule'); $panel->setWidth(AphrontPanelView::WIDTH_WIDE); $panel->appendChild($form); return $this->buildStandardPageResponse(array($error_view, $panel), array('title' => 'Edit Rule')); }
private function canRuleApplyToObject(HeraldRule $rule, HeraldAdapter $adapter) { // Rules which are not object rules can apply to anything. if (!$rule->isObjectRule()) { return true; } $trigger_phid = $rule->getTriggerObjectPHID(); $object_phids = $adapter->getTriggerObjectPHIDs(); if ($object_phids) { if (in_array($trigger_phid, $object_phids)) { return true; } } return false; }
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(); } }