1<?php 2 3use dokuwiki\Extension\Plugin; 4 5/** 6 * DokuWiki Plugin lms (Helper Component) 7 * 8 * @license GPL 2 http://www.gnu.org/licenses/gpl-2.0.html 9 * @author Andreas Gohr <dokuwiki@cosmocode.de> 10 */ 11class helper_plugin_lms extends Plugin 12{ 13 /** 14 * Return all lessons and info about the user's current completion status 15 * 16 * @param string|null $user Username, null for no user data 17 * @return array A list of lesson infos 18 */ 19 public function getLessons($user = null) 20 { 21 $cp = $this->getControlPage(); 22 if (!$cp) return []; 23 24 $lessons = array_fill_keys($this->parseControlPage($cp), 0); 25 if ($user !== null) { 26 $lessons = array_merge($lessons, $this->getUserLessons($user)); 27 } 28 29 return $lessons; 30 } 31 32 /** 33 * Find the nearest controlpage 34 * 35 * @return false|string 36 */ 37 public function getControlPage() 38 { 39 global $ID; 40 global $INFO; 41 42 $cp = $this->getConf('controlpage'); 43 $oldid = $ID; 44 $ID = $INFO['id']; 45 $cp = page_findnearest($cp, false); 46 $ID = $oldid; 47 return $cp; 48 } 49 50 /** 51 * @param string $id Page ID of the lesson 52 * @param bool $seen Mark as seen or unseen 53 * @param string $user Username 54 * @return bool 55 */ 56 public function markLesson($id, $user, $seen = true) 57 { 58 if ($user === null) return false; 59 60 $file = $this->getUserFile($user); 61 $line = time() . "\t" . $id . "\t" . ($seen ? 1 : 0) . "\n"; 62 return io_saveFile($file, $line, true); 63 } 64 65 /** 66 * Get the list of completed lessons for a user 67 * 68 * This skips all lessons that used to be seen but have been marked unseen later 69 * 70 * @param string $user 71 * @return array 72 */ 73 public function getUserLessons($user) 74 { 75 $file = $this->getUserFile($user); 76 if (!file_exists($file)) return []; 77 78 $lessons = []; 79 $lines = file($file); 80 foreach ($lines as $line) { 81 [$time, $id, $seen] = explode("\t", trim($line)); 82 83 // we use simple log files 84 if ($seen) { 85 $lessons[$id] = $time; 86 } elseif (isset($lessons[$id])) { 87 // an already seen lesson might have been marked unseen later 88 unset($lessons[$id]); 89 } 90 } 91 92 return $lessons; 93 } 94 95 /** 96 * Get Seen-Info of a single lesson 97 * 98 * @param string $id Page ID of the lesson 99 * @return int|false Either the lesson info or fals if given ID is not a lesson 100 */ 101 public function getLesson($id, $user) 102 { 103 $all = $this->getLessons($user); 104 return $all[$id] ?? false; 105 } 106 107 /** 108 * Get the next lesson relative to the given one 109 * 110 * @param string $id current lesson 111 * @param null|string $user When user is given, next unseen lesson is returned 112 * @return string 113 */ 114 public function getNextLesson($id, $user = null) 115 { 116 $all = $this->getLessons($user); 117 118 if (!isset($all[$id])) return false; // current page is not a lesson 119 120 $keys = array_keys($all); 121 $self = array_search($id, $keys); 122 $len = count($keys); 123 124 for ($i = $self + 1; $i < $len; $i++) { 125 if ($user !== null && $all[$keys[$i]] !== 0) { 126 continue; // next element has already been seen by user 127 } 128 return $keys[$i]; 129 } 130 131 // no more lessons 132 return false; 133 } 134 135 /** 136 * Get the previous lesson relative to the given one 137 * 138 * @param string $id current lesson 139 * @param null|string $user When user is given, previous unseen lesson is returned 140 * @return string 141 */ 142 public function getPrevLesson($id, $user = null) 143 { 144 $all = $this->getLessons($user); 145 146 if (!isset($all[$id])) return false; // current page is not a lesson 147 148 $keys = array_keys($all); 149 $self = array_search($id, $keys); 150 151 for ($i = $self - 1; $i >= 0; $i--) { 152 if ($user !== null && $all[$keys[$i]] !== 0) { 153 continue; // next element has already been seen by user 154 } 155 return $keys[$i]; 156 } 157 158 // no more lessons 159 return false; 160 } 161 162 163 /** 164 * Get the filename used for storing lesson completions 165 * 166 * @param string $user username 167 */ 168 protected function getUserFile($user) 169 { 170 global $conf; 171 172 // we're not using cache files but our own meta directory 173 $user = utf8_encodeFN($user); // make sure the user is clean for directories 174 return $conf['metadir'] . '_lms/' . $user . '.lms'; 175 } 176 177 public function getKnownUsers() 178 { 179 global $conf; 180 181 $lmsData = $conf['metadir'] . '_lms/'; 182 183 $s = scandir($lmsData); 184 185 $users = array_map(function ($file) { 186 if (!in_array($file, ['.', '..'])) { 187 return str_replace('.lms', '', $file); 188 } 189 }, $s); 190 191 return array_filter($users); 192 } 193 194 /** 195 * Get a list of links from the given control page 196 * 197 * @param string $cp The control page 198 * @return array 199 */ 200 protected function parseControlPage($cp) 201 { 202 $cpns = getNS($cp); 203 $exists = false; // ignored 204 $pages = []; 205 206 $instructions = p_cached_instructions(wikiFN($cp), false, $cp); 207 if ($instructions === null) return []; 208 209 foreach ($instructions as $instruction) { 210 if ($instruction[0] !== 'internallink') continue; 211 $link = $instruction[1][0]; 212 resolve_pageid($cpns, $link, $exists); 213 214 // Only pages below the control page's namespace are considered lessons 215 $ns = getNS($link); 216 $check = $cpns ? ":$cpns" : ''; 217 if (!preg_match("/^$check(:|$)/", ":$ns")) { 218 continue; 219 } 220 221 $pages[] = $link; 222 } 223 224 $pages = array_values(array_unique($pages)); 225 return $pages; 226 } 227} 228