Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Attribute Casting #62

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,8 @@
"require": {
"php": "^7.2.5|^8.0|^8.1",
"illuminate/console": "^6.0|^7.0|^8.0|^9.0|^10.0",
"illuminate/support": "^6.0|^7.0|^8.0|^9.0|^10.0"
"illuminate/support": "^6.0|^7.0|^8.0|^9.0|^10.0",
"illuminate/database": "^6.0|^7.0|^8.0|^9.0|^10.0"
},
"require-dev": {
"orchestra/testbench": "~3.8.0|^4.0|^5.0|^6.3|^7.0|^8.0",
Expand Down
10 changes: 5 additions & 5 deletions src/Compiler.php
Original file line number Diff line number Diff line change
Expand Up @@ -93,9 +93,9 @@ protected static function shortcodeRegex(string $tag): string
public static function resolveAttributes(string $attributesText): ?array
{
$attributesText = preg_replace("/[\x{00a0}\x{200b}]+/u", ' ', $attributesText);

$attributes = collect([]);

if (preg_match_all(static::attributeRegex(), $attributesText, $matches, PREG_SET_ORDER)) {
foreach ($matches as $match) {
if (! empty($match[1])) {
Expand All @@ -112,13 +112,13 @@ public static function resolveAttributes(string $attributesText): ?array
$attributes[] = stripcslashes($match[9]);
}
}

// Reject any unclosed HTML elements.
$filteredAttributes = $attributes->filter(function ($attribute) {
if (strpos($attribute, '<') === false) {
return true;
}

return preg_match('/^[^<]*+(?:<[^>]*+>[^<]*+)*+$/', $attribute);
});

Expand All @@ -141,6 +141,6 @@ public static function resolveAttributes(string $attributesText): ?array
*/
protected static function attributeRegex(): string
{
return '/([\w-]+)\s*=\s*"([^"]*)"(?:\s|$)|([\w-]+)\s*=\s*\'([^\']*)\'(?:\s|$)|([\w-]+)\s*=\s*([^\s\'"]+)(?:\s|$)|"([^"]*)"(?:\s|$)|\'([^\']*)\'(?:\s|$)|(\S+)(?:\s|$)/';
return '/([\w.:-]+)\s*=\s*"([^"]*)"(?:\s|$)|([\w.:-]+)\s*=\s*\'([^\']*)\'(?:\s|$)|([\w.:-]+)\s*=\s*([^\s\'"]+)(?:\s|$)|"([^"]*)"(?:\s|$)|\'([^\']*)\'(?:\s|$)|(\S+)(?:\s|$)/';
}
}
114 changes: 109 additions & 5 deletions src/Shortcode.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,12 @@

use Illuminate\Support\Str;
use Illuminate\Support\Collection;
use Illuminate\Database\Eloquent\Concerns\HasAttributes;

abstract class Shortcode
{
use HasAttributes;

/**
* The cache of a list of Shortcode classes.
*
Expand All @@ -29,11 +32,11 @@ abstract class Shortcode
protected $tag;

/**
* The shortcode's attributes.
* Optional closing tag to match in content.
*
* @var array|null
* @var string
*/
protected $attributes = null;
protected $closingTag;

/**
* The shortcode's body content.
Expand All @@ -48,7 +51,7 @@ abstract class Shortcode
* @param array|null $attributes
* @param string|null $body
*/
public function __construct(array $attributes = null, string $body = null)
public function __construct(array $attributes = [], string $body = null)
{
$this->attributes = $attributes;

Expand Down Expand Up @@ -103,13 +106,56 @@ public function dispatch(array $matches): ?string
}

// Set up our inputs and run our handle.
$this->attributes = Compiler::resolveAttributes($attributes);
$this->attributes = $this->setAttributeDefaults(
Compiler::resolveAttributes($attributes)
);

$this->body = $body;

return $this->handle();
}

/**
* Set defaults for attributes without a value that should be cast to boolean.
* e.g. [shortcode boolean-attribute string="value"]
*
* @return void
*/
protected function setAttributeDefaults($attributes)
{
$attributes = [
...$this->getCastAttributeDefaults(),
...collect($attributes ?? [])
->mapWithKeys(function ($value, $key) {
if (array_key_exists($value, $this->casts) && in_array($this->casts[$value], ['boolean', 'bool'])) {
return [$value => true];
}

return [$key => $value];
})
->toArray()
];

return empty($attributes)? null : $attributes;
}

/**
* Retrieve the default values for attributes that should be cast to boolean.
*
* @return array
*/
public function getCastAttributeDefaults(): array
{
return collect($this->casts)
->filter(function($value){
return in_array($value, ['boolean', 'bool']);
})
->map(function(){
return false;
})
->toArray();
}

/**
* Retrieve all of the Shortcode classes.
*
Expand Down Expand Up @@ -198,4 +244,62 @@ public static function compile(string $content, Collection $shortcodes = null):
{
return Compiler::compile($content, $shortcodes);
}

/**
* Get the attributes that should be converted to dates.
*
* @return array
*/
public function getDates()
{
return [];
}

/**
* Get the casts array.
*
* @return array
*/
public function getCasts()
{
return $this->casts;
}

/**
* Get an attribute from the class.
*
* @param string $key
* @return mixed
*/
public function getAttribute($key)
{
if (! $key) {
return;
}

$key = $key === Str::camel($key) && !array_key_exists($key, $this->casts)
? Str::snake($key, '-')
: $key;

// If the attribute exists in the attribute array or has a "get" mutator we will
// get the attribute's value
if (array_key_exists($key, $this->attributes ?? []) ||
array_key_exists($key, $this->casts) ||
$this->hasGetMutator($key)) {
return $this->getAttributeValue($key);
}

return null;
}

/**
* Dynamically retrieve attributes on the class.
*
* @param string $key
* @return mixed
*/
public function __get($key)
{
return $this->getAttribute($key);
}
}
40 changes: 40 additions & 0 deletions tests/ShortcodeTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,12 @@

use tehwave\Shortcodes\Compiler;
use tehwave\Shortcodes\Shortcode;
use Illuminate\Support\Facades\Date;
use tehwave\Shortcodes\Tests\Shortcodes\CastDate;
use tehwave\Shortcodes\Tests\Shortcodes\CastFloat;
use tehwave\Shortcodes\Tests\Shortcodes\OutputBody;
use tehwave\Shortcodes\Tests\Shortcodes\CastBoolean;
use tehwave\Shortcodes\Tests\Shortcodes\CastInteger;
use tehwave\Shortcodes\Tests\Shortcodes\OutputAttributes;

class ShortcodeTest extends TestCase
Expand All @@ -28,6 +33,10 @@ protected function setUp(): void
$this->shortcodes = collect([
new OutputBody,
new OutputAttributes,
new CastBoolean,
new CastDate,
new CastFloat,
new CastInteger,
]);
}

