Skip to content
Merged
Show file tree
Hide file tree
Changes from 4 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
1 change: 1 addition & 0 deletions packages/sprinkle-core/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/)
and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html).

## [6.0.0-beta.7](https://github.com/userfrosting/sprinkle-core/compare/6.0.0-beta.6...6.0.0-beta.7)
- [Markdown] MarkdownService now uses configuration from the config service to customize markdown parser behavior. New `markdown` config section added with `html_input`, `allow_unsafe_links`, and `max_nesting_level` options.

## [6.0.0-beta.6](https://github.com/userfrosting/sprinkle-core/compare/6.0.0-beta.5...6.0.0-beta.6)
- Fix deprecation with `thephpleague/csv`
Expand Down
13 changes: 13 additions & 0 deletions packages/sprinkle-core/app/config/default.php
Original file line number Diff line number Diff line change
Expand Up @@ -313,6 +313,19 @@
'path' => 'logs://userfrosting.log',
],

/*
* ----------------------------------------------------------------------
* Markdown Parser Config
* ----------------------------------------------------------------------
* Configuration for the CommonMark markdown parser.
* See https://commonmark.thephpleague.com/2.7/configuration/
*/
'markdown' => [
'html_input' => 'strip', // How to handle HTML (strip, escape, allow)
'allow_unsafe_links' => false, // Allow potentially unsafe links
'max_nesting_level' => 100, // Maximum nesting level
],

/*
* ----------------------------------------------------------------------
* Mail Service Config
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,14 +25,19 @@
* Markdown service. Add CommonMark markdown parser with GitHub Flavored Markdown frontmatter support.
*
* @see https://commonmark.thephpleague.com
*
* TODO : Should have a way to extend the markdown parser with custom extensions.
*/
class MarkdownService implements ServicesProviderInterface
{
public function register(): array
{
return [
ConverterInterface::class => function (Config $config) {
$environment = new Environment([]);
// Get markdown configuration from config service
$markdownConfig = $config->get('markdown', []);

$environment = new Environment($markdownConfig);
$environment->addExtension(new CommonMarkCoreExtension());
$environment->addExtension(new FrontMatterExtension());
$environment->addExtension(new GithubFlavoredMarkdownExtension());
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,149 @@
<?php

declare(strict_types=1);

/*
* UserFrosting Core Sprinkle (http://www.userfrosting.com)
*
* @link https://github.com/userfrosting/sprinkle-core
* @copyright Copyright (c) 2013-2024 Alexander Weissman & Louis Charette
* @license https://github.com/userfrosting/sprinkle-core/blob/master/LICENSE.md (MIT License)
*/

namespace UserFrosting\Sprinkle\Core\Tests\Unit\ServicesProvider;

use DI\Container;
use League\CommonMark\ConverterInterface;
use Mockery;
use Mockery\Adapter\Phpunit\MockeryPHPUnitIntegration;
use PHPUnit\Framework\TestCase;
use UserFrosting\Config\Config;
use UserFrosting\Sprinkle\Core\ServicesProvider\MarkdownService;
use UserFrosting\Testing\ContainerStub;

/**
* Unit tests for MarkdownService.
* Verify that the service properly uses config to customize markdown parser behavior.
*/
class MarkdownServiceTest extends TestCase
{
use MockeryPHPUnitIntegration;

protected Container $ci;

public function setUp(): void
{
parent::setUp();

// Create container with provider to test
$provider = new MarkdownService();
$this->ci = ContainerStub::create($provider->register());
}

public function testConverterWithDefaultConfig(): void
{
// Set mock Config with default markdown config
$config = Mockery::mock(Config::class)
->shouldReceive('get')
->with('markdown', [])
->once()
->andReturn([
'html_input' => 'strip',
'allow_unsafe_links' => false,
'max_nesting_level' => 100,
])
->getMock();

$this->ci->set(Config::class, $config);

// Get the converter
$converter = $this->ci->get(ConverterInterface::class);

// Verify it's a ConverterInterface
$this->assertInstanceOf(ConverterInterface::class, $converter);
}

public function testConverterWithEmptyConfig(): void
{
// Set mock Config that returns empty array
$config = Mockery::mock(Config::class)
->shouldReceive('get')
->with('markdown', [])
->once()
->andReturn([])
->getMock();

$this->ci->set(Config::class, $config);

// Get the converter - should still work with empty config
$converter = $this->ci->get(ConverterInterface::class);

$this->assertInstanceOf(ConverterInterface::class, $converter);
}

public function testConverterWithCustomConfig(): void
{
// Set mock Config with custom markdown config
$config = Mockery::mock(Config::class)
->shouldReceive('get')
->with('markdown', [])
->once()
->andReturn([
'html_input' => 'allow',
'allow_unsafe_links' => true,
'max_nesting_level' => 50,
])
->getMock();

$this->ci->set(Config::class, $config);

// Get the converter
$converter = $this->ci->get(ConverterInterface::class);

$this->assertInstanceOf(ConverterInterface::class, $converter);
}

public function testConverterCanConvertBasicMarkdown(): void
{
// Set mock Config
$config = Mockery::mock(Config::class)
->shouldReceive('get')
->with('markdown', [])
->once()
->andReturn([])
->getMock();

$this->ci->set(Config::class, $config);

// Get the converter
$converter = $this->ci->get(ConverterInterface::class);

// Test basic markdown conversion
$result = $converter->convert('# Hello World');
$this->assertStringContainsString('Hello World', (string) $result);
}

public function testConverterHandlesHtmlAccordingToConfig(): void
{
// Test with html_input set to 'strip'
$config = Mockery::mock(Config::class)
->shouldReceive('get')
->with('markdown', [])
->once()
->andReturn([
'html_input' => 'strip',
])
->getMock();

$this->ci->set(Config::class, $config);

$converter = $this->ci->get(ConverterInterface::class);

// Convert markdown with HTML
$result = $converter->convert('Hello <script>alert("xss")</script> World');
$output = (string) $result;

// With 'strip', the script tag should be removed
$this->assertStringNotContainsString('<script>', $output);
}
}
Loading