FlameCore/Infernum

View on GitHub
libraries/Application.php

Summary

Maintainability
B
5 hrs
Test Coverage
<?php
/**
 * Infernum
 * Copyright (C) 2015 IceFlame.net
 *
 * Permission to use, copy, modify, and/or distribute this software for
 * any purpose with or without fee is hereby granted, provided that the
 * above copyright notice and this permission notice appear in all copies.
 *
 * @package  FlameCore\Infernum
 * @version  0.1-dev
 * @link     http://www.flamecore.org
 * @license  http://opensource.org/licenses/ISC ISC License
 */

namespace FlameCore\Infernum;

use FlameCore\Infernum\Database\Database;
use FlameCore\Infernum\Interfaces\ExtensionMeta;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpFoundation\Cookie;

/**
 * The Application class
 *
 * @author   Christian Neff <christian.neff@gmail.com>
 */
final class Application implements \ArrayAccess
{
    /**
     * @var \FlameCore\Infernum\Container
     */
    private $container;

    /**
     * @var \FlameCore\Infernum\Site
     */
    private $site;

    /**
     * @var \FlameCore\Infernum\Kernel
     */
    private $kernel;

    /**
     * @var string
     */
    private $url;

    /**
     * @var \FlameCore\Infernum\Theme
     */
    private $theme;

    /**
     * Initializes the application.
     *
     * @param \FlameCore\Infernum\Site $site The site context
     * @param \FlameCore\Infernum\Kernel $kernel The kernel
     * @throws \UnexpectedValueException
     */
    public function __construct(Site $site, Kernel $kernel)
    {
        $this->site = $site;
        $this->kernel = $kernel;

        $this->container = new Container('application', [
            'settings' => 'array',
            'logger' => '\Psr\Log\LoggerInterface',
            'db' => '\FlameCore\Infernum\Database\DriverInterface',
            'cache' => '\FlameCore\Infernum\Cache',
            'session' => '\FlameCore\Infernum\Session',
            'intl' => '\FlameCore\Infernum\International',
            'tpl' => '\FlameCore\Infernum\Template\EngineInterface'
        ]);

        $this['logger'] = new Logger('site-'.$site->getName(), $kernel);

        // At first we have to load the settings
        if ($this->isCacheEnabled()) {
            $this['settings'] = $kernel->cache($site->getName().'/settings', [$site, 'loadSettings']);
        } else {
            $this['settings'] = $site->loadSettings();
        }

        // Now we can load our database driver
        $driver = $this['settings']['database']['driver'];
        $host = $this['settings']['database']['host'];
        $user = $this['settings']['database']['user'];
        $password = $this['settings']['database']['password'];
        $database = $this['settings']['database']['database'];
        $options = $this['settings']['database'];

        $this['db'] = Database::connect($driver, $host, $user, $password, $database, $options);

        // Set default timezone
        date_default_timezone_set($this['settings']['site']['timezone']);

        // Open cache instance
        $this['cache'] = new Cache($this->getCachePath('data'));

        // Set web URL
        $protocol = $kernel->isSecure() ? 'https' : 'http';
        $this->url = rtrim(sprintf('%s://%s%s', $protocol, $kernel->getDomain(), $this['settings']['web']['path']), '/');

        // Set theme
        $themeName = $this->setting('web.theme', 'flamecore/default');
        $this->setTheme($themeName);
    }

    /**
     * @return string
     */
    public function getUrl()
    {
        return $this->url;
    }

    /**
     * Returns the theme in use.
     *
     * @return \FlameCore\Infernum\Theme
     * @api
     */
    public function getTheme()
    {
        return $this->theme;
    }

    /**
     * Sets the name of the theme to use.
     *
     * @param string $name The name of the theme to use
     * @api
     */
    public function setTheme($name)
    {
        $this->theme = new Theme($name, $this->kernel);
    }

