Search moodle.org's
Developer Documentation

See Release Notes
Long Term Support Release

  • Bug fixes for general core bugs in 4.1.x will end 13 November 2023 (12 months).
  • Bug fixes for security issues in 4.1.x will end 10 November 2025 (36 months).
  • PHP version: minimum PHP 7.4.0 Note: minimum PHP version has increased since Moodle 4.0. PHP 8.0.x is supported too.
   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  /**
  18   * pdf data format writer
  19   *
  20   * @package    dataformat_pdf
  21   * @copyright  2019 Shamim Rezaie <shamim@moodle.com>
  22   * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  23   */
  24  
  25  namespace dataformat_pdf;
  26  
  27  defined('MOODLE_INTERNAL') || die();
  28  
  29  /**
  30   * pdf data format writer
  31   *
  32   * @package    dataformat_pdf
  33   * @copyright  2019 Shamim Rezaie <shamim@moodle.com>
  34   * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  35   */
  36  class writer extends \core\dataformat\base {
  37  
  38      public $mimetype = "application/pdf";
  39  
  40      public $extension = ".pdf";
  41  
  42      /**
  43       * @var \pdf The pdf object that is used to generate the pdf file.
  44       */
  45      protected $pdf;
  46  
  47      /**
  48       * @var float Each column's width in the current sheet.
  49       */
  50      protected $colwidth;
  51  
  52      /**
  53       * @var string[] Title of columns in the current sheet.
  54       */
  55      protected $columns;
  56  
  57      /**
  58       * writer constructor.
  59       */
  60      public function __construct() {
  61          global $CFG;
  62          require_once($CFG->libdir . '/pdflib.php');
  63  
  64          $this->pdf = new \pdf();
  65          $this->pdf->setPrintHeader(false);
  66          $this->pdf->SetFooterMargin(PDF_MARGIN_FOOTER);
  67  
  68          // Set background color for headings.
  69          $this->pdf->SetFillColor(238, 238, 238);
  70      }
  71  
  72      public function send_http_headers() {
  73      }
  74  
  75      /**
  76       * Start output to file, note that the actual writing of the file is done in {@see close_output_to_file()}
  77       */
  78      public function start_output_to_file(): void {
  79          $this->start_output();
  80      }
  81  
  82      public function start_output() {
  83          $this->pdf->AddPage('L');
  84      }
  85  
  86      public function start_sheet($columns) {
  87          $margins = $this->pdf->getMargins();
  88          $pagewidth = $this->pdf->getPageWidth() - $margins['left'] - $margins['right'];
  89  
  90          $this->colwidth = $pagewidth / count($columns);
  91          $this->columns = $columns;
  92  
  93          $this->print_heading($this->pdf);
  94      }
  95  
  96      /**
  97       * Method to define whether the dataformat supports export of HTML
  98       *
  99       * @return bool
 100       */
 101      public function supports_html(): bool {
 102          return true;
 103      }
 104  
 105      /**
 106       * When exporting images, we need to return their Base64 encoded content. Otherwise TCPDF will create a HTTP
 107       * request for them, which will lead to the login page (i.e. not the image it expects) and throw an exception
 108       *
 109       * Note: ideally we would copy the file to a temp location and return it's path, but a bug in TCPDF currently
 110       * prevents that
 111       *
 112       * @param \stored_file $file
 113       * @return string|null
 114       */
 115      protected function export_html_image_source(\stored_file $file): ?string {
 116          // Set upper dimensions for embedded images.
 117          $resizedimage = $file->resize_image(400, 300);
 118  
 119          return '@' . base64_encode($resizedimage);
 120      }
 121  
 122      /**
 123       * Write a single record
 124       *
 125       * @param array $record
 126       * @param int $rownum
 127       */
 128      public function write_record($record, $rownum) {
 129          $rowheight = 0;
 130  
 131          $record = $this->format_record($record);
 132          foreach ($record as $cell) {
 133              // We need to calculate the row height (accounting for any content). Unfortunately TCPDF doesn't provide an easy
 134              // method to do that, so we create a second PDF inside a transaction, add cell content and use the largest cell by
 135              // height. Solution similar to that at https://stackoverflow.com/a/1943096.
 136              $pdf2 = clone $this->pdf;
 137              $pdf2->startTransaction();
 138              $numpages = $pdf2->getNumPages();
 139              $pdf2->AddPage('L');
 140              $this->print_heading($pdf2);
 141              $pdf2->writeHTMLCell($this->colwidth, 0, '', '', $cell, 1, 1, false, true, 'L');
 142              $pagesadded = $pdf2->getNumPages() - $numpages;
 143              $margins = $pdf2->getMargins();
 144              $pageheight = $pdf2->getPageHeight() - $margins['top'] - $margins['bottom'];
 145              $cellheight = ($pagesadded - 1) * $pageheight + $pdf2->getY() - $margins['top'] - $this->get_heading_height();
 146              $rowheight = max($rowheight, $cellheight);
 147              $pdf2->rollbackTransaction();
 148          }
 149  
 150          $margins = $this->pdf->getMargins();
 151          if ($this->pdf->getNumPages() > 1 &&
 152                  ($this->pdf->GetY() + $rowheight + $margins['bottom'] > $this->pdf->getPageHeight())) {
 153              $this->pdf->AddPage('L');
 154              $this->print_heading($this->pdf);
 155          }
 156  
 157          // Get the last key for this record.
 158          end($record);
 159          $lastkey = key($record);
 160  
 161          // Reset the record pointer.
 162          reset($record);
 163  
 164          // Loop through each element.
 165          foreach ($record as $key => $cell) {
 166              // Determine whether we're at the last element of the record.
 167              $nextposition = ($lastkey === $key) ? 1 : 0;
 168              // Write the element.
 169              $this->pdf->writeHTMLCell($this->colwidth, $rowheight, '', '', $cell, 1, $nextposition, false, true, 'L');
 170          }
 171      }
 172  
 173      public function close_output() {
 174          $filename = $this->filename . $this->get_extension();
 175  
 176          $this->pdf->Output($filename, 'D');
 177      }
 178  
 179      /**
 180       * Write data to disk
 181       *
 182       * @return bool
 183       */
 184      public function close_output_to_file(): bool {
 185          $this->pdf->Output($this->filepath, 'F');
 186  
 187          return true;
 188      }
 189  
 190      /**
 191       * Prints the heading row for a given PDF.
 192       *
 193       * @param \pdf $pdf A pdf to print headings in
 194       */
 195      private function print_heading(\pdf $pdf) {
 196          $fontfamily = $pdf->getFontFamily();
 197          $fontstyle = $pdf->getFontStyle();
 198          $pdf->SetFont($fontfamily, 'B');
 199  
 200          $total = count($this->columns);
 201          $counter = 1;
 202          foreach ($this->columns as $columns) {
 203              $nextposition = ($counter == $total) ? 1 : 0;
 204              $pdf->Multicell($this->colwidth, $this->get_heading_height(), $columns, 1, 'C', true, $nextposition);
 205              $counter++;
 206          }
 207  
 208          $pdf->SetFont($fontfamily, $fontstyle);
 209      }
 210  
 211      /**
 212       * Returns the heading height.
 213       *
 214       * @return int
 215       */
 216      private function get_heading_height() {
 217          $height = 0;
 218          foreach ($this->columns as $columns) {
 219              $height = max($height, $this->pdf->getStringHeight($this->colwidth, $columns, false, true, '', 1));
 220          }
 221          return $height;
 222      }
 223  }