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.