Search moodle.org's
Developer Documentation


  • Bug fixes for general core bugs in 2.8.x ended 9 November 2015 (12 months).
  • Bug fixes for security issues in 2.8.x ended 9 May 2016 (18 months).
  • minimum PHP 5.4.4 (always use latest PHP 5.4.x or 5.5.x on Windows - http://windows.php.net/download/), PHP 7 is NOT supported
  • /theme/ -> image.php (source)

    Differences Between: [Versions 28 and 32] [Versions 28 and 33] [Versions 28 and 34] [Versions 28 and 35] [Versions 28 and 36] [Versions 28 and 37]

       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   * This file is responsible for serving the one theme and plugin images.
      19   *
      20   * @package   core
      21   * @copyright 2009 Petr Skoda (skodak)  {@link http://skodak.org}
      22   * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
      23   */
      24  
      25  
      26  // disable moodle specific debug messages and any errors in output,
      27  // comment out when debugging or better look into error log!
      28  define('NO_DEBUG_DISPLAY', true);
      29  
      30  // we need just the values from config.php and minlib.php
      31  define('ABORT_AFTER_CONFIG', true);
      32  require('../config.php'); // this stops immediately at the beginning of lib/setup.php
      33  
      34  if ($slashargument = min_get_slash_argument()) {
      35      $slashargument = ltrim($slashargument, '/');
      36      if (substr_count($slashargument, '/') < 3) {
      37          image_not_found();
      38      }
      39      if (strpos($slashargument, '_s/') === 0) {
      40          // Can't use SVG
      41          $slashargument = substr($slashargument, 3);
      42          $usesvg = false;
      43      } else {
      44          $usesvg = true;
      45      }
      46      // image must be last because it may contain "/"
      47      list($themename, $component, $rev, $image) = explode('/', $slashargument, 4);
      48      $themename = min_clean_param($themename, 'SAFEDIR');
      49      $component = min_clean_param($component, 'SAFEDIR');
      50      $rev       = min_clean_param($rev, 'INT');
      51      $image     = min_clean_param($image, 'SAFEPATH');
      52  
      53  } else {
      54      $themename = min_optional_param('theme', 'standard', 'SAFEDIR');
      55      $component = min_optional_param('component', 'core', 'SAFEDIR');
      56      $rev       = min_optional_param('rev', -1, 'INT');
      57      $image     = min_optional_param('image', '', 'SAFEPATH');
      58      $usesvg    = (bool)min_optional_param('svg', '1', 'INT');
      59  }
      60  
      61  if (empty($component) or $component === 'moodle' or $component === 'core') {
      62      $component = 'core';
      63  }
      64  
      65  if (empty($image)) {
      66      image_not_found();
      67  }
      68  
      69  if (file_exists("$CFG->dirroot/theme/$themename/config.php")) {
      70      // exists
      71  } else if (!empty($CFG->themedir) and file_exists("$CFG->themedir/$themename/config.php")) {
      72      // exists
      73  } else {
      74      image_not_found();
      75  }
      76  
      77  $candidatelocation = "$CFG->localcachedir/theme/$rev/$themename/pix/$component";
      78  $etag = sha1("$rev/$themename/$component/$image");
      79  
      80  if ($rev > 0) {
      81      if (file_exists("$candidatelocation/$image.error")) {
      82          // This is a major speedup if there are multiple missing images,
      83          // the only problem is that random requests may pollute our cache.
      84          image_not_found();
      85      }
      86      $cacheimage = false;
      87      if ($usesvg && file_exists("$candidatelocation/$image.svg")) {
      88          $cacheimage = "$candidatelocation/$image.svg";
      89          $ext = 'svg';
      90      } else if (file_exists("$candidatelocation/$image.png")) {
      91          $cacheimage = "$candidatelocation/$image.png";
      92          $ext = 'png';
      93      } else if (file_exists("$candidatelocation/$image.gif")) {
      94          $cacheimage = "$candidatelocation/$image.gif";
      95          $ext = 'gif';
      96      } else if (file_exists("$candidatelocation/$image.jpg")) {
      97          $cacheimage = "$candidatelocation/$image.jpg";
      98          $ext = 'jpg';
      99      } else if (file_exists("$candidatelocation/$image.jpeg")) {
     100          $cacheimage = "$candidatelocation/$image.jpeg";
     101          $ext = 'jpeg';
     102      } else if (file_exists("$candidatelocation/$image.ico")) {
     103          $cacheimage = "$candidatelocation/$image.ico";
     104          $ext = 'ico';
     105      }
     106      if ($cacheimage) {
     107          if (!empty($_SERVER['HTTP_IF_NONE_MATCH']) || !empty($_SERVER['HTTP_IF_MODIFIED_SINCE'])) {
     108              // we do not actually need to verify the etag value because our files
     109              // never change in cache because we increment the rev parameter
     110              $lifetime = 60*60*24*60; // 60 days only - the revision may get incremented quite often
     111              $mimetype = get_contenttype_from_ext($ext);
     112              header('HTTP/1.1 304 Not Modified');
     113              header('Expires: '. gmdate('D, d M Y H:i:s', time() + $lifetime) .' GMT');
     114              header('Cache-Control: public, max-age='.$lifetime.', no-transform');
     115              header('Content-Type: '.$mimetype);
     116              header('Etag: "'.$etag.'"');
     117              die;
     118          }
     119          send_cached_image($cacheimage, $etag);
     120      }
     121  }
     122  
     123  //=================================================================================
     124  // ok, now we need to start normal moodle script, we need to load all libs and $DB
     125  define('ABORT_AFTER_CONFIG_CANCEL', true);
     126  
     127  define('NO_MOODLE_COOKIES', true); // Session not used here
     128  define('NO_UPGRADE_CHECK', true);  // Ignore upgrade check
     129  
     130  require("$CFG->dirroot/lib/setup.php");
     131  
     132  $theme = theme_config::load($themename);
     133  $themerev = theme_get_revision();
     134  
     135  if ($themerev <= 0 or $rev != $themerev) {
     136      // Do not send caching headers if they do not request current revision,
     137      // we do not want to pollute browser caches with outdated images.
     138      $imagefile = $theme->resolve_image_location($image, $component, $usesvg);
     139      if (empty($imagefile) or !is_readable($imagefile)) {
     140          image_not_found();
     141      }
     142      send_uncached_image($imagefile);
     143  }
     144  
     145  make_localcache_directory('theme', false);
     146  
     147  // At this stage caching is enabled, and either:
     148  // * we have no cached copy of the image in any format (either SVG, or non-SVG); or
     149  // * we have a cached copy of the SVG, but the non-SVG was requested by the browser.
     150  //
     151  // Because of the way in which the cache return code works above:
     152  // * if we are allowed to return SVG, we do not need to cache the non-SVG version; however
     153  // * if the browser has requested the non-SVG version, we *must* cache _both_ the SVG, and the non-SVG versions.
     154  
     155  // First get all copies - including, potentially, the SVG version.
     156  $imagefile = $theme->resolve_image_location($image, $component, true);
     157  
     158  if (empty($imagefile) || !is_readable($imagefile)) {
     159      // Unable to find a copy of the image file in any format.
     160      // We write a .error file for the image now - this will be used above when searching for cached copies to prevent
     161      // trying to find the image in the future.
     162      if (!file_exists($candidatelocation)) {
     163          @mkdir($candidatelocation, $CFG->directorypermissions, true);
     164      }
     165      // Make note we can not find this file.
     166      $cacheimage = "$candidatelocation/$image.error";
     167      $fp = fopen($cacheimage, 'w');
     168      fclose($fp);
     169      image_not_found();
     170  }
     171  
     172  // The image was found, and it is readable.
     173  $pathinfo = pathinfo($imagefile);
     174  
     175  // Attempt to cache it if necessary.
     176  // We don't really want to overwrite any existing cache items just for the sake of it.
     177  $cacheimage = "$candidatelocation/$image.{$pathinfo['extension']}";
     178  if (!file_exists($cacheimage)) {
     179      // We don't already hold a cached copy of this image. Cache it now.
     180      $cacheimage = cache_image($image, $imagefile, $candidatelocation);
     181  }
     182  
     183  if (!$usesvg && $pathinfo['extension'] === 'svg') {
     184      // The browser has requested that a non-SVG version be returned.
     185      // The version found so far is the SVG version - try and find the non-SVG version.
     186      $imagefile = $theme->resolve_image_location($image, $component, false);
     187      if (empty($imagefile) || !is_readable($imagefile)) {
     188          // A non-SVG file could not be found at all.
     189          // The browser has requested a non-SVG version, so we must return image_not_found().
     190          // We must *not* write an .error file because the SVG is available.
     191          image_not_found();
     192      }
     193  
     194      // An non-SVG version of image was found - cache it.
     195      // This will be used below in the image serving code.
     196      $cacheimage = cache_image($image, $imagefile, $candidatelocation);
     197  }
     198  
     199  if (connection_aborted()) {
     200      // Request was cancelled - do not send anything.
     201      die;
     202  }
     203  
     204  // Make sure nothing failed.
     205  clearstatcache();
     206  if (file_exists($cacheimage)) {
     207      // The cached copy was found, and is accessible. Serve it.
     208      send_cached_image($cacheimage, $etag);
     209  }
     210  
     211  send_uncached_image($imagefile);
     212  
     213  //=================================================================================
     214  //=== utility functions ==
     215  // we are not using filelib because we need to fine tune all header
     216  // parameters to get the best performance.
     217  
     218  function send_cached_image($imagepath, $etag) {
     219      global $CFG;
     220      require("$CFG->dirroot/lib/xsendfilelib.php");
     221  
     222      $lifetime = 60*60*24*60; // 60 days only - the revision may get incremented quite often
     223      $pathinfo = pathinfo($imagepath);
     224      $imagename = $pathinfo['filename'].'.'.$pathinfo['extension'];
     225  
     226      $mimetype = get_contenttype_from_ext($pathinfo['extension']);
     227  
     228      header('Etag: "'.$etag.'"');
     229      header('Content-Disposition: inline; filename="'.$imagename.'"');
     230      header('Last-Modified: '. gmdate('D, d M Y H:i:s', filemtime($imagepath)) .' GMT');
     231      header('Expires: '. gmdate('D, d M Y H:i:s', time() + $lifetime) .' GMT');
     232      header('Pragma: ');
     233      header('Cache-Control: public, max-age='.$lifetime.', no-transform');
     234      header('Accept-Ranges: none');
     235      header('Content-Type: '.$mimetype);
     236      header('Content-Length: '.filesize($imagepath));
     237  
     238      if (xsendfile($imagepath)) {
     239          die;
     240      }
     241  
     242      // no need to gzip already compressed images ;-)
     243  
     244      readfile($imagepath);
     245      die;
     246  }
     247  
     248  function send_uncached_image($imagepath) {
     249      $pathinfo = pathinfo($imagepath);
     250      $imagename = $pathinfo['filename'].'.'.$pathinfo['extension'];
     251  
     252      $mimetype = get_contenttype_from_ext($pathinfo['extension']);
     253  
     254      header('Content-Disposition: inline; filename="'.$imagename.'"');
     255      header('Last-Modified: '. gmdate('D, d M Y H:i:s', time()) .' GMT');
     256      header('Expires: '. gmdate('D, d M Y H:i:s', time() + 15) .' GMT');
     257      header('Pragma: ');
     258      header('Accept-Ranges: none');
     259      header('Content-Type: '.$mimetype);
     260      header('Content-Length: '.filesize($imagepath));
     261  
     262      readfile($imagepath);
     263      die;
     264  }
     265  
     266  function image_not_found() {
     267      header('HTTP/1.0 404 not found');
     268      die('Image was not found, sorry.');
     269  }
     270  
     271  function get_contenttype_from_ext($ext) {
     272      switch ($ext) {
     273          case 'svg':
     274              return 'image/svg+xml';
     275          case 'png':
     276              return 'image/png';
     277          case 'gif':
     278              return 'image/gif';
     279          case 'jpg':
     280          case 'jpeg':
     281              return 'image/jpeg';
     282          case 'ico':
     283              return 'image/vnd.microsoft.icon';
     284      }
     285      return 'document/unknown';
     286  }
     287  
     288  /**
     289   * Caches a given image file.
     290   *
     291   * @param string $image The name of the image that was requested.
     292   * @param string $imagefile The location of the image file we want to cache.
     293   * @param string $candidatelocation The location to cache it in.
     294   * @return string The path to the cached image.
     295   */
     296  function cache_image($image, $imagefile, $candidatelocation) {
     297      global $CFG;
     298      $pathinfo = pathinfo($imagefile);
     299      $cacheimage = "$candidatelocation/$image.".$pathinfo['extension'];
     300  
     301      clearstatcache();
     302      if (!file_exists(dirname($cacheimage))) {
     303          @mkdir(dirname($cacheimage), $CFG->directorypermissions, true);
     304      }
     305  
     306      // Prevent serving of incomplete file from concurrent request,
     307      // the rename() should be more atomic than copy().
     308      ignore_user_abort(true);
     309      if (@copy($imagefile, $cacheimage.'.tmp')) {
     310          rename($cacheimage.'.tmp', $cacheimage);
     311          @chmod($cacheimage, $CFG->filepermissions);
     312          @unlink($cacheimage.'.tmp'); // just in case anything fails
     313      }
     314      return $cacheimage;
     315  }
    

    Search This Site: