Files
2026-07-01 16:44:17 +03:00

880 lines
27 KiB
PHP

<?php namespace ZN;
/**
* ZN PHP Web Framework
*
* "Simplicity is the ultimate sophistication." ~ Da Vinci
*
* @package ZN
* @license MIT [http://opensource.org/licenses/MIT]
* @author Ozan UYKUN [ozan@znframework.com]
*/
use Throwable;
use ZN\Filesystem;
use ZN\Services\Restful;
use ZN\Protection\Separator;
use ZN\ErrorHandling\Exceptions;
/**
* @codeCoverageIgnore
*/
class ZN
{
/**
* Constant SE structure paths
*/
const STRUCTURE_PATHS =
[
'DIRECTORY_INDEX' => 'zeroneed.php',
'INTERNAL_DIR' => 'Internal/',
'EXTERNAL_DIR' => 'External/',
'SETTINGS_DIR' => 'Settings/',
'PROJECTS_DIR' => 'Projects/',
'BUTCHERY_DIR' => 'Butchery/',
'CONTROLLERS_DIR' => 'Controllers/',
'VIEWS_DIR' => 'Views/',
'ROUTES_DIR' => 'Routes/',
'CONFIG_DIR' => 'Config/',
'DATABASES_DIR' => 'Databases/',
'STORAGE_DIR' => 'Storage/',
'COMMANDS_DIR' => 'Commands/',
'LANGUAGES_DIR' => 'Languages/',
'LIBRARIES_DIR' => 'Libraries/',
'MODELS_DIR' => 'Models/',
'STARTING_DIR' => 'Starting/',
'AUTOLOAD_DIR' => 'Starting/Autoload/',
'HANDLOAD_DIR' => 'Starting/Handload/',
'RESOURCES_DIR' => 'Resources/',
'FILES_DIR' => 'Resources/Files/',
'TEMPLATES_DIR' => 'Resources/Templates/',
'THEMES_DIR' => 'Resources/Themes/',
'PLUGINS_DIR' => 'Resources/Plugins/',
'UPLOADS_DIR' => 'Resources/Uploads/'
];
/**
* Keeps custom defines
*
* @var array
*/
protected static $defines = [];
/**
* Upgrade Error
*
* @var string
*/
public static $upgradeError;
/**
* Project Type
*
* @var string
*/
public static $projectType;
/**
* Protected Lastest Version
*
* @var string
*/
protected static $lastestVersion;
/**
* Magic call static
*
* @param string $class
* @param array $parameters
*
* @return mixed
*/
public static function __callStatic($class, $parameters)
{
return Singleton::class($class, $parameters);
}
/**
* Run ZN
*
* @param string $type = NULL - options[EIP|SE|CE]
* @param string $version = NULL
* @param string $dedicate = NULL
*
* @return void|false
*/
public static function run(?string $type = NULL, ?string $version = NULL, ?string $dedicate = NULL)
{
# PHP shows code errors.
ini_set('display_errors', true); error_reporting(1);
# ZN Version
define('ZN_VERSION', $version);
# Dedicated
define('ZN_DEDICATE', $dedicate);
# It shows you which framework you are using. SE for single edition, EIP for multi edition.
define('PROJECT_TYPE', $type === 'FE' ? 'EIP' : $type);
# Keeps origin type.
self::$projectType = $type;
# Define standart constants
self::predefinedConstants();
# Predefined Functions
self::predefinedFunctions();
# Defines constants required for system and user.
self::defineDirectoryConstants();
# Enables class loading by automatically activating the object call.
Autoloader::register();
# Provides data about the current working url.
Structure::defines();
# Configures the use of Composer autoloader.
if( $composer = Config::get('Autoloader', 'composer') )
{
Composer::loader($composer);
}
# If the operation is executed via console, the code flow is not continue.
if( defined('CONSOLE_ENABLED') || $type === NULL )
{
return false;
}
# It keeps the selected project configuration.
define('PROJECT_CONFIG', Config::get('Project'));
# It keeps the selected project mode.
define('PROJECT_MODE', strtolower(PROJECT_CONFIG['mode'] ?? 'development'));
# Run Kernel
try
{
Kernel::run();
}
catch( Throwable $e )
{
if( PROJECT_MODE !== 'publication' )
{
Exceptions::table($e->getCode(), $e->getMessage(), $e->getFile(), $e->getLine(), $e->getTrace());
}
}
}
/**
* Undo upgrade
*
* @param string $version = 'last'
*
* @return string|bool
*/
public static function undoUpgrade($version = 'last')
{
$getUpgradeDirectories = Filesystem::getFiles('UpgradeBackups/', 'dir');
$getContainerDirectory = NULL;
if( ! empty($getUpgradeDirectories) )
{
if( $version === 'last' )
{
rsort($getUpgradeDirectories);
$getContainerDirectory = $getUpgradeDirectories[0] ?? NULL;
}
else
{
$getContainerDirectory = $version;
}
}
$lastUpgradeDirectory = 'UpgradeBackups/' . $getContainerDirectory . '/';
if( is_dir($lastUpgradeDirectory) )
{
Filesystem::copy($lastUpgradeDirectory, REAL_BASE_DIR); Filesystem::deleteFolder($lastUpgradeDirectory);
return true;
}
else
{
return self::getValueLang('upgradeBackupNotFound');
}
}
/**
* Protected get value lang
*/
protected static function getValueLang($value)
{
return Lang::default('ZN\CoreDefaultLanguage')::select('ZN', 'zn:' . $value);
}
/**
* Upgrade system
*
* @param void
*
* @return bool
*/
public static function upgrade()
{
$return = self::getRestGithubResult();
if( ! empty($return) )
{
Filesystem::createFolder($upgradeFolder = 'Upgrade'.md5('upgrade').'/');
Filesystem::createFolder($backupFolder = 'UpgradeBackups/' . ZN_VERSION . '/');
foreach( $return as $file => $content )
{
$file = $upgradeFolder . ($origin = $file);
$dirname = pathinfo($file, PATHINFO_DIRNAME);
Filesystem::createFolder($dirname);
if( ! empty($content) )
{
# [5.7.6]added
# Backup upgrade files.
if( file_exists($origin) && ($originContent = file_get_contents($origin)) !== $content )
{
$backupFile = $backupFolder . $origin;
Filesystem::createFolder(pathinfo($backupFile, PATHINFO_DIRNAME));
file_put_contents($backupFile, $originContent);
}
if( $origin === DIRECTORY_INDEX )
{
Filesystem::replaceData(DIRECTORY_INDEX, ZN_VERSION, self::$lastestVersion);
}
else
{
file_put_contents($file, $content);
}
}
}
Filesystem::copy($upgradeFolder, REAL_BASE_DIR); Filesystem::deleteFolder($upgradeFolder);
return true;
}
return false;
}
/**
* Get upgrade error
*
* @return string
*/
public static function upgradeError()
{
return self::$upgradeError ?: false;
}
/**
* Get upgrade files
*
* @param void
*
* @return array
*/
public static function upgradeFiles()
{
return array_keys(self::getRestGithubResult());
}
/**
* Custom Defines
*
* @param array $defines
*
* @return self
*/
public static function defines(array $defines)
{
self::$defines = $defines;
return new self;
}
/**
* Protected Predefined Functions
*/
protected static function predefinedFunctions()
{
require __DIR__ . '/Functions.php';
}
/**
* Private select structure paths
*/
private static function selectStructurePaths()
{
# Project type can be specified as CE, SE, FE, EIP or NULL
if( ! in_array(PROJECT_TYPE, ['CE', 'SE', 'EIP', NULL]) )
{
Base::trace('The project type can be specified as [CE], [SE], [FE], [EIP] or [NULL]. ' . PROJECT_TYPE . ' is invalid type.');
}
# Get file definitions according to the project type.
$method = (PROJECT_TYPE ?? 'CE') . '_STRUCTURE_PATHS';
# Get project dirs
define('GET_DIRS', self::$method());
}
/**
* Protected Predefined Constants
*/
protected static function predefinedConstants()
{
# Defined Standart Constants
# The version required for ZN Framework should be minimum 7.0.0.
define('REQUIRED_PHP_VERSION', '7.0.0');
# The use of SSL are automatically checked for correct operation of the system.
# In particular, it allows the URL library to automatically detect the http prefix.
define('SSL_STATUS',
(
( ! empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'off' ) || $_SERVER['SERVER_PORT'] == 443
? 'https'
: 'http'
) . '://');
# The ZN Framework contains a constant namespace for the controllers.
define('PROJECT_CONTROLLER_NAMESPACE', 'Project\Controllers\\');
# The ZN Framework contains a constant namespace for the project commands.
define('PROJECT_COMMANDS_NAMESPACE', 'Project\Commands\\');
# The ZN Framework contains a constant namespace for the external commands.
define('EXTERNAL_COMMANDS_NAMESPACE', 'External\Commands\\');
# If the class names contain the 'Internal' prefix,
# then those classes automatically create one static class.
define('INTERNAL_ACCESS', 'Internal');
# Predefined constants for space characters.
define('EOL' , PHP_EOL);
define('CRLF', "\r\n");
define('CR' , "\r");
define('LF' , "\n");
define('HT' , "\t");
define('TAB' , "\t");
define('FF' , "\f");
# Abbreviation for DIRECTORY_SEPARATOR.
define('DS', DIRECTORY_SEPARATOR);
# Select structure paths - options[EIP|SE|CE]
self::selectStructurePaths();
# The system directory is determined according to ZN project type.
define('INTERNAL_DIR', GET_DIRS['INTERNAL_DIR']);
define('EXTERNAL_DIR', GET_DIRS['EXTERNAL_DIR']);
define('SETTINGS_DIR', GET_DIRS['SETTINGS_DIR']);
define('PROJECTS_DIR', GET_DIRS['PROJECTS_DIR']);
# Directory Index
define('DIRECTORY_INDEX', GET_DIRS['DIRECTORY_INDEX']);
define('BASE_DIR', Base::suffix(ltrim(explode(DIRECTORY_INDEX, $_SERVER['SCRIPT_NAME'])[0], '/')));
# The system gives the knowledge of the actual root directory.
define('REAL_BASE_DIR', self::getCurrentWorkingDirectory());
}
/**
* Defines required constants
*
* @param string $version
*
* @return void
*/
public static function defineDirectoryConstants()
{
# It keeps the settings related to the structure of the projects.
define('PROJECTS_CONFIG', Base::import(SETTINGS_DIR . 'Projects.php'));
# Preserves project information to be run as predefined.
# This value is set to Frontend by default.
define('DEFAULT_PROJECT', PROJECTS_CONFIG['directory']['default'] ?? NULL);
# Defines constants to hide active project information.
# If a request is made to a project, it will alert the screen.
self::defineCurrentProject();
# If there is another project covering the active project,
# the name of this project is obtained.
# Otherwise the name of the project is used.
define('CONTAINER_PROJECT', PROJECTS_CONFIG['containers'][DEFINED_CURRENT_PROJECT] ?? CURRENT_PROJECT);
# The path information of the container project directory is stored.
define('CONTAINER_PROJECT_DIR', PROJECTS_DIR . CONTAINER_PROJECT . '/');
# The directory where the views will be created.
define('VIEWS_DIR', PROJECT_DIR . GET_DIRS['VIEWS_DIR']);
# The PAGES_DIR constant can be used instead of VIEWS_DIR.
define('PAGES_DIR', VIEWS_DIR);
# Get Common Paths
$externalDirectories = array_diff_key(GET_DIRS, array_flip
([
'DIRECTORY_INDEX',
'INTERNAL_DIR',
'EXTERNAL_DIR',
'SETTINGS_DIR',
'PROJECTS_DIR',
'VIEWS_DIR'
]));
# Constants for both project and external directories are being created.
foreach( $externalDirectories as $key => $value )
{
# Define External Directory
define('EXTERNAL_' . $key, EXTERNAL_DIR . ($value[0] === '!' ? substr($value, 1) : $value));
# Define Project Directory
self::directoryDefiner($key, $value);
}
if( ! is_dir(CONTROLLERS_DIR) && self::$projectType !== NULL )
{
Base::trace
(
'The [controller directory] for the custom edition must be defined.
To do this, specify the corresponding controller directory in the [zeroneed.php] file.'
);
}
}
/**
* Protected Directory Definer
*
* @param string $key
* @param string $value
*
*/
protected static function directoryDefiner($key, $value)
{
if( ! defined($key) )
{
# For EIP edition
if( PROJECT_TYPE === 'EIP' )
{
define($key, $value[0] === '!' ? substr($value, 1) : self::getProjectContainerDir($value));
}
# For SE edition
else
{
define($key, $value);
}
}
}
/**
* Protected Get Current Working Directory
*
* @return string
*/
protected static function getCurrentWorkingDirectory()
{
return (getcwd() ?: pathinfo($_SERVER['SCRIPT_FILENAME'], PATHINFO_DIRNAME)) . '/';
}
/**
* Get project container directory
*
* Returns the project directory name according to the project in the system.
* Only for multi edition.
*
* @param string $path = NULL
*
* @return string
*/
protected static function getProjectContainerDir($path = NULL) : string
{
# Container projects are being called.
$containers = PROJECTS_CONFIG['containers'] ?? [];
# Gets internal directories according to the active project.
$getProjectContainerDirectory = PROJECT_DIR . $path;
# If a project is being repaired, the project will be used with the 'Restore' prefix.
$restoreFix = 'Restore';
# Does the project name have a 'restore' prefix?
$isRestorePrefix = strpos((string) REQUESTED_CURRENT_PROJECT, $restoreFix) === 0;
# If the requested project is a sub-project, the project containing this project is called.
if( ( ! empty($containers) || $isRestorePrefix ) && REQUESTED_CURRENT_PROJECT !== NULL )
{
# 5.3.8[added]
# If there is a restore operation related to the desired project,
# the project request is reshaped according to the restore directory.
if( $isRestorePrefix && is_dir(PROJECTS_DIR . ($restoreDirectory = ltrim(REQUESTED_CURRENT_PROJECT, $restoreFix))) )
{
# If the project being repaired is within the scope of another upstream project,
# then the project directory is enabled.
if( $containers[$restoreDirectory] ?? NULL )
{
$activeContainerDirectory = $containers[$restoreDirectory];
}
# Otherwise, the repaired project is enabled.
else
{
$activeContainerDirectory = $restoreDirectory;
}
}
else
{
# If the requested project is covered by another project, that project is called.
$activeContainerDirectory = $containers[REQUESTED_CURRENT_PROJECT] ?? NULL;
}
# It allows common directory usage for multiple projects.
# If the requested directory is not in the subproject,
# the top project directory covering this project is used in common.
return ! empty($activeContainerDirectory) && ! is_dir($getProjectContainerDirectory)
? PROJECTS_DIR . $activeContainerDirectory . '/' . $path
: $getProjectContainerDirectory;
}
# 5.3.33[edited]
if( is_dir($getProjectContainerDirectory) )
{
return $getProjectContainerDirectory;
}
# 5.1.5[added]
# The enclosures can be the opening controller
if( $container = ($containers[CURRENT_PROJECT] ?? NULL) )
{
$getProjectContainerDirectory = str_replace(CURRENT_PROJECT, $container, $getProjectContainerDirectory);
}
# Return active project directory.
return $getProjectContainerDirectory;
}
/**
* host
*
* Returns the system host information.
*
* @param void
*
* @return string
*/
protected static function host() : string
{
if( isset($_SERVER['HTTP_X_FORWARDED_HOST']) )
{
$host = $_SERVER['HTTP_X_FORWARDED_HOST'];
$elements = explode(',', $host);
$host = trim(end($elements));
}
else
{
$host = $_SERVER['HTTP_HOST'] ??
$_SERVER['SERVER_NAME'] ??
$_SERVER['SERVER_ADDR'] ??
'';
}
return trim($host);
}
/**
* Define current project
*
* It arranges some values according to the project which is valid in the system.
*
* @param void
*
* @return mixed
*/
protected static function defineCurrentProject()
{
# The .htaccess file is checked for writability.
# This check only applies to the apache software.
self::isWritable('.htaccess');
# This function is only available for EIP editions.
if( PROJECT_TYPE !== 'EIP' )
{
define('CURRENT_PROJECT', NULL);
define('PROJECT_DIR', NULL);
define('REQUESTED_CURRENT_PROJECT', NULL);
define('DEFINED_CURRENT_PROJECT', NULL);
return false;
}
# It gets other defined subprojects.
$getOtherDirectories = PROJECTS_CONFIG['directory']['others'] ?? [];
if( $getOtherDirectories )
{
if( ! empty($getOtherDirectories[Base::prefix(self::host(), 'www.')]) )
{
define('IS_MAIN_DOMAIN', true);
}
}
if( defined('CONSOLE_PROJECT_NAME') )
{
# Project name information from the console is kept.
$getProjectNameFromURI = CONSOLE_PROJECT_NAME;
}
else
{
# Active URI information is being retrieved.
# This process is done to capture the project name.
$currentPath = $_SERVER['PATH_INFO'] ?? $_SERVER['QUERY_STRING'] ?? false;
# 5.0.3[edited]
# QUERY_STRING & REQUEST URI Empty Control
if( empty($currentPath) && ($requestUri = ($_SERVER['REQUEST_URI'] ?? false)) !== '/' )
{
$currentPath = $requestUri;
}
# 5.7.5.4[fixed]
if( ! empty(BASE_DIR) && strpos($currentPath, BASE_DIR) === 0 )
{
$trimPrefix = BASE_DIR;
}
else
{
$trimPrefix = '/';
}
# Only the project name is obtained from the data.
$getProjectNameFromURI = ( ! empty($currentPath) ? explode('/', ltrim($currentPath, $trimPrefix))[0] : '' );
}
# The host information of the page is being retrieved.
# 5.7.5[added]
$baseHost = Base::host();
# Sub project information is being retrieved. If no alias is used, its name is used.
# If the URI does not contain directory information, it will not get value.
# 5.7.5[changed]
$getProjectNameFromURI = $getOtherDirectories[$getProjectNameFromURI] ?? $getProjectNameFromURI;
# If no subproject definition is made, the default project is activated.
$getProjectNameFromOthers = $getOtherDirectories[$baseHost] ?? DEFAULT_PROJECT;
# The key and value information of the other project array are flipped.
# It is for checking the project aliases.
$flipOthersArray = array_flip($getOtherDirectories);
if( ! empty($getProjectNameFromURI) && is_dir(PROJECTS_DIR . $getProjectNameFromURI) )
{
# This keeps the project name coming from the constant URL.
define('REQUESTED_CURRENT_PROJECT', $getProjectNameFromURI);
$getProjectNameFromOthers = REQUESTED_CURRENT_PROJECT;
# Project alias is checked.
# The name of the project that comes with the URL has a valid alias name.
$currentProjectDirectory = $flipOthersArray[$getProjectNameFromOthers] ?? $getProjectNameFromOthers;
}
else
{
# Predefined requested current project name is null.
define('REQUESTED_CURRENT_PROJECT', NULL);
}
# The active project name is transferred to this constant.
define('CURRENT_PROJECT', $currentProjectDirectory ?? $getProjectNameFromOthers);
# Defines the active project directory information.
define('DEFINED_CURRENT_PROJECT', REQUESTED_CURRENT_PROJECT ?? CURRENT_PROJECT);
# The path information of the active project is being defined.
define('PROJECT_DIR', Base::suffix(PROJECTS_DIR . $getProjectNameFromOthers));
# Define config directory
self::directoryDefiner('CONFIG_DIR', GET_DIRS['CONFIG_DIR']);
# Subdomains or different domains are prevented from opening each other.
# 5.7.5[added]
$getOtherDirectory = $getOtherDirectories[$baseHost] ?? NULL;
# Gets container directory name.
$getContainerDirectory = PROJECTS_CONFIG['containers'][$getProjectNameFromOthers] ?? NULL;
# Subdomains or domains must be defined in the Settings/Projects.php
# configuration file in order for this control to work.
if
(
# Only the sub projects should be opened.
(
$getOtherDirectory !== NULL &&
$getOtherDirectory !== $getProjectNameFromOthers &&
$getOtherDirectory !== $getContainerDirectory
)
||
# The subdomain request is rejected.
$getOtherDirectory === $getProjectNameFromURI
||
# The subdomain will reject its own directory.
DEFAULT_PROJECT === $getProjectNameFromURI
||
# The subdomain can not be run outside of itself.
($isHost = ($flipOthersArray[$getProjectNameFromURI] ?? '')) && preg_match('/\w+\.\w+\.\w+/', $isHost)
)
{
# All requests are directed to the home.
defined('CONSOLE_PROJECT_NAME') ?: Response::redirect(! empty($isHost) ? Base::prefix($isHost, SSL_STATUS) : '');
}
# If there is no valid Projects/ directory, it returns an error.
if( ! is_dir(PROJECT_DIR) )
{
Base::trace('["'.$getProjectNameFromOthers.'"] Project Directory Not Found!');
}
}
/**
* Get restful class
*
* @return \ZN\Services\Restful
*/
protected static function getRestfulClass()
{
return new Restful;
}
/**
* protected restful
*
* @param void
*
* @return array
*/
protected static function getRestGithubResult()
{
$restful = self::getRestfulClass();
$return = $restful->useragent(true)->get('https://api.github.com/repos/znframework/fullpack-edition/tags');
$updatedFiles = [];
if( isset($return->message) )
{
self::$upgradeError = $return->message;
return $updatedFiles;
}
self::$lastestVersion = $return[0]->name;
if( version_compare(self::$lastestVersion, ZN_VERSION, '>') ) foreach( $return as $version )
{
if( version_compare($version->name, ZN_VERSION, '>') )
{
$commit = $restful->useragent(true)->get($version->commit->url);
foreach( $commit->files as $file )
{
if( ! isset($updatedFiles[$file->filename]) )
{
if( ! empty($file->raw_url) )
{
# If the changes are in the project directory, they are ignored.
# If the changes are in the settings directory, they are ignored.
if( strpos($file->filename, PROJECTS_DIR) !== 0 && strpos($file->filename, SETTINGS_DIR) !== 0 )
{
$updatedFiles[$file->filename] = file_get_contents($file->raw_url);
}
}
}
}
}
else
{
break;
}
}
return $updatedFiles;
}
/**
* Is writable
*
* Controls whether file permission is required in the operating system where the system is installed.
*
* @param string $path
*
* @return void
*/
protected static function isWritable(string $path)
{
if( is_file($path) && ! is_writable($path) && IS::software() === 'apache' )
{
Base::trace
(
'Please check the [file permissions]. Click the
<a target="_blank" style="text-decoration:none" href="https://docs.znframework.com/getting-started/installation-instructions#sh42">
[documentation]
</a>
to see how to configure file permissions.'
);
}
}
/**
* Private CE structure paths
*/
private static function CE_STRUCTURE_PATHS()
{
self::$defines['DIRECTORY_INDEX'] = self::$defines['DIRECTORY_INDEX'] ?? 'zeroneed.php';
$paths = [];
foreach( self::STRUCTURE_PATHS as $const => $path )
{
$paths[$const] = NULL;
}
return self::$defines + $paths;
}
/**
* Private SE structure paths
*/
private static function SE_STRUCTURE_PATHS()
{
$differents =
[
'INTERNAL_DIR' => 'Libraries/',
'EXTERNAL_DIR' => NULL,
'SETTINGS_DIR' => 'Config/'
];
return self::$defines + $differents + self::STRUCTURE_PATHS;
}
/**
* Private CE structure paths
*/
private static function EIP_STRUCTURE_PATHS()
{
return self::$defines + self::STRUCTURE_PATHS;
}
}