1<?php 2/** 3 * Copyright (c) 2020. ComboStrap, Inc. and its affiliates. All Rights Reserved. 4 * 5 * This source code is licensed under the GPL license found in the 6 * COPYING file in the root directory of this source tree. 7 * 8 * @license GPL 3 (https://www.gnu.org/licenses/gpl-3.0.en.html) 9 * @author ComboStrap <support@combostrap.com> 10 * 11 */ 12 13namespace ComboStrap; 14 15use IntlDateFormatter; 16 17/** 18 * Class PipelineUtility 19 * @package ComboStrap 20 * A pipeline to perform filter transformation 21 * 22 * See also 23 * https://getbootstrap.com/docs/5.0/helpers/text-truncation/ 24 */ 25class PipelineUtility 26{ 27 const QUOTES_CHARACTERS = ['"', '\'']; 28 const SPACE_CHARACTER = " "; 29 30 31 /** 32 * @param $expression 33 * @param array|null $contextData 34 * @return string 35 * @throws ExceptionBadSyntax - if there is any syntax error 36 */ 37 static public function execute($expression, array $contextData = null): string 38 { 39 40 /** 41 * Get the value (called the message in a pipeline) 42 */ 43 $processedExpression = $expression; 44 $firstQuoteChar = strpos($processedExpression, '"'); 45 $firstPipeChar = strpos($processedExpression, '|'); 46 if ($firstQuoteChar < $firstPipeChar || $firstPipeChar === false) { 47 48 /** 49 * Example: 50 * a literal: "$title" 51 * a literal with a pipe: "World | Do" | replace ("world,"you") 52 */ 53 $message = null; 54 if ($firstQuoteChar !== false) { 55 $processedExpression = substr($processedExpression, $firstQuoteChar + 1); 56 $secondQuoteChar = strpos($processedExpression, '"'); 57 if ($secondQuoteChar !== false) { 58 $message = substr($processedExpression, 0, $secondQuoteChar); 59 } 60 } 61 62 $pipeCharPosition = strpos($processedExpression, '|'); 63 if ($pipeCharPosition !== false) { 64 $commandChain = substr($processedExpression, $pipeCharPosition + 1); 65 if ($message == null) { 66 // not quoted expression 67 // do we support that ? 68 $message = substr($processedExpression, 0, $pipeCharPosition); 69 } 70 } else { 71 if ($message == null) { 72 // not quoted expression 73 // do we support that ? 74 $message = $processedExpression; 75 } 76 $commandChain = ""; 77 } 78 79 } else { 80 81 /** 82 * Example: a variable with an expression 83 * $title | replace ("world,"you") 84 */ 85 $message = trim(substr($processedExpression, 0, $firstPipeChar)); 86 $commandChain = trim(substr($processedExpression, $firstPipeChar + 1)); 87 88 } 89 90 91 /** 92 * Command chain splits 93 */ 94 $commands = preg_split("/\|/", $commandChain); 95 96 97 /** 98 * We replace after the split to be sure that there is not a | separator in the variable value 99 * that would fuck up the process 100 */ 101 $message = \syntax_plugin_combo_variable::replaceVariablesWithValuesFromContext($message, $contextData); 102 103 $charactersToTrimFromCommand = implode("", self::QUOTES_CHARACTERS); 104 foreach ($commands as $command) { 105 $command = trim($command, " )"); 106 $leftParenthesis = strpos($command, "("); 107 $commandName = substr($command, 0, $leftParenthesis); 108 $signature = substr($command, $leftParenthesis + 1); 109 $commandArgs = preg_split("/\s*,\s*/", $signature); 110 /** 111 * Delete space characters 112 */ 113 $commandArgs = array_map( 114 'trim', 115 $commandArgs, 116 array_fill(0, sizeof($commandArgs), self::SPACE_CHARACTER) 117 ); 118 /** 119 * Delete quote characters 120 */ 121 $commandArgs = array_map( 122 'trim', 123 $commandArgs, 124 array_fill(0, sizeof($commandArgs), $charactersToTrimFromCommand) 125 ); 126 $commandName = trim($commandName); 127 if (!empty($commandName)) { 128 switch ($commandName) { 129 case "replace": 130 $message = self::replace($commandArgs, $message); 131 break; 132 case "head": 133 $message = self::head($commandArgs, $message); 134 break; 135 case "tail": 136 $message = self::tail($commandArgs, $message); 137 break; 138 case "rconcat": 139 $message = self::concat($commandArgs, $message, "right"); 140 break; 141 case "lconcat": 142 $message = self::concat($commandArgs, $message, "left"); 143 break; 144 case "cut": 145 $message = self::cut($commandArgs, $message); 146 break; 147 case "trim": 148 $message = trim($message); 149 break; 150 case "capitalize": 151 $message = ucwords($message); 152 break; 153 case "format": 154 $message = self::format($commandArgs, $message); 155 break; 156 default: 157 LogUtility::msg("command ($commandName) is unknown", LogUtility::LVL_MSG_ERROR, "pipeline"); 158 } 159 } 160 } 161 return $message; 162 } 163 164 private static function replace(array $commandArgs, $value) 165 { 166 $search = $commandArgs[0]; 167 $replace = $commandArgs[1]; 168 return str_replace($search, $replace, $value); 169 } 170 171 /** 172 * @param array $commandArgs 173 * @param $value 174 * @return false|string 175 * See also: https://getbootstrap.com/docs/5.0/helpers/text-truncation/ 176 */ 177 public static function head(array $commandArgs, $value) 178 { 179 $length = $commandArgs[0]; 180 if (strlen($value) < $length) { 181 return $value; 182 } 183 $words = explode(" ", $value); 184 $headValue = ""; 185 for ($i = 0; $i < sizeof($words); $i++) { 186 if ($i != 0) { 187 $headValue .= " "; 188 } 189 $headValue .= $words[$i]; 190 if (strlen($headValue) >= $length) { 191 break; 192 } 193 } 194 195 $tail = $commandArgs[1] ?? null; 196 if ($tail !== null) { 197 $headValue .= $tail; 198 } 199 200 return $headValue; 201 } 202 203 private 204 static function concat(array $commandArgs, $value, $side): string 205 { 206 $string = $commandArgs[0]; 207 switch ($side) { 208 case "left": 209 return $string . $value; 210 case "right": 211 return $value . $string; 212 default: 213 LogUtility::msg("The side value ($side) is unknown", LogUtility::LVL_MSG_ERROR, "pipeline"); 214 return $value . $string; 215 } 216 217 218 } 219 220 private 221 static function tail(array $commandArgs, $value) 222 { 223 $length = $commandArgs[0]; 224 return substr($value, strlen($value) - $length); 225 } 226 227 private 228 static function cut(array $commandArgs, $value) 229 { 230 $pattern = $commandArgs[0]; 231 $words = preg_split("/$pattern/i", $value); 232 if ($words !== false) { 233 $selector = $commandArgs[1]; 234 $startEndSelector = preg_split("/-/i", $selector); 235 $start = $startEndSelector[0] - 1; 236 $end = null; 237 if (isset($startEndSelector[1])) { 238 $end = $startEndSelector[1]; 239 if (empty($end)) { 240 $end = sizeof($words); 241 } 242 $end = $end - 1; 243 } 244 if ($end == null) { 245 if (isset($words[$start])) { 246 return $words[$start]; 247 } else { 248 return $value; 249 } 250 } else { 251 $result = ""; 252 for ($i = $start; $i <= $end; $i++) { 253 if (isset($words[$i])) { 254 if (!empty($result)) { 255 $result .= $pattern; 256 } 257 $result .= $words[$i]; 258 } 259 } 260 return $result; 261 } 262 263 } else { 264 return "An error occurred: could not split with the pattern `$pattern`, the value `$value`."; 265 } 266 } 267 268 /** 269 * @throws ExceptionBadSyntax 270 */ 271 public 272 static function format(array $commandArgs, $value): string 273 { 274 275 /** 276 * For now only date time are 277 */ 278 try { 279 $dateTime = Iso8601Date::createFromString($value); 280 } catch (ExceptionBadSyntax $e) { 281 throw new ExceptionBadSyntax("The format method allows for now only date. The value ($value) is not a date.", PipelineTag::CANONICAL); 282 } 283 284 $size = sizeof($commandArgs); 285 $pattern = null; 286 $locale = null; 287 switch ($size) { 288 case 0: 289 break; 290 case 1: 291 $pattern = $commandArgs[0]; 292 break; 293 case 2: 294 default: 295 $pattern = $commandArgs[0]; 296 $locale = $commandArgs[1]; 297 break; 298 } 299 $localeSeparator = '_'; 300 if ($locale === null) { 301 $path = ExecutionContext::getActualOrCreateFromEnv()->getContextPath(); 302 $page = MarkupPath::createPageFromPathObject($path); 303 $locale = Locale::createForPage($page)->getValueOrDefault(); 304 } 305 306 if ($locale === null) { 307 // should never happen but yeah 308 $locale = 'en_US'; 309 LogUtility::error("Internal Error: No default locale could be determined. The locale was set to $locale", DateTag::CANONICAL); 310 } 311 312 /** 313 * If the user has set a lang 314 * Transform it as locale 315 */ 316 if (strlen(trim($locale)) === 2) { 317 $derivedLocale = strtolower($locale) . $localeSeparator . strtoupper($locale); 318 } else { 319 $derivedLocale = $locale; 320 } 321 322 return $dateTime->formatLocale($pattern, $derivedLocale); 323 324 } 325 326} 327