Expand Down Expand Up @@ -135,4 +144,35 @@ public function testShortcodeAttributesSyntaxes(): void
$this->assertSame($expected, $compiledContent);
});
}

/**
* Test the various castings for attributes.
*
* @link https://unit-tests.svn.wordpress.org/trunk/tests/shortcode.php
*
* @return void
*/
public function testShortcodeAttributesCasting(): void
{
collect([
'[cast_boolean test-boolean]' => 'true',
// '[cast_boolean test-boolean="true"]' => 'true',
'[cast_boolean test-boolean="1"]' => 'true',
// '[cast_boolean test-boolean="false"]' => 'false',
'[cast_boolean test-boolean="0"]' => 'false',
'[cast_boolean /]' => 'false',
'[cast_date test-date="2023-06-29"]' => (string) Date::parse('2023-06-29')->timestamp,
'[cast_date test-date="2020-01-01"]' => (string) Date::parse('2020-01-01')->timestamp,
'[cast_integer test-int="3"]' => '6',
'[cast_integer test-int="35460"]' => '70920',
'[cast_float test-float="5.67"]' => '15.67',
'[cast_float test-float="15.011"]' => '25.011',
])->each(function ($output, $tag) {
$compiledContent = Compiler::compile($tag, $this->shortcodes);

$expected = $output;

$this->assertSame($expected, $compiledContent);
});
}
}
25 changes: 25 additions & 0 deletions tests/Shortcodes/CastBoolean.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
<?php

namespace tehwave\Shortcodes\Tests\Shortcodes;

use tehwave\Shortcodes\Shortcode;

class CastBoolean extends Shortcode
{
protected $casts = [
'test-boolean' => 'boolean',
];

/**
* The code to run when the Shortcode is being compiled.
*
* You may return a string from here, that will then
* be inserted into the content being compiled.
*
* @return string|null
*/
public function handle(): ?string
{
return $this->testBoolean? 'true' : 'false';
}
}
25 changes: 25 additions & 0 deletions tests/Shortcodes/CastDate.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
<?php

namespace tehwave\Shortcodes\Tests\Shortcodes;

use tehwave\Shortcodes\Shortcode;

class CastDate extends Shortcode
{
protected $casts = [
'test-date' => 'date',
];

/**
* The code to run when the Shortcode is being compiled.
*
* You may return a string from here, that will then
* be inserted into the content being compiled.
*
* @return string|null
*/
public function handle(): ?string
{
return (string) $this->testDate->timestamp;
}
}
25 changes: 25 additions & 0 deletions tests/Shortcodes/CastFloat.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
<?php

namespace tehwave\Shortcodes\Tests\Shortcodes;

use tehwave\Shortcodes\Shortcode;

class CastFloat extends Shortcode
{
protected $casts = [
'test-float' => 'float',
];

/**
* The code to run when the Shortcode is being compiled.
*
* You may return a string from here, that will then
* be inserted into the content being compiled.
*
* @return string|null
*/
public function handle(): ?string
{
return (string)($this->testFloat + 10);
}
}
25 changes: 25 additions & 0 deletions tests/Shortcodes/CastInteger.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
<?php

namespace tehwave\Shortcodes\Tests\Shortcodes;

use tehwave\Shortcodes\Shortcode;

class CastInteger extends Shortcode
{
protected $casts = [
'test-int' => 'integer',
];

/**
* The code to run when the Shortcode is being compiled.
*
* You may return a string from here, that will then
* be inserted into the content being compiled.
*
* @return string|null
*/
public function handle(): ?string
{
return (string)($this->testInt * 2);
}
}
Loading