commit 469e716b2e97dccf6b3e47111ff664846fea34ec
Author: Daniel Neis Araujo <danielneis@gmail.com>
Date:   Mon Dec 9 14:54:48 2019 -0300

    Insights de vĂ¡rios cursos da mesma categoria

diff --git a/report/insights/classes/output/multi_insights_list.php b/report/insights/classes/output/multi_insights_list.php
new file mode 100644
index 00000000000..51859765ee7
--- /dev/null
+++ b/report/insights/classes/output/multi_insights_list.php
@@ -0,0 +1,260 @@
+<?php
+// This file is part of Moodle - http://moodle.org/
+//
+// Moodle is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Moodle is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
+
+/**
+ * Multi Insights list page.
+ *
+ * @package    report_insights
+ * @copyright  2017 David Monllao {@link http://www.davidmonllao.com}
+ * @copyright  2019 Daniel Neis Araujo
+ * @copyright  2019 Thiago Livramento
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+namespace report_insights\output;
+
+defined('MOODLE_INTERNAL') || die();
+
+/**
+ * Shows report_insights multi insights list.
+ *
+ * @package    report_insights
+ * @copyright  2017 David Monllao {@link http://www.davidmonllao.com}
+ * @copyright  2019 Daniel Neis Araujo
+ * @copyright  2019 Thiago Livramento
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class multi_insights_list implements \renderable, \templatable {
+
+    /**
+     * @var \core_analytics\model
+     */
+    protected $model;
+
+    /**
+     * @var \context
+     */
+    protected $context;
+
+    /**
+     * @var \core_analytics\model[]
+     */
+    protected $othermodels;
+
+    /**
+     * @var int
+     */
+    protected $page;
+
+    /**
+     * @var int
+     */
+    protected $perpage;
+
+    /**
+     * Constructor
+     *
+     * @param \core_analytics\model $model
+     * @param \context[] $context
+     * @param \core_analytics\model[] $othermodels
+     * @param int $page
+     * @param int $perpage The max number of results to fetch
+     * @return void
+     */
+    public function __construct(\core_analytics\model $model, array $contexts, $othermodels, $page = 0, $perpage = 100) {
+        $this->model = $model;
+        $this->contexts = $contexts;
+        $this->othermodels = $othermodels;
+        $this->page = $page;
+        $this->perpage = $perpage;
+    }
+
+    /**
+     * Exports the data.
+     *
+     * @param \renderer_base $output
+     * @return \stdClass
+     */
+    public function export_for_template(\renderer_base $output) {
+        global $PAGE;
+
+        $target = $this->model->get_target();
+        $allusers = [];
+        $return['contexts'] = [];
+        foreach ($this->contexts as $context) {
+            $allusers += get_enrolled_users($context);
+            $return['contexts'][] = $context->get_context_name();
+        }
+        foreach ($allusers as $key => $user) {
+            $allusers[$key]->fullname = fullname($user);
+        }
+
+        foreach ($this->contexts as $context) {
+
+            $this->context = $context;
+
+            $data = new \stdClass();
+            $data->modelid = $this->model->get_id();
+            $data->contextid = $this->context->id;
+
+            $targetname = $target->get_name();
+            $data->insightname = format_string($targetname);
+
+            $targetinfostr = $targetname->get_identifier() . 'info';
+            if (get_string_manager()->string_exists($targetinfostr, $targetname->get_component())) {
+                $data->insightdescription = get_string($targetinfostr, $targetname->get_component());
+            }
+
+            $data->showpredictionheading = true;
+            if (!$target->is_linear()) {
+                $nclasses = count($target::get_classes());
+                $nignoredclasses = count($target->ignored_predicted_classes());
+                if ($nclasses - $nignoredclasses <= 1) {
+                    // Hide the prediction heading if there is only 1 class displayed. Otherwise it is redundant with the insight name.
+                    $data->showpredictionheading = false;
+                }
+            }
+
+            $total = 0;
+
+            if ($this->model->uses_insights()) {
+
+                //$target->add_bulk_actions_js();
+
+                $predictionsdata = $this->model->get_predictions($this->context, true, $this->page, $this->perpage);
+
+                if (!$this->model->is_static()) {
+                    $notification = new \core\output\notification(get_string('justpredictions', 'report_insights'));
+                    $data->nostaticmodelnotification = $notification->export_for_template($output);
+                }
+
+                $data->predictions = array();
+                $predictionvalues = array();
+                $insights = array();
+                if ($predictionsdata) {
+                    list($total, $predictions) = $predictionsdata;
+
+		    /*
+                    if ($predictions) {
+                        // No bulk actions if no predictions.
+                        $data->bulkactions = actions_exporter::add_bulk_actions($target, $output, $predictions, $this->context);
+                    }
+		     */
+
+                    $data->multiplepredictions = count($predictions) > 1 ? true : false;
+
+                    foreach ($predictions as $prediction) {
+                        $predictedvalue = $prediction->get_prediction_data()->prediction;
+                        $userid = $prediction->get_sample_data()['user']->id;
+
+                        // Only need to fill this data once.
+                        if (!isset($predictionvalues[$predictedvalue])) {
+                            $preddata = array();
+                            $preddata['predictiondisplayvalue'] = $target->get_display_value($predictedvalue);
+                            list($preddata['style'], $preddata['outcomeicon']) =
+                                insight::get_calculation_display($target, floatval($predictedvalue), $output);
+                            $predictionvalues[$predictedvalue] = $preddata;
+                        }
+
+                        $insightrenderable = new \report_insights\output\insight($prediction, $this->model, true, $this->context);
+                        $insightexported = $insightrenderable->export_for_template($output);
+                        $insights[$predictedvalue][] = $insightexported;
+                        if (!isset($allusers[$userid]->predictions)) {
+                            $allusers[$userid]->predictions = [];
+                        }
+                        $allusers[$userid]->predictions[$data->contextid]['insights'][] = $insightexported;
+                    }
+
+                    // Order predicted values.
+                    if ($target->is_linear()) {
+                        // During regression what we will be interested on most of the time is in low values so let's show them first.
+                        ksort($predictionvalues);
+                    } else {
+                        // During classification targets flag "not that important" samples as 0 so let's show them at the end.
+                        krsort($predictionvalues);
+                    }
+
+                    // Ok, now we have all the data we want, put it into a format that mustache can handle.
+                    foreach ($predictionvalues as $key => $prediction) {
+                        if (isset($insights[$key])) {
+
+				/*
+                            $toggleall = new \core\output\checkbox_toggleall('insight-bulk-action-' . $key, true, [
+                                    'id' => 'id-toggle-all-' . $key,
+                                    'name' => 'toggle-all-' . $key,
+                                    'label' => get_string('selectall'),
+                                    'labelclasses' => 'sr-only',
+                                    'checked' => false
+                            ]);
+                            $prediction['checkboxtoggleall'] = $output->render($toggleall);
+
+                            $prediction['predictedvalue'] = $key;
+				 */
+                            $prediction['insights'] = $insights[$key];
+                        }
+
+                        $data->predictions[] = $prediction;
+                    }
+                }
+
+                if (empty($insights) && $this->page == 0) {
+                    if ($this->model->any_prediction_obtained()) {
+                        $data->noinsights = get_string('noinsights', 'analytics');
+                    } else {
+                        $data->noinsights = get_string('nopredictionsyet', 'analytics');
+                    }
+                }
+            } else {
+                $data->noinsights = get_string('noinsights', 'analytics');
+            }
+
+            if (!empty($data->noinsights)) {
+                $notification = new \core\output\notification($data->noinsights);
+                $data->noinsights = $notification->export_for_template($output);
+            }
+
+            if ($this->othermodels) {
+
+                $options = array();
+                foreach ($this->othermodels as $model) {
+                    $options[$model->get_id()] = $model->get_target()->get_name();
+                }
+
+                // New moodle_url instance returned by magic_get_url.
+                $url = $PAGE->url;
+                $url->remove_params('modelid');
+                $modelselector = new \single_select($url, 'modelid', $options, '',
+                        array('' => get_string('selectotherinsights', 'report_insights')));
+                $data->modelselector = $modelselector->export_for_template($output);
+            }
+
+            $data->pagingbar = $output->render(new \paging_bar($total, $this->page, $this->perpage, $PAGE->url));
+
+            $return['data'][] = $data;
+            foreach ($allusers as $key => $user) {
+                if (!isset($user->predictions[$data->contextid])) {
+                    $allusers[$user->id]->predictions[$data->contextid] = ['insights' => []];
+                }
+            }
+        }
+        $return['allusers'] = array_values($allusers);
+        foreach ($return['allusers'] as $key => $user) {
+            $return['allusers'][$key]->predictions = array_values($return['allusers'][$key]->predictions);
+        }
+
+        return $return;
+    }
+}
diff --git a/report/insights/classes/output/renderer.php b/report/insights/classes/output/renderer.php
index b8fefdcb76d..2879cae72cb 100644
--- a/report/insights/classes/output/renderer.php
+++ b/report/insights/classes/output/renderer.php
@@ -50,6 +50,17 @@ class renderer extends plugin_renderer_base {
         return parent::render_from_template('report_insights/insights_list', $data);
     }
 
