Namespaces

  • Latte
    • Loaders
    • Macros
    • Runtime
  • Nette
    • Application
      • Responses
      • Routers
      • UI
    • Bridges
      • ApplicationDI
      • ApplicationLatte
      • ApplicationTracy
      • CacheDI
      • CacheLatte
      • DatabaseDI
      • DatabaseTracy
      • DITracy
      • FormsDI
      • FormsLatte
      • Framework
      • HttpDI
      • HttpTracy
      • MailDI
      • ReflectionDI
      • SecurityDI
      • SecurityTracy
    • Caching
      • Storages
    • ComponentModel
    • Database
      • Conventions
      • Drivers
      • Table
    • DI
      • Config
        • Adapters
      • Extensions
    • Forms
      • Controls
      • Rendering
    • Http
    • Iterators
    • Loaders
    • Localization
    • Mail
    • Neon
    • PhpGenerator
      • Traits
    • Reflection
    • Security
    • Tokenizer
    • Utils
  • Tracy
    • Bridges
      • Nette
  • none

Classes

  • CachingIterator
  • FilterExecutor
  • FilterInfo
  • Filters
  • Html
  • SnippetDriver
  • Template

Interfaces

  • IHtmlString
  • ISnippetBridge
  • Overview
  • Namespace
  • Class
  • Tree
  • Deprecated
  • Other releases
  1: <?php
  2: 
  3: /**
  4:  * This file is part of the Latte (https://latte.nette.org)
  5:  * Copyright (c) 2008 David Grudl (https://davidgrudl.com)
  6:  */
  7: 
  8: namespace Latte\Runtime;
  9: 
 10: use Latte;
 11: use Latte\Engine;
 12: 
 13: 
 14: /**
 15:  * Template filters. Uses UTF-8 only.
 16:  * @internal
 17:  */
 18: class Filters
 19: {
 20:     /** @deprecated */
 21:     public static $dateFormat = '%x';
 22: 
 23:     /** @internal @var bool  use XHTML syntax? */
 24:     public static $xhtml = false;
 25: 
 26: 
 27:     /**
 28:      * Escapes string for use inside HTML.
 29:      * @param  mixed  plain text
 30:      * @return string HTML
 31:      */
 32:     public static function escapeHtml($s)
 33:     {
 34:         return htmlspecialchars((string) $s, ENT_QUOTES, 'UTF-8');
 35:     }
 36: 
 37: 
 38:     /**
 39:      * Escapes string for use inside HTML.
 40:      * @param  mixed  plain text or IHtmlString
 41:      * @return string HTML
 42:      */
 43:     public static function escapeHtmlText($s)
 44:     {
 45:         return $s instanceof IHtmlString || $s instanceof \Nette\Utils\IHtmlString
 46:             ? $s->__toString(true)
 47:             : htmlspecialchars((string) $s, ENT_NOQUOTES, 'UTF-8');
 48:     }
 49: 
 50: 
 51:     /**
 52:      * Escapes string for use inside HTML attribute value.
 53:      * @param  string plain text
 54:      * @return string HTML
 55:      */
 56:     public static function escapeHtmlAttr($s, $double = true)
 57:     {
 58:         $double = $double && $s instanceof IHtmlString ? false : $double;
 59:         $s = (string) $s;
 60:         if (strpos($s, '`') !== false && strpbrk($s, ' <>"\'') === false) {
 61:             $s .= ' '; // protection against innerHTML mXSS vulnerability nette/nette#1496
 62:         }
 63:         return htmlspecialchars($s, ENT_QUOTES, 'UTF-8', $double);
 64:     }
 65: 
 66: 
 67:     /**
 68:      * Escapes HTML for use inside HTML attribute.
 69:      * @param  mixed  HTML text
 70:      * @return string HTML
 71:      */
 72:     public static function escapeHtmlAttrConv($s)
 73:     {
 74:         return self::escapeHtmlAttr($s, false);
 75:     }
 76: 
 77: 
 78:     /**
 79:      * Escapes string for use inside HTML attribute name.
 80:      * @param  string plain text
 81:      * @return string HTML
 82:      */
 83:     public static function escapeHtmlAttrUnquoted($s)
 84:     {
 85:         $s = (string) $s;
 86:         return preg_match('#^[a-z0-9:-]+$#i', $s)
 87:             ? $s
 88:             : '"' . self::escapeHtmlAttr($s) . '"';
 89:     }
 90: 
 91: 
 92:     /**
 93:      * Escapes string for use inside HTML comments.
 94:      * @param  string plain text
 95:      * @return string HTML
 96:      */
 97:     public static function escapeHtmlComment($s)
 98:     {
 99:         $s = (string) $s;
100:         if ($s && ($s[0] === '-' || $s[0] === '>' || $s[0] === '!')) {
101:             $s = ' ' . $s;
102:         }
103:         $s = str_replace('--', '- - ', $s);
104:         if (substr($s, -1) === '-') {
105:             $s .= ' ';
106:         }
107:         return $s;
108:     }
109: 
110: 
111:     /**
112:      * Escapes string for use inside XML 1.0 template.
113:      * @param  string plain text
114:      * @return string XML
115:      */
116:     public static function escapeXml($s)
117:     {
118:         // XML 1.0: \x09 \x0A \x0D and C1 allowed directly, C0 forbidden
119:         // XML 1.1: \x00 forbidden directly and as a character reference,
120:         //   \x09 \x0A \x0D \x85 allowed directly, C0, C1 and \x7F allowed as character references
121:         return htmlspecialchars(preg_replace('#[\x00-\x08\x0B\x0C\x0E-\x1F]+#', '', (string) $s), ENT_QUOTES, 'UTF-8');
122:     }
123: 
124: 
125:     /**
126:      * Escapes string for use inside XML attribute name.
127:      * @param  string plain text
128:      * @return string XML
129:      */
130:     public static function escapeXmlAttrUnquoted($s)
131:     {
132:         $s = (string) $s;
133:         return preg_match('#^[a-z0-9:-]+$#i', $s)
134:             ? $s
135:             : '"' . self::escapeXml($s) . '"';
136:     }
137: 
138: 
139:     /**
140:      * Escapes string for use inside CSS template.
141:      * @param  string plain text
142:      * @return string CSS
143:      */
144:     public static function escapeCss($s)
145:     {
146:         // http://www.w3.org/TR/2006/WD-CSS21-20060411/syndata.html#q6
147:         return addcslashes((string) $s, "\x00..\x1F!\"#$%&'()*+,./:;<=>?@[\\]^`{|}~");
148:     }
149: 
150: 
151:     /**
152:      * Escapes variables for use inside <script>.
153:      * @param  mixed  plain text
154:      * @return string JSON
155:      */
156:     public static function escapeJs($s)
157:     {
158:         if ($s instanceof IHtmlString || $s instanceof \Nette\Utils\IHtmlString) {
159:             $s = $s->__toString(true);
160:         }
161: 
162:         $json = json_encode($s, JSON_UNESCAPED_UNICODE);
163:         if ($error = json_last_error()) {
164:             throw new \RuntimeException(PHP_VERSION_ID >= 50500 ? json_last_error_msg() : 'JSON encode error', $error);
165:         }
166: 
167:         return str_replace(["\xe2\x80\xa8", "\xe2\x80\xa9", ']]>', '<!'], ['\u2028', '\u2029', ']]\x3E', '\x3C!'], $json);
168:     }
169: 
170: 
171:     /**
172:      * Escapes string for use inside iCal template.
173:      * @param  string plain text
174:      * @return string
175:      */
176:     public static function escapeICal($s)
177:     {
178:         // https://www.ietf.org/rfc/rfc5545.txt
179:         return addcslashes(preg_replace('#[\x00-\x08\x0B\x0C-\x1F]+#', '', (string) $s), "\";\\,:\n");
180:     }
181: 
182: 
183:     /**
184:      * Escapes CSS/JS for usage in <script> and <style>..
185:      * @param  string CSS/JS
186:      * @return string HTML RAWTEXT
187:      */
188:     public static function escapeHtmlRawText($s)
189:     {
190:         return preg_replace('#</(script|style)#i', '<\\/$1', (string) $s);
191:     }
192: 
193: 
194:     /**
195:      * Converts HTML to plain text.
196:      * @param
197:      * @param  string HTML
198:      * @return string plain text
199:      */
200:     public static function stripHtml(FilterInfo $info, $s)
201:     {
202:         if (!in_array($info->contentType, [null, 'html', 'xhtml', 'htmlAttr', 'xhtmlAttr', 'xml', 'xmlAttr'], true)) {
203:             trigger_error('Filter |stripHtml used with incompatible type ' . strtoupper($info->contentType), E_USER_WARNING);
204:         }
205:         $info->contentType = Engine::CONTENT_TEXT;
206:         return html_entity_decode(strip_tags((string) $s), ENT_QUOTES, 'UTF-8');
207:     }
208: 
209: 
210:     /**
211:      * Removes tags from HTML (but remains HTML entites).
212:      * @param
213:      * @param  string HTML
214:      * @return string HTML
215:      */
216:     public static function stripTags(FilterInfo $info, $s)
217:     {
218:         if (!in_array($info->contentType, [null, 'html', 'xhtml', 'htmlAttr', 'xhtmlAttr', 'xml', 'xmlAttr'], true)) {
219:             trigger_error('Filter |stripTags used with incompatible type ' . strtoupper($info->contentType), E_USER_WARNING);
220:         }
221:         return strip_tags((string) $s);
222:     }
223: 
224: 
225:     /**
226:      * Converts ... to ...
227:      * @return string
228:      */
229:     public static function convertTo(FilterInfo $info, $dest, $s)
230:     {
231:         $source = $info->contentType ?: Engine::CONTENT_TEXT;
232:         if ($source === $dest) {
233:             return $s;
234:         } elseif ($conv = self::getConvertor($source, $dest)) {
235:             $info->contentType = $dest;
236:             return $conv($s);
237:         } else {
238:             trigger_error('Filters: unable to convert content type ' . strtoupper($source) . ' to ' . strtoupper($dest), E_USER_WARNING);
239:             return $s;
240:         }
241:     }
242: 
243: 
244:     /**
245:      * @return callable|null
246:      */
247:     public static function getConvertor($source, $dest)
248:     {
249:         static $table = [
250:             Engine::CONTENT_TEXT => [
251:                 'html' => 'escapeHtmlText', 'xhtml' => 'escapeHtmlText',
252:                 'htmlAttr' => 'escapeHtmlAttr', 'xhtmlAttr' => 'escapeHtmlAttr',
253:                 'htmlAttrJs' => 'escapeHtmlAttr', 'xhtmlAttrJs' => 'escapeHtmlAttr',
254:                 'htmlAttrCss' => 'escapeHtmlAttr', 'xhtmlAttrCss' => 'escapeHtmlAttr',
255:                 'htmlAttrUrl' => 'escapeHtmlAttr', 'xhtmlAttrUrl' => 'escapeHtmlAttr',
256:                 'htmlComment' => 'escapeHtmlComment', 'xhtmlComment' => 'escapeHtmlComment',
257:                 'xml' => 'escapeXml', 'xmlAttr' => 'escapeXml',
258:             ],
259:             Engine::CONTENT_JS => [
260:                 'html' => 'escapeHtmlText', 'xhtml' => 'escapeHtmlText',
261:                 'htmlAttr' => 'escapeHtmlAttr', 'xhtmlAttr' => 'escapeHtmlAttr',
262:                 'htmlAttrJs' => 'escapeHtmlAttr', 'xhtmlAttrJs' => 'escapeHtmlAttr',
263:                 'htmlJs' => 'escapeHtmlRawText', 'xhtmlJs' => 'escapeHtmlRawText',
264:                 'htmlComment' => 'escapeHtmlComment', 'xhtmlComment' => 'escapeHtmlComment',
265:             ],
266:             Engine::CONTENT_CSS => [
267:                 'html' => 'escapeHtmlText', 'xhtml' => 'escapeHtmlText',
268:                 'htmlAttr' => 'escapeHtmlAttr', 'xhtmlAttr' => 'escapeHtmlAttr',
269:                 'htmlAttrCss' => 'escapeHtmlAttr', 'xhtmlAttrCss' => 'escapeHtmlAttr',
270:                 'htmlCss' => 'escapeHtmlRawText', 'xhtmlCss' => 'escapeHtmlRawText',
271:                 'htmlComment' => 'escapeHtmlComment', 'xhtmlComment' => 'escapeHtmlComment',
272:             ],
273:             Engine::CONTENT_HTML => [
274:                 'htmlAttr' => 'escapeHtmlAttrConv',
275:                 'htmlAttrJs' => 'escapeHtmlAttrConv',
276:                 'htmlAttrCss' => 'escapeHtmlAttrConv',
277:                 'htmlAttrUrl' => 'escapeHtmlAttrConv',
278:                 'htmlComment' => 'escapeHtmlComment',
279:             ],
280:             Engine::CONTENT_XHTML => [
281:                 'xhtmlAttr' => 'escapeHtmlAttrConv',
282:                 'xhtmlAttrJs' => 'escapeHtmlAttrConv',
283:                 'xhtmlAttrCss' => 'escapeHtmlAttrConv',
284:                 'xhtmlAttrUrl' => 'escapeHtmlAttrConv',
285:                 'xhtmlComment' => 'escapeHtmlComment',
286:             ],
287:         ];
288:         return isset($table[$source][$dest]) ? [__CLASS__, $table[$source][$dest]] : null;
289:     }
290: 
291: 
292:     /**
293:      * Sanitizes string for use inside href attribute.
294:      * @param  string plain text
295:      * @return string plain text
296:      */
297:     public static function safeUrl($s)
298:     {
299:         $s = (string) $s;
300:         return preg_match('~^(?:(?:https?|ftp)://[^@]+(?:/.*)?|mailto:.+|[/?#].*|[^:]+)\z~i', $s) ? $s : '';
301:     }
302: 
303: 
304:     /**
305:      * Replaces all repeated white spaces with a single space.
306:      * @param
307:      * @param  string text|HTML
308:      * @return string text|HTML
309:      */
310:     public static function strip(FilterInfo $info, $s)
311:     {
312:         return in_array($info->contentType, [Engine::CONTENT_HTML, Engine::CONTENT_XHTML], true)
313:             ? trim(self::spacelessHtml($s))
314:             : trim(self::spacelessText($s));
315:     }
316: 
317: 
318:     /**
319:      * Replaces all repeated white spaces with a single space.
320:      * @param  string HTML
321:      * @param  int output buffering phase
322:      * @param  bool stripping mode
323:      * @return string HTML
324:      */
325:     public static function spacelessHtml($s, $phase = null, &$strip = true)
326:     {
327:         if ($phase & PHP_OUTPUT_HANDLER_START) {
328:             $s = ltrim($s);
329:         }
330:         if ($phase & PHP_OUTPUT_HANDLER_FINAL) {
331:             $s = rtrim($s);
332:         }
333:         return preg_replace_callback(
334:             '#[ \t\r\n]+|<(/)?(textarea|pre|script)(?=\W)#si',
335:             function ($m) use (&$strip) {
336:                 if (empty($m[2])) {
337:                     return $strip ? ' ' : $m[0];
338:                 } else {
339:                     $strip = !empty($m[1]);
340:                     return $m[0];
341:                 }
342:             },
343:             $s
344:         );
345:     }
346: 
347: 
348:     /**
349:      * Replaces all repeated white spaces with a single space.
350:      * @param  string text
351:      * @return string text
352:      */
353:     public static function spacelessText($s)
354:     {
355:         return preg_replace('#[ \t\r\n]+#', ' ', $s);
356:     }
357: 
358: 
359:     /**
360:      * Indents the content from the left.
361:      * @param
362:      * @param  string text|HTML
363:      * @param  int
364:      * @param  string
365:      * @return string text|HTML
366:      */
367:     public static function indent(FilterInfo $info, $s, $level = 1, $chars = "\t")
368:     {
369:         if ($level < 1) {
370:             // do nothing
371:         } elseif (in_array($info->contentType, [Engine::CONTENT_HTML, Engine::CONTENT_XHTML], true)) {
372:             $s = preg_replace_callback('#<(textarea|pre).*?</\\1#si', function ($m) {
373:                 return strtr($m[0], " \t\r\n", "\x1F\x1E\x1D\x1A");
374:             }, $s);
375:             if (preg_last_error()) {
376:                 throw new Latte\RegexpException(null, preg_last_error());
377:             }
378:             $s = preg_replace('#(?:^|[\r\n]+)(?=[^\r\n])#', '$0' . str_repeat($chars, $level), $s);
379:             $s = strtr($s, "\x1F\x1E\x1D\x1A", " \t\r\n");
380:         } else {
381:             $s = preg_replace('#(?:^|[\r\n]+)(?=[^\r\n])#', '$0' . str_repeat($chars, $level), $s);
382:         }
383:         return $s;
384:     }
385: 
386: 
387:     /**
388:      * Repeats text.
389:      * @param
390:      * @param  string
391:      * @param  int
392:      * @return string plain text
393:      */
394:     public static function repeat(FilterInfo $info, $s, $count)
395:     {
396:         return str_repeat((string) $s, $count);
397:     }
398: 
399: 
400:     /**
401:      * Date/time formatting.
402:      * @param  string|int|\DateTime|\DateTimeInterface|\DateInterval
403:      * @param  string
404:      * @return string|null
405:      */
406:     public static function date($time, $format = null)
407:     {
408:         if ($time == null) { // intentionally ==
409:             return null;
410:         }
411: 
412:         if (!isset($format)) {
413:             $format = self::$dateFormat;
414:         }
415: 
416:         if ($time instanceof \DateInterval) {
417:             return $time->format($format);
418: 
419:         } elseif (is_numeric($time)) {
420:             $time = new \DateTime('@' . $time);
421:             $time->setTimeZone(new \DateTimeZone(date_default_timezone_get()));
422: 
423:         } elseif (!$time instanceof \DateTime && !$time instanceof \DateTimeInterface) {
424:             $time = new \DateTime($time);
425:         }
426:         return strpos($format, '%') === false
427:             ? $time->format($format) // formats using date()
428:             : strftime($format, $time->format('U') + 0); // formats according to locales
429:     }
430: 
431: 
432:     /**
433:      * Converts to human readable file size.
434:      * @param  float
435:      * @param  int
436:      * @return string plain text
437:      */
438:     public static function bytes($bytes, $precision = 2)
439:     {
440:         $bytes = round($bytes);
441:         $units = ['B', 'kB', 'MB', 'GB', 'TB', 'PB'];
442:         foreach ($units as $unit) {
443:             if (abs($bytes) < 1024 || $unit === end($units)) {
444:                 break;
445:             }
446:             $bytes = $bytes / 1024;
447:         }
448:         return round($bytes, $precision) . ' ' . $unit;
449:     }
450: 
451: 
452:     /**
453:      * Performs a search and replace.
454:      * @param
455:      * @param  string
456:      * @param  string
457:      * @param  string
458:      * @return string
459:      */
460:     public static function replace(FilterInfo $info, $subject, $search, $replacement = '')
461:     {
462:         return str_replace($search, $replacement, (string) $subject);
463:     }
464: 
465: 
466:     /**
467:      * Perform a regular expression search and replace.
468:      * @param  string
469:      * @param  string
470:      * @return string
471:      */
472:     public static function replaceRe($subject, $pattern, $replacement = '')
473:     {
474:         $res = preg_replace($pattern, $replacement, $subject);
475:         if (preg_last_error()) {
476:             throw new Latte\RegexpException(null, preg_last_error());
477:         }
478:         return $res;
479:     }
480: 
481: 
482:     /**
483:      * The data: URI generator.
484:      * @param  string plain text
485:      * @param  string
486:      * @return string plain text
487:      */
488:     public static function dataStream($data, $type = null)
489:     {
490:         if ($type === null) {
491:             $type = finfo_buffer(finfo_open(FILEINFO_MIME_TYPE), $data);
492:         }
493:         return 'data:' . ($type ? "$type;" : '') . 'base64,' . base64_encode($data);
494:     }
495: 
496: 
497:     /**
498:      * @param  string
499:      * @return string
500:      */
501:     public static function nl2br($value)
502:     {
503:         trigger_error('Filter |nl2br is deprecated, use |breaklines which correctly handles escaping.', E_USER_DEPRECATED);
504:         return nl2br($value, self::$xhtml);
505:     }
506: 
507: 
508:     /**
509:      * @param  string plain text
510:      * @return Html
511:      */
512:     public static function breaklines($s)
513:     {
514:         return new Html(nl2br(htmlspecialchars((string) $s, ENT_NOQUOTES, 'UTF-8'), self::$xhtml));
515:     }
516: 
517: 
518:     /**
519:      * Returns a part of string.
520:      * @param  string
521:      * @param  int
522:      * @param  int
523:      * @return string
524:      */
525:     public static function substring($s, $start, $length = null)
526:     {
527:         $s = (string) $s;
528:         if ($length === null) {
529:             $length = self::strLength($s);
530:         }
531:         if (function_exists('mb_substr')) {
532:             return mb_substr($s, $start, $length, 'UTF-8'); // MB is much faster
533:         }
534:         return iconv_substr($s, $start, $length, 'UTF-8');
535:     }
536: 
537: 
538:     /**
539:      * Truncates string to maximal length.
540:      * @param  string plain text
541:      * @param  int
542:      * @param  string plain text
543:      * @return string plain text
544:      */
545:     public static function truncate($s, $maxLen, $append = "\xE2\x80\xA6")
546:     {
547:         $s = (string) $s;
548:         if (self::strLength($s) > $maxLen) {
549:             $maxLen = $maxLen - self::strLength($append);
550:             if ($maxLen < 1) {
551:                 return $append;
552: 
553:             } elseif (preg_match('#^.{1,' . $maxLen . '}(?=[\s\x00-/:-@\[-`{-~])#us', $s, $matches)) {
554:                 return $matches[0] . $append;
555: 
556:             } else {
557:                 return self::substring($s, 0, $maxLen) . $append;
558:             }
559:         }
560:         return $s;
561:     }
562: 
563: 
564:     /**
565:      * Convert to lower case.
566:      * @param  string plain text
567:      * @return string plain text
568:      */
569:     public static function lower($s)
570:     {
571:         return mb_strtolower((string) $s, 'UTF-8');
572:     }
573: 
574: 
575:     /**
576:      * Convert to upper case.
577:      * @param  string plain text
578:      * @return string plain text
579:      */
580:     public static function upper($s)
581:     {
582:         return mb_strtoupper((string) $s, 'UTF-8');
583:     }
584: 
585: 
586:     /**
587:      * Convert first character to upper case.
588:      * @param  string plain text
589:      * @return string plain text
590:      */
591:     public static function firstUpper($s)
592:     {
593:         $s = (string) $s;
594:         return self::upper(self::substring($s, 0, 1)) . self::substring($s, 1);
595:     }
596: 
597: 
598:     /**
599:      * Capitalize string.
600:      * @param  string plain text
601:      * @return string plain text
602:      */
603:     public static function capitalize($s)
604:     {
605:         return mb_convert_case((string) $s, MB_CASE_TITLE, 'UTF-8');
606:     }
607: 
608: 
609:     /**
610:      * Returns length of string or iterable.
611:      * @param  array|\Countable|\Traversable|string
612:      * @return int
613:      */
614:     public static function length($val)
615:     {
616:         if (is_array($val) || $val instanceof \Countable) {
617:             return count($val);
618:         } elseif ($val instanceof \Traversable) {
619:             return iterator_count($val);
620:         } else {
621:             return self::strLength($val);
622:         }
623:     }
624: 
625: 
626:     /**
627:      * @param  string
628:      * @return int
629:      */
630:     private static function strLength($s)
631:     {
632:         return function_exists('mb_strlen') ? mb_strlen($s, 'UTF-8') : strlen(utf8_decode($s));
633:     }
634: 
635: 
636:     /**
637:      * Strips whitespace.
638:      * @param  string
639:      * @param  string
640:      * @return string
641:      */
642:     public static function trim(FilterInfo $info, $s, $charlist = " \t\n\r\0\x0B\xC2\xA0")
643:     {
644:         $charlist = preg_quote($charlist, '#');
645:         $s = preg_replace('#^[' . $charlist . ']+|[' . $charlist . ']+\z#u', '', (string) $s);
646:         if (preg_last_error()) {
647:             throw new Latte\RegexpException(null, preg_last_error());
648:         }
649:         return $s;
650:     }
651: 
652: 
653:     /**
654:      * Pad a string to a certain length with another string.
655:      * @param  string plain text
656:      * @param  int
657:      * @param  string
658:      * @return string
659:      */
660:     public static function padLeft($s, $length, $pad = ' ')
661:     {
662:         $s = (string) $s;
663:         $length = max(0, $length - self::strLength($s));
664:         $padLen = self::strLength($pad);
665:         return str_repeat($pad, (int) ($length / $padLen)) . self::substring($pad, 0, $length % $padLen) . $s;
666:     }
667: 
668: 
669:     /**
670:      * Pad a string to a certain length with another string.
671:      * @param  string plain text
672:      * @param  int
673:      * @param  string
674:      * @return string
675:      */
676:     public static function padRight($s, $length, $pad = ' ')
677:     {
678:         $s = (string) $s;
679:         $length = max(0, $length - self::strLength($s));
680:         $padLen = self::strLength($pad);
681:         return $s . str_repeat($pad, (int) ($length / $padLen)) . self::substring($pad, 0, $length % $padLen);
682:     }
683: 
684: 
685:     /**
686:      * Reverses string or array.
687:      * @param  string|array|\Traversable
688:      */
689:     public static function reverse($val, $preserveKeys = false)
690:     {
691:         if (is_array($val)) {
692:             return array_reverse($val, $preserveKeys);
693:         } elseif ($val instanceof \Traversable) {
694:             return array_reverse(iterator_to_array($val), $preserveKeys);
695:         } else {
696:             return iconv('UTF-32LE', 'UTF-8', strrev(iconv('UTF-8', 'UTF-32BE', (string) $val)));
697:         }
698:     }
699: 
700: 
701:     /**
702:      * Returns element's attributes.
703:      * @return string
704:      */
705:     public static function htmlAttributes($attrs)
706:     {
707:         if (!is_array($attrs)) {
708:             return '';
709:         }
710: 
711:         $s = '';
712:         foreach ($attrs as $key => $value) {
713:             if ($value === null || $value === false) {
714:                 continue;
715: 
716:             } elseif ($value === true) {
717:                 if (static::$xhtml) {
718:                     $s .= ' ' . $key . '="' . $key . '"';
719:                 } else {
720:                     $s .= ' ' . $key;
721:                 }
722:                 continue;
723: 
724:             } elseif (is_array($value)) {
725:                 $tmp = null;
726:                 foreach ($value as $k => $v) {
727:                     if ($v != null) { // intentionally ==, skip nulls & empty string
728:                         //  composite 'style' vs. 'others'
729:                         $tmp[] = $v === true ? $k : (is_string($k) ? $k . ':' . $v : $v);
730:                     }
731:                 }
732:                 if ($tmp === null) {
733:                     continue;
734:                 }
735: 
736:                 $value = implode($key === 'style' || !strncmp($key, 'on', 2) ? ';' : ' ', $tmp);
737: 
738:             } else {
739:                 $value = (string) $value;
740:             }
741: 
742:             $q = strpos($value, '"') === false ? '"' : "'";
743:             $s .= ' ' . $key . '=' . $q
744:                 . str_replace(
745:                     ['&', $q, '<'],
746:                     ['&amp;', $q === '"' ? '&quot;' : '&#39;', self::$xhtml ? '&lt;' : '<'],
747:                     $value
748:                 )
749:                 . (strpos($value, '`') !== false && strpbrk($value, ' <>"\'') === false ? ' ' : '')
750:                 . $q;
751:         }
752:         return $s;
753:     }
754: }
755: 
Nette 2.4-20180918 API API documentation generated by ApiGen 2.8.0