/**
  * Fetches the details of the question results, grouped by tag for this user.
  * Data is returned as an array with the calculations so that that it can 
  * be used in any render that's needed (HTML, PDF or email)
  * 
  * @return Array A nested array containing a list of tags, the tag details, and a list of the scores summarising how the user did for each tag. 
  */
 function fetch_quizzes_questionResultsByTag()
 {
     $tagBucketList = array();
     // Use the cached version if we have it.
     if ($this->cached_resultsByTag) {
         return $this->cached_resultsByTag;
     }
     // ### 1 - Check to make sure we have questions
     if (empty($this->unitQuizDetails) || empty($this->unitQuizDetails->questions) || empty($this->unitQuizProgress->quiz_data)) {
         $this->cached_resultsByTag = $tagBucketList;
         return $tagBucketList;
     }
     // ### 1A - Ensure that the questions all have tags (random questions will not have these tags, hence fetching
     // them here).
     foreach ($this->unitQuizDetails->questions as $questionID => $questionObj) {
         // Tags should always exist for normal questions. So this will just be triggered for randoms.
         // This is as practically efficient as we can make it.
         if (!isset($questionObj->tags)) {
             $this->unitQuizDetails->questions[$questionID]->tags = WPCW_questions_tags_getTagsForQuestion($questionID);
         }
     }
     // ### 2 - Build a list of tags used by the questions
     foreach ($this->unitQuizDetails->questions as $questionID => $questionObj) {
         // ### 3 - Got some tags for this question
         if (!empty($questionObj->tags)) {
             $userResponse = array('got_right' => false);
             // ### 4 - Get the question from the user's responses. Can't assume it's
             // there, hence the initialisation above.
             if (isset($this->unitQuizProgress->quiz_data[$questionID])) {
                 $userResponse = $this->unitQuizProgress->quiz_data[$questionID];
             }
             $isQuestionRight = 'yes' == $userResponse['got_right'];
             // ###Ê5 - Work out if an open-ended question or not.
             $isQuestionOpen = false;
             if ('open' == $questionObj->question_type || 'upload' == $questionObj->question_type) {
                 $isQuestionOpen = true;
             }
             // ### 5 - For each tag, add the tag details to the list, and then
             // add the association for this question.
             foreach ($questionObj->tags as $tagObj) {
                 // Is the tag already in the bucket?
                 if (isset($tagBucketList[$tagObj->question_tag_id])) {
                     // Append the response from the user.
                     $tagBucketList[$tagObj->question_tag_id]['question_responses'][$questionID] = $userResponse;
                     // Increase question count
                     $tagBucketList[$tagObj->question_tag_id]['question_count']++;
                     // Increase correct count (if correct)
                     $tagBucketList[$tagObj->question_tag_id]['score_correct_questions'] += $isQuestionRight ? 1 : 0;
                     // Increase question open count
                     $tagBucketList[$tagObj->question_tag_id]['question_open_count'] += $isQuestionOpen ? 1 : 0;
                 } else {
                     $tagBucketList[$tagObj->question_tag_id] = array('question_open_count' => $isQuestionOpen ? 1 : 0, 'question_count' => 1, 'score_correct_questions' => $isQuestionRight ? 1 : 0, 'score_total' => 0, 'tag_details' => $tagObj, 'question_responses' => array($questionID => $userResponse));
                 }
             }
             // end of foreach
         }
         // Check of question tags
     }
     // end of questionforeach
     // ### 6 - Now we need to calculate the total for each of these tags using the questions responses we have.
     if (!empty($tagBucketList)) {
         foreach ($tagBucketList as $tagID => $tagDetails) {
             $tagBucketList[$tagID]['score_total'] = WPCW_quizzes_calculateGradeForQuiz($tagDetails['question_responses'], 0);
             // Don't need responses now.
             unset($tagBucketList[$tagID]['question_responses']);
         }
     }
     // Update cache to save time later.
     $this->cached_resultsByTag = $tagBucketList;
     return $tagBucketList;
 }
/**
 * Function to show a list of questions in the question pool for use by a standard page or the AJAX thickbox.
 * 
 * @param Integer $itemsPerPage The number of items to show on each table page.
 * @param Array $paramSrc The array of parameters to use for filtering/searching the question pool.
 * @param String $actionMode The type of mode we're in (ajax or std). 
 * @param PageBuilder $page The current page object (optional).
 */
