<commit_before><?php

class reviewActions extends sfActions
{
  /**
   * Review graph page.
   *
   * @param sfRequest $request
   *
   * @return
   */
  public function executeIndex($request)
  {
    // set local pref default value
    $this->filter = $this->getUser()->getLocalPrefs()->sync('review.graph.filter', null, '');
    if ($this->filter == '')
    {
      $this->filter = 'all';
    }

    $this->flashcard_count = ReviewsPeer::getFlashcardCount($this->getUser()->getUserId());
  }

  /**
   * Custom Review modes 
   *
   * @param sfRequest $request
   */
  public function executeCustom($request)
  {
    $userId = sfContext::getInstance()->getUser()->getUserId();
    $this->knowncount = ReviewsPeer::getReviewedFlashcardCount($userId, LeitnerSRS::FAILEDSTACK + 1);
    $this->knowndefault = max($this->knowncount, 1);

    // set defaults for forms
    $request->setParameter('shuffle', 1);
  }

  public function executeVocab($request)
  {
    $userId = sfContext::getInstance()->getUser()->getUserId();
    $this->learnedcount = ReviewsPeer::getReviewedFlashcardCount($userId, rtkLabs::VOCABSHUFFLE_MINBOX);
  }

  /**
   * Review graph filter actions.
   *
   * @param sfRequest $request
   *
   * @return
   */
  public function executeAjaxLeitnerGraph($request)
  {
    $filter = $request->getParameter('filter', '');
    if (!preg_match('/^(all|rtk1|rtk3)$/', $filter))
    {
      throw new rtkAjaxException('Invalid parameters.');
    }

    if ($filter == 'all')
    {
      $filter = '';
    }

    // remember last filter selected
    $this->getUser()->getLocalPrefs()->sync('review.graph.filter', $filter, '');

    $tron = new JsTron();

    return $tron->renderComponent($this, 'review', 'LeitnerChart');
  }

  /**
   * Kanji Flashcard review WITHOUT SRS (no flashcard updates in database).
   *
   * See executeReview for parameters.
   *
   * @param sfRequest $request
   */
  public function executeFree($request)
  {
    $this->forward('review', 'review');
  }

