SmartString: Open-source string manipulation library with automatic HTML encoding and fluent chainable interface

12 posts by 4 authors in: Forums > CMS Builder
Last Post: Wednesday at 11:39am   (RSS)

By Dave - August 27 - edited: August 27

Hi All!

We're thrilled to announce the release of our latest open-source project: SmartString

SmartString is a powerful PHP library designed to simplify string manipulation while prioritizing web security. If you're familiar with how ZenDB fields work in CMSB, you'll find SmartString similar but with expanded features and standalone functionality.

Essentially instead of writing code like this: 

// Show last login date formatted as: Aug 27th, 2024
$timestamp = strtotime($user['lastLogin']);
$formattedDate = date('M jS, Y', $timestamp);
echo 'Last login: ' . htmlencode($formattedDate) . "\n";

You can write code like this: 

echo "Last login: {$user->lastLogin->dateFormat('M jS, Y')}}\n"; // Aug 27th, 2024

Here are the key Features:

  • Automatic XSS protection with HTML encoding
  • Fluent, chainable interface for expressive code
  • Flexible encoding options (HTML, URL, JSON)
  • Support for non-string data types and type conversion
  • Built-in formatting for dates and numbers
  • Conditional operations for cleaner code

You can read more and find the code on GitHub: https://github.com/interactivetools-com/SmartString/

This is a library we've been using internally and it's been greatly speeding up our development so we're excited to add it to CMSB over time and continue adding features.

If you can think of any common tasks in your frontend viewer code that previously required custom PHP code, let us know, and we may be able to automate them.  One feature on our roadmap is a maxWords() function for generating text snippets or article summaries.

Please feel free to post suggestions, bugs, or feature requests below.  Thank you for your continued support and happy coding!

Dave Edis - Senior Developer
interactivetools.com

Hi KennyH,

From what I understand, you can use the apply() method to use your functions:

// Example of usage
$text = "<p>This is a sample text with more than ten words to demonstrate the maxWords function.</p>";
$smartString = SmartString::new($text);

// Using apply() with the maxWords function
$result = $smartString->apply('maxWords', 5, true);

// Display the result
echo $result->value();  // Output: This is a sample text with more than ten words...
// Example of usage
$phoneNumber = "123-456-7890"; // Phone number to format
$smartString = SmartString::new($phoneNumber);

// Using apply() with the format_phone function
$result = $smartString->apply('format_phone', '1'); // Default country code is '1'

// Display the result
echo $result->value(); // Output: (123) 456-7890

But, Dave should be able to confirm.

Thanks,
Djulia

By Dave - August 30 - edited: August 30

Hi All, 

Djulia: Yes, you'll be able to use the functions as is, that's absolutely right and great examples, thanks!  We also want to add common ones to the library for people who don't already have them.  If you have any helper function you often use feel free to share.

Kenny: Why doesn't textLimit() strip out HTML?  Is the assumption that maxWords will be taking HTML content and textLimit will be taking text only content? 

Some possible methods I'm thinking about:

->toText()       // strip tags and convert HTML entities to chars
->maxWords()     // limit max words
->maxChars()     // limit max chars

// Then we could do: 
$product->description->toText()->maxWords(15, "...")

Let me know any thoughts or feedback.

Dave Edis - Senior Developer
interactivetools.com

Why doesn't textLimit() strip out HTML?  Is the assumption that maxWords will be taking HTML content and textLimit will be taking text only content? 