    /**
     * Returns the value of a setting.
     *
     * @param string $address The settings address in the form `<section>[:<keyname>]`
     * @param mixed $default Custom default value (optional)
     * @return mixed
     * @api
     */
    public function setting($address, $default = false)
    {
        $addrpart = explode('.', $address, 2);

        $section = $addrpart[0];
        $name = isset($addrpart[1]) ? $addrpart[1] : null;

        if (isset($name)) {
            return isset($this['settings'][$section][$name]) ? $this['settings'][$section][$name] : $default;
        } else {
            return isset($this['settings'][$section]) ? $this['settings'][$section] : $default;
        }
    }

    /**
     * Generates a URL to a path based on the application URL.
     *
     * @param string $path The relative path of the location
     * @param string $query Optional query string that is added to the URL
     * @return string
     * @api
     */
    public function makeUrl($path = '', $query = null)
    {
        $result = $this->url.'/'.$path;

        if (isset($query)) {
            $result .= '?'.$query;
        }

        return $result;
    }

    /**
     * Generates a URL to a module page.
     *
     * @param string $pagePath The path of the module page
     * @param string $query Optional query string that is added to the URL
     * @return string
     * @api
     */
    public function makePageUrl($pagePath, $query = null)
    {
        if ($this->setting('web.url_rewrite')) {
            $result = $this->url.'/'.$pagePath;

            if (isset($query)) {
                $result .= '?'.$query;
            }
        } else {
            $result = $this->url.'/?p='.$pagePath;

            if (isset($query)) {
                $result .= '&'.$query;
            }
        }

        return $result;
    }

    /**
     * Generates a URL to a file.
     *
     * @param string $filename The name of the file (appended to path)
     * @param bool $fromExtension Use file URL of the running extension. If FALSE, use global file URL.
     * @return string|bool
     * @api
     */
    public function makeFileUrl($filename, $fromExtension = false)
    {
        if ($fromExtension) {
            $extension = $this->kernel->getRunningExtension();

            if ($extension instanceof Module) {
                return $this->url.'/modules/'.$extension->getName().'/public/'.$filename;
            } elseif ($extension instanceof Plugin) {
                return $this->url.'/plugins/'.$extension->getName().'/public/'.$filename;
            } else {
                return false;
            }
        } else {
            return $this->url.'/themes/'.$this->theme->getName().'/public/'.$filename;
        }
    }

    /**
     * Reads data from cache. The $callback is used to generate the data if missing or expired.
     *
     * @param callable $callback The callback function that returns the data to store
     * @return mixed
     * @api
     */
    public function cache($name, callable $callback, $lifetime = null)
    {
        if ($this->isCacheEnabled()) {
            if ($this['cache']->contains($name)) {
                // We were able to retrieve data
                return $this['cache']->get($name);
            } else {
                // No data, so we use the given data callback and store the value
                $data = $callback();
                $this['cache']->set($name, $data, isset($lifetime) ? (int) $lifetime : $this->kernel->config('cache_lifetime', 0));
                return $data;
            }
        } else {
            // Caching is disabled, so we use the data callback directly
            return $callback();
        }
    }

    /**
     * Gets the real cookie name based on this context.
     *
     * @param string $name The generic cookie name
     * @return string
     * @api
     */
    public function getCookieName($name)
    {
        return $this->setting('cookie.name_prefix').$name;
    }

    /**
     * Creates a Cookie object based on this context.
     *
     * @param string $name The name of the cookie
     * @param string $value The value of the cookie
     * @param int|string|\DateTime $expire The time the cookie expires
     * @param string $path The path on the server in which the cookie will be available on (Default: settings value)
     * @param string $domain The domain that the cookie is available to (Default: settings value)
     * @param bool $secure Whether the cookie should only be transmitted over a secure HTTPS connection from the client (Default: FALSE)
     * @param bool $httpOnly Whether the cookie will be made accessible only through the HTTP protocol (Default: TRUE)
     * @return \Symfony\Component\HttpFoundation\Cookie
     * @api
     */
    public function createCookie($name, $value = null, $expire = 0, $path = null, $domain = null, $secure = false, $httpOnly = true)
    {
        $path = $path ?: $this->setting('cookie.path');
        $domain = $domain ?: $this->setting('cookie.domain');

        return new Cookie($name, $value, $expire, $path, $domain, $secure, $httpOnly);
    }

