Search moodle.org's
Developer Documentation

See Release Notes
Long Term Support Release

  • Bug fixes for general core bugs in 3.9.x will end* 10 May 2021 (12 months).
  • Bug fixes for security issues in 3.9.x will end* 8 May 2023 (36 months).
  • PHP version: minimum PHP 7.2.0 Note: minimum PHP version has increased since Moodle 3.8. PHP 7.3.x and 7.4.x are supported too.

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

   1  <?php
   2  
   3  namespace MatthiasMullie\PathConverter;
   4  
   5  /**
   6   * Convert paths relative from 1 file to another.
   7   *
   8   * E.g.
   9   *     ../../images/icon.jpg relative to /css/imports/icons.css
  10   * becomes
  11   *     ../images/icon.jpg relative to /css/minified.css
  12   *
  13   * Please report bugs on https://github.com/matthiasmullie/path-converter/issues
  14   *
  15   * @author Matthias Mullie <pathconverter@mullie.eu>
  16   * @copyright Copyright (c) 2015, Matthias Mullie. All rights reserved
  17   * @license MIT License
  18   */
  19  class Converter implements ConverterInterface
  20  {
  21      /**
  22       * @var string
  23       */
  24      protected $from;
  25  
  26      /**
  27       * @var string
  28       */
  29      protected $to;
  30  
  31      /**
  32       * @param string $from The original base path (directory, not file!)
  33       * @param string $to   The new base path (directory, not file!)
  34       * @param string $root Root directory (defaults to `getcwd`)
  35       */
  36      public function __construct($from, $to, $root = '')
  37      {
  38          $shared = $this->shared($from, $to);
  39          if ($shared === '') {
  40              // when both paths have nothing in common, one of them is probably
  41              // absolute while the other is relative
  42              $root = $root ?: getcwd();
  43              $from = strpos($from, $root) === 0 ? $from : preg_replace('/\/+/', '/', $root.'/'.$from);
  44              $to = strpos($to, $root) === 0 ? $to : preg_replace('/\/+/', '/', $root.'/'.$to);
  45  
  46              // or traveling the tree via `..`
  47              // attempt to resolve path, or assume it's fine if it doesn't exist
  48              $from = @realpath($from) ?: $from;
  49              $to = @realpath($to) ?: $to;
  50          }
  51  
  52          $from = $this->dirname($from);
  53          $to = $this->dirname($to);
  54  
  55          $from = $this->normalize($from);
  56          $to = $this->normalize($to);
  57  
  58          $this->from = $from;
  59          $this->to = $to;
  60      }
  61  
  62      /**
  63       * Normalize path.
  64       *
  65       * @param string $path
  66       *
  67       * @return string
  68       */
  69      protected function normalize($path)
  70      {
  71          // deal with different operating systems' directory structure
  72          $path = rtrim(str_replace(DIRECTORY_SEPARATOR, '/', $path), '/');
  73  
  74          /*
  75           * Example:
  76           *     /home/forkcms/frontend/cache/compiled_templates/../../core/layout/css/../images/img.gif
  77           * to
  78           *     /home/forkcms/frontend/core/layout/images/img.gif
  79           */
  80          do {
  81              $path = preg_replace('/[^\/]+(?<!\.\.)\/\.\.\//', '', $path, -1, $count);
  82          } while ($count);
  83  
  84          return $path;
  85      }
  86  
  87      /**
  88       * Figure out the shared path of 2 locations.
  89       *
  90       * Example:
  91       *     /home/forkcms/frontend/core/layout/images/img.gif
  92       * and
  93       *     /home/forkcms/frontend/cache/minified_css
  94       * share
  95       *     /home/forkcms/frontend
  96       *
  97       * @param string $path1
  98       * @param string $path2
  99       *
 100       * @return string
 101       */
 102      protected function shared($path1, $path2)
 103      {
 104          // $path could theoretically be empty (e.g. no path is given), in which
 105          // case it shouldn't expand to array(''), which would compare to one's
 106          // root /
 107          $path1 = $path1 ? explode('/', $path1) : array();
 108          $path2 = $path2 ? explode('/', $path2) : array();
 109  
 110          $shared = array();
 111  
 112          // compare paths & strip identical ancestors
 113          foreach ($path1 as $i => $chunk) {
 114              if (isset($path2[$i]) && $path1[$i] == $path2[$i]) {
 115                  $shared[] = $chunk;
 116              } else {
 117                  break;
 118              }
 119          }
 120  
 121          return implode('/', $shared);
 122      }
 123  
 124      /**
 125       * Convert paths relative from 1 file to another.
 126       *
 127       * E.g.
 128       *     ../images/img.gif relative to /home/forkcms/frontend/core/layout/css
 129       * should become:
 130       *     ../../core/layout/images/img.gif relative to
 131       *     /home/forkcms/frontend/cache/minified_css
 132       *
 133       * @param string $path The relative path that needs to be converted
 134       *
 135       * @return string The new relative path
 136       */
 137      public function convert($path)
 138      {
 139          // quit early if conversion makes no sense
 140          if ($this->from === $this->to) {
 141              return $path;
 142          }
 143  
 144          $path = $this->normalize($path);
 145          // if we're not dealing with a relative path, just return absolute
 146          if (strpos($path, '/') === 0) {
 147              return $path;
 148          }
 149  
 150          // normalize paths
 151          $path = $this->normalize($this->from.'/'.$path);
 152  
 153          // strip shared ancestor paths
 154          $shared = $this->shared($path, $this->to);
 155          $path = mb_substr($path, mb_strlen($shared));
 156          $to = mb_substr($this->to, mb_strlen($shared));
 157  
 158          // add .. for every directory that needs to be traversed to new path
 159          $to = str_repeat('../', count(array_filter(explode('/', $to))));
 160  
 161          return $to.ltrim($path, '/');
 162      }
 163  
 164      /**
 165       * Attempt to get the directory name from a path.
 166       *
 167       * @param string $path
 168       *
 169       * @return string
 170       */
 171      protected function dirname($path)
 172      {
 173          if (@is_file($path)) {
 174              return dirname($path);
 175          }
 176  
 177          if (@is_dir($path)) {
 178              return rtrim($path, '/');
 179          }
 180  
 181          // no known file/dir, start making assumptions
 182  
 183          // ends in / = dir
 184          if (mb_substr($path, -1) === '/') {
 185              return rtrim($path, '/');
 186          }
 187  
 188          // has a dot in the name, likely a file
 189          if (preg_match('/.*\..*$/', basename($path)) !== 0) {
 190              return dirname($path);
 191          }
 192  
 193          // you're on your own here!
 194          return $path;
 195      }
 196  }