<commit_before><?php

final class PhabricatorCalendarEventCancelController
  extends PhabricatorCalendarController {

  public function handleRequest(AphrontRequest $request) {
    $viewer = $request->getViewer();
    $id = $request->getURIData('id');

    // Just check CAN_VIEW first. Then we'll check if this is an import so
    // we can raise a better error.
    $event = id(new PhabricatorCalendarEventQuery())
      ->setViewer($viewer)
      ->withIDs(array($id))
      ->executeOne();
    if (!$event) {
      return new Aphront404Response();
    }

    $response = $this->newImportedEventResponse($event);
    if ($response) {
      return $response;
    }

    // Now that we've done the import check, check for CAN_EDIT.
    PhabricatorPolicyFilter::requireCapability(
      $viewer,
      $event,
      PhabricatorPolicyCapability::CAN_EDIT);

    $cancel_uri = $event->getURI();

    $is_parent = $event->isParentEvent();
    $is_child = $event->isChildEvent();

    $is_cancelled = $event->getIsCancelled();
    $is_recurring = $event->getIsRecurring();

    $validation_exception = null;
    if ($request->isFormPost()) {

      $targets = array();
      if ($is_recurring) {
        $mode = $request->getStr('mode');
        $is_future = ($mode == 'future');

        // We need to fork the event if we're cancelling just the parent, or
        // are cancelling a child and all future events.
        $must_fork = ($is_child && $is_future) ||
                     ($is_parent && !$is_future);

        if ($must_fork) {
          if ($is_child) {
            $xactions = array();

            $xaction = id(new PhabricatorCalendarEventTransaction())
              ->setTransactionType(
                PhabricatorCalendarEventForkTransaction::TRANSACTIONTYPE)
              ->setNewValue(true);

            $editor = id(new PhabricatorCalendarEventEditor())
              ->setActor($viewer)
              ->setContentSourceFromRequest($request)
              ->setContinueOnNoEffect(true)
              ->setContinueOnMissingFields(true);

            $editor->applyTransactions($event, array($xaction));

            $targets[] = $event;
          } else {
            // TODO: This is a huge mess; we need to load or generate the
            // first child, then fork that, then apply the event to the
            // parent. Just bail for now.
            throw new Exception(
              pht(
                'Individual edits to parent events are not yet supported '.
                'because they are real tricky to implement.'));
          }
        } else {
          $targets[] = $event;
        }

        if ($is_future) {
          // NOTE: If you can't edit some of the future events, we just
          // don't try to update them. This seems like it's probably what
          // users are likely to expect.

          // NOTE: This only affects events that are currently in the same
          // series, not all events that were ever in the original series.
          // We could use series PHIDs instead of parent PHIDs to affect more
          // events if this turns out to be counterintuitive. Other
          // applications differ in their behavior.

          $future = id(new PhabricatorCalendarEventQuery())
            ->setViewer($viewer)
            ->withParentEventPHIDs(array($event->getPHID()))
            ->withUTCInitialEpochBetween($event->getUTCInitialEpoch(), null)
            ->requireCapabilities(
              array(
                PhabricatorPolicyCapability::CAN_VIEW,
                PhabricatorPolicyCapability::CAN_EDIT,
              ))
            ->execute();
          foreach ($future as $future_event) {
            $targets[] = $future_event;
          }
        }
      } else {
        $targets = array($event);
      }

      foreach ($targets as $target) {
        $xactions = array();

        $xaction = id(new PhabricatorCalendarEventTransaction())
          ->setTransactionType(
            PhabricatorCalendarEventCancelTransaction::TRANSACTIONTYPE)
          ->setNewValue(!$is_cancelled);

        $editor = id(new PhabricatorCalendarEventEditor())
          ->setActor($viewer)
          ->setContentSourceFromRequest($request)
          ->setContinueOnNoEffect(true)
          ->setContinueOnMissingFields(true);

        try {
          $editor->applyTransactions($target, array($xaction));
        } catch (PhabricatorApplicationTransactionValidationException $ex) {
          $validation_exception = $ex;
          break;<commit_msg>Support "Edit just this event" on the parent event in a series<commit_after><?php

final class PhabricatorCalendarEventCancelController
  extends PhabricatorCalendarController {

  public function handleRequest(AphrontRequest $request) {
    $viewer = $request->getViewer();
    $id = $request->getURIData('id');

    // Just check CAN_VIEW first. Then we'll check if this is an import so
    // we can raise a better error.
    $event = id(new PhabricatorCalendarEventQuery())
      ->setViewer($viewer)
      ->withIDs(array($id))
      ->executeOne();
    if (!$event) {
      return new Aphront404Response();
    }

    $response = $this->newImportedEventResponse($event);
    if ($response) {
      return $response;
    }

    // Now that we've done the import check, check for CAN_EDIT.
    PhabricatorPolicyFilter::requireCapability(
      $viewer,
      $event,
      PhabricatorPolicyCapability::CAN_EDIT);

    $cancel_uri = $event->getURI();

    $is_parent = $event->isParentEvent();
    $is_child = $event->isChildEvent();

    $is_cancelled = $event->getIsCancelled();
    $is_recurring = $event->getIsRecurring();

    $validation_exception = null;
    if ($request->isFormPost()) {

      $targets = array($event);
      if ($is_recurring) {
        $mode = $request->getStr('mode');
        $is_future = ($mode == 'future');

        // We need to fork the event if we're cancelling just the parent, or
        // are cancelling a child and all future events.
        $must_fork = ($is_child && $is_future) ||
                     ($is_parent && !$is_future);

        if ($must_fork) {
          if ($is_child) {
            $fork_target = $event;
          } else {
            if ($event->isValidSequenceIndex($viewer, 1)) {
              $next_event = id(new PhabricatorCalendarEventQuery())
                ->setViewer($viewer)
                ->withInstanceSequencePairs(
                  array(
                    array($event->getPHID(), 1),
                  ))
                ->requireCapabilities(
                  array(
                    PhabricatorPolicyCapability::CAN_VIEW,
                    PhabricatorPolicyCapability::CAN_EDIT,
                  ))
                ->executeOne();

              if (!$next_event) {
                $next_event = $event->newStub($viewer, 1);
              }

              $fork_target = $next_event;
            } else {
              // This appears to be a "recurring" event with no valid
              // instances: for example, its "until" date is before the second
              // instance would occur. This can happen if we already forked the
              // event or if users entered silly stuff. Just edit the event
              // directly without forking anything.
              $fork_target = null;
            }
          }

          if ($fork_target) {
            $xactions = array();

            $xaction = id(new PhabricatorCalendarEventTransaction())
              ->setTransactionType(
                PhabricatorCalendarEventForkTransaction::TRANSACTIONTYPE)
              ->setNewValue(true);

            $editor = id(new PhabricatorCalendarEventEditor())
              ->setActor($viewer)
              ->setContentSourceFromRequest($request)
              ->setContinueOnNoEffect(true)
              ->setContinueOnMissingFields(true);

            $editor->applyTransactions($fork_target, array($xaction));
          }
        }

        if ($is_future) {
          // NOTE: If you can't edit some of the future events, we just
          // don't try to update them. This seems like it's probably what
          // users are likely to expect.

          // NOTE: This only affects events that are currently in the same
          // series, not all events that were ever in the original series.
          // We could use series PHIDs instead of parent PHIDs to affect more
          // events if this turns out to be counterintuitive. Other
          // applications differ in their behavior.

          $future = id(new PhabricatorCalendarEventQuery())
            ->setViewer($viewer)
            ->withParentEventPHIDs(array($event->getPHID()))
            ->withUTCInitialEpochBetween($event->getUTCInitialEpoch(), null)
            ->requireCapabilities(
              array(
                PhabricatorPolicyCapability::CAN_VIEW,
                PhabricatorPolicyCapability::CAN_EDIT,
              ))
            ->execute();
          foreach ($future as $future_event) {
            $targets[] = $future_event;
          }
        }
      }

      foreach ($targets as $target) {
        $xactions = array();

        $xaction = id(new PhabricatorCalendarEventTransaction())
          ->setTransactionType(
            PhabricatorCalendarEventCancelTransaction::TRANSACTIONTYPE)
          ->setNewValue(!$is_cancelled);

        $editor = id(new PhabricatorCalendarEventEditor())
          ->setActor($viewer)
          ->setContentSourceFromRequest($request)
          ->setContinueOnNoEffect(true)
          ->setContinueOnMissingFields(true);

        try {
          $editor->applyTransactions($target, array($xaction));
        } catch (PhabricatorApplicationTransactionValidationException $ex) {
          $validation_exception = $ex;
          break;
{'fraction_non_alphanumeric': 0.10665441926923862, 'fraction_numerical': 0.0009185548071034905, 'mean_word_length': 2.0545511221945136, 'pattern_counts': {'":': 0, '<': 5, '<?xml version=': 0, '>': 78, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '7324430', 'n_tokens_mistral': 2673, 'n_tokens_neox': 2556, 'n_words': 661}