OneBot: Leantime 改造版源码(BOM/Univer 表格/AI 接管/品牌替换等)
This commit is contained in:
34
app/Core/Support/String/AlphaNumeric.php
Normal file
34
app/Core/Support/String/AlphaNumeric.php
Normal file
@@ -0,0 +1,34 @@
|
||||
<?php
|
||||
|
||||
namespace Leantime\Core\Support\String;
|
||||
|
||||
/**
|
||||
* @mixin \Illuminate\Support\Stringable
|
||||
*/
|
||||
class AlphaNumeric
|
||||
{
|
||||
/**
|
||||
* Cleans a string by removing special characters and optionally spaces.
|
||||
*
|
||||
* @param bool $removeSpaces Whether to remove spaces from the string.
|
||||
* @return callable A function that cleans a string based on the given parameter.
|
||||
*/
|
||||
public function alphaNumeric($removeSpaces = false)
|
||||
{
|
||||
return function ($value) use ($removeSpaces) {
|
||||
$cleaned = preg_replace('/[^A-Za-z0-9 ]/', '', (string) $value);
|
||||
|
||||
if ($removeSpaces) {
|
||||
$cleaned = str_replace(' ', '', $cleaned);
|
||||
} else {
|
||||
// Step 2: Replace multiple spaces with a single space
|
||||
$cleaned = preg_replace('/\s+/', ' ', $cleaned);
|
||||
}
|
||||
|
||||
// Step 3: Trim leading and trailing spaces
|
||||
$cleaned = trim($cleaned);
|
||||
|
||||
return $cleaned;
|
||||
};
|
||||
}
|
||||
}
|
||||
41
app/Core/Support/String/BeautifyFilename.php
Normal file
41
app/Core/Support/String/BeautifyFilename.php
Normal file
@@ -0,0 +1,41 @@
|
||||
<?php
|
||||
|
||||
namespace Leantime\Core\Support\String;
|
||||
|
||||
/**
|
||||
* @mixin \Illuminate\Support\Stringable
|
||||
*/
|
||||
class BeautifyFilename
|
||||
{
|
||||
/**
|
||||
* Beautifies a filename by normalizing characters and formatting.
|
||||
*
|
||||
* @return callable A function that beautifies a filename
|
||||
*/
|
||||
public function beautifyFilename()
|
||||
{
|
||||
return function ($filename) {
|
||||
// reduce consecutive characters
|
||||
$filename = preg_replace([
|
||||
// "file name.zip" becomes "file-name.zip"
|
||||
'/ +/',
|
||||
// "file___name.zip" becomes "file-name.zip"
|
||||
'/_+/',
|
||||
// "file---name.zip" becomes "file-name.zip"
|
||||
'/-+/',
|
||||
], '-', $filename);
|
||||
$filename = preg_replace([
|
||||
// "file--.--.-.--name.zip" becomes "file.name.zip"
|
||||
'/-*\.-*/',
|
||||
// "file...name..zip" becomes "file.name.zip"
|
||||
'/\.{2,}/',
|
||||
], '.', $filename);
|
||||
// lowercase for windows/unix interoperability http://support.microsoft.com/kb/100625
|
||||
$filename = mb_strtolower($filename, mb_detect_encoding($filename));
|
||||
// ".file-name.-" becomes "file-name"
|
||||
$filename = trim($filename, '.-');
|
||||
|
||||
return $filename;
|
||||
};
|
||||
}
|
||||
}
|
||||
51
app/Core/Support/String/SanitizeFilename.php
Normal file
51
app/Core/Support/String/SanitizeFilename.php
Normal file
@@ -0,0 +1,51 @@
|
||||
<?php
|
||||
|
||||
namespace Leantime\Core\Support\String;
|
||||
|
||||
use Illuminate\Support\Str;
|
||||
|
||||
/**
|
||||
* @mixin \Illuminate\Support\Stringable
|
||||
*/
|
||||
class SanitizeFilename
|
||||
{
|
||||
/**
|
||||
* Sanitizes a filename by removing or replacing unsafe characters.
|
||||
*
|
||||
* @param bool $beautify Whether to beautify the filename
|
||||
* @return callable A function that sanitizes a filename
|
||||
*/
|
||||
public function sanitizeFilename($beautify = true)
|
||||
{
|
||||
return function ($filename) use ($beautify) {
|
||||
// sanitize filename
|
||||
$filename = preg_replace(
|
||||
'~
|
||||
[<>:"/\\\|?*]| # file system reserved https://en.wikipedia.org/wiki/Filename#Reserved_characters_and_words
|
||||
[\x00-\x1F]| # control characters http://msdn.microsoft.com/en-us/library/windows/desktop/aa365247%28v=vs.85%29.aspx
|
||||
[\x7F\xA0\xAD]| # non-printing characters DEL, NO-BREAK SPACE, SOFT HYPHEN
|
||||
[#\[\]@!$&\'()+,;=]| # URI reserved https://www.rfc-editor.org/rfc/rfc3986#section-2.2
|
||||
[{}^\~`] # URL unsafe characters https://www.ietf.org/rfc/rfc1738.txt
|
||||
~x',
|
||||
'-',
|
||||
$filename
|
||||
);
|
||||
// avoids ".", ".." or ".hiddenFiles"
|
||||
$filename = ltrim($filename, '.-');
|
||||
// optional beautification
|
||||
if ($beautify) {
|
||||
$filename = Str::beautifyFilename($filename);
|
||||
}
|
||||
// maximize filename length to 255 bytes http://serverfault.com/a/9548/44086
|
||||
$ext = pathinfo($filename, PATHINFO_EXTENSION);
|
||||
$filename = mb_strcut(
|
||||
pathinfo($filename, PATHINFO_FILENAME),
|
||||
0,
|
||||
255 - ($ext ? strlen($ext) + 1 : 0),
|
||||
mb_detect_encoding($filename)
|
||||
).($ext ? '.'.$ext : '');
|
||||
|
||||
return $filename;
|
||||
};
|
||||
}
|
||||
}
|
||||
99
app/Core/Support/String/SanitizeForLLM.php
Normal file
99
app/Core/Support/String/SanitizeForLLM.php
Normal file
@@ -0,0 +1,99 @@
|
||||
<?php
|
||||
|
||||
namespace Leantime\Core\Support\String;
|
||||
|
||||
/**
|
||||
* @mixin \Illuminate\Support\Stringable
|
||||
*/
|
||||
class SanitizeForLLM
|
||||
{
|
||||
/**
|
||||
* Sanitizes string for safe use with LLM APIs by removing potential prompt injection patterns
|
||||
* and other problematic characters that could interfere with JSON serialization or system prompts.
|
||||
*
|
||||
* @return callable A function that sanitizes a string for LLM processing
|
||||
*/
|
||||
public function sanitizeForLLM()
|
||||
{
|
||||
return function ($value, bool $removeNewlines = false) {
|
||||
|
||||
if (! is_string($value)) {
|
||||
return $value ?? '';
|
||||
}
|
||||
|
||||
// Step 1: Replace line breaks with space
|
||||
$result = str_replace(["\r\n", "\r"], "\n", $value);
|
||||
|
||||
// Step 2: Escape JSON special characters except newlines
|
||||
$result = str_replace(
|
||||
['\\', '"', "\t", "\f", "\b"],
|
||||
['\\\\', '\\"', ' ', ' ', ' '],
|
||||
$result
|
||||
);
|
||||
|
||||
// Step 3: Replace problematic characters with safe alternatives
|
||||
$replacements = [
|
||||
// Replace backslashes with forward slashes (for paths)
|
||||
'\\' => '/',
|
||||
|
||||
// Replace double quotes with single quotes
|
||||
'"' => "'",
|
||||
|
||||
// Replace special JSON characters with similar safe characters
|
||||
'{' => '(',
|
||||
'}' => ')',
|
||||
|
||||
// Collapse multiple spaces into single space
|
||||
' ' => ' ',
|
||||
];
|
||||
|
||||
$result = str_replace(array_keys($replacements), array_values($replacements), $result);
|
||||
|
||||
// Step 4: Remove any remaining potentially problematic characters
|
||||
$result = preg_replace('/[\x80-\x9F]/u', '', $result);
|
||||
|
||||
// Remove common delimiters that might be used to "break out" of a system prompt
|
||||
$attackPatterns = [
|
||||
// System prompt break patterns
|
||||
'/\<\/?system\>/', '/\<\/?assistant\>/', '/\<\/?user\>/', '/\<\/?human\>/',
|
||||
// XML-like tags that might be used in exploits
|
||||
'/\<\/?instructions\>/', '/\<\/?prompt\>/', '/\<\/?context\>/',
|
||||
// Special command patterns
|
||||
'/\[\[.*?\]\]/', '/\{\{.*?\}\}/',
|
||||
// Common attack prefix/suffix patterns
|
||||
'/ignore previous instructions/', '/ignore all previous commands/',
|
||||
'/disregard (previous|prior|all|your) instructions?/',
|
||||
'/forget (previous|prior|all|your) instructions?/',
|
||||
|
||||
// Additional boundary markers
|
||||
'/```system/', '/```instructions/', '/```prompt/',
|
||||
'/\$\$\$system/', '/\$\$\$instructions/', '/\$\$\$prompt/',
|
||||
];
|
||||
|
||||
$result = preg_replace($attackPatterns, '', $result);
|
||||
|
||||
// Step 5: Handle potential JSON serialization issues
|
||||
// Ensure the string is valid UTF-8
|
||||
if (! mb_check_encoding($result, 'UTF-8')) {
|
||||
$result = mb_convert_encoding($result, 'UTF-8', 'UTF-8');
|
||||
}
|
||||
|
||||
// Step 6: Additional sanitization for special patterns
|
||||
// Remove or replace specific problematic sequences
|
||||
$result = str_replace(
|
||||
['{{{', '}}}', '<<<', '>>>'],
|
||||
['{ { {', '} } }', '< < <', '> > >'],
|
||||
$result
|
||||
);
|
||||
|
||||
// Step 7: Remove consecutive spaces (which can occur after other replacements)
|
||||
$result = preg_replace('/ {2,}/', ' ', $result);
|
||||
|
||||
if ($removeNewlines) {
|
||||
$result = str_replace("\n", ' ', $value);
|
||||
}
|
||||
|
||||
return $result;
|
||||
};
|
||||
}
|
||||
}
|
||||
126
app/Core/Support/String/ToMarkdown.php
Normal file
126
app/Core/Support/String/ToMarkdown.php
Normal file
@@ -0,0 +1,126 @@
|
||||
<?php
|
||||
|
||||
namespace Leantime\Core\Support\String;
|
||||
|
||||
use Illuminate\Support\Str;
|
||||
|
||||
/**
|
||||
* @mixin \Illuminate\Support\Stringable
|
||||
*/
|
||||
class ToMarkdown
|
||||
{
|
||||
/**
|
||||
* Converts a PHP array into a formatted markdown string.
|
||||
*
|
||||
* @param int $headerLevel Starting header level (1-6)
|
||||
* @return callable A function that converts data to markdown format
|
||||
*/
|
||||
public function toMarkdown($headerLevel = 2)
|
||||
{
|
||||
$sanitizeForMarkdown = function ($value) {
|
||||
if ($value === null) {
|
||||
return '*null*';
|
||||
}
|
||||
|
||||
if (is_bool($value)) {
|
||||
return $value ? 'true' : 'false';
|
||||
}
|
||||
|
||||
$string = (string) $value;
|
||||
|
||||
// Use the sanitizeForLLM macro for consistent sanitization
|
||||
$string = Str::sanitizeForLLM($string);
|
||||
|
||||
return $string;
|
||||
};
|
||||
|
||||
return function ($data) use ($headerLevel, $sanitizeForMarkdown) {
|
||||
if (! is_array($data)) {
|
||||
|
||||
if (is_bool($data)) {
|
||||
return $data ? 'true' : 'false';
|
||||
}
|
||||
|
||||
$string = (string) $data;
|
||||
|
||||
// Use the sanitizeForLLM macro for consistent sanitization
|
||||
return Str::sanitizeForLLM($string);
|
||||
|
||||
}
|
||||
|
||||
$result = '';
|
||||
$indentLevel = 0;
|
||||
|
||||
// Internal function to process array recursively
|
||||
$processArray = function ($array, $level, $indent) use (&$processArray, &$result, $sanitizeForMarkdown) {
|
||||
foreach ($array as $key => $value) {
|
||||
// Skip numeric keys for sequential arrays if they're just indices
|
||||
$skipKey = is_int($key) && $key === count($array) - count($array);
|
||||
|
||||
if (! $skipKey) {
|
||||
$data = preg_split('/(?=[A-Z])/', $key);
|
||||
$string = implode(' ', $data);
|
||||
$string = ucwords($string);
|
||||
|
||||
$result .= str_repeat(' ', $indent).'**'.$sanitizeForMarkdown($string).':**';
|
||||
}
|
||||
|
||||
if (is_array($value)) {
|
||||
// Handle nested arrays
|
||||
if (empty($value)) {
|
||||
$result .= str_repeat(' ', $indent)."*Empty*\n\n";
|
||||
} elseif (array_keys($array) !== range(0, count($array) - 1)) {
|
||||
// Associative array - process recursively
|
||||
$processArray($value, $level + 1, $indent + 1);
|
||||
} else {
|
||||
// Sequential array - create a list
|
||||
foreach ($value as $item) {
|
||||
if (is_array($item)) {
|
||||
// Nested array item
|
||||
$result .= str_repeat(' ', $indent).'- ';
|
||||
$nestedResult = '';
|
||||
$processArray($item, $level + 2, 0);
|
||||
|
||||
// Format the nested result as an indented block
|
||||
$lines = explode("\n", trim($nestedResult));
|
||||
$result .= array_shift($lines)."\n";
|
||||
foreach ($lines as $line) {
|
||||
$result .= str_repeat(' ', $indent + 1).$line."\n";
|
||||
}
|
||||
} else {
|
||||
// Simple item
|
||||
$result .= str_repeat(' ', $indent).'- '.$sanitizeForMarkdown($item)."\n";
|
||||
}
|
||||
}
|
||||
$result .= "\n";
|
||||
}
|
||||
} elseif (is_bool($value)) {
|
||||
// Handle boolean values
|
||||
$result .= str_repeat(' ', $indent).($value ? '✅ Yes' : '❌ No')."\n\n";
|
||||
} elseif ($value === null) {
|
||||
// Handle null values
|
||||
$result .= str_repeat(' ', $indent)."*Not provided*\n\n";
|
||||
} else {
|
||||
// Handle scalar values
|
||||
$formattedValue = $sanitizeForMarkdown($value);
|
||||
|
||||
// Check if value is multi-line and format accordingly
|
||||
if (strpos($formattedValue, "\n") !== false) {
|
||||
$result .= str_repeat(' ', $indent)."```\n".$formattedValue."\n```\n\n";
|
||||
} else {
|
||||
$result .= str_repeat(' ', $indent).$formattedValue."\n\n";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $result;
|
||||
};
|
||||
|
||||
// Start processing
|
||||
$processArray($data, $headerLevel, $indentLevel);
|
||||
|
||||
// Clean up and return result
|
||||
return trim($result);
|
||||
};
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user