    /**
     * Returns whether caching is enabled.
     *
     * @return bool
     * @api
     */
    public function isCacheEnabled()
    {
        return $this->kernel->config('enable_caching');
    }

    /**
     * Returns whether debug mode is enabled.
     *
     * @return bool
     * @api
     */
    public function isDebugModeEnabled()
    {
        return $this->kernel->config('enable_debugmode');
    }

    /**
     * Gets the requested page path.
     *
     * @return string Returns the requested page path or FALSE if no request is handled yet.
     * @api
     */
    public function getPagePath()
    {
        return $this->kernel->getPagePath();
    }

    /**
     * Gets the path to the cache directory. The directory is created, if it does not exist on the filesystem.
     *
     * @param string $subpath A sub path insiside base cache path (optional)
     * @return string Returns the full cache path.
     * @api
     */
    public function getCachePath($subpath = null)
    {
        $completeSubpath = $this->site->getName();

        if (isset($subpath)) {
            $completeSubpath .= '/'.$subpath;
        }

        return $this->kernel->getCachePath($completeSubpath);
    }

    /**
     * Gets the theme path.
     *
     * @return string
     * @api
     */
    public function getThemePath()
    {
        return $this->theme->getPath();
    }

    /**
     * Gets the template path.
     *
     * @param bool $fromExtension Use template path of the running extension. If FALSE, use global template path.
     * @return string|bool Returns the full template path or FALSE if it could not be determined.
     * @api
     */
    public function getTemplatePath($fromExtension = false)
    {
        if ($fromExtension) {
            $extension = $this->kernel->getRunningExtension();

            if ($extension instanceof ExtensionMeta) {
                return $extension->getPath().'/templates';
            } else {
                return false;
            }
        } else {
            return $this->getThemePath().'/templates';
        }
    }

    /**
     * Finalizes the response.
     *
     * @param \Symfony\Component\HttpFoundation\Response $response The response
     * @internal
     */
    public function finalize(Response &$response)
    {
        if (isset($this['session'])) {
            $name = $this->getCookieName('session');

            if ($this['session']->isActive()) {
                $value = $this['session']->getID();
                $expire = $this['session']->getExpire();

                $cookie = $this->createCookie($name, $value, $expire);
                $response->headers->setCookie($cookie);
            } else {
                $response->headers->clearCookie($name);
            }
        }
    }

    /**
     * Returns the value with specified key.
     *
     * @param string $offset The name of the key
     * @return mixed
     */
    public function offsetGet($offset)
    {
        return $this->container->get($offset);
    }

    /**
     * Returns whether or not a key exists.
     *
     * @param string $offset The name of the key
     * @return bool
     */
    public function offsetExists($offset)
    {
        return $this->container->has($offset);
    }

    /**
     * Assigns a value to the specified key.
     *
     * @param string $offset The name of the key
     * @param mixed $value The value to assign
     * @throws \InvalidArgumentException if a key with empty name should be set or if the value for a given internal key is invalid.
     * @throws \LogicException if an internal key should be overridden, which is not allowed.
     */
    public function offsetSet($offset, $value)
    {
        $this->container->set($offset, $value, true);
    }

    /**
     * Unsets the specified key.
     *
     * @param string $offset The name of the key
     * @throws \LogicException if the given key is an internal key, which cannot be unset.
     */
    public function offsetUnset($offset)
    {
        $this->container->remove($offset);
    }
}