9 #[\AllowDynamicProperties] 
   56         $this->cacheData = [];
 
   58         $this->fragments = [];
 
   59         $this->references = [];
 
   60         $this->absoluteImports = [];
 
   61         $this->charset = 
null;
 
   65         $this->misc = new \stdClass();
 
   66         $this->input = new \stdClass();
 
   67         $this->output = new \stdClass();
 
   68         $this->tokens = 
new Tokens();
 
   69         $this->functions = 
new Functions();
 
   70         $this->sourceMap = 
null;
 
   71         $this->selectorAliases = [];
 
   72         $this->selectorAliasesPatt = 
null;
 
   81         $this->aliases = 
$config->aliases;
 
   84         $this->options = 
new Options($user_options, 
$config->options);
 
   87         $context += [
'type' => 
'filter', 
'data' => 
''];
 
   91         $this->options->global_vars = 
$config->vars;
 
   94         $this->docRoot = isset($this->options->doc_root) ? $this->options->doc_root : 
$config->docRoot;
 
   95         $this->generateMap = $this->ioContext === 
'file' && $this->options->__get(
'source_map');
 
   96         $this->ruleFormatter = $this->options->__get(
'formatter');
 
   97         $this->minifyOutput = $this->options->__get(
'minify');
 
   98         $this->newline = $this->options->__get(
'newlines');
 
  100         $useContextOption = ! empty($this->options->context)
 
  101         && (php_sapi_name() === 
'cli' || 
$context[
'type'] === 
'filter');
 
  105             $this->input->raw = $file;
 
  106             if (! ($inputFile = Util::resolveUserPath($file, 
null, $this->docRoot))) {
 
  107                 throw new \Exception(
'Input file \'' . basename($file) . 
'\' not found.
'); 
  109             $inputDir = $useContextOption 
  110             ? $this->options->context 
  111             : dirname($inputFile); 
  112             $this->resolveContext($inputDir, $inputFile); 
  114         elseif ($context['type
'] === 'filter
') { 
  115             if ($useContextOption) { 
  116                 $this->resolveContext($this->options->context); 
  119                 $this->resolveContext(); 
  121             $this->input->string = $context['data
']; 
  125     public function release() 
  135                 $this->selectorAliases 
  139     public function resolveContext($input_dir = null, $input_file = null) 
  142             $this->input->path = $input_file; 
  143             $this->input->filename = basename($input_file); 
  144             $this->input->mtime = filemtime($input_file); 
  147             $this->input->path = null; 
  148             $this->input->filename = null; 
  151         $this->input->dir = $input_dir ?: $this->docRoot; 
  152         $this->input->dirUrl = substr($this->input->dir, strlen($this->docRoot)); 
  153         $this->output->dir = $this->io->getOutputDir(); 
  154         $this->output->filename = $this->io->getOutputFileName(); 
  155         $this->output->dirUrl = substr($this->output->dir, strlen($this->docRoot)); 
  157         $context_resolved = true; 
  159             $output_dir = $this->output->dir; 
  161             if (! file_exists($output_dir)) { 
  162                 warning("Output directory '$output_dir
' doesn't exist.
"); 
  163                 $context_resolved = false; 
  165             elseif (! is_writable($output_dir)) { 
  167                 debug('Attempting to change permissions.'); 
  169                 if (! @chmod($output_dir, 0755)) { 
  170                     warning("Output directory 
'$output_dir' is unwritable.
"); 
  171                     $context_resolved = false; 
  174                     debug('Permissions updated.'); 
  181         return $context_resolved; 
  185     ############################# 
  188     protected function getBoilerplate() 
  191         $boilerplateOption = $this->options->boilerplate; 
  193         if ($boilerplateOption === true) { 
  194             $file = Crush::$dir . '/boilerplate.txt'; 
  196         elseif (is_string($boilerplateOption)) { 
  197             if (file_exists($boilerplateOption)) { 
  198                 $file = $boilerplateOption; 
  202         // Return an empty string if no file is found. 
  207         $boilerplate = file_get_contents($file); 
  209         // Substitute any tags 
  210         if (preg_match_all('~\{\{([^}]+)\}\}~', $boilerplate, $boilerplateMatches)) { 
  212             // Command line arguments (if any). 
  213             $commandArgs = 'n/a'; 
  214             if (isset($_SERVER['argv'])) { 
  215                 $argv = $_SERVER['argv']; 
  217                 $commandArgs = 'csscrush ' . implode(' ', $argv); 
  221                 'datetime' => @date('Y-m-d H:i:s O'), 
  222                 'year' => @date('Y'), 
  223                 'command' => $commandArgs, 
  224                 'plugins' => implode(',', $this->plugins), 
  225                 'version' => function ()  { 
  226                 return Version::detect(); 
  228                 'compile_time' => function () { 
  229                 $now = microtime(true) - Crush::$process->stat['compile_start_time']; 
  230                 return round($now, 4) . ' seconds'; 
  236             foreach (array_keys($boilerplateMatches[0]) as $index) { 
  237                 $tagName = trim($boilerplateMatches[1][$index]); 
  239                 if (isset($tags[$tagName])) { 
  240                     $replacement =  is_callable($tags[$tagName]) ? $tags[$tagName]() : $tags[$tagName]; 
  242                 $replacements[] = $replacement; 
  244             $boilerplate = str_replace($boilerplateMatches[0], $replacements, $boilerplate); 
  248         $EOL = $this->newline; 
  249         $boilerplate = preg_split('~[\t]*'. Regex::$classes->newline . '[\t]*~', trim($boilerplate)); 
  250         $boilerplate = array_map('trim', $boilerplate); 
  251         $boilerplate = "$EOL * 
" . implode("$EOL * 
", $boilerplate); 
  257     ############################# 
  260     protected function resolveSelectorAliases() 
  262         $this->string->pregReplaceCallback( 
  263                 Regex::make('~@selector(?:-(?<type>alias|splat))? +\:?(?<name>{{ident}}) +(?<handler>[^;]+) *;~iS'), 
  265                     $name = strtolower($m['name']); 
  266                     $type = ! empty($m['type']) ? strtolower($m['type']) : 'alias'; 
  267                     $handler = Util::stripCommentTokens($m['handler']); 
  268                     Crush::$process->selectorAliases[$name] = new SelectorAlias($handler, $type); 
  271         // Create the selector aliases pattern and store it. 
  272         if ($this->selectorAliases) { 
  273             $names = implode('|', array_keys($this->selectorAliases)); 
  274             $this->selectorAliasesPatt 
  275             = Regex::make('~\:(' . $names . '){{RB}}(\()?~iS'); 
  279     public function addSelectorAlias($name, $handler, $type = 'alias') 
  281         if ($type != 'callback') { 
  282             $handler = $this->tokens->capture($handler, 's'); 
  284         $this->selectorAliases[$name] = new SelectorAlias($handler, $type); 
  288     ############################# 
  291     protected function filterAliases() 
  293         // If a vendor target is given, we prune the aliases array. 
  294         $vendors = $this->options->vendor_target; 
  296         // Default vendor argument, so use all aliases as normal. 
  297         if ('all' === $vendors) { 
  302         // For expicit 'none' argument turn off aliases. 
  303         if ('none' === $vendors) { 
  304             $this->aliases = Crush::$config->bareAliases; 
  309         // Normalize vendor names and create regex patt. 
  310         $vendor_names = (array) $vendors; 
  311         foreach ($vendor_names as &$vendor_name) { 
  312             $vendor_name = trim($vendor_name, '-'); 
  314         $vendor_patt = '~^\-(' . implode('|', $vendor_names) . ')\-~i'; 
  317         // Loop the aliases array, filter down to the target vendor. 
  318         foreach ($this->aliases as $section => $group_array) { 
  320             // Declarations aliases. 
  321             if ($section === 'declarations') { 
  323                 foreach ($group_array as $property => $values) { 
  324                     foreach ($values as $value => $prefix_values) { 
  325                         foreach ($prefix_values as $index => $declaration) { 
  327                             if (in_array($declaration[2], $vendor_names)) { 
  331                             // Unset uneeded aliases. 
  332                             unset($this->aliases[$section][$property][$value][$index]); 
  334                             if (empty($this->aliases[$section][$property][$value])) { 
  335                                 unset($this->aliases[$section][$property][$value]); 
  337                             if (empty($this->aliases[$section][$property])) { 
  338                                 unset($this->aliases[$section][$property]); 
  345             // Function group aliases. 
  346             elseif ($section === 'function_groups') { 
  348                 foreach ($group_array as $func_group => $vendors) { 
  349                     foreach (array_keys($vendors) as $vendor) { 
  350                         if (! in_array($vendor, $vendor_names)) { 
  351                             unset($this->aliases['function_groups'][$func_group][$vendor]); 
  359                 foreach ($group_array as $alias_keyword => $prefix_array) { 
  361                     // Skip over pointers to function groups. 
  362                     if ($prefix_array[0] === '.') { 
  368                     foreach ($prefix_array as $prefix) { 
  369                         if (preg_match($vendor_patt, $prefix)) { 
  374                     // Prune the whole alias keyword if there is no result. 
  375                     if (empty($result)) { 
  376                         unset($this->aliases[$section][$alias_keyword]); 
  379                         $this->aliases[$section][$alias_keyword] = $result; 
  387     ############################# 
  390     protected function filterPlugins() 
  392         $this->plugins = array_unique($this->options->plugins); 
  394         foreach ($this->plugins as $plugin) { 
  395             Crush::enablePlugin($plugin); 
  400     ############################# 
  403     protected function captureVars() 
  405         Crush::$process->vars = Crush::$process->string->captureDirectives(['set', 'define'], [ 
  407             'lowercase_keys' => false, 
  408         ]) + Crush::$process->vars; 
  410         // For convenience adding a runtime variable for cache busting linked resources. 
  411         $this->vars['timestamp'] = (int) $this->stat['compile_start_time']; 
  413         // In-file variables override global variables. 
  414         $this->vars += Crush::$config->vars; 
  416         // Runtime variables override in-file variables. 
  417         if (! empty($this->options->vars)) { 
  418             $this->vars = $this->options->vars + $this->vars; 
  421         // Place variables referenced inside variables. 
  422         foreach ($this->vars as &$value) { 
  423             $this->placeVars($value); 
  427     protected function placeAllVars() 
  429         $this->placeVars($this->string->raw); 
  431         $rawTokens =& $this->tokens->store; 
  433         // Repeat above steps for variables embedded in string tokens. 
  434         foreach ($rawTokens->s as $label => &$value) { 
  435             $this->placeVars($value); 
  438         // Repeat above steps for variables embedded in URL tokens. 
  439         foreach ($rawTokens->u as $label => $url) { 
  440             if (! $url->isData && $this->placeVars($url->value)) { 
  441                 // Re-evaluate $url->value if anything has been interpolated. 
  447     protected function placeVars(&$value) 
  449         static $varFunction, $varFunctionSimple; 
  450         if (! $varFunction) { 
  451             $varFunctionSimple = Regex::make('~\$\( \s* ({{ ident }}) \s* \)~xS'); 
  452             $varFunction = new Functions(['$' => function ($rawArgs) { 
  453                 $args = Functions::parseArgsSimple($rawArgs); 
  454                 if (isset(Crush::$process->vars[$args[0]])) { 
  455                     return Crush::$process->vars[$args[0]]; 
  458                     return isset($args[1]) ? $args[1] : ''; 
  463         // Variables with no default value. 
  464         $value = preg_replace_callback($varFunctionSimple, function ($m) { 
  466             if (isset(Crush::$process->vars[$varName])) { 
  467                 return Crush::$process->vars[$varName]; 
  469         }, $value ?? '', -1, $varsPlaced); 
  471             // Variables with default value. 
  472             if (strpos($value, '$(') !== false) { 
  474                 // Assume at least one replace. 
  477                 // Variables may be nested so need to apply full function parsing. 
  478                 $value = $varFunction->apply($value); 
  481             // If we know replacements have been made we may want to update $value. e.g URL tokens. 
  485     ############################# 
  488     protected function resolveLoops() 
  490         $LOOP_VAR_PATT = '~\#\( \s* (?<arg>[a-zA-Z][\.a-zA-Z0-9-_]*) \s* \)~x'; 
  491         $LOOP_PATT = Regex::make('~ 
  493                 @for \s+ (?<var>{{ident}}) \s+ in \s+ (?<list>[^{]+) 
  498         $apply_scope = function ($str, $context) use ($LOOP_VAR_PATT, $LOOP_PATT) { 
  499             // Need to temporarily hide child block scopes. 
  501             $str = preg_replace_callback($LOOP_PATT, function ($m) use (&$child_scopes) { 
  502                 $label = '?B' . count($child_scopes) . '?'; 
  503                 $child_scopes[$label] = $m['block']; 
  504                 return $m['expression'] . $label; 
  507                 $str = preg_replace_callback($LOOP_VAR_PATT, function ($m) use ($context) { 
  508                     // Normalize casing of built-in loop variables. 
  509                     // User variables are case-sensitive. 
  510                     $arg = preg_replace_callback('~^loop\.(parent\.)?counter0?$~i', function ($m) { 
  511                         return strtolower($m[0]); 
  514                         return isset($context[$arg]) ? $context[$arg] : ''; 
  517                     return str_replace(array_keys($child_scopes), array_values($child_scopes), $str); 
  520         $resolve_list = function ($list) { 
  521             // Resolve the list of items for iteration. 
  522             // Either a generator function or a plain list. 
  524             $this->placeVars($list); 
  525             $list = $this->functions->apply($list); 
  526             if (preg_match(Regex::make('~(?<func>range){{ parens }}~ix'), $list, $m)) { 
  527                 $func = strtolower($m['func']); 
  528                 $args = Functions::parseArgs($m['parens_content']); 
  531                         $items = range(...$args); 
  536                 $items = Util::splitDelimList($list); 
  542         $unroll = function ($str, $context = []) use (&$unroll, $LOOP_PATT, $apply_scope, $resolve_list) { 
  543             $str = $apply_scope($str, $context); 
  544             while (preg_match($LOOP_PATT, $str, $m, PREG_OFFSET_CAPTURE)) { 
  545                 $str = substr_replace($str, '', $m[0][1], strlen($m[0][0])); 
  546                 $context['loop.parent.counter'] = isset($context['loop.counter']) ? $context['loop.counter'] : -1; 
  547                 $context['loop.parent.counter0'] = isset($context['loop.counter0']) ? $context['loop.counter0'] : -1; 
  548                 foreach ($resolve_list($m['list'][0]) as $index => $value) { 
  549                     $str .= $unroll($m['block_content'][0], [ 
  550                         $m['var'][0] => $value, 
  551                         'loop.counter' => $index + 1, 
  552                         'loop.counter0' => $index, 
  560         $this->string->pregReplaceCallback($LOOP_PATT, function ($m) use ($unroll) { 
  561             return Template::tokenize($unroll(Template::unTokenize($m[0]))); 
  565     ############################# 
  568     protected function resolveIfDefines() 
  570         $ifdefinePatt = Regex::make('~@if(?:set|define) \s+ (?<negate>not \s+)? (?<name>{{ ident }}) \s* {{ parens }}? \s* \{~ixS'); 
  572         $matches = $this->string->matchAll($ifdefinePatt); 
  574         while ($match = array_pop($matches)) { 
  576             $curlyMatch = new BalancedMatch($this->string, $match[0][1]); 
  578             if (! $curlyMatch->match) { 
  582             $negate = $match['negate'][1] != -1; 
  583             $nameDefined = isset($this->vars[$match['name'][0]]); 
  585             $valueDefined = isset($match['parens_content'][0]); 
  587             if ($nameDefined && $valueDefined) { 
  588                 $testValue = Util::rawValue(trim($match['parens_content'][0])); 
  589                 $varValue = Util::rawValue($this->vars[$match['name'][0]]); 
  590                 $valueMatch = $varValue == $testValue; 
  594                     ( $valueDefined && !$negate && $valueMatch ) 
  595                     || ( $valueDefined && $negate && !$valueMatch ) 
  596                     || ( !$valueDefined && !$negate && $nameDefined ) 
  597                     || ( !$valueDefined && $negate && !$nameDefined ) 
  599                         $curlyMatch->unWrap(); 
  602                         $curlyMatch->replace(''); 
  608     ############################# 
  611     protected function captureMixins() 
  613         $this->string->pregReplaceCallback(Regex::$patt->mixin, function ($m) { 
  614             Crush::$process->mixins[$m['name']] = new Mixin($m['block_content']); 
  619     ############################# 
  622     protected function resolveFragments() 
  624         $fragments =& Crush::$process->fragments; 
  626         $this->string->pregReplaceCallback(Regex::$patt->fragmentCapture, function ($m) use (&$fragments) { 
  627             $fragments[$m['name']] = new Fragment( 
  629                     ['name' => strtolower($m['name'])] 
  634             $this->string->pregReplaceCallback(Regex::$patt->fragmentInvoke, function ($m) use (&$fragments) { 
  635                 $fragment = isset($fragments[$m['name']]) ? $fragments[$m['name']] : null; 
  638                     if (isset($m['parens'])) { 
  639                         $args = Functions::parseArgs($m['parens_content']); 
  641                     return $fragment($args); 
  648     ############################# 
  651     public function captureRules() 
  653         $tokens = $this->tokens; 
  655         $rulePatt = Regex::make('~ 
  656             (?<trace_token> {{ t_token }}) 
  662         $rulesAndMediaPatt = Regex::make('~{{ r_token }}|@media[^\{]+{{ block }}~iS'); 
  664         $count = preg_match_all(Regex::$patt->t_token, $this->string->raw, $traceMatches, PREG_OFFSET_CAPTURE); 
  667             $traceOffset = $traceMatches[0][$count][1]; 
  669             preg_match($rulePatt, $this->string->raw, $ruleMatch, PREG_UNMATCHED_AS_NULL, $traceOffset); 
  671             $selector = trim($ruleMatch['selector'] ?? ''); 
  672             $block = trim($ruleMatch['block_content'] ?? ''); 
  675             // If rules are nested inside we set their parent property. 
  676             if (preg_match_all(Regex::$patt->r_token, $block, $childMatches)) { 
  678                 $block = preg_replace_callback($rulesAndMediaPatt, function ($m) use (&$replace) { 
  683                     $rule = new Rule($selector, $block, $ruleMatch['trace_token']); 
  684                     foreach ($childMatches[0] as $childToken) { 
  685                         $childRule = $tokens->get($childToken); 
  686                         if (! $childRule->parent) { 
  687                             $childRule->parent = $rule; 
  692                 $rule = new Rule($selector, $block, $ruleMatch['trace_token'] ?? ''); 
  695             $replace = $tokens->add($rule, 'r', $rule->label) . $replace; 
  697             $this->string->splice($replace, $traceOffset, strlen($ruleMatch[0]) ?? ''); 
  700         // Flip, since we just captured rules in reverse order. 
  701         $tokens->store->r = array_reverse($tokens->store->r); 
  703         foreach ($tokens->store->r as $rule) { 
  705                 $rule->selectors->merge(array_keys($rule->parent->selectors->store)); 
  709         // Cleanup unusable rules. 
  710         $this->string->pregReplaceCallback(Regex::$patt->r_token, function ($m) use ($tokens) { 
  712             $rule = $tokens->store->r[$ruleToken]; 
  713             if (empty($rule->declarations->store) && ! $rule->extendArgs) { 
  714                 unset($tokens->store->r[$ruleToken]); 
  721     protected function processRules() 
  723         // Create table of name/selector to rule references. 
  724         $namedReferences = []; 
  726         $previousRule = null; 
  727         foreach ($this->tokens->store->r as $rule) { 
  729                 $namedReferences[$rule->name] = $rule; 
  731             foreach ($rule->selectors as $selector) { 
  732                 $this->references[$selector->readableValue] = $rule; 
  735                 $rule->previous = $previousRule; 
  736                 $previousRule->next = $rule; 
  738             $previousRule = $rule; 
  741         // Explicit named references take precedence. 
  742         $this->references = $namedReferences + $this->references; 
  744         foreach ($this->tokens->store->r as $rule) { 
  746             $rule->declarations->flatten(); 
  747             $rule->declarations->process(); 
  749             $this->emit('rule_prealias', $rule); 
  751             $rule->declarations->aliasProperties($rule->vendorContext); 
  752             $rule->declarations->aliasFunctions($rule->vendorContext); 
  753             $rule->declarations->aliasDeclarations($rule->vendorContext); 
  755             $this->emit('rule_postalias', $rule); 
  757             $rule->selectors->expand(); 
  758             $rule->applyExtendables(); 
  760             $this->emit('rule_postprocess', $rule); 
  765     ############################# 
  768     protected function aliasAtRules() 
  770         if (empty($this->aliases['at-rules'])) { 
  775         $aliases = $this->aliases['at-rules']; 
  776         $regex = Regex::$patt; 
  778         foreach ($aliases as $at_rule => $at_rule_aliases) { 
  780             $matches = $this->string->matchAll("~@$at_rule
" . '[\s{]~i'); 
  782             // Find at-rules that we want to alias. 
  783             while ($match = array_pop($matches)) { 
  785                 $curly_match = new BalancedMatch($this->string, $match[0][1]); 
  787                 if (! $curly_match->match) { 
  788                     // Couldn't match the block. 
  792                 // Build up string with aliased blocks for splicing. 
  793                 $original_block = $curly_match->whole(); 
  796                 foreach ($at_rule_aliases as $alias) { 
  798                     // Copy original block, replacing at-rule with alias name. 
  799                     $copy_block = str_replace("@$at_rule
", "@$alias
", $original_block); 
  801                     // Aliases are nearly always prefixed, capture the current vendor name. 
  802                     preg_match($regex->vendorPrefix, $alias, $vendor); 
  804                     $vendor = $vendor ? $vendor[1] : null; 
  807                     if (preg_match_all($regex->r_token, $copy_block, $copy_matches)) { 
  812                         foreach ($copy_matches[0] as $rule_label) { 
  814                             // Clone the matched rule. 
  815                             $originals[] = $rule_label; 
  816                             $clone_rule = clone $this->tokens->get($rule_label); 
  818                             $clone_rule->vendorContext = $vendor; 
  821                             $replacements[] = $this->tokens->add($clone_rule); 
  824                         // Finally replace the original labels with the cloned rule labels. 
  825                         $copy_block = str_replace($originals, $replacements, $copy_block); 
  828                     // Add the copied block to the stack. 
  829                     $new_blocks[] = $copy_block; 
  832                 // The original version is always pushed last in the list. 
  833                 $new_blocks[] = $original_block; 
  835                 // Splice in the blocks. 
  836                 $curly_match->replace(implode("\n
", $new_blocks)); 
  842     ############################# 
  845     protected function collate() 
  847         $options = $this->options; 
  848         $minify = $options->minify; 
  849         $EOL = $this->newline; 
  851         // Formatting replacements. 
  852         // Strip newlines added during processing. 
  853         $regex_replacements = []; 
  854         $regex_replacements['~\n+~'] = ''; 
  857             // Strip whitespace around colons used in @-rule arguments. 
  858             $regex_replacements['~ ?\: ?~'] = ':'; 
  862             $regex_replacements['~}~'] = "$0$EOL$EOL
"; 
  863             $regex_replacements['~([^\s])\{~'] = "$1 {
"; 
  864             $regex_replacements['~ ?(@[^{]+\{)~'] = "$1$EOL
"; 
  865             $regex_replacements['~ ?(@[^;]+\;)~'] = "$1$EOL
"; 
  867             // Trim leading spaces on @-rules and some tokens. 
  868             $regex_replacements[Regex::make('~ +([@}]|\?[rc]{{token_id}}\?)~S')] = "$1
"; 
  870             // Additional newline between adjacent rules and comments. 
  871             $regex_replacements[Regex::make('~({{r_token}}) (\s*) ({{c_token}})~xS')] = "$1$EOL$2$3
"; 
  874         // Apply all formatting replacements. 
  875         $this->string->pregReplaceHash($regex_replacements)->lTrim(); 
  877         $this->string->restore('r'); 
  879         // Record stats then drop rule objects to reclaim memory. 
  880         Crush::runStat('selector_count', 'rule_count', 'vars'); 
  881         $this->tokens->store->r = []; 
  883         // If specified, apply advanced minification. 
  884         if (is_array($minify)) { 
  885             if (in_array('colors', $minify)) { 
  886                 $this->minifyColors(); 
  893             // Add newlines after comments. 
  894             foreach ($this->tokens->store->c as $token => &$comment) { 
  898             // Insert comments and do final whitespace cleanup. 
  906         $urls = $this->tokens->store->u; 
  909             $link = Util::getLinkBetweenPaths($this->output->dir, $this->input->dir); 
  910             $make_urls_absolute = $options->rewrite_import_urls === 'absolute'; 
  912             foreach ($urls as $token => $url) { 
  914                 if ($url->isRelative && ! $url->noRewrite) { 
  915                     if ($make_urls_absolute) { 
  918                     // If output dir is different to input dir prepend a link between the two. 
  919                     elseif ($link && $options->rewrite_import_urls) { 
  920                         $url->prepend($link); 
  926         if ($this->absoluteImports) { 
  927             $absoluteImports = ''; 
  928             $closing = $minify ? ';' : ";$EOL
"; 
  929             foreach ($this->absoluteImports as $import) { 
  930                 $absoluteImports .= "@import $import->url
" . ($import->media ? " $import->media
" : '') . $closing; 
  932             $this->string->prepend($absoluteImports); 
  935         if ($options->boilerplate) { 
  936             $this->string->prepend($this->getBoilerplate()); 
  939         if ($this->charset) { 
  940             $this->string->prepend("@charset \
"$this->charset\";$EOL");
 
  943         $this->string->restore([
'u', 
's']);
 
  945         if ($this->generateMap) {
 
  946             $this->generateSourceMap();
 
  950     private $iniOriginal = [];
 
  954             'pcre.backtrack_limit' => 1000000,
 
  956             'memory_limit' => 
'128M',
 
  959             if (
$name === 
'memory_limit' && $this->returnBytes(ini_get(
$name)) > $this->returnBytes(
$value)) {
 
  965         $this->filterPlugins();
 
  966         $this->filterAliases();
 
  968         $this->functions->setPattern(
true);
 
  970         $this->stat[
'compile_start_time'] = microtime(
true);
 
  973     private function returnBytes(
string $value)
 
  996         Crush::runStat(
'compile_time');
 
 1005         $this->preCompile();
 
 1007         $importer = 
new Importer($this);
 
 1008         $this->
string = 
new StringObject($importer->collate());
 
 1011         $this->emit(
'capture_phase0', $this);
 
 1013         $this->captureVars();
 
 1015         $this->resolveIfDefines();
 
 1017         $this->resolveLoops();
 
 1019         $this->placeAllVars();
 
 1022         $this->emit(
'capture_phase1', $this);
 
 1024         $this->resolveSelectorAliases();
 
 1026         $this->captureMixins();
 
 1028         $this->resolveFragments();
 
 1031         $this->emit(
'capture_phase2', $this);
 
 1033         $this->captureRules();
 
 1037         $this->
string->pregReplaceCallback(
'~@media\s+(?<media_list>[^{]+)\{~i', 
function ($m) use (&$process) {
 
 1038             return "@media {$process->functions->apply($m['media_list'])}{";
 
 1041             $this->aliasAtRules();
 
 1043             $this->processRules();
 
 1047             $this->postCompile();
 
 1049             return $this->string;
 
 1053     ############################# 
 1058         $this->sourceMap = [
 
 1060             'file' => $this->output->filename,
 
 1063         foreach ($this->sources as 
$source) {
 
 1064             $this->sourceMap[
'sources'][] = Util::getLinkBetweenPaths($this->output->dir, 
$source, 
false);
 
 1067         $token_patt = Regex::make(
'~\?[tm]{{token_id}}\?~S');
 
 1069         $lines = preg_split(Regex::$patt->newline, $this->string->raw);
 
 1070         $tokens =& $this->tokens->store;
 
 1073         $previous_dest_col = 0;
 
 1074         $previous_src_file = 0;
 
 1075         $previous_src_line = 0;
 
 1076         $previous_src_col = 0;
 
 1078         foreach ($lines as &$line_text) {
 
 1080             $line_segments = [];
 
 1082             while (preg_match($token_patt, $line_text, $m, PREG_OFFSET_CAPTURE)) {
 
 1084                 list(
$token, $dest_col) = $m[0];
 
 1087                 if (isset($tokens->{$token_type}[
$token])) {
 
 1089                     list($src_file, $src_line, $src_col) = explode(
',', $tokens->{$token_type}[
$token]);
 
 1091                     Util::vlqEncode($dest_col - $previous_dest_col) .
 
 1092                     Util::vlqEncode($src_file - $previous_src_file) .
 
 1093                     Util::vlqEncode($src_line - $previous_src_line) .
 
 1094                     Util::vlqEncode($src_col - $previous_src_col);
 
 1096                     $previous_dest_col = $dest_col;
 
 1097                     $previous_src_file = $src_file;
 
 1098                     $previous_src_line = $src_line;
 
 1099                     $previous_src_col = $src_col;
 
 1101                 $line_text = substr_replace($line_text, 
'', $dest_col, strlen(
$token));
 
 1104             $mappings[] = implode(
',', $line_segments);
 
 1107         $this->
string->raw = implode($this->newline, $lines);
 
 1108         $this->sourceMap[
'mappings'] = implode(
';', $mappings);
 
 1112     ############################# 
 1117         return $this->
string->pregReplaceHash([
 
 1120             '~([: \(,])(-?)0(\.\d+)~S' => 
'$1$2$3',
 
 1126             '~(\: *)(?:0 0 0|0 0 0 0) *([;}])~S' => 
'${1}0$2',
 
 1129             '~(padding|margin|border-radius) ?(\: *)0 0 *([;}])~iS' => 
'${1}${2}0$3',
 
 1132             '~(\: *)(-?(?:\d+)?\.?\d+[a-z]{1,4}) 0 0 0 *([;}])~iS' => 
'$1$2 0 0$3',
 
 1133             '~(\: *)0 0 (-?(?:\d+)?\.?\d+[a-z]{1,4}) 0 *([;}])~iS' => 
'${1}0 0 $2$3',
 
 1136             Regex::$patt->cruftyHex => 
'#$1$2$3',
 
 1141     ############################# 
 1142     #  Advanced minification. 
 1146         static $keywords_patt, $functions_patt;
 
 1148         $minified_keywords = Color::getMinifyableKeywords();
 
 1150         if (! $keywords_patt) {
 
 1151             $keywords_patt = 
'~(?<![\w\.#-])(' . implode(
'|', array_keys($minified_keywords)) . 
')(?![\w\.#\]-])~iS';
 
 1152             $functions_patt = Regex::make(
'~{{ LB }}(rgb|hsl)\(([^\)]{5,})\)~iS');
 
 1155         $this->
string->pregReplaceCallback($keywords_patt, 
function ($m) use ($minified_keywords) {
 
 1156             return $minified_keywords[strtolower($m[0])];
 
 1159             $this->
string->pregReplaceCallback($functions_patt, 
function ($m) {
 
 1160                 $args = Functions::parseArgs(trim($m[2]));
 
 1161                 if (stripos($m[1], 
'hsl') === 0) {
 
 1164                 return Color::rgbToHex(
$args);
 
if(! $user||! $user->canDelete()) $name
 
__construct($user_options=[], $context=[])
 
$config
Advanced site settings, debugging section.
 
$args
Some servers don't allow PHP to check the rewrite, so try via AJAX.
 
The main class for compiling.
 
if(parse_url(elgg_get_site_url(), PHP_URL_PATH) !=='/') if(file_exists(elgg_get_root_path() . 'robots.txt'))
Set robots.txt.