Subversion Repositories web.active

Rev

Rev 1 | Go to most recent revision | Blame | Compare with Previous | Last modification | View Log | Download

<?php namespace ProcessWire;

/**
 * Controller for ProcessWire Admin
 *
 * This file is designed for inclusion by /site/templates/admin.php template and all the variables 
 * it references are from your template namespace. 
 *
 * Copyright 2021 by Ryan Cramer
 * 
 * @var Config $config
 * @var User $user
 * @var Modules $modules
 * @var Pages $pages
 * @var Page $page
 * @var ProcessWire $wire
 * @var WireInput $input
 * @var Sanitizer $sanitizer
 * @var Session $session
 * @var Notices $notices
 * @var AdminTheme $adminTheme
 * 
 *
 */

if(!defined("PROCESSWIRE")) die("This file may not be accessed directly.");

header("X-Frame-Options: SAMEORIGIN"); 

/**
 * Ensures a modal GET variable is retained through redirects, when appropriate
 * 
 * @param HookEvent $event
 *
 */
function _hookSessionRedirectModal(HookEvent $event) {
    $url = $event->arguments(0);    
    if(strpos($url, 'modal=1') === false && strpos($url, '://') === false) {
        $url .= (strpos($url, '?') === false ? '?' : '&') . 'modal=1';
        $event->arguments(0, $url);    
    }
}

/**
 * Check if the current HTTP host is recognized and generate error if not
 * 
 * @param Config $config
 * 
 */
function _checkForHttpHostError(Config $config) {

    $valid = false;
    $httpHost = strtolower($config->httpHost); 

    if(isset($_SERVER['HTTP_HOST']) && $httpHost === strtolower($_SERVER['HTTP_HOST'])) {
        $valid = true; 
    } else if(isset($_SERVER['SERVER_NAME']) && $httpHost === strtolower($_SERVER['SERVER_NAME'])) {
        $valid = true; 
    } else if(in_array($httpHost, $config->httpHosts)) {
        $valid = true; 
    }

    if(!$valid) $config->error(
        __('Unrecognized HTTP host:') . "'"  . 
        htmlentities($_SERVER['HTTP_HOST'], ENT_QUOTES, 'UTF-8') . "' - " . 
        __('Please update your $config->httpHosts setting in /site/config.php') . " - " . 
        "<a target='_blank' href='https://processwire.com/api/variables/config/#httphosts'>" . __('read more') . "</a>", 
        Notice::allowMarkup
        ); 
}

/**
 * Check if two factor authentication is being required and display warning with link to configure
 *
 * @param Session $session
 *
 */
function _checkForTwoFactorAuth(Session $session) {
    $tfaUrl = $session->getFor('_user', 'requireTfa'); // contains URL to configure TFA
    if(!$tfaUrl || strpos($tfaUrl, $session->wire('page')->url()) === 0) return;
    $sanitizer = $session->wire('sanitizer');
    $session->wire('user')->warning(
        '<strong>' . $sanitizer->entities1(__('Action required')) . '</strong> ' .
        wireIconMarkup('angle-right') . ' ' . 
        "<a href='$tfaUrl'>" . $sanitizer->entities1(__('Enable two-factor authentication')) . " </a>",
        Notice::allowMarkup
    );
}

/**
 * Check if POST request exceeds PHP’s max_input_vars
 * 
 * @param WireInput $input
 * 
 */
function _checkForMaxInputVars(WireInput $input) {
    $max = (int) ini_get('max_input_vars');
    if($max && count($_POST) >= $max) {
        $input->error(sprintf(__('You have reached PHP’s “max_input_vars” setting of %d — please increase it.'), $max)); 
    }
}

// fallback theme if one not already present
if(empty($adminTheme)) {
    $adminTheme = $modules->get($config->defaultAdminTheme ? $config->defaultAdminTheme : 'AdminThemeUikit');
    if(empty($adminTheme)) $adminTheme = $modules->get('AdminThemeUikit');
    if($adminTheme) $wire->wire('adminTheme', $adminTheme);
}

// notify superuser if there is an http host error
if($user->isSuperuser()) _checkForHttpHostError($config); 

// ensure core jQuery modules are loaded before others
$modules->get("JqueryCore"); 
$modules->get("JqueryUI"); 

// tell ProcessWire that any pages loaded from this point forward should have their outputFormatting turned off
$pages->setOutputFormatting(false); 

