Dateien nach „bavarian-rank-engine/includes/Helpers“ hochladen
This commit is contained in:
parent
dd3110b93e
commit
438603951e
4 changed files with 262 additions and 0 deletions
28
bavarian-rank-engine/includes/Helpers/BulkQueue.php
Normal file
28
bavarian-rank-engine/includes/Helpers/BulkQueue.php
Normal file
|
|
@ -0,0 +1,28 @@
|
||||||
|
<?php
|
||||||
|
namespace BavarianRankEngine\Helpers;
|
||||||
|
|
||||||
|
class BulkQueue {
|
||||||
|
private const LOCK_KEY = 'bre_bulk_running';
|
||||||
|
private const LOCK_TTL = 900; // 15 minutes
|
||||||
|
|
||||||
|
public static function acquire(): bool {
|
||||||
|
if ( self::isLocked() ) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
set_transient( self::LOCK_KEY, time(), self::LOCK_TTL );
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function release(): void {
|
||||||
|
delete_transient( self::LOCK_KEY );
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function isLocked(): bool {
|
||||||
|
return get_transient( self::LOCK_KEY ) !== false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function lockAge(): int {
|
||||||
|
$started = get_transient( self::LOCK_KEY );
|
||||||
|
return $started !== false ? ( time() - (int) $started ) : 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
52
bavarian-rank-engine/includes/Helpers/FallbackMeta.php
Normal file
52
bavarian-rank-engine/includes/Helpers/FallbackMeta.php
Normal file
|
|
@ -0,0 +1,52 @@
|
||||||
|
<?php
|
||||||
|
namespace BavarianRankEngine\Helpers;
|
||||||
|
|
||||||
|
class FallbackMeta {
|
||||||
|
private const MIN = 150;
|
||||||
|
private const MAX = 160;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Extract a clean 150–160 char meta description from post content.
|
||||||
|
* Ends on a complete sentence or word boundary. No HTML.
|
||||||
|
*
|
||||||
|
* @param object $post Object with post_content property (WP_Post compatible)
|
||||||
|
*/
|
||||||
|
public static function extract( object $post ): string {
|
||||||
|
$text = wp_strip_all_tags( $post->post_content ?? '' );
|
||||||
|
$text = preg_replace( '/\s+/', ' ', $text );
|
||||||
|
$text = trim( $text );
|
||||||
|
|
||||||
|
if ( $text === '' ) {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( mb_strlen( $text ) <= self::MAX ) {
|
||||||
|
return $text;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Try to end on a sentence boundary within MAX chars
|
||||||
|
$candidate = mb_substr( $text, 0, self::MAX );
|
||||||
|
$last_period = mb_strrpos( $candidate, '. ' );
|
||||||
|
$last_exclaim = mb_strrpos( $candidate, '! ' );
|
||||||
|
$last_question = mb_strrpos( $candidate, '? ' );
|
||||||
|
|
||||||
|
$last_sentence = max(
|
||||||
|
$last_period === false ? -1 : $last_period,
|
||||||
|
$last_exclaim === false ? -1 : $last_exclaim,
|
||||||
|
$last_question === false ? -1 : $last_question
|
||||||
|
);
|
||||||
|
|
||||||
|
if ( $last_sentence >= 0 && $last_sentence >= self::MIN - 1 ) {
|
||||||
|
return mb_substr( $text, 0, $last_sentence + 1 );
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fall back to last word boundary within MAX
|
||||||
|
$last_space = mb_strrpos( $candidate, ' ' );
|
||||||
|
if ( $last_space !== false && $last_space >= self::MIN - 20 ) {
|
||||||
|
return mb_substr( $text, 0, $last_space ) . '…';
|
||||||
|
}
|
||||||
|
|
||||||
|
// Last resort: hard cut with ellipsis
|
||||||
|
return mb_substr( $text, 0, self::MAX - 1 ) . '…';
|
||||||
|
}
|
||||||
|
}
|
||||||
79
bavarian-rank-engine/includes/Helpers/KeyVault.php
Normal file
79
bavarian-rank-engine/includes/Helpers/KeyVault.php
Normal file
|
|
@ -0,0 +1,79 @@
|
||||||
|
<?php
|
||||||
|
namespace BavarianRankEngine\Helpers;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Obfuscates API keys for database storage using XOR with a derived WP-salt key.
|
||||||
|
*
|
||||||
|
* No OpenSSL or other PHP extensions required — only core string functions.
|
||||||
|
* Keys stored as "bre1:<base64(xor(plaintext, salt))>".
|
||||||
|
*
|
||||||
|
* Note: XOR with a static salt is obfuscation, not encryption. It prevents
|
||||||
|
* plain-text keys from appearing in database backups or export files, but
|
||||||
|
* does not protect against an attacker with access to both the database
|
||||||
|
* AND the wp-config.php salts. For stronger protection, users can define
|
||||||
|
* BRE_<PROVIDER>_KEY constants in wp-config.php and leave the DB field empty.
|
||||||
|
*/
|
||||||
|
class KeyVault {
|
||||||
|
private const PREFIX = 'bre1:';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Obfuscate a plain API key for database storage.
|
||||||
|
*/
|
||||||
|
public static function encrypt( string $key ): string {
|
||||||
|
if ( $key === '' ) {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
return self::PREFIX . base64_encode( self::xor( $key, self::salt() ) );
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Recover the plain API key from a stored obfuscated value.
|
||||||
|
* Returns empty string if the stored value is not in bre1: format (legacy/invalid).
|
||||||
|
*/
|
||||||
|
public static function decrypt( string $stored ): string {
|
||||||
|
if ( $stored === '' ) {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
if ( ! str_starts_with( $stored, self::PREFIX ) ) {
|
||||||
|
// Legacy OpenSSL-encrypted value or unknown format — return empty so user re-enters.
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
$raw = base64_decode( substr( $stored, strlen( self::PREFIX ) ), true );
|
||||||
|
if ( $raw === false ) {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
return self::xor( $raw, self::salt() );
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns masked version for display: ••••••Ab3c9
|
||||||
|
*/
|
||||||
|
public static function mask( string $plain ): string {
|
||||||
|
if ( $plain === '' ) {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
return str_repeat( '•', max( 0, mb_strlen( $plain ) - 5 ) ) . mb_substr( $plain, -5 );
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* XOR each byte of $data with the corresponding byte of $key (wrapping).
|
||||||
|
*/
|
||||||
|
private static function xor( string $data, string $key ): string {
|
||||||
|
$out = '';
|
||||||
|
$keyLen = strlen( $key );
|
||||||
|
for ( $i = 0, $n = strlen( $data ); $i < $n; $i++ ) {
|
||||||
|
$out .= $data[ $i ] ^ $key[ $i % $keyLen ];
|
||||||
|
}
|
||||||
|
return $out;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Derives a 64-character hex salt from WP's AUTH_KEY and SECURE_AUTH_KEY.
|
||||||
|
* Falls back to known strings if the constants are not defined (local dev / unit tests).
|
||||||
|
*/
|
||||||
|
private static function salt(): string {
|
||||||
|
$a = defined( 'AUTH_KEY' ) ? AUTH_KEY : 'bre-fallback-a';
|
||||||
|
$b = defined( 'SECURE_AUTH_KEY' ) ? SECURE_AUTH_KEY : 'bre-fallback-b';
|
||||||
|
return hash( 'sha256', $a . $b ); // 64 hex chars, no extension needed
|
||||||
|
}
|
||||||
|
}
|
||||||
103
bavarian-rank-engine/includes/Helpers/TokenEstimator.php
Normal file
103
bavarian-rank-engine/includes/Helpers/TokenEstimator.php
Normal file
|
|
@ -0,0 +1,103 @@
|
||||||
|
<?php
|
||||||
|
namespace BavarianRankEngine\Helpers;
|
||||||
|
|
||||||
|
class TokenEstimator {
|
||||||
|
/**
|
||||||
|
* Pricing per 1k tokens [provider][model][input|output]
|
||||||
|
* Update these when provider pricing changes.
|
||||||
|
*/
|
||||||
|
private const PRICING = array(
|
||||||
|
'openai' => array(
|
||||||
|
'gpt-4.1' => array(
|
||||||
|
'input' => 0.002,
|
||||||
|
'output' => 0.008,
|
||||||
|
),
|
||||||
|
'gpt-4o' => array(
|
||||||
|
'input' => 0.0025,
|
||||||
|
'output' => 0.01,
|
||||||
|
),
|
||||||
|
'gpt-4o-mini' => array(
|
||||||
|
'input' => 0.00015,
|
||||||
|
'output' => 0.0006,
|
||||||
|
),
|
||||||
|
'gpt-3.5-turbo' => array(
|
||||||
|
'input' => 0.0005,
|
||||||
|
'output' => 0.0015,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
'anthropic' => array(
|
||||||
|
'claude-sonnet-4-6' => array(
|
||||||
|
'input' => 0.003,
|
||||||
|
'output' => 0.015,
|
||||||
|
),
|
||||||
|
'claude-opus-4-6' => array(
|
||||||
|
'input' => 0.015,
|
||||||
|
'output' => 0.075,
|
||||||
|
),
|
||||||
|
'claude-haiku-4-5-20251001' => array(
|
||||||
|
'input' => 0.00025,
|
||||||
|
'output' => 0.00125,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
'gemini' => array(
|
||||||
|
'gemini-2.0-flash' => array(
|
||||||
|
'input' => 0.00010,
|
||||||
|
'output' => 0.00040,
|
||||||
|
),
|
||||||
|
'gemini-2.0-flash-lite' => array(
|
||||||
|
'input' => 0.000038,
|
||||||
|
'output' => 0.00015,
|
||||||
|
),
|
||||||
|
'gemini-1.5-pro' => array(
|
||||||
|
'input' => 0.00125,
|
||||||
|
'output' => 0.005,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
'grok' => array(
|
||||||
|
'grok-3' => array(
|
||||||
|
'input' => 0.003,
|
||||||
|
'output' => 0.015,
|
||||||
|
),
|
||||||
|
'grok-3-mini' => array(
|
||||||
|
'input' => 0.0003,
|
||||||
|
'output' => 0.0005,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
/** Estimate token count (~4 chars per token) */
|
||||||
|
public static function estimate( string $text ): int {
|
||||||
|
return (int) ceil( mb_strlen( $text ) / 4 );
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Truncate text to approximately $max_tokens */
|
||||||
|
public static function truncate( string $text, int $max_tokens ): string {
|
||||||
|
$max_chars = $max_tokens * 4;
|
||||||
|
if ( mb_strlen( $text ) <= $max_chars ) {
|
||||||
|
return $text;
|
||||||
|
}
|
||||||
|
return mb_substr( $text, 0, $max_chars );
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Estimate cost in USD.
|
||||||
|
*
|
||||||
|
* @param int $tokens Number of tokens
|
||||||
|
* @param string $provider Provider ID
|
||||||
|
* @param string $model Model ID
|
||||||
|
* @param string $type 'input' or 'output'
|
||||||
|
*/
|
||||||
|
public static function estimateCost( int $tokens, string $provider, string $model, string $type = 'input' ): float {
|
||||||
|
$price_per_1k = self::PRICING[ $provider ][ $model ][ $type ] ?? 0.002;
|
||||||
|
return round( ( $tokens / 1000 ) * $price_per_1k, 6 );
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Human-readable cost string e.g. "~0,05 €" */
|
||||||
|
public static function formatCost( float $usd ): string {
|
||||||
|
$eur = $usd * 0.92;
|
||||||
|
if ( $eur < 0.01 ) {
|
||||||
|
return '< 0,01 €';
|
||||||
|
}
|
||||||
|
return '~' . number_format( $eur, 2, ',', '.' ) . ' €';
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Reference in a new issue