diff --git a/backup/moodle2/backup_stepslib.php b/backup/moodle2/backup_stepslib.php
index 2d2552dbe4b..e98b92ec558 100644
--- a/backup/moodle2/backup_stepslib.php
+++ b/backup/moodle2/backup_stepslib.php
@@ -1284,7 +1284,6 @@ class backup_userscompletion_structure_step extends backup_structure_step {
     protected function define_structure() {
 
         // Define each element separated
-
         $completions = new backup_nested_element('completions');
 
         $completion = new backup_nested_element('completion', array('id'), array(
@@ -1302,8 +1301,22 @@ class backup_userscompletion_structure_step extends backup_structure_step {
 
         $completion->annotate_ids('user', 'userid');
 
-        // Return the root element (completions)
+        $completionviews = new backup_nested_element('completionviews');
+        $completionview = new backup_nested_element('completionview', ['id'], ['userid', 'timecreated']);
+
+        // Build the tree.
+        $completionviews->add_child($completionview);
+
+        // Define sources.
+        $completionview->set_source_table('course_modules_viewed', ['coursemoduleid' => backup::VAR_MODID]);
+
+        // Define id annotations.
+        $completionview->annotate_ids('user', 'userid');
+
+        $completions->add_child($completionviews);
+        // Return the root element (completions).
         return $completions;
+
     }
 }
 
diff --git a/backup/moodle2/restore_stepslib.php b/backup/moodle2/restore_stepslib.php
index 4d4cd316de6..8fb41b35232 100644
--- a/backup/moodle2/restore_stepslib.php
+++ b/backup/moodle2/restore_stepslib.php
@@ -4680,8 +4680,12 @@ class restore_userscompletion_structure_step extends restore_structure_step {
 
         $paths = array();
 
+        // Restore completion.
         $paths[] = new restore_path_element('completion', '/completions/completion');
 
+        // Restore completion view.
+        $paths[] = new restore_path_element('completionview', '/completions/completionviews/completionview');
+
         return $paths;
     }
 
@@ -4710,6 +4714,29 @@ class restore_userscompletion_structure_step extends restore_structure_step {
             // Normal entry where it doesn't exist already
             $DB->insert_record('course_modules_completion', $data);
         }
+
+        // Add viewed to course_modules_viewed.
+        if (isset($data->viewed) && $data->viewed) {
+            $dataview = clone($data);
+            unset($dataview->id);
+            unset($dataview->viewed);
+            $dataview->timecreated = $data->timemodified;
+            $DB->insert_record('course_modules_viewed', $dataview);
+        }
+    }
+
+    /**
+     * Process the completioinview data.
+     * @param array $data The data from the XML file.
+     */
+    protected function process_completionview(array $data) {
+        global $DB;
+
+        $data = (object)$data;
+        $data->coursemoduleid = $this->task->get_moduleid();
+        $data->userid = $this->get_mappingid('user', $data->userid);
+
+        $DB->insert_record('course_modules_viewed', $data);
     }
 }
 
diff --git a/backup/moodle2/tests/restore_stepslib_date_test.php b/backup/moodle2/tests/restore_stepslib_date_test.php
index cdbc491abf1..8ff878c55d3 100644
--- a/backup/moodle2/tests/restore_stepslib_date_test.php
+++ b/backup/moodle2/tests/restore_stepslib_date_test.php
@@ -314,6 +314,41 @@ class restore_stepslib_date_test extends \restore_date_testcase {
         $this->assertEquals($coursemodulecompletion->timemodified, $newcoursemodulecompletion->timemodified);
     }
 
+    /**
+     * Checking that the user completion of an activity relating to the view field does not change
+     * when doing a course restore.
+     * @covers ::backup_and_restore
+     */
+    public function test_usercompletion_view_restore() {
+        global $DB;
+        // More completion...
+        $course = $this->getDataGenerator()->create_course(['startdate' => time(), 'enablecompletion' => 1]);
+        $student = $this->getDataGenerator()->create_user();
+        $this->getDataGenerator()->enrol_user($student->id, $course->id, 'student');
+        $assign = $this->getDataGenerator()->create_module('assign', [
+            'course' => $course->id,
+            'completion' => COMPLETION_TRACKING_AUTOMATIC, // Show activity as complete when conditions are met.
+            'completionview' => 1
+        ]);
+        $cm = $DB->get_record('course_modules', ['course' => $course->id, 'instance' => $assign->id]);
+
+        // Mark the activity as completed.
+        $completion = new \completion_info($course);
+        $completion->set_module_viewed($cm, $student->id);
+
+        $coursemodulecompletion = $DB->get_record('course_modules_viewed', ['coursemoduleid' => $cm->id]);
+
+        // Back up and restore.
+        $newcourseid = $this->backup_and_restore($course);
+        $newcourse = get_course($newcourseid);
+
+        $assignid = $DB->get_field('assign', 'id', ['course' => $newcourseid]);
+        $cm = $DB->get_record('course_modules', ['course' => $newcourse->id, 'instance' => $assignid]);
+        $newcoursemodulecompletion = $DB->get_record('course_modules_viewed', ['coursemoduleid' => $cm->id]);
+
+        $this->assertEquals($coursemodulecompletion->timecreated, $newcoursemodulecompletion->timecreated);
+    }
+
     /**
      * Ensuring that the timemodified field of the question attempt steps table does not change when
      * a course restore is done.
diff --git a/completion/classes/privacy/provider.php b/completion/classes/privacy/provider.php
index fb3a4f9a870..93a7a692c2e 100644
--- a/completion/classes/privacy/provider.php
+++ b/completion/classes/privacy/provider.php
@@ -66,10 +66,14 @@ class provider implements
                 'userid' => 'privacy:metadata:userid',
                 'coursemoduleid' => 'privacy:metadata:coursemoduleid',
                 'completionstate' => 'privacy:metadata:completionstate',
-                'viewed' => 'privacy:metadata:viewed',
                 'overrideby' => 'privacy:metadata:overrideby',
                 'timemodified' => 'privacy:metadata:timemodified'
             ], 'privacy:metadata:coursemodulesummary');
+        $collection->add_database_table('course_modules_viewed', [
+            'userid' => 'privacy:metadata:userid',
+            'coursemoduleid' => 'privacy:metadata:coursemoduleid',
+            'timecreated' => 'privacy:metadata:timecreated',
+        ], 'privacy:metadata:coursemodulesummary');
         $collection->add_database_table('course_completion_crit_compl', [
                 'userid' => 'privacy:metadata:userid',
                 'course' => 'privacy:metadata:course',
@@ -91,16 +95,18 @@ class provider implements
     public static function get_course_completion_join_sql(int $userid, string $prefix, string $joinfield) : array {
         $cccalias = "{$prefix}_ccc"; // Course completion criteria.
         $cmcalias = "{$prefix}_cmc"; // Course modules completion.
+        $cmvalias = "{$prefix}_cmv"; // Course modules viewed.
         $ccccalias = "{$prefix}_cccc"; // Course completion criteria completion.
 
         $join = "JOIN {course_completion_criteria} {$cccalias} ON {$joinfield} = {$cccalias}.course
              LEFT JOIN {course_modules_completion} {$cmcalias} ON {$cccalias}.moduleinstance = {$cmcalias}.coursemoduleid
                         AND {$cmcalias}.userid = :{$prefix}_moduleuserid
+             LEFT JOIN {course_modules_viewed} {$cmvalias} ON {$cccalias}.moduleinstance = {$cmvalias}.coursemoduleid
+                        AND {$cmvalias}.userid = :{$prefix}_moduleuserid2
              LEFT JOIN {course_completion_crit_compl} {$ccccalias} ON {$ccccalias}.criteriaid = {$cccalias}.id
                         AND {$ccccalias}.userid = :{$prefix}_courseuserid";
-        $where = "{$cmcalias}.id IS NOT NULL OR {$ccccalias}.id IS NOT NULL";
-        $params = ["{$prefix}_moduleuserid" => $userid, "{$prefix}_courseuserid" => $userid];
-
+        $where = "{$cmcalias}.id IS NOT NULL OR {$ccccalias}.id IS NOT NULL OR {$cmvalias}.id IS NOT NULL";
+        $params = ["{$prefix}_moduleuserid" => $userid, "{$prefix}_moduleuserid2" => $userid, "{$prefix}_courseuserid" => $userid];
         return [$join, $where, $params];
     }
 
@@ -126,6 +132,14 @@ class provider implements
 
         $userlist->add_from_sql('userid', $sql, $params);
 
+        $sql = "SELECT cmv.userid
+                  FROM {course} c
+                  JOIN {course_completion_criteria} ccc ON ccc.course = c.id
+                  JOIN {course_modules_viewed} cmv ON cmv.coursemoduleid = ccc.moduleinstance
+                 WHERE c.id = :courseid";
+
+        $userlist->add_from_sql('userid', $sql, $params);
+
         $sql = "SELECT ccc_compl.userid
                  FROM {course} c
                  JOIN {course_completion_criteria} ccc ON ccc.course = c.id
@@ -231,6 +245,7 @@ class provider implements
         if (isset($courseid)) {
 
             $usersql = isset($user) ? 'AND cmc.userid = :userid' : '';
+            $usercmvsql = isset($user) ? 'AND cmv.userid = :userid' : '';
             $params = isset($user) ? ['course' => $courseid, 'userid' => $user->id] : ['course' => $courseid];
 
             // Find records relating to course modules.
@@ -245,6 +260,19 @@ class provider implements
                 $deletesql = 'id ' . $deletesql;
                 $DB->delete_records_select('course_modules_completion', $deletesql, $deleteparams);
             }
+            // Find records relating to course modules completion viewed.
+            $sql = "SELECT cmv.id
+                      FROM {course_completion_criteria} ccc
+                      JOIN {course_modules_viewed} cmv ON ccc.moduleinstance = cmv.coursemoduleid
+                     WHERE ccc.course = :course $usercmvsql";
+            $recordids = $DB->get_records_sql($sql, $params);
+            $ids = array_keys($recordids);
+            if (!empty($ids)) {
+                list($deletesql, $deleteparams) = $DB->get_in_or_equal($ids);
+                $deletesql = 'id ' . $deletesql;
+                $DB->delete_records_select('course_modules_viewed', $deletesql, $deleteparams);
+            }
+
             $DB->delete_records('course_completion_crit_compl', $params);
             $DB->delete_records('course_completions', $params);
         }
@@ -273,6 +301,7 @@ class provider implements
             // Only delete the record for course modules completion.
             $sql = "coursemoduleid = :coursemoduleid AND userid {$useridsql}";
             $DB->delete_records_select('course_modules_completion', $sql, $params);
+            $DB->delete_records_select('course_modules_viewed', $sql, $params);
             return;
         }
 
@@ -292,6 +321,19 @@ class provider implements
                 $DB->delete_records_select('course_modules_completion', $deletesql, $deleteparams);
             }
 
+            // Find records relating to course modules.
+            $sql = "SELECT cmv.id
+                      FROM {course_completion_criteria} ccc
+                      JOIN {course_modules_viewed} cmv ON ccc.moduleinstance = cmv.coursemoduleid
+                     WHERE ccc.course = :course AND cmv.userid {$useridsql}";
+            $recordids = $DB->get_records_sql($sql, $params);
+            $ids = array_keys($recordids);
+            if (!empty($ids)) {
+                list($deletesql, $deleteparams) = $DB->get_in_or_equal($ids);
+                $deletesql = 'id ' . $deletesql;
+                $DB->delete_records_select('course_modules_viewed', $deletesql, $deleteparams);
+            }
+
             $sql = "course = :course AND userid {$useridsql}";
             $DB->delete_records_select('course_completion_crit_compl', $sql, $params);
             $DB->delete_records_select('course_completions', $sql, $params);
diff --git a/completion/tests/behat/backup_restore_completion.feature b/completion/tests/behat/backup_restore_completion.feature
new file mode 100644
index 00000000000..c3bbd98ec21
--- /dev/null
+++ b/completion/tests/behat/backup_restore_completion.feature
@@ -0,0 +1,57 @@
+@core @core_completion
+Feature: Backup and restore the activity with the completion
+
+  Background:
+    Given the following "courses" exist:
+      | fullname | shortname | category | enablecompletion |
+      | Course 1 | C1        | 0        | 1                |
+    And the following "users" exist:
+      | username | firstname | lastname | email                |
+      | student1 | Student   | First    | student1@example.com |
+      | student2 | Student   | Second   | student2@example.com |
+    And the following "course enrolments" exist:
+      | user | course | role           |
+      | student1 | C1 | student        |
+      | student2 | C1 | student        |
+    And the following "activity" exists:
+      | activity                            | assign                  |
+      | course                              | C1                      |
+      | idnumber                            | a1                      |
+      | name                                | Test assignment name    |
+      | intro                               | Submit your online text |
+      | assignsubmission_onlinetext_enabled | 1                       |
+      | assignsubmission_file_enabled       | 0                       |
+      | completion                          | 2                       |
+      | completionview                      | 1                       |
+      | completionusegrade                  | 1                       |
+      | gradepass                           | 50                      |
+      | completionpassgrade                 | 1                       |
+    And I am on the "Test assignment name" "assign activity" page logged in as student1
+    And I log out
+
+  @javascript @_file_upload
+  Scenario: Restore the legacy assignment with completion condition.
+    Given I am on the "Course 1" "restore" page logged in as "admin"
+    And I press "Manage backup files"
+    And I upload "completion/tests/fixtures/legacy_course_completion.mbz" file to "Files" filemanager
+    And I press "Save changes"
+    And I restore "legacy_course_completion.mbz" backup into a new course using this options:
+      | Schema | Course name       | Course 2 |
+      | Schema | Course short name | C2       |
+    When I am on the "Course 2" course page logged in as student1
+    Then the "View" completion condition of "Test assignment name" is displayed as "done"
+    And I am on the "Course 2" course page logged in as student2
+    And the "View" completion condition of "Test assignment name" is displayed as "todo"
+
+  @javascript @_file_upload
+  Scenario: Backup and restore the assignment with the viewed and not-viewed completion condition
+    Given I am on the "Course 1" course page logged in as admin
+    And I backup "Course 1" course using this options:
+      | Confirmation | Filename | test_backup.mbz |
+    And I restore "test_backup.mbz" backup into a new course using this options:
+      | Schema | Course name       | Course 2 |
+      | Schema | Course short name | C2       |
+    When I am on the "Course 2" course page logged in as student1
+    Then the "View" completion condition of "Test assignment name" is displayed as "done"
+    And I am on the "Course 2" course page logged in as student2
+    And the "View" completion condition of "Test assignment name" is displayed as "todo"
diff --git a/completion/tests/behat/enable_completion_on_view_and_grade.feature b/completion/tests/behat/enable_completion_on_view_and_grade.feature
index 69fe918b26a..f762c49dd91 100644
--- a/completion/tests/behat/enable_completion_on_view_and_grade.feature
+++ b/completion/tests/behat/enable_completion_on_view_and_grade.feature
@@ -67,3 +67,25 @@ Feature: Students will be marked as completed and pass/fail
     And the "View" completion condition of "Test assignment name" is displayed as "todo"
     And the "Receive a grade" completion condition of "Test assignment name" is displayed as "done"
     And the "Receive a passing grade" completion condition of "Test assignment name" is displayed as "failed"
+
+  @javascript
+  Scenario: Keep current view completion condition when the teacher does the action 'Unlock completion settings'.
+    Given I am on the "Course 1" course page logged in as teacher1
+    And I navigate to "View > Grader report" in the course gradebook
+    And I turn editing mode on
+    And I give the grade "21" to the user "Student First" for the grade item "Test assignment name"
+    And I give the grade "50" to the user "Student Second" for the grade item "Test assignment name"
+    And I press "Save changes"
+    And I am on the "Test assignment name" "assign activity" page logged in as teacher1
+    And I navigate to "Settings" in current page administration
+    And I expand all fieldsets
+    And I press "Unlock completion settings"
+    And I expand all fieldsets
+    And I should see "Completion options unlocked"
+    And I click on "Save and display" "button"
+    And I log out
+    When I am on the "Course 1" course page logged in as student1
+    Then the "View" completion condition of "Test assignment name" is displayed as "done"
+    And I log out
+    When I am on the "Course 1" course page logged in as student2
+    Then the "View" completion condition of "Test assignment name" is displayed as "done"
diff --git a/completion/tests/fixtures/legacy_course_completion.mbz b/completion/tests/fixtures/legacy_course_completion.mbz
new file mode 100644
index 00000000000..4d84d4e8fad
Binary files /dev/null and b/completion/tests/fixtures/legacy_course_completion.mbz differ
diff --git a/course/lib.php b/course/lib.php
index 4298b24caab..b6745221c70 100644
--- a/course/lib.php
+++ b/course/lib.php
@@ -933,6 +933,7 @@ function course_delete_module($cmid, $async = false) {
     // features are not turned on, in case they were turned on previously (these will be
     // very quick on an empty table).
     $DB->delete_records('course_modules_completion', array('coursemoduleid' => $cm->id));
+    $DB->delete_records('course_modules_viewed', ['coursemoduleid' => $cm->id]);
     $DB->delete_records('course_completion_criteria', array('moduleinstance' => $cm->id,
                                                             'course' => $cm->course,
                                                             'criteriatype' => COMPLETION_CRITERIA_TYPE_ACTIVITY));
diff --git a/lang/en/completion.php b/lang/en/completion.php
index 041a1bd372a..41aec434b90 100644
--- a/lang/en/completion.php
+++ b/lang/en/completion.php
@@ -208,6 +208,7 @@ $string['privacy:metadata:timecompleted'] = 'The time that the course was comple
 $string['privacy:metadata:timeenrolled'] = 'The time that the user was enrolled in the course';
 $string['privacy:metadata:timemodified'] = 'The time that the activity completion was modified';
 $string['privacy:metadata:timestarted'] = 'The time the course was started.';
+$string['privacy:metadata:timecreated'] = 'The time that the activity completion was created';
 $string['privacy:metadata:viewed'] = 'If the activity was viewed';
 $string['privacy:metadata:userid'] = 'The user ID of the person with course and activity completion data';
 $string['privacy:metadata:unenroled'] = 'If the user has been unenrolled from the course';
diff --git a/lib/completionlib.php b/lib/completionlib.php
index eb8057da8dc..94cd4b0c924 100644
--- a/lib/completionlib.php
+++ b/lib/completionlib.php
@@ -1091,13 +1091,16 @@ class completion_info {
         // If we're not caching the completion data, then just fetch the completion data for the user in this course module.
         if ($usecache && $wholecourse) {
             // Get whole course data for cache.
-            $alldatabycmc = $DB->get_records_sql("SELECT cm.id AS cmid, cmc.*
+            $alldatabycmc = $DB->get_records_sql("SELECT cm.id AS cmid, cmc.*,
+                                                         CASE WHEN cmv.id IS NULL THEN 0 ELSE 1 END AS viewed
                                                     FROM {course_modules} cm
-                                               LEFT JOIN {course_modules_completion} cmc ON cmc.coursemoduleid = cm.id
-                                                         AND cmc.userid = ?
+                                               LEFT JOIN {course_modules_completion} cmc
+                                                         ON cmc.coursemoduleid = cm.id  AND cmc.userid = ?
+                                               LEFT JOIN {course_modules_viewed} cmv
+                                                         ON cmv.coursemoduleid = cm.id  AND cmv.userid = ?
                                               INNER JOIN {modules} m ON m.id = cm.module
-                                                   WHERE m.visible = 1 AND cm.course = ?", [$userid, $this->course->id]);
-
+                                                   WHERE m.visible = 1 AND cm.course = ?",
+                [$userid, $userid, $this->course->id]);
             $cminfos = get_fast_modinfo($cm->course, $userid)->get_cms();
 
             // Reindex by course module id.
@@ -1125,14 +1128,7 @@ class completion_info {
             $data = $cacheddata[$cminfo->id];
         } else {
             // Get single record
-            $data = $DB->get_record('course_modules_completion', array('coursemoduleid' => $cminfo->id, 'userid' => $userid));
-            if ($data) {
-                $data = (array)$data;
-            } else {
-                // Row not present counts as 'not complete'.
-                $data = $defaultdata;
-            }
-
+            $data = $this->get_completion_data($cminfo->id, $userid, $defaultdata);
             // Put in cache.
             $cacheddata[$cminfo->id] = $data;
         }
@@ -1188,10 +1184,8 @@ class completion_info {
         // If view is required, try and fetch from the db. In some cases, cache can be invalid.
         if ($cm->completionview == COMPLETION_VIEW_REQUIRED) {
             $data['viewed'] = COMPLETION_INCOMPLETE;
-            $record = $DB->get_record('course_modules_completion', array('coursemoduleid' => $cm->id, 'userid' => $userid));
-            if ($record) {
-                $data['viewed'] = ($record->viewed == COMPLETION_VIEWED ? COMPLETION_COMPLETE : COMPLETION_INCOMPLETE);
-            }
+            $record = $DB->record_exists('course_modules_viewed', ['coursemoduleid' => $cm->id, 'userid' => $userid]);
+            $data['viewed'] = $record ? COMPLETION_COMPLETE : COMPLETION_INCOMPLETE;
         }
 
         return $data;
@@ -1265,6 +1259,19 @@ class completion_info {
             // Has real (nonzero) id meaning that a database row exists, update
             $DB->update_record('course_modules_completion', $data);
         }
+        $dataview = new stdClass();
+        $dataview->coursemoduleid = $data->coursemoduleid;
+        $dataview->userid = $data->userid;
+        $dataview->id = $DB->get_field('course_modules_viewed', 'id',
+            ['coursemoduleid' => $dataview->coursemoduleid, 'userid' => $dataview->userid]);
+        if (!$data->viewed && $dataview->id) {
+            $DB->delete_records('course_modules_viewed', ['id' => $dataview->id]);
+        }
+
+        if (!$dataview->id && $data->viewed) {
+            $dataview->timecreated = time();
+            $dataview->id = $DB->insert_record('course_modules_viewed', $dataview);
+        }
         $transaction->allow_commit();
 
         $cmcontext = context_module::instance($data->coursemoduleid);
@@ -1602,6 +1609,49 @@ class completion_info {
         throw new moodle_exception('err_system','completion',
             $CFG->wwwroot.'/course/view.php?id='.$this->course->id,null,$error);
     }
+
+    /**
+     * Get completion data include viewed field.
+     *
+     * @param int $coursemoduleid The course module id.
+     * @param int $userid The User ID.
+     * @param array $defaultdata Default data completion.
+     * @return array Data completion retrieved.
+     */
+    public function get_completion_data(int $coursemoduleid, int $userid, array $defaultdata): array {
+        global $DB;
+
+        // MySQL doesn't support FULL JOIN syntax, so we use UNION in the below SQL to help MySQL.
+        $sql = "SELECT cmc.*, cmv.coursemoduleid as cmvcoursemoduleid, cmv.userid as cmvuserid
+                  FROM {course_modules_completion} cmc
+             LEFT JOIN {course_modules_viewed} cmv ON cmc.coursemoduleid = cmv.coursemoduleid AND cmc.userid = cmv.userid
+                 WHERE cmc.coursemoduleid = :cmccoursemoduleid AND cmc.userid = :cmcuserid
+                UNION
+                SELECT cmc2.*, cmv2.coursemoduleid as cmvcoursemoduleid, cmv2.userid as cmvuserid
+                  FROM {course_modules_completion} cmc2
+            RIGHT JOIN {course_modules_viewed} cmv2
+                    ON cmc2.coursemoduleid = cmv2.coursemoduleid AND cmc2.userid = cmv2.userid
+                 WHERE cmv2.coursemoduleid = :cmvcoursemoduleid AND cmv2.userid = :cmvuserid";
+
+        $data = $DB->get_record_sql($sql, ['cmccoursemoduleid' => $coursemoduleid, 'cmcuserid' => $userid,
+            'cmvcoursemoduleid' => $coursemoduleid, 'cmvuserid' => $userid]);
+
+        if (!$data) {
+            $data = $defaultdata;
+        } else {
+            if (empty($data->coursemoduleid) && empty($data->userid)) {
+                $data->coursemoduleid = $data->cmvcoursemoduleid;
+                $data->userid = $data->cmvuserid;
+            }
+            unset($data->cmvcoursemoduleid);
+            unset($data->cmvuserid);
+
+            // When reseting all state in the completion, we need to keep current view state.
+            $data->viewed = 1;
+        }
+
+        return (array)$data;
+    }
 }
 
 /**
diff --git a/lib/db/install.xml b/lib/db/install.xml
index 12e63fa531c..8dd7a7324d2 100644
--- a/lib/db/install.xml
+++ b/lib/db/install.xml
@@ -1,5 +1,5 @@
 <?xml version="1.0" encoding="UTF-8" ?>
-<XMLDB PATH="lib/db" VERSION="20220825" COMMENT="XMLDB file for core Moodle tables"
+<XMLDB PATH="lib/db" VERSION="20221013" COMMENT="XMLDB file for core Moodle tables"
     xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
     xsi:noNamespaceSchemaLocation="../../lib/xmldb/xmldb.xsd"
 >
@@ -162,7 +162,7 @@
         <FIELD NAME="course" TYPE="int" LENGTH="10" NOTNULL="true" DEFAULT="0" SEQUENCE="false"/>
         <FIELD NAME="criteriatype" TYPE="int" LENGTH="10" NOTNULL="true" DEFAULT="0" SEQUENCE="false" COMMENT="Type of criteria"/>
         <FIELD NAME="module" TYPE="char" LENGTH="100" NOTNULL="false" SEQUENCE="false" COMMENT="Type of module (if using module criteria type)"/>
-        <FIELD NAME="moduleinstance" TYPE="int" LENGTH="10" NOTNULL="false" SEQUENCE="false" COMMENT="Module instance id (if using module criteria type)"/>
+        <FIELD NAME="moduleinstance" TYPE="int" LENGTH="10" NOTNULL="false" SEQUENCE="false" COMMENT="Course module id (if using module criteria type)"/>
         <FIELD NAME="courseinstance" TYPE="int" LENGTH="10" NOTNULL="false" SEQUENCE="false" COMMENT="Course instance id (if using course criteria type)"/>
         <FIELD NAME="enrolperiod" TYPE="int" LENGTH="10" NOTNULL="false" SEQUENCE="false" COMMENT="Number of days after enrolment the course is completed (if using enrolperiod criteria type)"/>
         <FIELD NAME="timeend" TYPE="int" LENGTH="10" NOTNULL="false" SEQUENCE="false" COMMENT="Timestamp of the date for course completion (if using date criteria type)"/>
@@ -331,7 +331,6 @@
         <FIELD NAME="coursemoduleid" TYPE="int" LENGTH="10" NOTNULL="true" SEQUENCE="false" COMMENT="Activity that has been completed (or not)."/>
         <FIELD NAME="userid" TYPE="int" LENGTH="10" NOTNULL="true" SEQUENCE="false" COMMENT="ID of user who has (or hasn't) completed the activity."/>
         <FIELD NAME="completionstate" TYPE="int" LENGTH="1" NOTNULL="true" SEQUENCE="false" COMMENT="Whether or not the user has completed the activity. Available states: 0 = not completed [if there's no row in this table, that also counts as 0] 1 = completed 2 = completed, show passed 3 = completed, show failed"/>
-        <FIELD NAME="viewed" TYPE="int" LENGTH="1" NOTNULL="false" SEQUENCE="false" COMMENT="Tracks whether or not this activity has been viewed. NULL = we are not tracking viewed for this activity 0 = not viewed 1 = viewed"/>
         <FIELD NAME="overrideby" TYPE="int" LENGTH="10" NOTNULL="false" SEQUENCE="false" COMMENT="Tracks whether this completion state has been set manually to override a previous state."/>
         <FIELD NAME="timemodified" TYPE="int" LENGTH="10" NOTNULL="true" SEQUENCE="false" COMMENT="Time at which the completion state last changed."/>
       </FIELDS>
@@ -4745,5 +4744,20 @@
         <INDEX NAME="adminpresetapplyid" UNIQUE="false" FIELDS="adminpresetapplyid"/>
       </INDEXES>
     </TABLE>
+    <TABLE NAME="course_modules_viewed" COMMENT="Tracks the completion viewed (viewed with cmid/userid and otherwise no row) of each user on each activity.">
+      <FIELDS>
+        <FIELD NAME="id" TYPE="int" LENGTH="10" NOTNULL="true" SEQUENCE="true"/>
+        <FIELD NAME="coursemoduleid" TYPE="int" LENGTH="10" NOTNULL="true" DEFAULT="0" SEQUENCE="false" COMMENT="Activity that has been viewed (or not)."/>
+        <FIELD NAME="userid" TYPE="int" LENGTH="10" NOTNULL="true" DEFAULT="0" SEQUENCE="false" COMMENT="ID of user who has (or hasn't) viewed the activity."/>
+        <FIELD NAME="timecreated" TYPE="int" LENGTH="10" NOTNULL="true" DEFAULT="0" SEQUENCE="false" COMMENT="Time at which the completion viewed created."/>
+      </FIELDS>
+      <KEYS>
+        <KEY NAME="primary" TYPE="primary" FIELDS="id"/>
+      </KEYS>
+      <INDEXES>
+        <INDEX NAME="coursemoduleid" UNIQUE="false" FIELDS="coursemoduleid" COMMENT="For quick access via course-module (e.g. when displaying course module settings page and we need to determine whether anyone has completed it)."/>
+        <INDEX NAME="userid-coursemoduleid" UNIQUE="true" FIELDS="userid, coursemoduleid"/>
+      </INDEXES>
+    </TABLE>
   </TABLES>
 </XMLDB>
diff --git a/lib/db/upgrade.php b/lib/db/upgrade.php
index 97776ccba2f..9cf4044c21e 100644
--- a/lib/db/upgrade.php
+++ b/lib/db/upgrade.php
@@ -2925,5 +2925,60 @@ privatefiles,moodle|/user/files.php';
         upgrade_main_savepoint(true, 2022092200.01);
     }
 
+    if ($oldversion < 2022101300.00) {
+        // Define table to store completion viewed.
+        $table = new xmldb_table('course_modules_viewed');
+
+        // Adding fields to table course_modules_viewed.
+        $table->add_field('id', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, XMLDB_SEQUENCE, null);
+        $table->add_field('coursemoduleid', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, null, 'id');
+        $table->add_field('userid', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, null, 'coursemoduleid');
+        $table->add_field('timecreated', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, '0', 'userid');
+
+        // Adding keys to table course_modules_viewed.
+        $table->add_key('primary', XMLDB_KEY_PRIMARY, ['id']);
+
+        // Adding indexes to table course_modules_viewed.
+        $table->add_index('coursemoduleid', XMLDB_INDEX_NOTUNIQUE, ['coursemoduleid']);
+        $table->add_index('userid-coursemoduleid', XMLDB_INDEX_UNIQUE, ['userid', 'coursemoduleid']);
+
+        if (!$dbman->table_exists($table)) {
+            $dbman->create_table($table);
+        }
+
+        // Main savepoint reached.
+        upgrade_main_savepoint(true, 2022101300.00);
+    }
+
+    if ($oldversion < 2022101300.01) {
+        // Add legacy data to the new table.
+        $transaction = $DB->start_delegated_transaction();
+        upgrade_set_timeout(3600);
+        $sql = "INSERT INTO {course_modules_viewed}
+                            (userid, coursemoduleid, timecreated)
+                     SELECT userid, coursemoduleid, timemodified
+                       FROM {course_modules_completion}
+                      WHERE viewed = 1";
+        $DB->execute($sql);
+        $transaction->allow_commit();
+
+        // Main savepoint reached.
+        upgrade_main_savepoint(true, 2022101300.01);
+    }
+
+    if ($oldversion < 2022101300.02) {
+        // Define field viewed to be dropped from course_modules_completion.
+        $table = new xmldb_table('course_modules_completion');
+        $field = new xmldb_field('viewed');
+
+        // Conditionally launch drop field viewed.
+        if ($dbman->field_exists($table, $field)) {
+            $dbman->drop_field($table, $field);
+        }
+
+        // Main savepoint reached.
+        upgrade_main_savepoint(true, 2022101300.02);
+    }
+
     return true;
 }
diff --git a/lib/moodlelib.php b/lib/moodlelib.php
index 5cc89ea432c..a1369b34d17 100644
--- a/lib/moodlelib.php
+++ b/lib/moodlelib.php
@@ -5208,6 +5208,7 @@ function remove_course_contents($courseid, $showfeedback = true, array $options
                         // Delete cm and its context - orphaned contexts are purged in cron in case of any race condition.
                         context_helper::delete_instance(CONTEXT_MODULE, $cm->id);
                         $DB->delete_records('course_modules_completion', ['coursemoduleid' => $cm->id]);
+                        $DB->delete_records('course_modules_viewed', ['coursemoduleid' => $cm->id]);
                         $DB->delete_records('course_modules', array('id' => $cm->id));
                         rebuild_course_cache($cm->course, true);
                     }
@@ -5231,6 +5232,8 @@ function remove_course_contents($courseid, $showfeedback = true, array $options
     // features are not enabled now, in case they were enabled previously.
     $DB->delete_records_subquery('course_modules_completion', 'coursemoduleid', 'id',
             'SELECT id from {course_modules} WHERE course = ?', [$courseid]);
+    $DB->delete_records_subquery('course_modules_viewed', 'coursemoduleid', 'id',
+        'SELECT id from {course_modules} WHERE course = ?', [$courseid]);
 
     // Remove course-module data that has not been removed in modules' _delete_instance callbacks.
     $cms = $DB->get_records('course_modules', array('course' => $course->id));
diff --git a/lib/tests/completionlib_test.php b/lib/tests/completionlib_test.php
index ecd75696b2f..e34f69dc8d9 100644
--- a/lib/tests/completionlib_test.php
+++ b/lib/tests/completionlib_test.php
@@ -767,11 +767,16 @@ class completionlib_test extends advanced_testcase {
                 'coursemoduleid' => $cm->id,
                 'userid' => $user->id,
                 'completionstate' => $completion,
-                'viewed' => 0,
                 'overrideby' => null,
                 'timemodified' => 0,
             ];
+            $cmcompletionviewrecord = (object)[
+                'coursemoduleid' => $cm->id,
+                'userid' => $user->id,
+                'timecreated' => 0,
+            ];
             $DB->insert_record('course_modules_completion', $cmcompletionrecord);
+            $DB->insert_record('course_modules_viewed', $cmcompletionviewrecord);
         }
 
         // Whether we expect for the returned completion data to be stored in the cache.
@@ -832,11 +837,16 @@ class completionlib_test extends advanced_testcase {
             'coursemoduleid' => $cm->id,
             'userid' => $this->user->id,
             'completionstate' => COMPLETION_NOT_VIEWED,
-            'viewed' => 0,
             'overrideby' => null,
             'timemodified' => 0,
         ];
+        $cmcompletionviewrecord = (object)[
+            'coursemoduleid' => $cm->id,
+            'userid' => $this->user->id,
+            'timecreated' => 0,
+        ];
         $DB->insert_record('course_modules_completion', $cmcompletionrecord);
+        $DB->insert_record('course_modules_viewed', $cmcompletionviewrecord);
 
         // Mock other completion data.
         $completioninfo = new completion_info($this->course);
@@ -856,7 +866,6 @@ class completionlib_test extends advanced_testcase {
 
             $this->assertEquals($testcm->id, $result->coursemoduleid);
             $this->assertEquals($this->user->id, $result->userid);
-            $this->assertEquals(0, $result->viewed);
 
             $results[$testcm->id] = $result;
         }
@@ -872,6 +881,59 @@ class completionlib_test extends advanced_testcase {
         }
     }
 
+    /**
+     * Tests for get_completion_data().
+     *
+     * @covers ::get_completion_data
+     */
+    public function test_get_completion_data() {
+        $this->setup_data();
+        $choicegenerator = $this->getDataGenerator()->get_plugin_generator('mod_choice');
+        $choice = $choicegenerator->create_instance([
+            'course' => $this->course->id,
+            'completion' => COMPLETION_TRACKING_AUTOMATIC,
+            'completionview' => true,
+            'completionsubmit' => true,
+        ]);
+        $cm = get_coursemodule_from_instance('choice', $choice->id);
+
+        // Mock other completion data.
+        $completioninfo = new completion_info($this->course);
+        // Default data to return when no completion data is found.
+        $defaultdata = [
+            'id' => 0,
+            'coursemoduleid' => $cm->id,
+            'userid' => $this->user->id,
+            'completionstate' => 0,
+            'viewed' => 0,
+            'overrideby' => null,
+            'timemodified' => 0,
+        ];
+
+        $completiondatabeforeview = $completioninfo->get_completion_data($cm->id, $this->user->id, $defaultdata);
+        $this->assertTrue(array_key_exists('viewed', $completiondatabeforeview));
+        $this->assertTrue(array_key_exists('coursemoduleid', $completiondatabeforeview));
+        $this->assertEquals(0, $completiondatabeforeview['viewed']);
+        $this->assertEquals($cm->id, $completiondatabeforeview['coursemoduleid']);
+
+        // Set viewed.
+        $completioninfo->set_module_viewed($cm, $this->user->id);
+
+        $completiondata = $completioninfo->get_completion_data($cm->id, $this->user->id, $defaultdata);
+        $this->assertTrue(array_key_exists('viewed', $completiondata));
+        $this->assertTrue(array_key_exists('coursemoduleid', $completiondata));
+        $this->assertEquals(1, $completiondata['viewed']);
+        $this->assertEquals($cm->id, $completiondatabeforeview['coursemoduleid']);
+
+        $completioninfo->reset_all_state($cm);
+
+        $completiondataafterreset = $completioninfo->get_completion_data($cm->id, $this->user->id, $defaultdata);
+        $this->assertTrue(array_key_exists('viewed', $completiondataafterreset));
+        $this->assertTrue(array_key_exists('coursemoduleid', $completiondataafterreset));
+        $this->assertEquals(1, $completiondataafterreset['viewed']);
+        $this->assertEquals($cm->id, $completiondatabeforeview['coursemoduleid']);
+    }
+
     /**
      * Tests for completion_info::get_other_cm_completion_data().
      *
diff --git a/version.php b/version.php
index 38b6beea2d2..8dbbc34e95e 100644
--- a/version.php
+++ b/version.php
@@ -29,7 +29,7 @@
 
 defined('MOODLE_INTERNAL') || die();
 
-$version  = 2022101100.00;              // YYYYMMDD      = weekly release date of this DEV branch.
+$version  = 2022101400.00;              // YYYYMMDD      = weekly release date of this DEV branch.
                                         //         RR    = release increments - 00 in DEV branches.
                                         //           .XX = incremental changes.
 $release  = '4.1dev+ (Build: 20221011)'; // Human-friendly version name