Good question. I might have gotten it backwards, beacause I do need the HTML stripped out of textLimit, since I only use it for meta descriptions. I probably should use a custom function. I think this would work (I'll do some more testing):

function metaDescriptionLimit($string, $length = 160, $replacer = '...', $breakWords = false) {
    // Strip HTML tags
    $string = strip_tags($string);
    
    // Convert to UTF-8 if not already
    $encoding = mb_detect_encoding($string, 'UTF-8, ISO-8859-1', true);
    $string = mb_convert_encoding($string, 'UTF-8', $encoding);

    // Trim whitespace
    $string = trim($string);

    if (mb_strlen($string, 'UTF-8') <= $length) {
        return $string;
    }

    if ($breakWords) {
        $limited = mb_substr($string, 0, $length, 'UTF-8');
    } else {
        $limited = mb_substr($string, 0, $length, 'UTF-8');
        $lastSpace = mb_strrpos($limited, ' ', 0, 'UTF-8');
        if ($lastSpace !== false) {
            $limited = mb_substr($limited, 0, $lastSpace, 'UTF-8');
        }
    }

    // Remove any partial words or punctuation at the end
    $limited = rtrim($limited, "!,.-");
    $limited = preg_replace('/\s+?(\S+)?$/', '', $limited);

    return $limited . $replacer;
}

By Djulia - September 3 - edited: September 4

Hi All,

I’m not sure if this will be useful for some, but these examples illustrate how to use the SmartString class for various operations such as type conversion, encoding, string manipulation, formatting, and arithmetic.

1. Creating a new SmartString Object

// Creating a SmartString from a string
$str = SmartString::new("Hello, World!");
echo $str->string(); // "Hello, World!"

// Creating a SmartString from an integer
$num = SmartString::new(123);
echo $num->int(); // 123

// Creating a SmartString from an array
$data = ['key1' => 'value1', 'key2' => 42];
$smartArray = SmartString::new($data);
print_r($smartArray);

2. Accessing and encoding values

$script = SmartString::new("<script>alert('XSS');</script>");

// Access original value
echo $script->value(); // <script>alert('XSS');</script>

// Output original value in string context
echo "{$script->noEncode()}"; // <script>alert('XSS');</script>

// HTML-encoded in string context
echo "$script"; // &lt;script&gt;alert(&apos;XSS&apos;);&lt;/script&gt;

3. Encoding methods

$comment = SmartString::new("<script>alert('XSS');</script>");

// HTML encode
echo $comment->htmlEncode(); // &lt;script&gt;alert(&apos;XSS&apos;);&lt;/script&gt;

// URL encode
echo $comment->urlEncode(); // %3Cscript%3Ealert%28%27XSS%27%29%3B%3C%2Fscript%3E

// JavaScript encode
echo $comment->apply('json_encode'); // "{\"script\":\"alert('XSS');\"}"

4. Type conversion

$value = SmartString::new("123.45");

// Convert to integer
echo $value->int(); // 123

// Convert to float
echo $value->float(); // 123.45

// Convert to boolean
echo $value->bool(); // 1 (true)

// Convert to string
echo $value->string(); // "123.45"

5. String manipulation

$htmlText = SmartString::new("<b>Some HTML</b>");

// Remove HTML tags
echo $htmlText->stripTags(); // Some HTML

// Convert newlines to <br> tags
$multiLineText = SmartString::new("Hello\nWorld");
echo $multiLineText->nl2br(); // Hello<br />World

// Trim whitespace
$whitespaceText = SmartString::new("  Trim me  ");
echo $whitespaceText->trim(); // Trim me

6. Formatting

$dateString = SmartString::new("2024-09-01");
echo $dateString->dateFormat('d/m/Y'); // 01/09/2024

$number = SmartString::new(1234567.89);
echo $number->numberFormat(2, ',', ' '); // 1 234 567,89

7. Numeric operations

$amount = SmartString::new(0.1234);
echo $amount->percent(); // 12.34%

$total = SmartString::new(200);
echo $amount->percentOf($total); // 6.17%

$subtracted = SmartString::new(100)->subtract(30);
echo $subtracted->float(); // 70

$divided = SmartString::new(100)->divide(4);
echo $divided->float(); // 25

8. Conditional operations

$empty = SmartString::new(null);
echo $empty->or("Default value"); // Default value

$nullable = SmartString::new(null);
echo $nullable->ifNull("Default value"); // Default value

$blank = SmartString::new("");
echo $blank->ifBlank("Default value"); // ""

9. Applying functions

$text = SmartString::new("hello world");

// Apply a function (convert to uppercase)
$upper = $text->apply('strtoupper');
echo $upper->string(); // HELLO WORLD

10. Display and debugging

$smartString = SmartString::new("Debug this");

// Using __toString()
echo $smartString; // HTML-encoded string

// Using __debugInfo()
print_r($smartString); // Displays debugging information

--

Dave: Do you see anything to correct or add?

Would it be possible to add the "add" and "multiply" methods in order to achieve full coverage of the basic arithmetic operations (addition, subtraction, multiplication, division, and percentage)?

Thanks,
Djulia

My apologies as I have not heard of the smartstring library until now, and I'm not the most proficient coder.  Is this a library that has to be called/initialized/loaded somewhere before using such functions...or is it ready for usage on the .php pages?  I have a need to limit a field (in a cmsb section) called last_name in which the client only wants the first letter of the last_name input to display on the public php page (testimonialDetails.php). So my code for display is currently (with no limit on the the number of characters):

<?php if ($testimonialsRecord['last_name']): ?>
  <br><br>
  <span class="pointtexti">
   <?php echo htmlencode($testimonialsRecord['last_name']) ?>
  </span>
<?php endif ?>

How would I specifically recode the above to limit the display of last_name to just the first letter of the person's last name?

By Djulia - September 4 - edited: September 4

Hi Codee,

I don't think SmartString can help you right now but you can use PHP's substr function to extract the first character of the last_name. Here's how you can update your code:

<?php if (!empty($testimonialsRecord['last_name'])): ?>
  <br><br>
  <span class="pointtexti">
    <?php 
      // Extract the first letter of the last name and encode it
      echo htmlencode(substr($testimonialsRecord['last_name'], 0, 1)); 
    ?>
  </span>
<?php endif ?>

https://www.php.net/manual/fr/function.substr.php

--
In the latest Beta version, the library SmartString is available without needing to install.

You can simply copy and paste a code snippet like this to test it, for example:

$dateString = SmartString::new("2024-09-01");
echo $dateString->dateFormat('d/m/Y'); // 01/09/2024

$number = SmartString::new(1234567.89);
echo $number->numberFormat(2, ',', ' '); // 1 234 567,89

Thanks,
Djulia

Hi Djulia,

Thank you!  That looks a bit cleaner than the method I was already using, which is to place the function before the last_name field line as:

<?PHP function textLimit($string, $length, $replacer = '...')
{
if(strlen($string) > $length)
return (preg_match('/^(.*)\W.*$/', substr($string, 0, $length+1), $matches) ? $matches[1] : substr($string, 0, $length)) . $replacer;

return $string;
}

?>

and then the php field line:

<?PHP echo textLimit($record['last_name'], 1) ?>

which works, but I'm going to try yours out. Thank you kindly!

Hi Codee,

Here’s a function I use across several projects.

/**
 * Shortens a full name by keeping only the initial of the last name.
 *
 * This function takes a full name (consisting of a first name and last name, 
 * possibly with middle names) and returns the name with the initial of the last name 
 * followed by a period.
 *
 * @param string $name The full name to be shortened.
 * @return string The shortened name with the last name's initial.
 *
 * @example
 * echo nameWithLastInitial("John Doe"); // Outputs "John D."
 * echo nameWithLastInitial("Jane Mary Smith"); // Outputs "Jane Mary S."
 */
function nameWithLastInitial($name) {
    // Split the full name into an array of words using space as a delimiter.
    $names = explode(' ', $name);
    
    // Retrieve the last element from the array (the last name) and extract the first letter.
    $last_initial = substr(array_pop($names), 0, 1);
    
    // Reconstruct the name with the last name's initial followed by a period.
    return implode(' ', $names) . ' ' . $last_initial . '.';
}

Thanks,
Djulia