( ′∀`)σ≡σ☆))Д′)レ(゚∀゚;)ヘ=З=З=Зε≡(ノ´_ゝ`)ノ HEX
HEX
Server: Apache/2.4.58 (Ubuntu)
System: Linux mail.thebrand.ai 6.8.0-107-generic #107-Ubuntu SMP PREEMPT_DYNAMIC Fri Mar 13 19:51:50 UTC 2026 x86_64
User: www-data (33)
PHP: 8.3.6
Disabled: NONE
Upload Files
File: /var/www/html/tmpr/../tmpr/../tmpr/../tmpr/../tmpr/../tools/app/controllers/DocumentCreate.php
<?php
/*
 * Copyright (c) 2026 AltumCode (https://altumcode.com/)
 *
 * This software is licensed exclusively by AltumCode and is sold only via https://altumcode.com/.
 * Unauthorized distribution, modification, or use of this software without a valid license is not permitted and may be subject to applicable legal actions.
 *
 * 🌍 View all other existing AltumCode projects via https://altumcode.com/
 * 📧 Get in touch for support or general queries via https://altumcode.com/contact
 * 📤 Download the latest version via https://altumcode.com/downloads
 *
 * 🐦 X/Twitter: https://x.com/AltumCode
 * 📘 Facebook: https://facebook.com/altumcode
 * 📸 Instagram: https://instagram.com/altumcode
 */

namespace Altum\Controllers;

use Altum\Alerts;
use Altum\Response;

defined('ALTUMCODE') || die();

class DocumentCreate extends Controller {

    public function index() {
        \Altum\Authentication::guard();

        if(!\Altum\Plugin::is_active('aix') || !settings()->aix->documents_is_enabled) {
            throw_404();
        }

        /* Team checks */
        if(\Altum\Teams::is_delegated() && !\Altum\Teams::has_access('create.documents')) {
            Alerts::add_error(l('global.info_message.team_no_access'));
            redirect('documents');
        }

        /* Check for the plan limit */
        $documents_current_month = db()->where('user_id', $this->user->user_id)->getValue('users', '`aix_documents_current_month`');
        if($this->user->plan_settings->documents_per_month_limit != -1 && $documents_current_month >= $this->user->plan_settings->documents_per_month_limit) {
            Alerts::add_error(l('global.info_message.plan_feature_limit') . (settings()->payment->is_enabled ? ' <a href="' . url('plan') . '" class="font-weight-bold text-reset">' . l('global.info_message.plan_upgrade') . '.</a>' : null));
            redirect('documents');
        }

        $available_words = $this->user->plan_settings->words_per_month_limit - db()->where('user_id', $this->user->user_id)->getValue('users', '`aix_words_current_month`');

        if($this->user->plan_settings->words_per_month_limit != -1 && $available_words <= 0) {
            Alerts::add_error(l('global.info_message.plan_feature_limit') . (settings()->payment->is_enabled ? ' <a href="' . url('plan') . '" class="font-weight-bold text-reset">' . l('global.info_message.plan_upgrade') . '.</a>' : null));
            redirect('documents');
        }

        /* Check for exclusive personal API usage limitation */
        if($this->user->plan_settings->exclusive_personal_api_keys && empty($this->user->preferences->openai_api_key)) {
            Alerts::add_error(sprintf(l('account_preferences.error_message.aix.openai_api_key'), '<a href="' . url('account-preferences') . '"><strong>' . l('account_preferences.menu') . '</strong></a>'));
        }

        /* Get available projects */
        $projects = (new \Altum\Models\Projects())->get_projects_by_user_id($this->user->user_id);

        /* Get available templates categories */
        $templates_categories = (new \Altum\Models\TemplatesCategories())->get_templates_categories();

        /* Templates */
        $templates = (new \Altum\Models\Templates())->get_templates();

        /* Clear $_GET */
        foreach($_GET as $key => $value) {
            $_GET[$key] = input_clean($value);
        }

        $values = [
            'name' => $_POST['name'] ?? $_GET['name'] ?? sprintf(l('document_create.name_x'), \Altum\Date::get()),
            'language' => $_GET['language'] ?? $_POST['language'] ?? reset(settings()->aix->documents_available_languages),
            'variants' => $_GET['variants'] ?? $_POST['variants'] ?? 1,
            'max_words_per_variant' => $_GET['max_words_per_variant'] ?? $_POST['max_words_per_variant'] ?? null,
            'creativity_level' => $_GET['creativity_level'] ?? $_POST['creativity_level'] ?? 'optimal',
            'type' => $_GET['type'] ?? $_POST['type'] ?? 'summarize',
            'project_id' => $_GET['project_id'] ?? $_POST['project_id'] ?? null,
            'template_id' => $_GET['template_id'] ?? $_POST['template_id'] ?? null,
            'template_category_id' => $_GET['template_category_id'] ?? $_POST['template_category_id'] ?? null,
        ];

        foreach($templates as $template_id => $template) {
            foreach($template->settings->inputs as $key => $value) {
                $values[$template_id . '_' . $key] = $_GET[$template_id . '_' . $key] ?? $_POST[$template_id . '_' . $key] ?? null;
            }
        }

        /* Prepare the view */
        $data = [
            'values' => $values,
            'available_words' => $available_words,
            'projects' => $projects ?? [],
            'templates' => $templates,
            'templates_categories' => $templates_categories,
        ];

        $view = new \Altum\View(\Altum\Plugin::get('aix')->path . 'views/document-create/index', (array) $this, true);

        $this->add_view_content('content', $view->run($data));

    }