// setup breadcrumbs to current page, and the Process may modify, add to or replace them as needed
$breadcrumbs = $wire->wire('breadcrumbs', new Breadcrumbs()); 
foreach($page->parents() as $p) {
    if($p->id > 1) $breadcrumbs->add(new Breadcrumb($p->url, $p->get("title|name"))); 
}

$controller = null;
$content = '';
$ajax = $config->ajax;
$modal = $input->get('modal') ? true : false;
$demo = $config->demo;

// enable modules to output their own ajax responses if they choose to
if($ajax) ob_start();

if($page->process && $page->process != 'ProcessPageView') {
    try {

        if($demo && $page->process != 'ProcessLogin') {
            if(count($_POST)) $wire->error("Features that use POST variables are disabled in this demo"); 
            foreach($_POST as $k => $v) unset($_POST[$k]); 
            foreach($_FILES as $k => $v) unset($_FILES[$k]); 
            $input->post->removeAll();
        } else if($input->requestMethod('POST') && $user->isLoggedin() && $user->hasPermission('page-edit')) {
            _checkForMaxInputVars($input);
        }

        $controller = new ProcessController(); 
        $wire->wire($controller);
        $controller->setProcessName($page->process); 
        $initFile = $config->paths->adminTemplates . 'init.php'; 
        if(is_file($initFile)) {
            if(strpos($initFile, $config->paths->site) === 0) {
                // admin themes in /site/modules/ may be compiled
                $initFile = $wire->files->compile($initFile);
            }
            /** @noinspection PhpIncludeInspection */
            include_once($initFile);
        }
        if($modal) $session->addHookBefore('redirect', null, '_hookSessionRedirectModal'); 
        $content = $controller->execute();
        $process = $controller->wire('process');
        
        if(!$ajax && !$modal && !$demo && $user->isLoggedin()) _checkForTwoFactorAuth($session);
        if($process) {} // ignore

    } catch(Wire404Exception $e) {
        $wire->setStatusFailed($e, "404 from $page->process", $page);
        $wire->error($e->getMessage()); 

    } catch(WirePermissionException $e) {
        $wire->setStatusFailed($e, "Permission error from $page->process", $page); 

        if($controller && $controller->isAjax()) {
            $content = $controller->jsonMessage($e->getMessage(), true); 

        } else if($user->isGuest()) {
            /** @var Process $process */
            $process = $modules->get("ProcessLogin"); 
            $content = $process->execute();
        } else {
            $wire->error($e->getMessage());     
        }

    } catch(\Exception $e) {
        $wire->setStatusFailed($e, "Error from $page->process", $page); 
        $msg = $e->getMessage(); 
        if($config->debug) {
            $msg = $sanitizer->entities($msg);
            $msg .= "<pre>\n" . 
                __('DEBUG MODE BACKTRACE') . " " . 
                "(\$config->debug == true):\n" . 
                $sanitizer->entities($e->getTraceAsString()) . 
                "</pre>";
            $wire->error("$page->process: $msg", Notice::allowMarkup);
        } else {
            $wire->error($msg);
        }
        if($controller && $controller->isAjax()) {
            $content = $controller->jsonMessage($e->getMessage(), true);
            $wire->trackException($e, false);
        } else {
            $wire->trackException($e, true);
        }
    }

} else {
    $content = '<p>' . __('This page has no process assigned.') . '</p>';
}

if($ajax) {
    // enable modules to output their own ajax responses if they choose to
    if(!$content) $content = ob_get_contents();
    ob_end_clean();
}

// config properties that should be mirrored to ProcessWire.config.property in JS
$config->js(array('httpHost', 'httpHosts', 'https'), true); 

if($controller && $controller->isAjax()) {
    if(empty($content) && count($notices)) $content = $controller->jsonMessage($notices->last()->text); 
    echo $content; 
} else {
    if(!strlen($content)) $content = '<p>' . __('The process returned no content.') . '</p>';
    if($adminTheme) {
        $adminThemeFile = $adminTheme->path() . 'default.php';
    } else {
        $adminThemeFile = $config->paths->adminTemplates . 'default.php';
    }
    if(strpos($adminThemeFile, $config->paths->site) === 0) {
        // @todo determine if compilation needed
        $adminThemeFile = $wire->files->compile($adminThemeFile);
    }
    /** @noinspection PhpIncludeInspection */
    require($adminThemeFile);
    $session->removeNotices();
    if($content) {} // ignore
}