+    /**
+     * Renders the list of insights
+     *
+     * @param renderable $renderable
+     * @return string HTML
+     */
+    protected function render_multi_insights_list(renderable $renderable) {
+        $data = $renderable->export_for_template($this);
+        return parent::render_from_template('report_insights/multi_insights_list', $data);
+    }
+
     /**
      * Renders an insight
      *
diff --git a/report/insights/insights.php b/report/insights/insights.php
index c45aaf9e79b..6642bcfe8c2 100644
--- a/report/insights/insights.php
+++ b/report/insights/insights.php
@@ -127,6 +127,9 @@ echo $OUTPUT->header();
 $renderable = new \report_insights\output\insights_list($model, $context, $othermodels, $page, $perpage);
 echo $renderer->render($renderable);
 
+$multi = new moodle_url('/report/insights/multiinsights.php', ['contextid' => $contextid, 'modelid' => $modelid]);
+echo html_writer::link($multi, 'Ver todos os cursos nesta categoria');
+
 $eventdata = array (
     'context' => $context,
     'other' => array('modelid' => $model->get_id())
diff --git a/report/insights/multiinsights.php b/report/insights/multiinsights.php
new file mode 100644
index 00000000000..f933ae48e00
--- /dev/null
+++ b/report/insights/multiinsights.php
@@ -0,0 +1,174 @@
+<?php
+// This file is part of Moodle - http://moodle.org/
+//
+// Moodle is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Moodle is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
+
+/**
+ * View model insights.
+ *
+ * @package    report_insights
+ * @copyright  2017 David Monllao {@link http://www.davidmonllao.com}
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+require_once(__DIR__ . '/../../config.php');
+require_once($CFG->libdir . '/adminlib.php');
+
+$contextid = required_param('contextid', PARAM_INT);
+$modelid = optional_param('modelid', false, PARAM_INT);
+$page = optional_param('page', 0, PARAM_INT);
+$perpage = optional_param('perpage', 100, PARAM_INT);
+
+if ($perpage > 1000) {
+    $perpage = 1000;
+}
+
+list($context, $course, $cm) = get_context_info_array($contextid);
+require_login($course, false, $cm);
+if ($context->contextlevel < CONTEXT_COURSE) {
+    // Only for higher levels than course.
+    $PAGE->set_context($context);
+}
+
+#if (!\core_analytics\manager::is_analytics_enabled()) {
+#    $renderer = $PAGE->get_renderer('report_insights');
+#    echo $renderer->render_analytics_disabled();
+#    exit(0);
+#}
+
+\core_analytics\manager::check_can_list_insights($context);
+
+// Get all models that are enabled, trained and have predictions at this context.
+$othermodels = \core_analytics\manager::get_all_models(true, true, $context);
+array_filter($othermodels, function($model) use ($context) {
+
+    // Discard insights that are not linked unless you are a manager.
+	/*
+    if (!$model->get_target()->link_insights_report()) {
+        try {
+            \core_analytics\manager::check_can_manage_models();
+        } catch (\required_capability_exception $e) {
+            return false;
+        }
+    }
+	 */
+    return true;
+});
+
+if (!$modelid && count($othermodels)) {
+    // Autoselect the only available model.
+    $model = reset($othermodels);
+    $modelid = $model->get_id();
+}
+if ($modelid) {
+    unset($othermodels[$modelid]);
+}
+
+// The URL in navigation only contains the contextid.
+$params = array('contextid' => $contextid);
+$navurl = new \moodle_url('/report/insights/insights.php', $params);
+
+// This is the real page url, we need it to include the modelid so pagination and
+// other stuff works as expected.
+$url = clone $navurl;
+if ($modelid) {
+    $url->param('modelid', $modelid);
+}
+
+$PAGE->set_url($url);
+$PAGE->set_pagelayout('report');
+
+if ($context->contextlevel === CONTEXT_SYSTEM) {
+    admin_externalpage_setup('reportinsights', '', $url->params(), $url->out(false), array('pagelayout' => 'report'));
+} else if ($context->contextlevel === CONTEXT_USER) {
+    $user = \core_user::get_user($context->instanceid, '*', MUST_EXIST);
+    $PAGE->navigation->extend_for_user($user);
+    $PAGE->add_report_nodes($user->id, array(
+        'name' => get_string('insights', 'report_insights'),
+        'url' => $url
+    ));
+}
+$PAGE->navigation->override_active_url($navurl);
+
+$renderer = $PAGE->get_renderer('report_insights');
+
+// No models with insights available at this context level.
+if (!$modelid) {
+    echo $renderer->render_no_insights($context);
+    exit(0);
+}
+
+$model = new \core_analytics\model($modelid);
+
+/*
+if (!$model->get_target()->link_insights_report()) {
+
+    // Only manager access if this target does not link the insights report.
+    \core_analytics\manager::check_can_manage_models();
+}
+ */
+$insightinfo = new stdClass();
+$insightinfo->contextname = $context->get_context_name();
+$insightinfo->insightname = $model->get_target()->get_name();
+
+if (!$model->is_enabled()) {
+    echo $renderer->render_model_disabled($insightinfo);
+    exit(0);
+}
+
+if (!$model->uses_insights()) {
+    echo $renderer->render_no_insights_model($context);
+    exit(0);
+}
+
+if ($context->id == SYSCONTEXTID) {
+    $PAGE->set_heading(get_site()->shortname);
+} else {
+    $PAGE->set_heading($insightinfo->contextname);
+}
+$PAGE->set_title($insightinfo->insightname);
+
+// Some models generate one single prediction per context. We can directly show the prediction details in this case.
+if ($model->get_analyser()::one_sample_per_analysable()) {
+
+    // Param $perpage to 2 so we can detect if this model's analyser is using one_sample_per_analysable incorrectly.
+    $predictionsdata = $model->get_predictions($context, true, 0, 2);
+    if ($predictionsdata) {
+        list($total, $predictions) = $predictionsdata;
+        if ($total > 1) {
+            throw new \coding_exception('This model\'s analyser processed more than one sample for a single analysable element.' .
+                'Therefore, the analyser\'s one_sample_per_analysable() method should return false.');
+        }
+        $prediction = reset($predictions);
+        $redirecturl = new \moodle_url('/report/insights/prediction.php', ['id' => $prediction->get_prediction_data()->id]);
+        redirect($redirecturl);
+    }
+}
+echo $OUTPUT->header();
+
+$category = core_course_category::get($course->category);
+$contexts = [];
+foreach ($category->get_courses() as $c) {
+    $contexts[] = \context_course::instance($c->id);
+}
+$renderable = new \report_insights\output\multi_insights_list($model, $contexts, $othermodels, $page, $perpage);
+echo $renderer->render($renderable);
+
+$eventdata = array (
+    'context' => $context,
+    'other' => array('modelid' => $model->get_id())
+);
+\core\event\insights_viewed::create($eventdata)->trigger();
+
+echo $OUTPUT->footer();
diff --git a/report/insights/templates/multi_insights_list.mustache b/report/insights/templates/multi_insights_list.mustache
new file mode 100644
index 00000000000..5cf31ece5c4
--- /dev/null
+++ b/report/insights/templates/multi_insights_list.mustache
@@ -0,0 +1,153 @@
+{{!
+    This file is part of Moodle - http://moodle.org/
+
+    Moodle is free software: you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published by
+    the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Moodle is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
+}}
+{{!
+    @template report_insights/insights_list
+
+    Template for the insights list.
+
+    Classes required for JS:
+    * none
+
+    Data attributes required for JS:
+    * none
+
+    Context variables required for this template:
+    * none
+
+    Example context (json):
+    {
+        "contextid": 123,
+        "modelid": 321,
+        "insightname": "Best insight ever",
+        "nostaticmodelnotification": {
+            "message": "This is just a prediction."
+        },
+        "showpredictionheading": "true",
+        "predictions": [
+            {
+                "predictiondisplayvalue": "This dev will understand it",
+                "style": "success",
+                "outcomeicon": {
+                    "attributes": [
+                        {"name": "src", "value": "https://moodle.org/logo/moodle-logo.svg" }
+                    ]
+                },
+                "multiplepredictions": "true",
+                "insights": [
+                    {
+                        "sampleimage": "<a href=\"#\">Link</a>",
+                        "sampledescription": "Sample description",
+                        "actions": [
+                            {
+                                "classes": "",
+                                "primary": {
+                                    "items": [{"rawhtml": "<p>View details</p>"}]
+                                },
+                                "secondary": {
+                                    "items": [{"rawhtml": "<p>Not useful</p>"}]
+                                }
+                            }
+                        ]
+                    }, {
+                        "sampleimage": "<a href=\"#\">Link</a>",
+                        "sampledescription": "Sample description",
+                        "actions": [
+                            {
+                                "classes": "",
+                                "primary": {
+                                    "items": [{"rawhtml": "<p>View details</p>"}]
+                                },
+                                "secondary": {
+                                    "items": [{"rawhtml": "<p>Not useful</p>"}]
+                                }
+                            }
+                        ]
+                    }
+                ]
+            }, {
+                "predictiondisplayvalue": "This dev will not understand it",
+                "style": "danger",
+                "outcomeicon": {
+                    "attributes": [
+                        {"name": "src", "value": "https://moodle.org/logo/moodle-logo.svg" }
+                    ]
+                },
+                "insights": [
+                    {
+                        "sampleimage": "<a href=\"#\">Any renderable</a>",
+                        "sampledescription": "Another sample description"
+                    }
+                ]
+            }
+        ],
+        "noinsights": false
+    }
+}}
+
+{{#modelselector}}
+    <div class="mb-2">
+        {{> core/single_select}}
+    </div>
+{{/modelselector}}
+
+<h2 class="mb-2">{{insightname}}</h2>
+
+{{{insightdescription}}}
+
+{{#showpredictionheading}}
+    <caption>
+        {{#str}}prediction, report_insights{{/str}}:
+        <span class="{{#style}}table-{{style}}{{/style}}">
+            {{#outcomeicon}}
+                {{> core/pix_icon}}
+            {{/outcomeicon}}
+            {{predictiondisplayvalue}}
+        </span>
+    </caption>
+{{/showpredictionheading}}
+
+<div class="insight-container" data-context-id="{{contextid}}" data-model-id="{{modelid}}">
+<table class="generaltable insights-list mt-3">
+    <thead>
+        <tr>
+            <th>{{#str}} fullname {{/str}}</th>
+            {{#contexts}}
+                <th>{{{.}}}</th>
+            {{/contexts}}
+        </tr>
+    </thead>
+    <tbody>
+        {{#allusers}}
+            <tr>
+            <td>{{{fullname}}}</td>
+            {{#predictions}}
+                {{#insights}}
+                        <td data-prediction-id="{{predictionid}}">
+                        {{#actions}}
+                            {{> core/action_menu}}
+                        {{/actions}}
+                        </td>
+                {{/insights}}
+                {{^insights}}
+                <td>--</td>
+                {{/insights}}
+            {{/predictions}}
+            </tr>
+        {{/allusers}}
+    </tbody>
+</table>
+</div>