    public function create_ajax() {
        //ALTUMCODE:DEMO if(DEMO) if($this->user->user_id == 1) Response::json('Please create an account on the demo to test out this function.', 'error');

        if(empty($_POST)) {
            throw_404();
        }

        set_time_limit(0);

        \Altum\Authentication::guard();

        if(!\Altum\Plugin::is_active('aix') || !settings()->aix->documents_is_enabled) {
            throw_404();
        }

        /* Team checks */
        if(\Altum\Teams::is_delegated() && !\Altum\Teams::has_access('create.documents')) {
            Response::json(l('global.info_message.team_no_access'), 'error');
        }

        /* Check for the plan limit */
        $documents_current_month = db()->where('user_id', $this->user->user_id)->getValue('users', '`aix_documents_current_month`');
        if($this->user->plan_settings->documents_per_month_limit != -1 && $documents_current_month >= $this->user->plan_settings->documents_per_month_limit) {
            Response::json(l('global.info_message.plan_feature_limit'), 'error');
        }

        $available_words = $this->user->plan_settings->words_per_month_limit != -1 ? $this->user->plan_settings->words_per_month_limit - db()->where('user_id', $this->user->user_id)->getValue('users', '`aix_words_current_month`') : -1;

        if($this->user->plan_settings->words_per_month_limit != -1 && $available_words <= 0) {
            Response::json(l('global.info_message.plan_feature_limit'), 'error');
        }

        $this->user->plan_settings->documents_model = $this->user->plan_settings->documents_model ?? 'gpt-3.5-turbo';

        /* AI Text models */
        $ai_text_models = require \Altum\Plugin::get('aix')->path . 'includes/ai_text_models.php';
        $max_tokens_for_current_model = $ai_text_models[$this->user->plan_settings->documents_model]['max_tokens'];
        $max_words_for_current_model = floor($max_tokens_for_current_model / 1.333);

        /* Get available projects */
        $projects = (new \Altum\Models\Projects())->get_projects_by_user_id($this->user->user_id);

        /* Templates */
        $templates = (new \Altum\Models\Templates())->get_templates();

        $_POST['name'] = input_clean($_POST['name'], 64);
        $_POST['language'] = in_array($_POST['language'], settings()->aix->documents_available_languages) ? input_clean($_POST['language']) : reset(settings()->aix->documents_available_languages);
        $_POST['variants'] = (int) $_POST['variants'] < 0 || (int) $_POST['variants'] > 3 ? 1 : (int) $_POST['variants'];
        if(is_numeric($_POST['max_words_per_variant'])) {
            $_POST['max_words_per_variant'] = (int) $_POST['max_words_per_variant'];

            if($_POST['max_words_per_variant'] < 5) {
                $_POST['max_words_per_variant'] = 10;
            }

            if($_POST['max_words_per_variant'] > $max_words_for_current_model) {
                $_POST['max_words_per_variant'] = $max_words_for_current_model;
            }

            if($available_words != -1 && $_POST['max_words_per_variant'] > $available_words) {
                $_POST['max_words_per_variant'] = $available_words;
            }
        } else {
            $_POST['max_words_per_variant'] = $available_words != -1 ? $available_words : null;
        }
        $_POST['creativity_level'] = $_POST['creativity_level'] && in_array($_POST['creativity_level'], ['none', 'low', 'optimal', 'high', 'maximum', 'custom']) ? $_POST['creativity_level'] : 'optimal';
        $_POST['creativity_level_custom'] = isset($_POST['creativity_level_custom']) ? ((float) $_POST['creativity_level_custom'] < 0 || (float) $_POST['creativity_level_custom'] > 2 ? 0.8 : (float) $_POST['creativity_level_custom']) : null;
        $_POST['type'] = $_POST['type'] && array_key_exists($_POST['type'], $templates) ? $_POST['type'] : null;
        $_POST['project_id'] = !empty($_POST['project_id']) && array_key_exists($_POST['project_id'], $projects) ? (int) $_POST['project_id'] : null;

        /* Check for any errors */
        $required_fields = ['name', 'type'];
        foreach($required_fields as $field) {
            if(!isset($_POST[$field]) || trim($_POST[$field]) === '') {
                Response::json(l('global.error_message.empty_fields'), 'error');
            }
        }

        if(!\Altum\Csrf::check('global_token')) {
            Response::json(l('global.error_message.invalid_csrf_token'), 'error');
        }

        /* Check for timeouts */
        if(settings()->aix->input_moderation_is_enabled) {
            $cache_instance = cache()->getItem('user?flagged=' . $this->user->user_id);
            if(!is_null($cache_instance->get())) {
                Response::json(l('documents.error_message.timed_out'), 'error');
            }
        }

        /* Input */
        $prompt = 'Write answer in ' . $_POST['language'] . '. ' . $templates[$_POST['type']]->prompt;

        $inputs = [];
        foreach($templates[$_POST['type']]->settings->inputs as $key => $value) {
            $inputs[$key] = input_clean($_POST[$_POST['type'] . '_' . $key]);
            $prompt = str_replace('{' . $key . '}', $inputs[$key], $prompt);
        }

        $input = json_encode($inputs);

        /* Calculate tokens used by prompt */
        $token_calculator_model = $this->user->plan_settings->documents_model;
        $token_calculator_model = 'gpt-4o';
        $tokens_used_by_prompt = ceil(\Rajentrivedi\TokenizerX\TokenizerX::count($prompt, $token_calculator_model) * 1.3);

        /* Make sure the prompt tokens do not take more than 50% of the available tokens, to leave room for a proper response */
        if($tokens_used_by_prompt > floor($max_tokens_for_current_model / 2)){
            Response::json(l('documents.error_message.prompt_tokens'), 'error');
        }

        /* Decide max tokens based on input and max words per variant */
        $max_tokens = $max_tokens_for_current_model;
        if($_POST['max_words_per_variant']) {
            $max_tokens = (int) floor(($_POST['max_words_per_variant'] * 1.4));
            if($max_tokens > $max_tokens_for_current_model) {
                $max_tokens = $max_tokens_for_current_model;
            }
        }

        /* Double check to not reach the limit of tokens per request of selected model */
        if(($max_tokens + $tokens_used_by_prompt) > $max_tokens_for_current_model) {
            $max_tokens -= $tokens_used_by_prompt;
        }

        /* Temperature */
        $temperature = 0.8;
        switch($_POST['creativity_level']) {
            case 'none': $temperature = 0; break;
            case 'low': $temperature = 0.5; break;
            case 'optimal': $temperature = 0.8; break;
            case 'high': $temperature = 1.4; break;
            case 'maximum': $temperature = 2; break;
            case 'custom:': $temperature = number_format($_POST['creativity_level'], 1); break;
        }

        if(in_array($this->user->plan_settings->documents_model, ['gpt-5', 'gpt-5-mini', 'gpt-5-nano', 'o1', 'o1-mini', 'o3-mini'])) {
            $temperature = 1;
        }

        /* Try to increase the database timeout as well */
        database()->query("set session wait_timeout=600;");

        /* Do not use sessions anymore to not lockout the user from doing anything else on the site */
        session_write_close();

        /* Check for moderation */
        if(settings()->aix->input_moderation_is_enabled) {
            try {
                $response = \Unirest\Request::post(
                    'https://api.openai.com/v1/moderations',
                    [
                        'Authorization' => 'Bearer '  . get_random_line_from_text($this->user->plan_settings->exclusive_personal_api_keys ? $this->user->preferences->openai_api_key : settings()->aix->openai_api_key),
                        'Content-Type' => 'application/json',
                    ],
                    \Unirest\Request\Body::json([
                        'input' => $prompt,
                        'model' => 'omni-moderation-latest',
                    ])
                );

                if($response->code >= 400) {
                    Response::json($response->body->error->message, 'error');
                }

                if($response->body->results[0]->flagged ?? null) {
                    /* Time out the user for a few minutes */
                    cache()->save(
                        $cache_instance->set('true')->expiresAfter(3 * 60)->addTag('user_id=' . $this->user->user_id)
                    );

                    /* Return the error */
                    Response::json(l('documents.error_message.flagged'), 'error');
                }

            } catch (\Exception $exception) {
                Response::json($exception->getMessage(), 'error');
            }
        }


        /* Prepare the main API request */
        $api_endpoint_url = 'https://api.openai.com/v1/chat/completions';

        $body = [
            'model' => $this->user->plan_settings->documents_model,
            'messages' => [
                [
                    'role' => 'user',
                    'content' => $prompt
                ]
            ],
            'temperature' => $temperature,
            'n' => $_POST['variants'],
            'user' => 'user_id:' . $this->user->user_id,
        ];

        if($_POST['max_words_per_variant']) {
            $body['max_completion_tokens'] = $max_tokens;
        }

        try {
            $response = \Unirest\Request::post(
                $api_endpoint_url,
                [
                    'Authorization' => 'Bearer '  . get_random_line_from_text($this->user->plan_settings->exclusive_personal_api_keys ? $this->user->preferences->openai_api_key : settings()->aix->openai_api_key),
                    'Content-Type' => 'application/json',
                ],
                \Unirest\Request\Body::json($body)
            );

            if($response->code >= 400) {
                Response::json($response->body->error->message, 'error');
            }

        } catch (\Exception $exception) {
            Response::json($exception->getMessage(), 'error');
        }

        /* Get info after the request */
        $info = \Unirest\Request::getInfo();

        /* Some needed variables */
        $api_response_time = $info['total_time'] * 1000;

        /* Words */
        $words = floor($response->body->usage->completion_tokens * 0.75);

        /* AI Content */
        if(count($response->body->choices) > 1) {
            $content = '';

            foreach($response->body->choices as $key => $choice) {
                $content .= sprintf(l('documents.variant_x'), ($key+1)) . "\r\n";
                $content .= "--------------------\r\n";
                $content .= trim($choice->message->content) . "\r\n\r\n\r\n";
                //$words += count(explode(' ', (trim($choice->message->content))));
            }
        } else {
            $content = trim($response->body->choices[0]->message->content);
            //$words += count(explode(' ', ($content)));
        }

        $content = trim($content);

        /* Settings of request */
        $settings = json_encode([
            'language' => $_POST['language'],
            'variants' => $_POST['variants'],
            'max_words_per_variant' => $_POST['max_words_per_variant'],
            'creativity_level' => $_POST['creativity_level'],
            'creativity_level_custom' => $_POST['creativity_level_custom'],
        ]);

        /* Database query */
        $document_id = db()->insert('documents', [
            'user_id' => $this->user->user_id,
            'project_id' => $_POST['project_id'],
            'template_category_id' => $templates[$_POST['type']]->template_category_id,
            'template_id' => $_POST['type'],
            'name' => $_POST['name'],
            'type' => $_POST['type'],
            'input' => $input,
            'content' => $content,
            'words' => $words,
            'model' => $this->user->plan_settings->documents_model,
            'api_response_time' => $api_response_time,
            'settings' => $settings,
            'datetime' => get_date(),
        ]);

        /* Database query */
        db()->where('template_id', $_POST['type'])->update('templates', [
            'total_usage' => db()->inc()
        ]);

        /* Database query */
        db()->where('user_id', $this->user->user_id)->update('users', [
            'aix_words_current_month' => db()->inc($words),
            'aix_documents_current_month' => db()->inc()
        ]);

        /* Set a nice success message */
        Response::json(sprintf(l('global.success_message.create1'), '<strong>' . $_POST['name'] . '</strong>'), 'success', ['url' => url('document-update/' . $document_id)]);

    }

}