Search moodle.org's
Developer Documentation

See Release Notes

  • Bug fixes for general core bugs in 4.0.x will end 8 May 2023 (12 months).
  • Bug fixes for security issues in 4.0.x will end 13 November 2023 (18 months).
  • PHP version: minimum PHP 7.3.0 Note: the minimum PHP version has increased since Moodle 3.10. PHP 7.4.x is also supported.

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

   1  <?php
   2  
   3  namespace Sabberworm\CSS;
   4  
   5  use Sabberworm\CSS\Parsing\OutputException;
   6  
   7  /**
   8   * Class OutputFormat
   9   *
  10   * @method OutputFormat setSemicolonAfterLastRule( bool $bSemicolonAfterLastRule ) Set whether semicolons are added after last rule.
  11   */
  12  class OutputFormat {
  13  	 /**
  14  	 * Value format
  15  	 */
  16  	 // " means double-quote, ' means single-quote
  17  	 public $sStringQuotingType = '"';
  18  	 // Output RGB colors in hash notation if possible
  19  	 public $bRGBHashNotation = true;
  20  	 
  21  	 /**
  22  	 * Declaration format
  23  	 */
  24  	 // Semicolon after the last rule of a declaration block can be omitted. To do that, set this false.
  25  	 public $bSemicolonAfterLastRule = true;
  26  	 
  27  	 /**
  28  	 * Spacing
  29  	 * Note that these strings are not sanity-checked: the value should only consist of whitespace
  30  	 * Any newline character will be indented according to the current level.
  31  	 * The triples (After, Before, Between) can be set using a wildcard (e.g. `$oFormat->set('Space*Rules', "\n");`)
  32  	 */
  33  	 public $sSpaceAfterRuleName = ' ';
  34  
  35  	 public $sSpaceBeforeRules = '';
  36  	 public $sSpaceAfterRules = '';
  37  	 public $sSpaceBetweenRules = '';
  38  
  39  	 public $sSpaceBeforeBlocks = '';
  40  	 public $sSpaceAfterBlocks = '';
  41  	 public $sSpaceBetweenBlocks = "\n";
  42  
  43  	 // Content injected in and around @-rule blocks.
  44  	 public $sBeforeAtRuleBlock = '';
  45  	 public $sAfterAtRuleBlock = '';
  46  
  47  	 // This is what’s printed before and after the comma if a declaration block contains multiple selectors.
  48  	 public $sSpaceBeforeSelectorSeparator = '';
  49  	 public $sSpaceAfterSelectorSeparator = ' ';
  50  	 // This is what’s printed after the comma of value lists
  51  	 public $sSpaceBeforeListArgumentSeparator = '';
  52  	 public $sSpaceAfterListArgumentSeparator = '';
  53  	 
  54  	 public $sSpaceBeforeOpeningBrace = ' ';
  55  
  56  	 // Content injected in and around declaration blocks.
  57  	 public $sBeforeDeclarationBlock = '';
  58  	 public $sAfterDeclarationBlockSelectors = '';
  59  	 public $sAfterDeclarationBlock = '';
  60  
  61  	 /**
  62  	 * Indentation
  63  	 */
  64  	 // Indentation character(s) per level. Only applicable if newlines are used in any of the spacing settings.
  65  	 public $sIndentation = "\t";
  66  	 
  67  	 /**
  68  	 * Output exceptions.
  69  	 */
  70  	 public $bIgnoreExceptions = false;
  71  	 
  72  	 
  73  	 private $oFormatter = null;
  74  	 private $oNextLevelFormat = null;
  75  	 private $iIndentationLevel = 0;
  76  	 
  77  	public function __construct() {
  78  	 }
  79  	 
  80  	public function get($sName) {
  81  	 	 $aVarPrefixes = array('a', 's', 'm', 'b', 'f', 'o', 'c', 'i');
  82  	 	 foreach($aVarPrefixes as $sPrefix) {
  83  	 	 	 $sFieldName = $sPrefix.ucfirst($sName);
  84  	 	 	 if(isset($this->$sFieldName)) {
  85  	 	 	 	 return $this->$sFieldName;
  86  	 	 	 }
  87  	 	 }
  88  	 	 return null;
  89  	 }
  90  	 
  91  	public function set($aNames, $mValue) {
  92  	 	 $aVarPrefixes = array('a', 's', 'm', 'b', 'f', 'o', 'c', 'i');
  93  	 	 if(is_string($aNames) && strpos($aNames, '*') !== false) {
  94  	 	 	 $aNames = array(str_replace('*', 'Before', $aNames), str_replace('*', 'Between', $aNames), str_replace('*', 'After', $aNames));
  95  	 	 } else if(!is_array($aNames)) {
  96  	 	 	 $aNames = array($aNames);
  97  	 	 }
  98  	 	 foreach($aVarPrefixes as $sPrefix) {
  99  	 	 	 $bDidReplace = false;
 100  	 	 	 foreach($aNames as $sName) {
 101  	 	 	 	 $sFieldName = $sPrefix.ucfirst($sName);
 102  	 	 	 	 if(isset($this->$sFieldName)) {
 103  	 	 	 	 	 $this->$sFieldName = $mValue;
 104  	 	 	 	 	 $bDidReplace = true;
 105  	 	 	 	 }
 106  	 	 	 }
 107  	 	 	 if($bDidReplace) {
 108  	 	 	 	 return $this;
 109  	 	 	 }
 110  	 	 }
 111  	 	 // Break the chain so the user knows this option is invalid
 112  	 	 return false;
 113  	 }
 114  	 
 115  	public function __call($sMethodName, $aArguments) {
 116  	 	 if(strpos($sMethodName, 'set') === 0) {
 117  	 	 	 return $this->set(substr($sMethodName, 3), $aArguments[0]);
 118  	 	 } else if(strpos($sMethodName, 'get') === 0) {
 119  	 	 	 return $this->get(substr($sMethodName, 3));
 120  	 	 } else if(method_exists('\\Sabberworm\\CSS\\OutputFormatter', $sMethodName)) {
 121  	 	 	 return call_user_func_array(array($this->getFormatter(), $sMethodName), $aArguments);
 122  	 	 } else {
 123  	 	 	 throw new \Exception('Unknown OutputFormat method called: '.$sMethodName);
 124  	 	 }
 125  	 }
 126  	 
 127  	public function indentWithTabs($iNumber = 1) {
 128  	 	 return $this->setIndentation(str_repeat("\t", $iNumber));
 129  	 }
 130  	 
 131  	public function indentWithSpaces($iNumber = 2) {
 132  	 	 return $this->setIndentation(str_repeat(" ", $iNumber));
 133  	 }
 134  	 
 135  	public function nextLevel() {
 136  	 	 if($this->oNextLevelFormat === null) {
 137  	 	 	 $this->oNextLevelFormat = clone $this;
 138  	 	 	 $this->oNextLevelFormat->iIndentationLevel++;
 139  	 	 	 $this->oNextLevelFormat->oFormatter = null;
 140  	 	 }
 141  	 	 return $this->oNextLevelFormat;
 142  	 }
 143  	 
 144  	public function beLenient() {
 145  	 	 $this->bIgnoreExceptions = true;
 146  	 }
 147  	 
 148  	public function getFormatter() {
 149  	 	 if($this->oFormatter === null) {
 150  	 	 	 $this->oFormatter = new OutputFormatter($this);
 151  	 	 }
 152  	 	 return $this->oFormatter;
 153  	 }
 154  	 
 155  	public function level() {
 156  	 	 return $this->iIndentationLevel;
 157  	 }
 158  
 159  	 /**
 160  	  * Create format.
 161  	  *
 162  	  * @return OutputFormat Format.
 163  	  */
 164  	public static function create() {
 165  	 	 return new OutputFormat();
 166  	 }
 167  
 168  	 /**
 169  	  * Create compact format.
 170  	  *
 171  	  * @return OutputFormat Format.
 172  	  */
 173  	public static function createCompact() {
 174  	 	 $format = self::create();
 175  	 	 $format->set('Space*Rules', "")->set('Space*Blocks', "")->setSpaceAfterRuleName('')->setSpaceBeforeOpeningBrace('')->setSpaceAfterSelectorSeparator('');
 176  	 	 return $format;
 177  	 }
 178  
 179  	 /**
 180  	  * Create pretty format.
 181  	  *
 182  	  * @return OutputFormat Format.
 183  	  */
 184  	public static function createPretty() {
 185  	 	 $format = self::create();
 186  	 	 $format->set('Space*Rules', "\n")->set('Space*Blocks', "\n")->setSpaceBetweenBlocks("\n\n")->set('SpaceAfterListArgumentSeparator', array('default' => '', ',' => ' '));
 187  	 	 return $format;
 188  	 }
 189  }
 190  
 191  class OutputFormatter {
 192  	 private $oFormat;
 193  	 
 194  	public function __construct(OutputFormat $oFormat) {
 195  	 	 $this->oFormat = $oFormat;
 196  	 }
 197  	 
 198  	public function space($sName, $sType = null) {
 199  	 	 $sSpaceString = $this->oFormat->get("Space$sName");
 200  	 	 // If $sSpaceString is an array, we have multple values configured depending on the type of object the space applies to
 201  	 	 if(is_array($sSpaceString)) {
 202  	 	 	 if($sType !== null && isset($sSpaceString[$sType])) {
 203  	 	 	 	 $sSpaceString = $sSpaceString[$sType];
 204  	 	 	 } else {
 205  	 	 	 	 $sSpaceString = reset($sSpaceString);
 206  	 	 	 }
 207  	 	 }
 208  	 	 return $this->prepareSpace($sSpaceString);
 209  	 }
 210  	 
 211  	public function spaceAfterRuleName() {
 212  	 	 return $this->space('AfterRuleName');
 213  	 }
 214  	 
 215  	public function spaceBeforeRules() {
 216  	 	 return $this->space('BeforeRules');
 217  	 }
 218  	 
 219  	public function spaceAfterRules() {
 220  	 	 return $this->space('AfterRules');
 221  	 }
 222  	 
 223  	public function spaceBetweenRules() {
 224  	 	 return $this->space('BetweenRules');
 225  	 }
 226  	 
 227  	public function spaceBeforeBlocks() {
 228  	 	 return $this->space('BeforeBlocks');
 229  	 }
 230  	 
 231  	public function spaceAfterBlocks() {
 232  	 	 return $this->space('AfterBlocks');
 233  	 }
 234  	 
 235  	public function spaceBetweenBlocks() {
 236  	 	 return $this->space('BetweenBlocks');
 237  	 }
 238  	 
 239  	public function spaceBeforeSelectorSeparator() {
 240  	 	 return $this->space('BeforeSelectorSeparator');
 241  	 }
 242  
 243  	public function spaceAfterSelectorSeparator() {
 244  	 	 return $this->space('AfterSelectorSeparator');
 245  	 }
 246  
 247  	public function spaceBeforeListArgumentSeparator($sSeparator) {
 248  	 	 return $this->space('BeforeListArgumentSeparator', $sSeparator);
 249  	 }
 250  
 251  	public function spaceAfterListArgumentSeparator($sSeparator) {
 252  	 	 return $this->space('AfterListArgumentSeparator', $sSeparator);
 253  	 }
 254  
 255  	public function spaceBeforeOpeningBrace() {
 256  	 	 return $this->space('BeforeOpeningBrace');
 257  	 }
 258  
 259  	 /**
 260  	 * Runs the given code, either swallowing or passing exceptions, depending on the bIgnoreExceptions setting.
 261  	 */
 262  	public function safely($cCode) {
 263  	 	 if($this->oFormat->get('IgnoreExceptions')) {
 264  	 	 	 // If output exceptions are ignored, run the code with exception guards
 265  	 	 	 try {
 266  	 	 	 	 return $cCode();
 267  	 	 	 } catch (OutputException $e) {
 268  	 	 	 	 return null;
 269  	 	 	 } //Do nothing
 270  	 	 } else {
 271  	 	 	 // Run the code as-is
 272  	 	 	 return $cCode();
 273  	 	 }
 274  	 }
 275  
 276  	 /**
 277  	 * Clone of the implode function but calls ->render with the current output format instead of __toString()
 278  	 */
 279  	public function implode($sSeparator, $aValues, $bIncreaseLevel = false) {
 280  	 	 $sResult = '';
 281  	 	 $oFormat = $this->oFormat;
 282  	 	 if($bIncreaseLevel) {
 283  	 	 	 $oFormat = $oFormat->nextLevel();
 284  	 	 }
 285  	 	 $bIsFirst = true;
 286  	 	 foreach($aValues as $mValue) {
 287  	 	 	 if($bIsFirst) {
 288  	 	 	 	 $bIsFirst = false;
 289  	 	 	 } else {
 290  	 	 	 	 $sResult .= $sSeparator;
 291  	 	 	 }
 292  	 	 	 if($mValue instanceof \Sabberworm\CSS\Renderable) {
 293  	 	 	 	 $sResult .= $mValue->render($oFormat);
 294  	 	 	 } else {
 295  	 	 	 	 $sResult .= $mValue;
 296  	 	 	 }
 297  	 	 }
 298  	 	 return $sResult;
 299  	 }
 300  	 
 301  	public function removeLastSemicolon($sString) {
 302  	 	 if($this->oFormat->get('SemicolonAfterLastRule')) {
 303  	 	 	 return $sString;
 304  	 	 }
 305  	 	 $sString = explode(';', $sString);
 306  	 	 if(count($sString) < 2) {
 307  	 	 	 return $sString[0];
 308  	 	 }
 309  	 	 $sLast = array_pop($sString);
 310  	 	 $sNextToLast = array_pop($sString);
 311  	 	 array_push($sString, $sNextToLast.$sLast);
 312  	 	 return implode(';', $sString);
 313  	 }
 314  
 315  	private function prepareSpace($sSpaceString) {
 316  	 	 return str_replace("\n", "\n".$this->indent(), $sSpaceString);
 317  	 }
 318  
 319  	private function indent() {
 320  	 	 return str_repeat($this->oFormat->sIndentation, $this->oFormat->level());
 321  	 }
 322  }