1 <?php 2 // This file is part of Moodle - http://moodle.org/ 3 // 4 // Moodle is free software: you can redistribute it and/or modify 5 // it under the terms of the GNU General Public License as published by 6 // the Free Software Foundation, either version 3 of the License, or 7 // (at your option) any later version. 8 // 9 // Moodle is distributed in the hope that it will be useful, 10 // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 // GNU General Public License for more details. 13 // 14 // You should have received a copy of the GNU General Public License 15 // along with Moodle. If not, see <http://www.gnu.org/licenses/>. 16 17 namespace core\progress; 18 19 defined('MOODLE_INTERNAL') || die(); 20 21 /** 22 * Base class for handling progress information. 23 * 24 * Subclasses should generally override the {@link current_progress} function which 25 * summarises all progress information. 26 * 27 * @package core_progress 28 * @copyright 2013 The Open University 29 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 30 */ 31 abstract class base { 32 /** 33 * @var int Constant indicating that the number of progress calls is unknown. 34 */ 35 const INDETERMINATE = -1; 36 37 /** 38 * This value is set rather high to ensure there are no regressions from 39 * previous behaviour. For testing, it may be useful to set the 40 * frontendservertimeout config option to a lower value, such as 180 41 * seconds (default for some commercial products). 42 * 43 * @var int The number of seconds that can pass without {@link progress()} calls. 44 */ 45 const TIME_LIMIT_WITHOUT_PROGRESS = 3600; 46 47 /** 48 * @var int Time of last progress call. 49 */ 50 protected $lastprogresstime; 51 52 /** 53 * @var int Number of progress calls (restricted to ~ 1/second). 54 */ 55 protected $count; 56 57 /** 58 * @var array Array of progress descriptions for each stack level. 59 */ 60 protected $descriptions = array(); 61 62 /** 63 * @var array Array of maximum progress values for each stack level. 64 */ 65 protected $maxes = array(); 66 67 /** 68 * @var array Array of current progress values. 69 */ 70 protected $currents = array(); 71 72 /** 73 * @var int[] Array of counts within parent progress entry (ignored for first) 74 */ 75 protected $parentcounts = array(); 76 77 /** 78 * Marks the start of an operation that will display progress. 79 * 80 * This can be called multiple times for nested progress sections. It must 81 * be paired with calls to end_progress. 82 * 83 * The progress maximum may be {@link self::INDETERMINATE} if the current operation has 84 * an unknown number of steps. (This is default.) 85 * 86 * Calling this function will always result in a new display, so this 87 * should not be called exceedingly frequently. 88 * 89 * When it is complete by calling {@link end_progress()}, each {@link start_progress} section 90 * automatically adds progress to its parent, as defined by $parentcount. 91 * 92 * @param string $description Description to display 93 * @param int $max Maximum value of progress for this section 94 * @param int $parentcount How many progress points this section counts for 95 * @throws \coding_exception If max is invalid 96 */ 97 public function start_progress($description, $max = self::INDETERMINATE, 98 $parentcount = 1) { 99 if ($max != self::INDETERMINATE && $max < 0) { 100 throw new \coding_exception( 101 'start_progress() max value cannot be negative'); 102 } 103 if ($parentcount < 1) { 104 throw new \coding_exception( 105 'start_progress() parent progress count must be at least 1'); 106 } 107 if (!empty($this->descriptions)) { 108 $prevmax = end($this->maxes); 109 if ($prevmax !== self::INDETERMINATE) { 110 $prevcurrent = end($this->currents); 111 if ($prevcurrent + $parentcount > $prevmax) { 112 throw new \coding_exception( 113 'start_progress() parent progress would exceed max'); 114 } 115 } 116 } else { 117 if ($parentcount != 1) { 118 throw new \coding_exception( 119 'start_progress() progress count must be 1 when no parent'); 120 } 121 } 122 $this->descriptions[] = $description; 123 $this->maxes[] = $max; 124 $this->currents[] = 0; 125 $this->parentcounts[] = $parentcount; 126 $this->update_progress(); 127 } 128 129 /** 130 * Marks the end of an operation that will display progress. 131 * 132 * This must be paired with each {@link start_progress} call. 133 * 134 * If there is a parent progress section, its progress will be increased 135 * automatically to reflect the end of the child section. 136 * 137 * @throws \coding_exception If progress hasn't been started 138 */ 139 public function end_progress() { 140 if (!count($this->descriptions)) { 141 throw new \coding_exception('end_progress() without start_progress()'); 142 } 143 array_pop($this->descriptions); 144 array_pop($this->maxes); 145 array_pop($this->currents); 146 $parentcount = array_pop($this->parentcounts); 147 if (!empty($this->descriptions)) { 148 $lastmax = end($this->maxes); 149 if ($lastmax != self::INDETERMINATE) { 150 $lastvalue = end($this->currents); 151 $this->currents[key($this->currents)] = $lastvalue + $parentcount; 152 } 153 } 154 $this->update_progress(); 155 } 156 157 /** 158 * Indicates that progress has occurred. 159 * 160 * The progress value should indicate the total progress so far, from 0 161 * to the value supplied for $max (inclusive) in {@link start_progress}. 162 * 163 * You do not need to call this function for every value. It is OK to skip 164 * values. It is also OK to call this function as often as desired; it 165 * doesn't update the display if called more than once per second. 166 * 167 * It must be INDETERMINATE if {@link start_progress} was called with $max set to 168 * INDETERMINATE. Otherwise it must not be indeterminate. 169 * 170 * @param int $progress Progress so far 171 * @throws \coding_exception If progress value is invalid 172 */ 173 public function progress($progress = self::INDETERMINATE) { 174 // Check we are inside a progress section. 175 $max = end($this->maxes); 176 if ($max === false) { 177 throw new \coding_exception( 178 'progress() without start_progress'); 179 } 180 181 // Check and apply new progress. 182 if ($progress === self::INDETERMINATE) { 183 // Indeterminate progress. 184 if ($max !== self::INDETERMINATE) { 185 throw new \coding_exception( 186 'progress() INDETERMINATE, expecting value'); 187 } 188 } else { 189 // Determinate progress. 190 $current = end($this->currents); 191 if ($max === self::INDETERMINATE) { 192 throw new \coding_exception( 193 'progress() with value, expecting INDETERMINATE'); 194 } else if ($progress < 0 || $progress > $max) { 195 throw new \coding_exception( 196 'progress() value out of range'); 197 } else if ($progress < $current) { 198 throw new \coding_exception( 199 'progress() value may not go backwards'); 200 } 201 $this->currents[key($this->currents)] = $progress; 202 } 203 204 // Don't update progress bar too frequently (more than once per second). 205 $now = $this->get_time(); 206 if ($now === $this->lastprogresstime) { 207 return; 208 } 209 210 // Update progress. 211 $this->count++; 212 $this->lastprogresstime = $now; 213 214 // Update time limit before next progress display. 215 \core_php_time_limit::raise(self::TIME_LIMIT_WITHOUT_PROGRESS); 216 $this->update_progress(); 217 } 218 219 /** 220 * An alternative to calling progress. This keeps track of the number of items done internally. Call this method 221 * with no parameters to increment the internal counter by one or you can use the $incby parameter to specify a positive 222 * change in progress. The internal progress counter should not exceed $max as passed to {@link start_progress} for this 223 * section. 224 * 225 * If you called {@link start_progress} with parameter INDETERMINATE then you cannot call this method. 226 * 227 * @var int $incby The positive change to apply to the internal progress counter. Defaults to 1. 228 */ 229 public function increment_progress($incby = 1) { 230 $current = end($this->currents); 231 $this->progress($current + $incby); 232 } 233 234 /** 235 * Gets time (this is provided so that unit tests can override it). 236 * 237 * @return int Current system time 238 */ 239 protected function get_time() { 240 return time(); 241 } 242 243 /** 244 * Called whenever new progress should be displayed. 245 */ 246 protected abstract function update_progress(); 247 248 /** 249 * @return bool True if currently inside a progress section 250 */ 251 public function is_in_progress_section() { 252 return !empty($this->descriptions); 253 } 254 255 /** 256 * Checks max value of current progress section. 257 * 258 * @return int Current max value - may be {@link \core\progress\base::INDETERMINATE}. 259 * @throws \coding_exception If not in a progress section 260 */ 261 public function get_current_max() { 262 $max = end($this->maxes); 263 if ($max === false) { 264 throw new \coding_exception('Not inside progress section'); 265 } 266 return $max; 267 } 268 269 /** 270 * @throws \coding_exception 271 * @return string Current progress section description 272 */ 273 public function get_current_description() { 274 $description = end($this->descriptions); 275 if ($description === false) { 276 throw new \coding_exception('Not inside progress section'); 277 } 278 return $description; 279 } 280 281 /** 282 * Obtains current progress in a way suitable for drawing a progress bar. 283 * 284 * Progress is returned as a minimum and maximum value. If there is no 285 * indeterminate progress, these values will be identical. If there is 286 * intermediate progress, these values can be different. (For example, if 287 * the top level progress sections is indeterminate, then the values will 288 * always be 0.0 and 1.0.) 289 * 290 * @return array Minimum and maximum possible progress proportions 291 */ 292 public function get_progress_proportion_range() { 293 // If there is no progress underway, we must have finished. 294 if (empty($this->currents)) { 295 return array(1.0, 1.0); 296 } 297 $count = count($this->currents); 298 $min = 0.0; 299 $max = 1.0; 300 for ($i = 0; $i < $count; $i++) { 301 // Get max value at that section - if it's indeterminate we can tell 302 // no more. 303 $sectionmax = $this->maxes[$i]; 304 if ($sectionmax === self::INDETERMINATE) { 305 return array($min, $max); 306 } 307 308 // Special case if current value is max (this should only happen 309 // just before ending a section). 310 $sectioncurrent = $this->currents[$i]; 311 if ($sectioncurrent === $sectionmax) { 312 return array($max, $max); 313 } 314 315 // Using the current value at that section, we know we are somewhere 316 // between 'current' and the next 'current' value which depends on 317 // the parentcount of the nested section (if any). 318 $newmin = ($sectioncurrent / $sectionmax) * ($max - $min) + $min; 319 $nextcurrent = $sectioncurrent + 1; 320 if ($i + 1 < $count) { 321 $weight = $this->parentcounts[$i + 1]; 322 $nextcurrent = $sectioncurrent + $weight; 323 } 324 $newmax = ($nextcurrent / $sectionmax) * ($max - $min) + $min; 325 $min = $newmin; 326 $max = $newmax; 327 } 328 329 // If there was nothing indeterminate, we use the min value as current. 330 return array($min, $min); 331 } 332 333 /** 334 * Obtains current indeterminate progress in a way suitable for adding to 335 * the progress display. 336 * 337 * This returns the number of indeterminate calls (at any level) during the 338 * lifetime of this progress reporter, whether or not there is a current 339 * indeterminate step. (The number will not be ridiculously high because 340 * progress calls are limited to one per second.) 341 * 342 * @return int Number of indeterminate progress calls 343 */ 344 public function get_progress_count() { 345 return $this->count; 346 } 347 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body