  /**
   * Kanji Flashcard review page with FlashcardReview.
   *
   * Custom Review modes:
   *
   *   from, to     Range of Heisig numbers (1-xxxx)
   *   lesson       Lesson Id (sets from, to)
   *   known        N cards to review from known kanji.
   *   from_text    String of unique kanji to create deck from
   *
   * Options:
   *
   *   shuffle      Shuffle cards (with from, to)
   *   reverse      Kanki to Keyword
   *
   * SRS mode:
   *
   *   type = 'expired'|'untested'|'relearned'|'fresh'
   *   box  = 'all'|[1-5]
   *   filt = ''|'rtk1'|'rtk3'
   *
   * @param object $request
   */
  public function executeReview($request)
  {
    $this->setLayout('fullscreenLayout');

    $reviewFrom = (int) $request->getParameter('from', 0);
    $reviewTo = (int) $request->getParameter('to', 0);
    $reviewKnown = (int) $request->getParameter('known', 0);
    $reviewFromText = $request->getParameter('from_text', '');

    $reviewShuffle = !!$request->getParameter('shuffle', 0);
    $reviewFlip = !!$request->getParameter('reverse', 0);
    // DBG::request();exit;
    
    // if any of these options is set it is assumed to be a Custom Review (not SRS)
    $options['freemode'] = $reviewFrom > 0 || $reviewKnown > 0 || $reviewFromText;

    if ($lessonId = (int) $request->getParameter('lesson', 0))
    {
      $lessonInfo = rtkIndex::getLessonData($lessonId);
      $this->forward404If(!$lessonInfo);
      $reviewFrom = $lessonInfo['lesson_from'];
      $reviewTo = $lessonInfo['lesson_from'] + $lessonInfo['lesson_count'] - 1;
    }

    // set review template options
    $options['fc_reverse'] = $reviewFlip;
    $options['ts_start'] = UsersPeer::intLocalTime();

    if (false === $options['freemode'])
    {
      // SRS review
      $reviewBox = $request->getParameter('box', 'all');
      $reviewType = $request->getParameter('type', 'expired');
      $reviewFilt = $request->getParameter('filt', '');
      $reviewMerge = $request->getParameter('merge') ? true : false;

      // validate
      $this->forward404Unless(preg_match('/^(all|[0-9]+)$/', $reviewBox));
      $this->forward404Unless(preg_match('/^(expired|untested|relearned|fresh|known)$/', $reviewType));
      $this->forward404Unless($reviewFilt == '' || preg_match('/(rtk1|rtk3)/', $reviewFilt));

      // pick title
      //$this->setReviewTitle($reviewType, $reviewFilt);

      // options for setting up the review template
      $options['items'] = ReviewsPeer::getFlashcardsForReview($reviewBox, $reviewType, $reviewFilt, $reviewMerge);

      $options['ajax_url'] = $this->getController()->genUrl('review/ajaxsrs');
    }
    else
    {
      $options['ajax_url'] = $this->getController()->genUrl('review/ajaxfree');
      $options['fc_rept'] = null;

      if ($request->hasParameter('known'))
      {
        // free review :: known cards

        $this->forward404If(!BaseValidators::validateInteger($reviewKnown)
                            || !BaseValidators::validateNotEmpty($reviewKnown)
                            || $reviewKnown <= 0, 'Invalid card range');
        $cards = ReviewsPeer::getFlashcardsForReview('all', 'known', '');
        $options['items'] = array_slice($cards, 0, $reviewKnown);
        //DBG::printr($options['items']);exit;

        // NO repeat button because the randomized card set can change
        $options['fc_rept'] = null;
      }
      else if ($reviewFromText) {
        // Custom Review : Create a Review Deck from Japanese Text

        $chars = CJK::getKanji($reviewFromText);
        $this->forward404Unless(count($chars) > 0, 'from_text is invalid');

        // just in case client didn't remove the duplicates
        $uniqueChars = array_unique($chars);
        
        $options['items'] = array_map(fn($char) => mb_ord($char), $uniqueChars);

        // shuffle the cards


        // set the options to repeat the review at the Review Summary screen
        $options['fc_rept'] = json_encode([
          'action' => $this->getController()->genUrl('review/free'),<commit_msg>Fix handling of "Shuffle cards" option for new review mode<commit_after><?php

class reviewActions extends sfActions
{
  /**
   * Review graph page.
   *
   * @param sfRequest $request
   *
   * @return
   */
  public function executeIndex($request)
  {
    // set local pref default value
    $this->filter = $this->getUser()->getLocalPrefs()->sync('review.graph.filter', null, '');
    if ($this->filter == '')
    {
      $this->filter = 'all';
    }

    $this->flashcard_count = ReviewsPeer::getFlashcardCount($this->getUser()->getUserId());
  }

  /**
   * Custom Review modes 
   *
   * @param sfRequest $request
   */
  public function executeCustom($request)
  {
    $userId = sfContext::getInstance()->getUser()->getUserId();
    $this->knowncount = ReviewsPeer::getReviewedFlashcardCount($userId, LeitnerSRS::FAILEDSTACK + 1);
    $this->knowndefault = max($this->knowncount, 1);

    // set defaults for forms
    $request->setParameter('shuffle', 1);
  }

  public function executeVocab($request)
  {
    $userId = sfContext::getInstance()->getUser()->getUserId();
    $this->learnedcount = ReviewsPeer::getReviewedFlashcardCount($userId, rtkLabs::VOCABSHUFFLE_MINBOX);
  }

  /**
   * Review graph filter actions.
   *
   * @param sfRequest $request
   *
   * @return
   */
  public function executeAjaxLeitnerGraph($request)
  {
    $filter = $request->getParameter('filter', '');
    if (!preg_match('/^(all|rtk1|rtk3)$/', $filter))
    {
      throw new rtkAjaxException('Invalid parameters.');
    }

    if ($filter == 'all')
    {
      $filter = '';
    }

    // remember last filter selected
    $this->getUser()->getLocalPrefs()->sync('review.graph.filter', $filter, '');

    $tron = new JsTron();

    return $tron->renderComponent($this, 'review', 'LeitnerChart');
  }

  /**
   * Kanji Flashcard review WITHOUT SRS (no flashcard updates in database).
   *
   * See executeReview for parameters.
   *
   * @param sfRequest $request
   */
  public function executeFree($request)
  {
    $this->forward('review', 'review');
  }

  /**
   * Kanji Flashcard review page with FlashcardReview.
   *
   * Custom Review modes:
   *
   *   from, to     Range of Heisig numbers (1-xxxx)
   *   lesson       Lesson Id (sets from, to)
   *   known        N cards to review from known kanji.
   *   from_text    String of unique kanji to create deck from
   *
   * Options:
   *
   *   shuffle      Shuffle cards (with from, to)
   *   reverse      Kanki to Keyword
   *
   * SRS mode:
   *
   *   type = 'expired'|'untested'|'relearned'|'fresh'
   *   box  = 'all'|[1-5]
   *   filt = ''|'rtk1'|'rtk3'
   *
   * @param object $request
   */
  public function executeReview($request)
  {
    $this->setLayout('fullscreenLayout');

    $reviewFrom = (int) $request->getParameter('from', 0);
    $reviewTo = (int) $request->getParameter('to', 0);
    $reviewKnown = (int) $request->getParameter('known', 0);
    $reviewFromText = $request->getParameter('from_text', '');

    $reviewShuffle = !!$request->getParameter('shuffle', 0);
    $reviewFlip = !!$request->getParameter('reverse', 0);
    // DBG::request();exit;
    
    // if any of these options is set it is assumed to be a Custom Review (not SRS)
    $options['freemode'] = $reviewFrom > 0 || $reviewKnown > 0 || $reviewFromText;

    if ($lessonId = (int) $request->getParameter('lesson', 0))
    {
      $lessonInfo = rtkIndex::getLessonData($lessonId);
      $this->forward404If(!$lessonInfo);
      $reviewFrom = $lessonInfo['lesson_from'];
      $reviewTo = $lessonInfo['lesson_from'] + $lessonInfo['lesson_count'] - 1;
    }

    // set review template options
    $options['fc_reverse'] = $reviewFlip;
    $options['ts_start'] = UsersPeer::intLocalTime();

    if (false === $options['freemode'])
    {
      // SRS review
      $reviewBox = $request->getParameter('box', 'all');
      $reviewType = $request->getParameter('type', 'expired');
      $reviewFilt = $request->getParameter('filt', '');
      $reviewMerge = $request->getParameter('merge') ? true : false;

      // validate
      $this->forward404Unless(preg_match('/^(all|[0-9]+)$/', $reviewBox));
      $this->forward404Unless(preg_match('/^(expired|untested|relearned|fresh|known)$/', $reviewType));
      $this->forward404Unless($reviewFilt == '' || preg_match('/(rtk1|rtk3)/', $reviewFilt));

      // pick title
      //$this->setReviewTitle($reviewType, $reviewFilt);

      // options for setting up the review template
      $options['items'] = ReviewsPeer::getFlashcardsForReview($reviewBox, $reviewType, $reviewFilt, $reviewMerge);

      $options['ajax_url'] = $this->getController()->genUrl('review/ajaxsrs');
    }
    else
    {
      $options['ajax_url'] = $this->getController()->genUrl('review/ajaxfree');
      $options['fc_rept'] = null;

      if ($request->hasParameter('known'))
      {
        // free review :: known cards

        $this->forward404If(!BaseValidators::validateInteger($reviewKnown)
                            || !BaseValidators::validateNotEmpty($reviewKnown)
                            || $reviewKnown <= 0, 'Invalid card range');
        $cards = ReviewsPeer::getFlashcardsForReview('all', 'known', '');
        $options['items'] = array_slice($cards, 0, $reviewKnown);
        //DBG::printr($options['items']);exit;

        // NO repeat button because the randomized card set can change
        $options['fc_rept'] = null;
      }
      else if ($reviewFromText) {
        // Custom Review : Create a Review Deck from Japanese Text

        $chars = CJK::getKanji($reviewFromText);
        $this->forward404Unless(count($chars) > 0, 'from_text is invalid');

        // just in case client didn't remove the duplicates
        $uniqueChars = array_unique($chars);
        
        $cards = array_map(fn($char) => mb_ord($char), $uniqueChars);

        if ($reviewShuffle) {
          shuffle($cards);
        }

        $options['items'] = $cards;

        // set the options to repeat the review at the Review Summary screen
        $options['fc_rept'] = json_encode([
          'action' => $this->getController()->genUrl('review/free'),
{'fraction_non_alphanumeric': 0.16436213991769547, 'fraction_numerical': 0.007242798353909465, 'mean_word_length': 3.058450233800935, 'pattern_counts': {'":': 0, '<': 7, '<?xml version=': 0, '>': 113, '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': '3868581', 'n_tokens_mistral': 3921, 'n_tokens_neox': 3757, 'n_words': 778}