function WPCW_questionPool_showPoolTable($itemsPerPage, $paramSrc, $actionMode = 'std', $page = false)
{
    global $wpcwdb, $wpdb;
    $wpdb->show_errors();
    // AJAX loader
    if ('ajax' == $actionMode) {
        printf('<img src="%simg/ajax_loader.gif" class="wpcw_loader" style="display: none;" />', WPCW_plugin_getPluginPath());
    }
    // Check to see if we've got questions to process
    if ('std' == $actionMode) {
        WPCW_showPage_QuestionPool_processActionForm($page);
    }
    $paging_pageWanted = WPCW_arrays_getValue($paramSrc, 'pagenum') + 0;
    if ($paging_pageWanted == 0) {
        $paging_pageWanted = 1;
    }
    // Handle the sorting and filtering
    $orderBy = WPCW_arrays_getValue($paramSrc, 'orderby');
    $ordering = WPCW_arrays_getValue($paramSrc, 'order');
    // Validate ordering
    switch ($orderBy) {
        case 'question_question':
        case 'question_type':
            break;
            // Default and question_id
            //case 'question_id':
        // Default and question_id
        //case 'question_id':
        default:
            $orderBy = 'qs.question_id';
            break;
    }
    // Create opposite ordering for reversing it.
    $ordering_opposite = false;
    switch ($ordering) {
        case 'desc':
            $ordering_opposite = 'asc';
            break;
        case 'asc':
            $ordering_opposite = 'desc';
            break;
        default:
            $ordering = 'desc';
            $ordering_opposite = 'asc';
            break;
    }
    // Was a search string specified? Or a specific item?
    $searchString = WPCW_arrays_getValue($paramSrc, 's');
    // Create WHERE string based search - Title or Description of Quiz
    $SQL_WHERE = false;
    if ($searchString) {
        $SQL_WHERE = $wpdb->prepare(" AND question_question LIKE %s", '%' . $searchString . '%');
    }
    $summaryPageURL = admin_url('admin.php?page=WPCW_showPage_QuestionPool');
    // Show the form for searching
    ?>
			
	<form id="wpcw_questions_search_box" method="get" action="<?php 
    echo $summaryPageURL;
    ?>
">
	<p class="search-box">
		<label class="screen-reader-text" for="wpcw_questions_search_input"><?php 
    _e('Search Questions', 'wp_courseware');
    ?>
</label>
		<input id="wpcw_questions_search_input" type="text" value="<?php 
    echo $searchString;
    ?>
" name="s"/>
		<input class="button" type="submit" value="<?php 
    _e('Search Questions', 'wp_courseware');
    ?>
"/>
		
		<input type="hidden" name="page" value="WPCW_showPage_QuestionPool" />
	</p>
	</form>
	<?php 
    $SQL_TAG_FILTER = false;
    $tagFilter = intval(WPCW_arrays_getValue($paramSrc, 'filter', false));
    // See if we have any tag filtering to do.
    if ($tagFilter > 0) {
        // Ensure we add the tag mapping table to the query
        $SQL_TAG_FILTER = "\n\t\t\tLEFT JOIN {$wpcwdb->question_tag_mapping} qtm ON qtm.question_id = qs.question_id\t\n\t\t";
        $SQL_WHERE .= $wpdb->prepare("\n\t\t\tAND qtm.tag_id = %d\n\t\t\tAND qs.question_question IS NOT NULL  \n\t\t", $tagFilter);
        //
    }
    $SQL_PAGING = "\n\t\t\tSELECT COUNT(*) as question_count \n\t\t\tFROM {$wpcwdb->quiz_qs} qs\n\t\t\t{$SQL_TAG_FILTER}\n\t\t\tWHERE question_type <> 'random_selection'\n\t\t\t{$SQL_WHERE} \n\t\t";
    $paging_resultsPerPage = $itemsPerPage;
    $paging_totalCount = $wpdb->get_var($SQL_PAGING);
    $paging_recordStart = ($paging_pageWanted - 1) * $paging_resultsPerPage + 1;
    $paging_recordEnd = $paging_pageWanted * $paging_resultsPerPage;
    $paging_pageCount = ceil($paging_totalCount / $paging_resultsPerPage);
    $paging_sqlStart = $paging_recordStart - 1;
    // Show search message - that a search has been tried.
    if ($searchString) {
        printf('<div class="wpcw_search_count">%s "%s" (%s %s) (<a href="%s">%s</a>)</div>', __('Search results for', 'wp_courseware'), htmlentities($searchString), $paging_totalCount, _n('result', 'results', $paging_totalCount, 'wp_courseware'), $summaryPageURL, __('reset', 'wp_courseware'));
    }
    // Do main query
    $SQL = "SELECT * \n\t\t\tFROM {$wpcwdb->quiz_qs} qs\t\t\t\n\t\t\t{$SQL_TAG_FILTER}\t\t\t\n\t\t\tWHERE question_type <> 'random_selection'\n\t\t\t{$SQL_WHERE}\n\t\t\tORDER BY {$orderBy} {$ordering}\n\t\t\tLIMIT {$paging_sqlStart}, {$paging_resultsPerPage}\t\t\t \n\t\t\t";
    // These are already checked, so they are safe, hence no prepare()
    // Generate paging code
    $baseURL = WPCW_urls_getURLWithParams($summaryPageURL, 'pagenum') . "&pagenum=";
    $questions = $wpdb->get_results($SQL);
    $tbl = new TableBuilder();
    $tbl->attributes = array('id' => 'wpcw_tbl_question_pool', 'class' => 'widefat wpcw_tbl');
    // Checkbox Col
    //$tblCol = new TableColumn(false, 'question_selection');
    //$tblCol->cellClass = "question_selection wpcw_center";
    //$tbl->addColumn($tblCol);
    // Wanting sorting links... in standard mode
    if ('std' == $actionMode) {
        // Checkbox field (no name, as we'll use jQuery to do a check all)
        $tblCol = new TableColumn('<input type="checkbox" />', 'question_id_cb');
        $tblCol->cellClass = "wpcw_center wpcw_select_cb";
        $tblCol->headerClass = "wpcw_center wpcw_select_cb";
        $tbl->addColumn($tblCol);
        // ID - sortable
        $sortableLink = sprintf('<a href="%s&order=%s&orderby=question_id"><span>%s</span><span class="sorting-indicator"></span></a>', $baseURL, 'question_id' == $orderBy ? $ordering_opposite : 'asc', __('ID', 'wp_courseware'));
        // ID - render
        $tblCol = new TableColumn($sortableLink, 'question_id');
        $tblCol->headerClass = 'question_id' == $orderBy ? 'sorted ' . $ordering : 'sortable';
        $tblCol->cellClass = "question_id";
        $tbl->addColumn($tblCol);
        // Question - sortable
        $sortableLink = sprintf('<a href="%s&order=%s&orderby=question_question"><span>%s</span><span class="sorting-indicator"></span></a>', $baseURL, 'question_question' == $orderBy ? $ordering_opposite : 'asc', __('Question', 'wp_courseware'));
        // Question - render
        $tblCol = new TableColumn($sortableLink, 'question_question');
        $tblCol->headerClass = 'question_question' == $orderBy ? 'sorted ' . $ordering : 'sortable';
        $tblCol->cellClass = "question_question";
        $tbl->addColumn($tblCol);
        // Question Type - sortable
        $sortableLink = sprintf('<a href="%s&order=%s&orderby=question_type"><span>%s</span><span class="sorting-indicator"></span></a>', $baseURL, 'question_type' == $orderBy ? $ordering_opposite : 'asc', __('Question Type', 'wp_courseware'));
        // Question Type - render
        $tblCol = new TableColumn($sortableLink, 'question_type');
        $tblCol->headerClass = ('question_type' == $orderBy ? 'sorted ' . $ordering : 'sortable') . ' wpcw_center';
        $tblCol->cellClass = "question_type";
        $tbl->addColumn($tblCol);
    } else {
        $tblCol = new TableColumn(__('ID', 'wp_courseware'), 'question_id');
        $tblCol->cellClass = "question_id";
        $tbl->addColumn($tblCol);
        $tblCol = new TableColumn(__('Question', 'wp_courseware'), 'question_question');
        $tblCol->cellClass = "question_question";
        $tbl->addColumn($tblCol);
        $tblCol = new TableColumn(__('Question Type', 'wp_courseware'), 'question_type');
        $tblCol->cellClass = "question_type";
        $tbl->addColumn($tblCol);
    }
    $tblCol = new TableColumn(__('Associated Quizzes', 'wp_courseware'), 'associated_quizzes');
    $tblCol->headerClass = "wpcw_center";
    $tblCol->cellClass = "associated_quizzes wpcw_center";
    $tbl->addColumn($tblCol);
    $tblCol = new TableColumn(__('Tags', 'wp_courseware'), 'question_tags');
    $tblCol->cellClass = "question_tags wpcw_center";
    $tbl->addColumn($tblCol);
    // Actions
    $tblCol = new TableColumn(__('Actions', 'wp_courseware'), 'actions');
    $tblCol->cellClass = "actions actions_right";
    $tblCol->headerClass = "actions_right";
    $tbl->addColumn($tblCol);
    // Stores course details in a mini cache to save lots of MySQL lookups.
    $miniCourseDetailCache = array();
    // Format row data and show it.
    if ($questions) {
        $odd = false;
        foreach ($questions as $singleQuestion) {
            $data = array();
            // URLs
            $editURL = admin_url('admin.php?page=WPCW_showPage_ModifyQuestion&question_id=' . $singleQuestion->question_id);
            // Maintain paging where possible.
            $deleteURL = $baseURL . '&action=delete&question_id=' . $singleQuestion->question_id;
            // Basic Details
            $data['question_id'] = $singleQuestion->question_id;
            $data['question_type'] = WPCW_quizzes_getQuestionTypeName($singleQuestion->question_type);
            $data['question_id_cb'] = sprintf('<input type="checkbox" name="question_%d" />', $singleQuestion->question_id);
            // Association Count
            $data['associated_quizzes'] = $singleQuestion->question_usage_count;
            // Actions - Std mode
            if ('std' == $actionMode) {
                // Edit by clicking
                $data['question_question'] = sprintf('<a href="%s">%s</a>', $editURL, $singleQuestion->question_question);
                $data['actions'] = '<ul class="wpcw_action_link_list">';
                $data['actions'] .= sprintf('<li><a href="%s" class="button-primary">%s</a></li>', $editURL, __('Edit', 'wp_courseware'));
                $data['actions'] .= sprintf('<li><a href="%s" class="button-secondary wpcw_action_link_delete_question wpcw_action_link_delete" rel="%s">%s</a></li>', $deleteURL, __('Are you sure you wish to delete this question? This cannot be undone.', 'wp_courseware'), __('Delete', 'wp_courseware'));
                $data['actions'] .= '</ul>';
            } else {
                if ('ajax' == $actionMode) {
                    // No Edit by clicking
                    $data['question_question'] = $singleQuestion->question_question . sprintf('<span class="wpcw_action_status wpcw_action_status_added">%s</span>', __('Added', 'wp_courseware'));
                    $data['actions'] = '<ul class="wpcw_action_link_list">';
                    $data['actions'] .= sprintf('<li><a href="#" class="button-primary wpcw_tb_action_add" data-questionnum="%d">%s</a></li>', $singleQuestion->question_id, __('Add To Quiz', 'wp_courseware'));
                    $data['actions'] .= '</ul>';
                }
            }
            // Tags
            $data['question_tags'] = sprintf('<span class="wpcw_quiz_details_question_tags" data-questionid="%d" id="wpcw_quiz_details_question_tags_%d">', $singleQuestion->question_id, $singleQuestion->question_id);
            $data['question_tags'] .= WPCW_questions_tags_render($singleQuestion->question_id, WPCW_questions_tags_getTagsForQuestion($singleQuestion->question_id));
            $data['question_tags'] .= '</span>';
            // Odd/Even row colouring.
            $odd = !$odd;
            $tbl->addRow($data, $odd ? 'alternate' : '');
        }
    } else {
        // No questions - show error in table.
        $tbl->addRowObj(new RowDataSimple('wpcw_center wpcw_none_found', __('There are currently no questions to show.', 'wp_courseware'), 7));
    }
    // Add the form for the start of the multiple-add
    $formWrapper_start = false;
    if ('std' == $actionMode) {
        // Set the action URL to preserve parameters that we have.
        $formWrapper_start = sprintf('<form method="POST" action="%s">', WPCW_urls_getURLWithParams($summaryPageURL, 'pagenum'));
    }
    // Create tag filter (uses a form)
    $tagFilter = WPCW_questions_tags_createTagFilter($tagFilter, 'WPCW_showPage_QuestionPool');
    // Work out paging and filtering
    $paging = WPCW_tables_showPagination($baseURL, $paging_pageWanted, $paging_pageCount, $paging_totalCount, $paging_recordStart, $paging_recordEnd, $tagFilter);
    // Show the actions
    $formWrapper_end = false;
    if ('std' == $actionMode) {
        $formWrapper_end = WPCW_showPage_QuestionPool_actionForm();
        // Form tag - needed for processing
        $formWrapper_end .= '</form>';
    }
    // Finally show table
    return $paging . $formWrapper_start . $tbl->toString() . $formWrapper_end . $paging;
}