Import.php 8.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291
  1. <?php
  2. /**
  3. * CSS @import node
  4. *
  5. * The general strategy here is that we don't want to wait
  6. * for the parsing to be completed, before we start importing
  7. * the file. That's because in the context of a browser,
  8. * most of the time will be spent waiting for the server to respond.
  9. *
  10. * On creation, we push the import path to our import queue, though
  11. * `import,push`, we also pass it a callback, which it'll call once
  12. * the file has been fetched, and parsed.
  13. *
  14. * @package Less
  15. * @subpackage tree
  16. */
  17. class Less_Tree_Import extends Less_Tree {
  18. public $options;
  19. public $index;
  20. public $path;
  21. public $features;
  22. public $currentFileInfo;
  23. public $css;
  24. public $skip;
  25. public $root;
  26. public $type = 'Import';
  27. public function __construct( $path, $features, $options, $index, $currentFileInfo = null ) {
  28. $this->options = $options;
  29. $this->index = $index;
  30. $this->path = $path;
  31. $this->features = $features;
  32. $this->currentFileInfo = $currentFileInfo;
  33. if ( is_array( $options ) ) {
  34. $this->options += array( 'inline' => false );
  35. if ( isset( $this->options['less'] ) || $this->options['inline'] ) {
  36. $this->css = !isset( $this->options['less'] ) || !$this->options['less'] || $this->options['inline'];
  37. } else {
  38. $pathValue = $this->getPath();
  39. if ( $pathValue && preg_match( '/css([\?;].*)?$/', $pathValue ) ) {
  40. $this->css = true;
  41. }
  42. }
  43. }
  44. }
  45. //
  46. // The actual import node doesn't return anything, when converted to CSS.
  47. // The reason is that it's used at the evaluation stage, so that the rules
  48. // it imports can be treated like any other rules.
  49. //
  50. // In `eval`, we make sure all Import nodes get evaluated, recursively, so
  51. // we end up with a flat structure, which can easily be imported in the parent
  52. // ruleset.
  53. //
  54. public function accept( $visitor ) {
  55. if ( $this->features ) {
  56. $this->features = $visitor->visitObj( $this->features );
  57. }
  58. $this->path = $visitor->visitObj( $this->path );
  59. if ( !$this->options['inline'] && $this->root ) {
  60. $this->root = $visitor->visit( $this->root );
  61. }
  62. }
  63. /**
  64. * @see Less_Tree::genCSS
  65. */
  66. public function genCSS( $output ) {
  67. if ( $this->css ) {
  68. $output->add( '@import ', $this->currentFileInfo, $this->index );
  69. $this->path->genCSS( $output );
  70. if ( $this->features ) {
  71. $output->add( ' ' );
  72. $this->features->genCSS( $output );
  73. }
  74. $output->add( ';' );
  75. }
  76. }
  77. public function toCSS() {
  78. $features = $this->features ? ' ' . $this->features->toCSS() : '';
  79. if ( $this->css ) {
  80. return "@import " . $this->path->toCSS() . $features . ";\n";
  81. } else {
  82. return "";
  83. }
  84. }
  85. /**
  86. * @return string
  87. */
  88. public function getPath() {
  89. if ( $this->path instanceof Less_Tree_Quoted ) {
  90. $path = $this->path->value;
  91. $path = ( isset( $this->css ) || preg_match( '/(\.[a-z]*$)|([\?;].*)$/', $path ) ) ? $path : $path . '.less';
  92. } else if ( $this->path instanceof Less_Tree_URL ) {
  93. $path = $this->path->value->value;
  94. } else {
  95. return null;
  96. }
  97. // remove query string and fragment
  98. return preg_replace( '/[\?#][^\?]*$/', '', $path );
  99. }
  100. public function compileForImport( $env ) {
  101. return new Less_Tree_Import( $this->path->compile( $env ), $this->features, $this->options, $this->index, $this->currentFileInfo );
  102. }
  103. public function compilePath( $env ) {
  104. $path = $this->path->compile( $env );
  105. $rootpath = '';
  106. if ( $this->currentFileInfo && $this->currentFileInfo['rootpath'] ) {
  107. $rootpath = $this->currentFileInfo['rootpath'];
  108. }
  109. if ( !( $path instanceof Less_Tree_URL ) ) {
  110. if ( $rootpath ) {
  111. $pathValue = $path->value;
  112. // Add the base path if the import is relative
  113. if ( $pathValue && Less_Environment::isPathRelative( $pathValue ) ) {
  114. $path->value = $this->currentFileInfo['uri_root'].$pathValue;
  115. }
  116. }
  117. $path->value = Less_Environment::normalizePath( $path->value );
  118. }
  119. return $path;
  120. }
  121. public function compile( $env ) {
  122. $evald = $this->compileForImport( $env );
  123. // get path & uri
  124. $path_and_uri = null;
  125. if ( is_callable( Less_Parser::$options['import_callback'] ) ) {
  126. $path_and_uri = call_user_func( Less_Parser::$options['import_callback'], $evald );
  127. }
  128. if ( !$path_and_uri ) {
  129. $path_and_uri = $evald->PathAndUri();
  130. }
  131. if ( $path_and_uri ) {
  132. list( $full_path, $uri ) = $path_and_uri;
  133. } else {
  134. $full_path = $uri = $evald->getPath();
  135. }
  136. // import once
  137. if ( $evald->skip( $full_path, $env ) ) {
  138. return array();
  139. }
  140. if ( $this->options['inline'] ) {
  141. // todo needs to reference css file not import
  142. //$contents = new Less_Tree_Anonymous($this->root, 0, array('filename'=>$this->importedFilename), true );
  143. Less_Parser::AddParsedFile( $full_path );
  144. $contents = new Less_Tree_Anonymous( file_get_contents( $full_path ), 0, array(), true );
  145. if ( $this->features ) {
  146. return new Less_Tree_Media( array( $contents ), $this->features->value );
  147. }
  148. return array( $contents );
  149. }
  150. // optional (need to be before "CSS" to support optional CSS imports. CSS should be checked only if empty($this->currentFileInfo))
  151. if ( isset( $this->options['optional'] ) && $this->options['optional'] && !file_exists( $full_path ) && ( !$evald->css || !empty( $this->currentFileInfo ) ) ) {
  152. return array();
  153. }
  154. // css ?
  155. if ( $evald->css ) {
  156. $features = ( $evald->features ? $evald->features->compile( $env ) : null );
  157. return new Less_Tree_Import( $this->compilePath( $env ), $features, $this->options, $this->index );
  158. }
  159. return $this->ParseImport( $full_path, $uri, $env );
  160. }
  161. /**
  162. * Using the import directories, get the full absolute path and uri of the import
  163. *
  164. * @param Less_Tree_Import $evald
  165. */
  166. public function PathAndUri() {
  167. $evald_path = $this->getPath();
  168. if ( $evald_path ) {
  169. $import_dirs = array();
  170. if ( Less_Environment::isPathRelative( $evald_path ) ) {
  171. // if the path is relative, the file should be in the current directory
  172. if ( $this->currentFileInfo ) {
  173. $import_dirs[ $this->currentFileInfo['currentDirectory'] ] = $this->currentFileInfo['uri_root'];
  174. }
  175. } else {
  176. // otherwise, the file should be relative to the server root
  177. if ( $this->currentFileInfo ) {
  178. $import_dirs[ $this->currentFileInfo['entryPath'] ] = $this->currentFileInfo['entryUri'];
  179. }
  180. // if the user supplied entryPath isn't the actual root
  181. $import_dirs[ $_SERVER['DOCUMENT_ROOT'] ] = '';
  182. }
  183. // always look in user supplied import directories
  184. $import_dirs = array_merge( $import_dirs, Less_Parser::$options['import_dirs'] );
  185. foreach ( $import_dirs as $rootpath => $rooturi ) {
  186. if ( is_callable( $rooturi ) ) {
  187. list( $path, $uri ) = call_user_func( $rooturi, $evald_path );
  188. if ( is_string( $path ) ) {
  189. $full_path = $path;
  190. return array( $full_path, $uri );
  191. }
  192. } elseif ( !empty( $rootpath ) ) {
  193. $path = rtrim( $rootpath, '/\\' ).'/'.ltrim( $evald_path, '/\\' );
  194. if ( file_exists( $path ) ) {
  195. $full_path = Less_Environment::normalizePath( $path );
  196. $uri = Less_Environment::normalizePath( dirname( $rooturi.$evald_path ) );
  197. return array( $full_path, $uri );
  198. } elseif ( file_exists( $path.'.less' ) ) {
  199. $full_path = Less_Environment::normalizePath( $path.'.less' );
  200. $uri = Less_Environment::normalizePath( dirname( $rooturi.$evald_path.'.less' ) );
  201. return array( $full_path, $uri );
  202. }
  203. }
  204. }
  205. }
  206. }
  207. /**
  208. * Parse the import url and return the rules
  209. *
  210. * @return Less_Tree_Media|array
  211. */
  212. public function ParseImport( $full_path, $uri, $env ) {
  213. $import_env = clone $env;
  214. if ( ( isset( $this->options['reference'] ) && $this->options['reference'] ) || isset( $this->currentFileInfo['reference'] ) ) {
  215. $import_env->currentFileInfo['reference'] = true;
  216. }
  217. if ( ( isset( $this->options['multiple'] ) && $this->options['multiple'] ) ) {
  218. $import_env->importMultiple = true;
  219. }
  220. $parser = new Less_Parser( $import_env );
  221. $root = $parser->parseFile( $full_path, $uri, true );
  222. $ruleset = new Less_Tree_Ruleset( array(), $root->rules );
  223. $ruleset->evalImports( $import_env );
  224. return $this->features ? new Less_Tree_Media( $ruleset->rules, $this->features->value ) : $ruleset->rules;
  225. }
  226. /**
  227. * Should the import be skipped?
  228. *
  229. * @return boolean|null
  230. */
  231. private function Skip( $path, $env ) {
  232. $path = Less_Parser::AbsPath( $path, true );
  233. if ( $path && Less_Parser::FileParsed( $path ) ) {
  234. if ( isset( $this->currentFileInfo['reference'] ) ) {
  235. return true;
  236. }
  237. return !isset( $this->options['multiple'] ) && !$env->importMultiple;
  238. }
  239. }
  240. }