Search moodle.org's
Developer Documentation

See Release Notes

  • Bug fixes for general core bugs in 4.3.x will end 7 October 2024 (12 months).
  • Bug fixes for security issues in 4.3.x will end 21 April 2025 (18 months).
  • PHP version: minimum PHP 8.0.0 Note: minimum PHP version has increased since Moodle 4.1. PHP 8.2.x is supported too.

Differences Between: [Versions 310 and 403] [Versions 311 and 403] [Versions 39 and 403] [Versions 400 and 403] [Versions 401 and 403] [Versions 402 and 403]

   1  <?php
   2  
   3  namespace PhpOffice\PhpSpreadsheet\Helper;
   4  
   5  use PhpOffice\PhpSpreadsheet\Chart\Chart;
   6  use PhpOffice\PhpSpreadsheet\IOFactory;
   7  use PhpOffice\PhpSpreadsheet\Settings;
   8  use PhpOffice\PhpSpreadsheet\Spreadsheet;
   9  use PhpOffice\PhpSpreadsheet\Worksheet\Worksheet;
  10  use PhpOffice\PhpSpreadsheet\Writer\IWriter;
  11  use RecursiveDirectoryIterator;
  12  use RecursiveIteratorIterator;
  13  use RecursiveRegexIterator;
  14  use ReflectionClass;
  15  use RegexIterator;
  16  use RuntimeException;
  17  use Throwable;
  18  
  19  /**
  20   * Helper class to be used in sample code.
  21   */
  22  class Sample
  23  {
  24      /**
  25       * Returns whether we run on CLI or browser.
  26       *
  27       * @return bool
  28       */
  29      public function isCli()
  30      {
  31          return PHP_SAPI === 'cli';
  32      }
  33  
  34      /**
  35       * Return the filename currently being executed.
  36       *
  37       * @return string
  38       */
  39      public function getScriptFilename()
  40      {
  41          return basename($_SERVER['SCRIPT_FILENAME'], '.php');
  42      }
  43  
  44      /**
  45       * Whether we are executing the index page.
  46       *
  47       * @return bool
  48       */
  49      public function isIndex()
  50      {
  51          return $this->getScriptFilename() === 'index';
  52      }
  53  
  54      /**
  55       * Return the page title.
  56       *
  57       * @return string
  58       */
  59      public function getPageTitle()
  60      {
  61          return $this->isIndex() ? 'PHPSpreadsheet' : $this->getScriptFilename();
  62      }
  63  
  64      /**
  65       * Return the page heading.
  66       *
  67       * @return string
  68       */
  69      public function getPageHeading()
  70      {
  71          return $this->isIndex() ? '' : '<h1>' . str_replace('_', ' ', $this->getScriptFilename()) . '</h1>';
  72      }
  73  
  74      /**
  75       * Returns an array of all known samples.
  76       *
  77       * @return string[][] [$name => $path]
  78       */
  79      public function getSamples()
  80      {
  81          // Populate samples
  82          $baseDir = realpath(__DIR__ . '/../../../samples');
  83          if ($baseDir === false) {
  84              // @codeCoverageIgnoreStart
  85              throw new RuntimeException('realpath returned false');
  86              // @codeCoverageIgnoreEnd
  87          }
  88          $directory = new RecursiveDirectoryIterator($baseDir);
  89          $iterator = new RecursiveIteratorIterator($directory);
  90          $regex = new RegexIterator($iterator, '/^.+\.php$/', RecursiveRegexIterator::GET_MATCH);
  91  
  92          $files = [];
  93          foreach ($regex as $file) {
  94              $file = str_replace(str_replace('\\', '/', $baseDir) . '/', '', str_replace('\\', '/', $file[0]));
  95              if (is_array($file)) {
  96                  // @codeCoverageIgnoreStart
  97                  throw new RuntimeException('str_replace returned array');
  98                  // @codeCoverageIgnoreEnd
  99              }
 100              $info = pathinfo($file);
 101              $category = str_replace('_', ' ', $info['dirname'] ?? '');
 102              $name = str_replace('_', ' ', (string) preg_replace('/(|\.php)/', '', $info['filename']));
 103              if (!in_array($category, ['.', 'boostrap', 'templates'])) {
 104                  if (!isset($files[$category])) {
 105                      $files[$category] = [];
 106                  }
 107                  $files[$category][$name] = $file;
 108              }
 109          }
 110  
 111          // Sort everything
 112          ksort($files);
 113          foreach ($files as &$f) {
 114              asort($f);
 115          }
 116  
 117          return $files;
 118      }
 119  
 120      /**
 121       * Write documents.
 122       *
 123       * @param string $filename
 124       * @param string[] $writers
 125       */
 126      public function write(Spreadsheet $spreadsheet, $filename, array $writers = ['Xlsx', 'Xls'], bool $withCharts = false, ?callable $writerCallback = null): void
 127      {
 128          // Set active sheet index to the first sheet, so Excel opens this as the first sheet
 129          $spreadsheet->setActiveSheetIndex(0);
 130  
 131          // Write documents
 132          foreach ($writers as $writerType) {
 133              $path = $this->getFilename($filename, mb_strtolower($writerType));
 134              $writer = IOFactory::createWriter($spreadsheet, $writerType);
 135              $writer->setIncludeCharts($withCharts);
 136              if ($writerCallback !== null) {
 137                  $writerCallback($writer);
 138              }
 139              $callStartTime = microtime(true);
 140              $writer->save($path);
 141              $this->logWrite($writer, $path, /** @scrutinizer ignore-type */ $callStartTime);
 142              if ($this->isCli() === false) {
 143                  echo '<a href="/download.php?type=' . pathinfo($path, PATHINFO_EXTENSION) . '&name=' . basename($path) . '">Download ' . basename($path) . '</a><br />';
 144              }
 145          }
 146  
 147          $this->logEndingNotes();
 148      }
 149  
 150      protected function isDirOrMkdir(string $folder): bool
 151      {
 152          return \is_dir($folder) || \mkdir($folder);
 153      }
 154  
 155      /**
 156       * Returns the temporary directory and make sure it exists.
 157       *
 158       * @return string
 159       */
 160      public function getTemporaryFolder()
 161      {
 162          $tempFolder = sys_get_temp_dir() . '/phpspreadsheet';
 163          if (!$this->isDirOrMkdir($tempFolder)) {
 164              throw new RuntimeException(sprintf('Directory "%s" was not created', $tempFolder));
 165          }
 166  
 167          return $tempFolder;
 168      }
 169  
 170      /**
 171       * Returns the filename that should be used for sample output.
 172       *
 173       * @param string $filename
 174       * @param string $extension
 175       */
 176      public function getFilename($filename, $extension = 'xlsx'): string
 177      {
 178          $originalExtension = pathinfo($filename, PATHINFO_EXTENSION);
 179  
 180          return $this->getTemporaryFolder() . '/' . str_replace('.' . /** @scrutinizer ignore-type */ $originalExtension, '.' . $extension, basename($filename));
 181      }
 182  
 183      /**
 184       * Return a random temporary file name.
 185       *
 186       * @param string $extension
 187       *
 188       * @return string
 189       */
 190      public function getTemporaryFilename($extension = 'xlsx')
 191      {
 192          $temporaryFilename = tempnam($this->getTemporaryFolder(), 'phpspreadsheet-');
 193          if ($temporaryFilename === false) {
 194              // @codeCoverageIgnoreStart
 195              throw new RuntimeException('tempnam returned false');
 196              // @codeCoverageIgnoreEnd
 197          }
 198          unlink($temporaryFilename);
 199  
 200          return $temporaryFilename . '.' . $extension;
 201      }
 202  
 203      public function log(string $message): void
 204      {
 205          $eol = $this->isCli() ? PHP_EOL : '<br />';
 206          echo($this->isCli() ? date('H:i:s ') : '') . $message . $eol;
 207      }
 208  
 209      public function renderChart(Chart $chart, string $fileName): void
 210      {
 211          if ($this->isCli() === true) {
 212              return;
 213          }
 214  
 215          Settings::setChartRenderer(\PhpOffice\PhpSpreadsheet\Chart\Renderer\MtJpGraphRenderer::class);
 216  
 217          $fileName = $this->getFilename($fileName, 'png');
 218  
 219          try {
 220              $chart->render($fileName);
 221              $this->log('Rendered image: ' . $fileName);
 222              $imageData = file_get_contents($fileName);
 223              if ($imageData !== false) {
 224                  echo '<div><img src="data:image/gif;base64,' . base64_encode($imageData) . '" /></div>';
 225              }
 226          } catch (Throwable $e) {
 227              $this->log('Error rendering chart: ' . $e->getMessage() . PHP_EOL);
 228          }
 229      }
 230  
 231      public function titles(string $category, string $functionName, ?string $description = null): void
 232      {
 233          $this->log(sprintf('%s Functions:', $category));
 234          $description === null
 235              ? $this->log(sprintf('Function: %s()', rtrim($functionName, '()')))
 236              : $this->log(sprintf('Function: %s() - %s.', rtrim($functionName, '()'), rtrim($description, '.')));
 237      }
 238  
 239      public function displayGrid(array $matrix): void
 240      {
 241          $renderer = new TextGrid($matrix, $this->isCli());
 242          echo $renderer->render();
 243      }
 244  
 245      public function logCalculationResult(
 246          Worksheet $worksheet,
 247          string $functionName,
 248          string $formulaCell,
 249          ?string $descriptionCell = null
 250      ): void {
 251          if ($descriptionCell !== null) {
 252              $this->log($worksheet->getCell($descriptionCell)->getValue());
 253          }
 254          $this->log($worksheet->getCell($formulaCell)->getValue());
 255          $this->log(sprintf('%s() Result is ', $functionName) . $worksheet->getCell($formulaCell)->getCalculatedValue());
 256      }
 257  
 258      /**
 259       * Log ending notes.
 260       */
 261      public function logEndingNotes(): void
 262      {
 263          // Do not show execution time for index
 264          $this->log('Peak memory usage: ' . (memory_get_peak_usage(true) / 1024 / 1024) . 'MB');
 265      }
 266  
 267      /**
 268       * Log a line about the write operation.
 269       *
 270       * @param string $path
 271       * @param float $callStartTime
 272       */
 273      public function logWrite(IWriter $writer, $path, $callStartTime): void
 274      {
 275          $callEndTime = microtime(true);
 276          $callTime = $callEndTime - $callStartTime;
 277          $reflection = new ReflectionClass($writer);
 278          $format = $reflection->getShortName();
 279  
 280          $message = ($this->isCli() === true)
 281              ? "Write {$format} format to {$path}  in " . sprintf('%.4f', $callTime) . ' seconds'
 282              : "Write {$format} format to <code>{$path}</code>  in " . sprintf('%.4f', $callTime) . ' seconds';
 283  
 284          $this->log($message);
 285      }
 286  
 287      /**
 288       * Log a line about the read operation.
 289       *
 290       * @param string $format
 291       * @param string $path
 292       * @param float $callStartTime
 293       */
 294      public function logRead($format, $path, $callStartTime): void
 295      {
 296          $callEndTime = microtime(true);
 297          $callTime = $callEndTime - $callStartTime;
 298          $message = "Read {$format} format from <code>{$path}</code>  in " . sprintf('%.4f', $callTime) . ' seconds';
 299  
 300          $this->log($message);
 301      }
 302  }