diff --git a/src/applications/base/controller/PhabricatorController.php b/src/applications/base/controller/PhabricatorController.php index 1551667591..b884b13557 100644 --- a/src/applications/base/controller/PhabricatorController.php +++ b/src/applications/base/controller/PhabricatorController.php @@ -1,615 +1,602 @@ shouldRequireLogin()) { return false; } if (!$this->shouldRequireEnabledUser()) { return false; } if ($this->shouldAllowPartialSessions()) { return false; } $user = $this->getRequest()->getUser(); if (!$user->getIsStandardUser()) { return false; } return PhabricatorEnv::getEnvConfig('security.require-multi-factor-auth'); } public function shouldAllowLegallyNonCompliantUsers() { return false; } public function isGlobalDragAndDropUploadEnabled() { return false; } public function addExtraQuicksandConfig($config) { $this->extraQuicksandConfig += $config; return $this; } private function getExtraQuicksandConfig() { return $this->extraQuicksandConfig; } public function willBeginExecution() { $request = $this->getRequest(); if ($request->getUser()) { // NOTE: Unit tests can set a user explicitly. Normal requests are not // permitted to do this. PhabricatorTestCase::assertExecutingUnitTests(); $user = $request->getUser(); } else { $user = new PhabricatorUser(); $session_engine = new PhabricatorAuthSessionEngine(); $phsid = $request->getCookie(PhabricatorCookies::COOKIE_SESSION); if (strlen($phsid)) { $session_user = $session_engine->loadUserForSession( PhabricatorAuthSession::TYPE_WEB, $phsid); if ($session_user) { $user = $session_user; } } else { // If the client doesn't have a session token, generate an anonymous // session. This is used to provide CSRF protection to logged-out users. $phsid = $session_engine->establishSession( PhabricatorAuthSession::TYPE_WEB, null, $partial = false); // This may be a resource request, in which case we just don't set // the cookie. if ($request->canSetCookies()) { $request->setCookie(PhabricatorCookies::COOKIE_SESSION, $phsid); } } if (!$user->isLoggedIn()) { $user->attachAlternateCSRFString(PhabricatorHash::digest($phsid)); } $request->setUser($user); } PhabricatorEnv::setLocaleCode($user->getTranslation()); $preferences = $user->loadPreferences(); if (PhabricatorEnv::getEnvConfig('darkconsole.enabled')) { $dark_console = PhabricatorUserPreferences::PREFERENCE_DARK_CONSOLE; if ($preferences->getPreference($dark_console) || PhabricatorEnv::getEnvConfig('darkconsole.always-on')) { $console = new DarkConsoleCore(); $request->getApplicationConfiguration()->setConsole($console); } } // NOTE: We want to set up the user first so we can render a real page // here, but fire this before any real logic. $restricted = array( 'code', ); foreach ($restricted as $parameter) { if ($request->getExists($parameter)) { if (!$this->shouldAllowRestrictedParameter($parameter)) { throw new Exception( pht( 'Request includes restricted parameter "%s", but this '. 'controller ("%s") does not whitelist it. Refusing to '. 'serve this request because it might be part of a redirection '. 'attack.', $parameter, get_class($this))); } } } if ($this->shouldRequireEnabledUser()) { if ($user->isLoggedIn() && !$user->getIsApproved()) { $controller = new PhabricatorAuthNeedsApprovalController(); return $this->delegateToController($controller); } if ($user->getIsDisabled()) { $controller = new PhabricatorDisabledUserController(); return $this->delegateToController($controller); } } - $event = new PhabricatorEvent( - PhabricatorEventType::TYPE_CONTROLLER_CHECKREQUEST, - array( - 'request' => $request, - 'controller' => $this, - )); - $event->setUser($user); - PhutilEventEngine::dispatchEvent($event); - $checker_controller = $event->getValue('controller'); - if ($checker_controller != $this) { - return $this->delegateToController($checker_controller); - } - $auth_class = 'PhabricatorAuthApplication'; $auth_application = PhabricatorApplication::getByClass($auth_class); // Require partial sessions to finish login before doing anything. if (!$this->shouldAllowPartialSessions()) { if ($user->hasSession() && $user->getSession()->getIsPartial()) { $login_controller = new PhabricatorAuthFinishController(); $this->setCurrentApplication($auth_application); return $this->delegateToController($login_controller); } } // Check if the user needs to configure MFA. $need_mfa = $this->shouldRequireMultiFactorEnrollment(); $have_mfa = $user->getIsEnrolledInMultiFactor(); if ($need_mfa && !$have_mfa) { // Check if the cache is just out of date. Otherwise, roadblock the user // and require MFA enrollment. $user->updateMultiFactorEnrollment(); if (!$user->getIsEnrolledInMultiFactor()) { $mfa_controller = new PhabricatorAuthNeedsMultiFactorController(); $this->setCurrentApplication($auth_application); return $this->delegateToController($mfa_controller); } } if ($this->shouldRequireLogin()) { // This actually means we need either: // - a valid user, or a public controller; and // - permission to see the application; and // - permission to see at least one Space if spaces are configured. $allow_public = $this->shouldAllowPublic() && PhabricatorEnv::getEnvConfig('policy.allow-public'); // If this controller isn't public, and the user isn't logged in, require // login. if (!$allow_public && !$user->isLoggedIn()) { $login_controller = new PhabricatorAuthStartController(); $this->setCurrentApplication($auth_application); return $this->delegateToController($login_controller); } if ($user->isLoggedIn()) { if ($this->shouldRequireEmailVerification()) { if (!$user->getIsEmailVerified()) { $controller = new PhabricatorMustVerifyEmailController(); $this->setCurrentApplication($auth_application); return $this->delegateToController($controller); } } } // If Spaces are configured, require that the user have access to at // least one. If we don't do this, they'll get confusing error messages // later on. $spaces = PhabricatorSpacesNamespaceQuery::getSpacesExist(); if ($spaces) { $viewer_spaces = PhabricatorSpacesNamespaceQuery::getViewerSpacesExist( $user); if (!$viewer_spaces) { $controller = new PhabricatorSpacesNoAccessController(); return $this->delegateToController($controller); } } // If the user doesn't have access to the application, don't let them use // any of its controllers. We query the application in order to generate // a policy exception if the viewer doesn't have permission. $application = $this->getCurrentApplication(); if ($application) { id(new PhabricatorApplicationQuery()) ->setViewer($user) ->withPHIDs(array($application->getPHID())) ->executeOne(); } } if (!$this->shouldAllowLegallyNonCompliantUsers()) { $legalpad_class = 'PhabricatorLegalpadApplication'; $legalpad = id(new PhabricatorApplicationQuery()) ->setViewer($user) ->withClasses(array($legalpad_class)) ->withInstalled(true) ->execute(); $legalpad = head($legalpad); $doc_query = id(new LegalpadDocumentQuery()) ->setViewer($user) ->withSignatureRequired(1) ->needViewerSignatures(true); if ($user->hasSession() && !$user->getSession()->getIsPartial() && !$user->getSession()->getSignedLegalpadDocuments() && $user->isLoggedIn() && $legalpad) { $sign_docs = $doc_query->execute(); $must_sign_docs = array(); foreach ($sign_docs as $sign_doc) { if (!$sign_doc->getUserSignature($user->getPHID())) { $must_sign_docs[] = $sign_doc; } } if ($must_sign_docs) { $controller = new LegalpadDocumentSignController(); $this->getRequest()->setURIMap(array( 'id' => head($must_sign_docs)->getID(), )); $this->setCurrentApplication($legalpad); return $this->delegateToController($controller); } else { $engine = id(new PhabricatorAuthSessionEngine()) ->signLegalpadDocuments($user, $sign_docs); } } } // NOTE: We do this last so that users get a login page instead of a 403 // if they need to login. if ($this->shouldRequireAdmin() && !$user->getIsAdmin()) { return new Aphront403Response(); } } public function buildStandardPageView() { $view = new PhabricatorStandardPageView(); $view->setRequest($this->getRequest()); $view->setController($this); return $view; } public function buildStandardPageResponse($view, array $data) { $page = $this->buildStandardPageView(); $page->appendChild($view); return $this->buildPageResponse($page); } private function buildPageResponse($page) { if ($this->getRequest()->isQuicksand()) { $response = id(new AphrontAjaxResponse()) ->setContent($page->renderForQuicksand( $this->getExtraQuicksandConfig())); } else { $response = id(new AphrontWebpageResponse()) ->setContent($page->render()); } return $response; } public function getApplicationURI($path = '') { if (!$this->getCurrentApplication()) { throw new Exception(pht('No application!')); } return $this->getCurrentApplication()->getApplicationURI($path); } public function buildApplicationPage($view, array $options) { $page = $this->buildStandardPageView(); $title = PhabricatorEnv::getEnvConfig('phabricator.serious-business') ? 'Phabricator' : pht('Bacon Ice Cream for Breakfast'); $application = $this->getCurrentApplication(); $page->setTitle(idx($options, 'title', $title)); if ($application) { $page->setApplicationName($application->getName()); if ($application->getTitleGlyph()) { $page->setGlyph($application->getTitleGlyph()); } } if (!($view instanceof AphrontSideNavFilterView)) { $nav = new AphrontSideNavFilterView(); $nav->appendChild($view); $view = $nav; } $user = $this->getRequest()->getUser(); $view->setUser($user); $page->appendChild($view); $object_phids = idx($options, 'pageObjects', array()); if ($object_phids) { $page->appendPageObjects($object_phids); foreach ($object_phids as $object_phid) { PhabricatorFeedStoryNotification::updateObjectNotificationViews( $user, $object_phid); } } if (idx($options, 'device', true)) { $page->setDeviceReady(true); } $page->setShowFooter(idx($options, 'showFooter', true)); $page->setShowChrome(idx($options, 'chrome', true)); $application_menu = $this->buildApplicationMenu(); if ($application_menu) { $page->setApplicationMenu($application_menu); } return $this->buildPageResponse($page); } public function didProcessRequest($response) { // If a bare DialogView is returned, wrap it in a DialogResponse. if ($response instanceof AphrontDialogView) { $response = id(new AphrontDialogResponse())->setDialog($response); } $request = $this->getRequest(); $response->setRequest($request); $seen = array(); while ($response instanceof AphrontProxyResponse) { $hash = spl_object_hash($response); if (isset($seen[$hash])) { $seen[] = get_class($response); throw new Exception( pht('Cycle while reducing proxy responses: %s', implode(' -> ', $seen))); } $seen[$hash] = get_class($response); $response = $response->reduceProxyResponse(); } if ($response instanceof AphrontDialogResponse) { if (!$request->isAjax() && !$request->isQuicksand()) { $dialog = $response->getDialog(); $title = $dialog->getTitle(); $short = $dialog->getShortTitle(); $crumbs = $this->buildApplicationCrumbs(); $crumbs->addTextCrumb(coalesce($short, $title)); $page_content = array( $crumbs, $response->buildResponseString(), ); $view = id(new PhabricatorStandardPageView()) ->setRequest($request) ->setController($this) ->setDeviceReady(true) ->setTitle($title) ->appendChild($page_content); $response = id(new AphrontWebpageResponse()) ->setContent($view->render()) ->setHTTPResponseCode($response->getHTTPResponseCode()); } else { $response->getDialog()->setIsStandalone(true); return id(new AphrontAjaxResponse()) ->setContent(array( 'dialog' => $response->buildResponseString(), )); } } else if ($response instanceof AphrontRedirectResponse) { if ($request->isAjax() || $request->isQuicksand()) { return id(new AphrontAjaxResponse()) ->setContent( array( 'redirect' => $response->getURI(), )); } } return $response; } /** * WARNING: Do not call this in new code. * * @deprecated See "Handles Technical Documentation". */ protected function loadViewerHandles(array $phids) { return id(new PhabricatorHandleQuery()) ->setViewer($this->getRequest()->getUser()) ->withPHIDs($phids) ->execute(); } public function buildApplicationMenu() { return null; } protected function buildApplicationCrumbs() { $crumbs = array(); $application = $this->getCurrentApplication(); if ($application) { $icon = $application->getFontIcon(); if (!$icon) { $icon = 'fa-puzzle'; } $crumbs[] = id(new PHUICrumbView()) ->setHref($this->getApplicationURI()) ->setName($application->getName()) ->setIcon($icon); } $view = new PHUICrumbsView(); foreach ($crumbs as $crumb) { $view->addCrumb($crumb); } return $view; } protected function hasApplicationCapability($capability) { return PhabricatorPolicyFilter::hasCapability( $this->getRequest()->getUser(), $this->getCurrentApplication(), $capability); } protected function requireApplicationCapability($capability) { PhabricatorPolicyFilter::requireCapability( $this->getRequest()->getUser(), $this->getCurrentApplication(), $capability); } protected function explainApplicationCapability( $capability, $positive_message, $negative_message) { $can_act = $this->hasApplicationCapability($capability); if ($can_act) { $message = $positive_message; $icon_name = 'fa-play-circle-o lightgreytext'; } else { $message = $negative_message; $icon_name = 'fa-lock'; } $icon = id(new PHUIIconView()) ->setIconFont($icon_name); require_celerity_resource('policy-css'); $phid = $this->getCurrentApplication()->getPHID(); $explain_uri = "/policy/explain/{$phid}/{$capability}/"; $message = phutil_tag( 'div', array( 'class' => 'policy-capability-explanation', ), array( $icon, javelin_tag( 'a', array( 'href' => $explain_uri, 'sigil' => 'workflow', ), $message), )); return array($can_act, $message); } public function getDefaultResourceSource() { return 'phabricator'; } /** * Create a new @{class:AphrontDialogView} with defaults filled in. * * @return AphrontDialogView New dialog. */ public function newDialog() { $submit_uri = new PhutilURI($this->getRequest()->getRequestURI()); $submit_uri = $submit_uri->getPath(); return id(new AphrontDialogView()) ->setUser($this->getRequest()->getUser()) ->setSubmitURI($submit_uri); } protected function buildTransactionTimeline( PhabricatorApplicationTransactionInterface $object, PhabricatorApplicationTransactionQuery $query, PhabricatorMarkupEngine $engine = null, $render_data = array()) { $viewer = $this->getRequest()->getUser(); $xaction = $object->getApplicationTransactionTemplate(); $view = $xaction->getApplicationTransactionViewObject(); $pager = id(new AphrontCursorPagerView()) ->readFromRequest($this->getRequest()) ->setURI(new PhutilURI( '/transactions/showolder/'.$object->getPHID().'/')); $xactions = $query ->setViewer($viewer) ->withObjectPHIDs(array($object->getPHID())) ->needComments(true) ->executeWithCursorPager($pager); $xactions = array_reverse($xactions); if ($engine) { foreach ($xactions as $xaction) { if ($xaction->getComment()) { $engine->addObject( $xaction->getComment(), PhabricatorApplicationTransactionComment::MARKUP_FIELD_COMMENT); } } $engine->process(); $view->setMarkupEngine($engine); } $timeline = $view ->setUser($viewer) ->setObjectPHID($object->getPHID()) ->setTransactions($xactions) ->setPager($pager) ->setRenderData($render_data) ->setQuoteTargetID($this->getRequest()->getStr('quoteTargetID')) ->setQuoteRef($this->getRequest()->getStr('quoteRef')); $object->willRenderTimeline($timeline, $this->getRequest()); return $timeline; } } diff --git a/src/docs/user/userguide/events.diviner b/src/docs/user/userguide/events.diviner index 5bb8eb313a..8959585f4a 100644 --- a/src/docs/user/userguide/events.diviner +++ b/src/docs/user/userguide/events.diviner @@ -1,330 +1,314 @@ @title Events User Guide: Installing Event Listeners @group userguide Using Phabricator event listeners to customize behavior. = Overview = Phabricator and Arcanist allow you to install custom runtime event listeners which can react to certain things happening (like a Maniphest Task being edited or a user creating a new Differential Revision) and run custom code to perform logging, synchronize with other systems, or modify workflows. These listeners are PHP classes which you install beside Phabricator or Arcanist, and which Phabricator loads at runtime and runs in-process. They require somewhat more effort upfront than simple configuration switches, but are the most direct and powerful way to respond to events. = Installing Event Listeners (Phabricator) = To install event listeners in Phabricator, follow these steps: - Write a listener class which extends @{class@libphutil:PhutilEventListener}. - Add it to a libphutil library, or create a new library (for instructions, see @{article@contributor:Adding New Classes}. - Configure Phabricator to load the library by adding it to `load-libraries` in the Phabricator config. - Configure Phabricator to install the event listener by adding the class name to `events.listeners` in the Phabricator config. You can verify your listener is registered in the "Events" tab of DarkConsole. It should appear at the top under "Registered Event Listeners". You can also see any events the page emitted there. For details on DarkConsole, see @{article:Using DarkConsole}. = Installing Event Listeners (Arcanist) = To install event listeners in Arcanist, follow these steps: - Write a listener class which extends @{class@libphutil:PhutilEventListener}. - Add it to a libphutil library, or create a new library (for instructions, see @{article@contributor:Adding New Classes}. - Configure Phabricator to load the library by adding it to `load` in the Arcanist config (e.g., `.arcconfig`, or user/global config). - Configure Arcanist to install the event listener by adding the class name to `events.listeners` in the Arcanist config. You can verify your listener is registered by running any `arc` command with `--trace`. You should see output indicating your class was registered as an event listener. = Example Listener = Phabricator includes an example event listener, @{class:PhabricatorExampleEventListener}, which may be useful as a starting point in developing your own listeners. This listener listens for a test event that is emitted by the script `scripts/util/emit_test_event.php`. If you run this script normally, it should output something like this: $ ./scripts/util/emit_test_event.php Emitting event... Done. This is because there are no listeners for the event, so nothing reacts to it when it is emitted. You can add the example listener by either adding it to your `events.listeners` configuration or with the `--listen` command-line flag: $ ./scripts/util/emit_test_event.php --listen PhabricatorExampleEventListener Installing 'PhabricatorExampleEventListener'... Emitting event... PhabricatorExampleEventListener got test event at 1341344566 Done. This time, the listener was installed and had its callback invoked when the test event was emitted. = Available Events = You can find a list of all Phabricator events in @{class:PhabricatorEventType}. == All Events == The special constant `PhutilEventType::TYPE_ALL` will let you listen for all events. Normally, you want to listen only to specific events, but if you're writing a generic handler you can listen to all events with this constant rather than by enumerating each event. == Arcanist Events == Arcanist event constants are listed in @{class@arcanist:ArcanistEventType}. All Arcanist events have this data available: - `workflow` The active @{class@arcanist:ArcanistWorkflow}. == Arcanist: Commit: Will Commit SVN == The constant for this event is `ArcanistEventType::TYPE_COMMIT_WILLCOMMITSVN`. This event is dispatched before an `svn commit` occurs and allows you to modify the commit message. Data available on this event: - `message` The text of the message. == Arcanist: Diff: Will Build Message == The constant for this event is `ArcanistEventType::TYPE_DIFF_WILLBUILDMESSAGE`. This event is dispatched before an editable message is presented to the user, and allows you to, e.g., fill in default values for fields. Data available on this event: - `fields` A map of field values to be compiled into a message. == Arcanist: Diff: Was Created == The constant for this event is `ArcanistEventType::TYPE_DIFF_WASCREATED`. This event is dispatched after a diff is created. It is currently only useful for collecting timing information. No data is available on this event. == Arcanist: Revision: Will Create Revision == The constant for this event is `ArcanistEventType::TYPE_REVISION_WILLCREATEREVISION`. This event is dispatched before a revision is created. It allows you to modify fields to, e.g., edit revision titles. Data available on this event: - `specification` Parameters that will be used to invoke the `differential.createrevision` Conduit call. -== Controller: Check Request == - -The constant for this event is -`PhabricatorEventType::TYPE_CONTROLLER_CHECKREQUEST`. - -This event is dispatched when controller is about to begin execution. It is -meant for checking if the user is allowed to use the application at the moment. -It can check if the user has performed too many operations recently, if his IP -address is allowed or if the servers are overloaded to process the request. -Data available on this event: - -- `request` Object of class @{class:AphrontRequest}. -- `controller` Class name of the current controller. - -You can delegate the execution to another controller by modifying `controller`. - == Maniphest: Will Edit Task == The constant for this event is `PhabricatorEventType::TYPE_MANIPHEST_WILLEDITTASK`. This event is dispatched before a task is edited, and allows you to respond to or alter the edit. Data available on this event: - `task` The @{class:ManiphestTask} being edited. - `transactions` The list of edits (objects of class @{class:ManiphestTransaction}) being applied. - `new` A boolean indicating if this task is being created. - `mail` If this edit originates from email, the @{class:PhabricatorMetaMTAReceivedMail} object. This is similar to the next event (did edit task) but occurs before the edit begins. == Maniphest: Did Edit Task == The constant for this event is `PhabricatorEventType::TYPE_MANIPHEST_DIDEDITTASK`. This event is dispatched after a task is edited, and allows you to react to the edit. Data available on this event: - `task` The @{class:ManiphestTask} that was edited. - `transactions` The list of edits (objects of class @{class:ManiphestTransaction}) that were applied. - `new` A boolean indicating if this task was newly created. - `mail` If this edit originates from email, the @{class:PhabricatorMetaMTAReceivedMail} object. This is similar to the previous event (will edit task) but occurs after the edit completes. == Differential: Will Mark Generated == The constant for this event is `PhabricatorEventType::TYPE_DIFFERENTIAL_WILLMARKGENERATED`. This event is dispatched before Differential decides if a file is generated (and doesn't need to be reviewed) or not. Data available on this event: - `corpus` Body of the file. - `is_generated` Boolean indicating if this file should be treated as generated. == Diffusion: Did Discover Commit == The constant for this event is `PhabricatorEventType::TYPE_DIFFUSION_DIDDISCOVERCOMMIT`. This event is dispatched when the daemons discover a commit for the first time. This event happens very early in the pipeline, and not all commit information will be available yet. Data available on this event: - `commit` The @{class:PhabricatorRepositoryCommit} that was discovered. - `repository` The @{class:PhabricatorRepository} the commit was discovered in. == Diffusion: Lookup User == The constant for this event is `PhabricatorEventType::TYPE_DIFFUSION_LOOKUPUSER`. This event is dispatched when the daemons are trying to link a commit to a Phabricator user account. You can listen for it to improve the accuracy of associating users with their commits. By default, Phabricator will try to find matches based on usernames, real names, or email addresses, but this can result in incorrect matches (e.g., if you have several employees with the same name) or failures to match (e.g., if someone changed their email address). Listening for this event allows you to intercept the lookup and supplement the results from another datasource. Data available on this event: - `commit` The @{class:PhabricatorRepositoryCommit} that data is being looked up for. - `query` The author or committer string being looked up. This will usually be something like "Abraham Lincoln ", but comes from the commit metadata so it may not be well-formatted. - `result` The current result from the lookup (Phabricator's best guess at the user PHID of the user named in the "query"). To substitute the result with a different result, replace this with the correct PHID in your event listener. Using @{class@libphutil:PhutilEmailAddress} may be helpful in parsing the query. == Search: Did Update Index == The constant for this event is `PhabricatorEventType::TYPE_SEARCH_DIDUPDATEINDEX`. This event is dispatched from the Search application's indexing engine, after it indexes a document. It allows you to publish search-like indexes into other systems. Note that this event happens after the update is fully complete: you can not prevent or modify the update. Further, the event may fire significantly later in real time than the update, as indexing may occur in the background. You should use other events if you need guarantees about when the event executes. Finally, this event may fire more than once for a single update. For example, if the search indexes are rebuilt, this event will fire on objects which have not actually changed. So, good use cases for event listeners are: - Updating secondary search indexes. Bad use cases are: - Editing the object or document. - Anything with side effects, like sending email. Data available on this event: - `phid` The PHID of the updated object. - `object` The object which was updated (like a @{class:ManiphesTask}). - `document` The @{class:PhabricatorSearchAbstractDocument} which was indexed. This contains an abstract representation of the object, and may be useful in populating secondary indexes because it provides a uniform API. == Test: Did Run Test == The constant for this event is `PhabricatorEventType::TYPE_TEST_DIDRUNTEST`. This is a test event for testing event listeners. See above for details. == UI: Did Render Actions == The constant for this event is `PhabricatorEventType::TYPE_UI_DIDRENDERACTIONS`. This event is dispatched after a @{class:PhabricatorActionListView} is built by the UI. It allows you to add new actions that your application may provide, like "Fax this Object". Data available on this event: - `object` The object which actions are being rendered for. - `actions` The current list of available actions. NOTE: This event is unstable and subject to change. = Debugging Listeners = If you're having problems with your listener, try these steps: - If you're getting an error about Phabricator being unable to find the listener class, make sure you've added it to a libphutil library and configured Phabricator to load the library with `load-libraries`. - Make sure the listener is registered. It should appear in the "Events" tab of DarkConsole. If it's not there, you may have forgotten to add it to `events.listeners`. - Make sure it calls `listen()` on the right events in its `register()` method. If you don't listen for the events you're interested in, you won't get a callback. - Make sure the events you're listening for are actually happening. If they occur on a normal page they should appear in the "Events" tab of DarkConsole. If they occur on a POST, you could add a `phlog()` to the source code near the event and check your error log to make sure the code ran. - You can check if your callback is getting invoked by adding `phlog()` with a message and checking the error log. - You can try listening to `PhutilEventType::TYPE_ALL` instead of a specific event type to get all events, to narrow down whether problems are caused by the types of events you're listening to. - You can edit the `emit_test_event.php` script to emit other types of events instead, to test that your listener reacts to them properly. You might have to use fake data, but this gives you an easy way to test the at least the basics. - For scripts, you can run under `--trace` to see which events are emitted and how many handlers are listening to each event. = Next Steps = Continue by: - taking a look at @{class:PhabricatorExampleEventListener}; or - building a library with @{article:libphutil Libraries User Guide}. diff --git a/src/infrastructure/events/constant/PhabricatorEventType.php b/src/infrastructure/events/constant/PhabricatorEventType.php index 3c555ac4c3..22644bc522 100644 --- a/src/infrastructure/events/constant/PhabricatorEventType.php +++ b/src/infrastructure/events/constant/PhabricatorEventType.php @@ -1,37 +1,35 @@