diff --git a/Router.php b/Router.php
index 332c36e..6fff50a 100644
--- a/Router.php
+++ b/Router.php
@@ -16,11 +16,14 @@ require_once 'vendor/autoload.php';
// Application and Models autoload
spl_autoload_register(function ($class_name) {
- if(file_exists(__DIR__."/application/".$class_name.'.php')){
- include __DIR__."/application/".$class_name.'.php';
+ if (file_exists(__DIR__ . "/application/" . $class_name . '.php')) {
+ include __DIR__ . "/application/" . $class_name . '.php';
+// echo "Load class " . __DIR__ . "/application/" . $class_name . '.php' . "
\n";
}
- elseif(file_exists(__DIR__."/models/".$class_name.'.php')) {
- include __DIR__."/models/".$class_name.'.php';
+ elseif(file_exists(__DIR__ . "/models/" . $class_name . ".php")) {
+ include_once __DIR__."/models/Model.php";
+ include_once __DIR__ . "/models/" . $class_name . ".php";
+// echo "Load class ".__DIR__."/models/".$class_name.'.php'."
\n";
}
});
@@ -31,6 +34,12 @@ $twigSettings = PRODUCTION?['cache' => __dir__.'/tmp/']:[];
$loader = new Twig\Loader\FilesystemLoader(__dir__ . '/templates/'.Config::get('system', 'template', 'default')."/");
$twig = new Twig\Environment($loader, $twigSettings);
+$filter = new Twig\TwigFilter('t', function ($string) {
+ return _(strip_tags($string));
+});
+
+$twig->addFilter($filter);
+
class Config {
private static $instance = null;
@@ -53,4 +62,16 @@ class Config {
}
return $default;
}
+}
+
+function model($name){
+ return;
+ if(file_exists(__DIR__ . "/models/" . $name . ".php")){
+ include_once __DIR__ . "/models/Model.php";
+ include_once __DIR__ . "/models/" . $name . ".php";
+ }
+ else {
+ echo "FAULT
\n";
+ echo __DIR__ . "/models/" . $name . ".php". " doesn't exist";
+ }
}
\ No newline at end of file
diff --git a/application/Auth.php b/application/Auth.php
index 72b2f36..115ba58 100644
--- a/application/Auth.php
+++ b/application/Auth.php
@@ -17,10 +17,21 @@ class Auth {
}
public static function loginWithCredentials($email, $pass, bool $stayLoggedIn = false){
- $Auth = new Auth();
+ session_destroy();
+ self::$instance = null;
+ $Auth = Auth::getInstance();
$err = [];
+ if(strlen($email) < 3){
+ $err[] = __("Please enter a valid email.");
+ }
+ if(strlen($pass) < 3){
+ $err[] = __("Please enter a valid password. Hint: The passwords need to have at least 6 characters.");
+ }
+
+ if(!empty($err)){ return $err; }
+
// get user from database
$getUserSQL = "SELECT pwd, user_id, user_email FROM user WHERE user_email = ? LIMIT 1;";
$getUserRes = DB::query($getUserSQL, $email);
@@ -41,14 +52,16 @@ class Auth {
$userKey = GenKey();
$shaUserKey = sha1($userKey);
$expire = $stayLoggedIn?30:0; // if "stay logged in", stay logged in for 30 days.
+ $ctime = time();
$updateUserSQL = "INSERT INTO user_login (user_id, ckey, ctime, expire, agent) VALUES (?, ?, ?, ?, ?);";
try {
- DB::query($updateUserSQL, $dbUserId, $userKey, time(), $expire, $md5agent);
+ DB::query($updateUserSQL, $dbUserId, $userKey, $ctime, $expire, $md5agent);
$_SESSION['user_id'] = $dbUserId;
$_SESSION['user_name'] = $dbUserName;
$_SESSION['user_agent'] = $md5agent;
$_SESSION['user_key'] = $shaUserKey;
+ $_SESSION['user_ctime'] = $ctime;
if($stayLoggedIn){
setcookie("auth", $shaUserKey.".".$dbUserId, time() + (30 * 24*60*60), "/", $_SERVER['HTTP_HOST'], isset($_SERVER['HTTPS']), true );
@@ -56,11 +69,11 @@ class Auth {
return true;
}
catch(DatabaseException $e){
- $err[] = "Failed to login.\n".$e;
+ $err[] = __("Failed to login.")."\n".$e;
}
}
else {
- $err[] = "Username and/or Password is wrong.";
+ $err[] = __("The provided combination of username and password is not recognized in the system. Verify your details or create an account.");
}
return $err;
@@ -102,8 +115,76 @@ class Auth {
return false;
}
- public static function logout(){}
+ public static function logout($everywhere = false){
+ if(Auth::checkLogin()){
+ $logoutSql = "DELETE FROM user_login WHERE user_id = ?";
+ $sqlparams[] = $_SESSION['user_id'];
+ if(!$everywhere){
+// $logoutSql .= " AND agent = ?";
+// $sqlparams[] = md5($_SERVER['HTTP_USER_AGENT']);
+ $logoutSql .= " AND ctime = ?";
+ $sqlparams[] = $_SESSION['user_ctime'];
+ }
+ DB::query($logoutSql, $sqlparams);
+ }
+
+ // Delete session-cookie
+ $params = session_get_cookie_params();
+ setcookie(session_name(), '', time() - 42000,
+ $params["path"], $params["domain"],
+ $params["secure"], $params["httponly"]
+ );
+
+ setcookie('auth', '', time() - 42000, "/", $_SERVER['HTTP_HOST'], isset($_SERVER['HTTPS']), true );
+
+ // Finally, destroy the session.
+ session_destroy();
+ }
+
+
+ public static function register($userEmail, $userPass, $metadata = []){
+ $err = [];
+
+ $userName = $metadata['newName'] ?? '';
+
+ if(strlen($userPass) < 6){
+ $err[] = __("Password is too short. Password needs to be at least 6 characters!");
+ }
+ else if(strlen($userPass) > 30){
+ $err[] = __("Password is too long. Max length is set to 30 characters. If you believe it should be higher please contact the developers.");
+ }
+
+ if(strlen($userEmail) > 220){
+ $err[] = __("Your email-address is too long. Can you please register with another email?");
+ }
+
+ if(strlen($userName) > 200){
+ $err[] = __("Your name seems to be too long to fit this system. Maybe you can short it down somehow?");
+ }
+
+ if(empty($err)){
+ // HASH PASSWORD
+ $newPass = PwdGen($userPass, true);
+
+ $createUserSQL = "INSERT INTO user SET user_email = ?, pwd = ?, full_name = ?;";
+ try {
+ DB::query($createUserSQL, $userEmail, $newPass, $userName);
+ $newID = DB::insert_id();
+
+ if(empty($newID)){ return false; }
+
+ $updateUserSQL = "UPDATE user SET md5_id = ? WHERE user_id = ?;";
+ DB::query($updateUserSQL, md5($newID), $newID);
+
+ return true;
+ } catch (DatabaseException $e) {
+ $err[] = __("Something went wrong").":
" . $e;
+ }
+ }
+
+ return $err;
+ }
public static function checkLogin($strict = false): bool {
$Auth = Auth::getInstance();
diff --git a/application/DB.php b/application/DB.php
index 234cbd1..867aef5 100644
--- a/application/DB.php
+++ b/application/DB.php
@@ -4,16 +4,24 @@ class DB {
private static $instance = null;
private $db;
+ /**
+ * @throws DatabaseException
+ */
private function __construct(){
- $this->db = new mysqli(
- Config::get('database', 'host'),
- Config::get('database', "user"),
- Config::get('database', "pass"),
- Config::get('database', "database")
- );
+ try {
+ $this->db = new mysqli(
+ Config::get('database', 'host'),
+ Config::get('database', "user"),
+ Config::get('database', "pass"),
+ Config::get('database', "database")
+ );
- if($this->db->connect_error){
- die("Connection failed: " . $this->db->connect_error);
+ if($this->db->connect_error){
+ throw new DatabaseException("Database connection failed: ". $this->db->connect_error);
+ }
+ }
+ catch (mysqli_sql_exception $e){
+ throw new DatabaseException($e->getMessage(), $e->getCode());
}
}
@@ -52,16 +60,12 @@ class DB {
}
/**
+ * @param integer $mode 0: Execute normally - 1: Execute, but print query - 2: Only print query
+ * @param string $sql Input SQL as string
+ * @param mixed ...$params A list or an array of values for the query
* @throws DatabaseException
*/
- private static function doQuery($mode, $sql, ...$params){
- /*
- Modes:
- 0: Execute normally
- 1: Execute, but print query
- 2: Only print query
- */
-
+ private static function doQuery(int $mode, string $sql, ...$params){
// Check if query parameters comes as an array, or as individual arguments
if(count($params) == 1 && is_array($params[0])){
$params = $params[0];
@@ -69,7 +73,7 @@ class DB {
// Create type-specification for query
$types = "";
- foreach($params as &$value){
+ foreach($params as $value){
if(is_double($value)){
$types .= "d";
}
@@ -90,17 +94,44 @@ class DB {
return [];
}
- $stmt = $db->db->prepare($sql);
- if(
- $stmt === false ||
- $stmt->bind_param($types, ...$params) === false ||
- $stmt->execute() === false
- ){
- throw new DatabaseException($db->db->error);
+ try {
+ $stmt = $db->db->prepare($sql);
+ if(
+ $stmt === false ||
+ (!empty($params) && $stmt->bind_param($types, ...$params) === false) ||
+ $stmt->execute() === false
+ ){
+ throw new DatabaseException($db->db->error);
+ }
+ }
+ catch (mysqli_sql_exception $e){
+ throw new DatabaseException($e->getMessage(), $e->getCode());
}
return $stmt->get_result();
}
+
+ /**
+ * @throws DatabaseException
+ */
+ public static function multiQuery(...$sql){
+ $db = DB::getInstance();
+
+ foreach ($sql as $s){
+ try {
+ $db->db->multi_query($s);
+ }
+ catch (mysqli_sql_exception $e){
+ throw new DatabaseException($e->getMessage(), $e->getCode());
+ }
+ }
+ }
+
+ public static function insert_id(){
+ $db = DB::getInstance();
+
+ return $db->db->insert_id;
+ }
}
class DatabaseException extends Exception {
diff --git a/application/Translator.php b/application/Translator.php
new file mode 100644
index 0000000..4564b4a
--- /dev/null
+++ b/application/Translator.php
@@ -0,0 +1,170 @@
+language = "nb";
+ $this->country = "NO";
+ $this->charset = "utf8";
+
+ $this->locale = $this->language."_".$this->country.".".$this->charset;
+
+ if (defined('LC_MESSAGES')) {
+ setlocale(LC_MESSAGES, $this->locale);
+ bindtextdomain("domain1", __dir__."/../locale/");
+ } else {
+ echo "IS IN THE ELSE - PART";
+ }
+
+ textdomain("domain1");
+ }
+}
+
+/*
+use Gettext\Loader\PoLoader;
+
+class Translator {
+ private static $instance = null;
+ private $translations;
+ private $language;
+ private $country;
+ private $charset;
+ private $locale;
+
+ private function __construct($lang = "nb", $contry = "NO", $charset = "utf8"){
+
+ $this->language = $lang;
+ $this->country = $contry;
+ $this->charset = $charset;
+ $this->locale = $this->language."_".$this->country.".".$this->charset;
+
+ //import from a .po file:
+ $poloader = new PoLoader();
+ $this->translations = $poloader->loadFile('locale/'.$this->language.'.po');
+ }
+
+ public static function getInstance(): Translator {
+ if(self::$instance == null){
+ self::$instance = new Translator();
+ }
+
+ return self::$instance;
+ }
+
+ static public function translate($string, $domain = "messages"): string {
+ $trans = self::getInstance();
+
+ return $trans->translations;
+ }
+
+ static public function getBrowserSupportedLanguage(){
+ $language = null;
+ $input = $_SERVER['HTTP_ACCEPT_LANGUAGE'];
+
+ $supported_languages = ["en", "no"];
+
+ $browserLanguages = explode(",", str_replace(' ', '', strtolower($input)));
+
+ foreach ($browserLanguages as $languageString){
+ // Stop looking for languages if one is found
+ if(!empty($language)){ break; }
+
+ // Check for language in format "en_US"
+ $stringParts = explode('-', $languageString);
+
+ // If not in that format, check for "en;q=0.9" format
+ if(count($stringParts) <= 1){
+ $stringParts = explode(';', $languageString);
+ }
+
+ // If whichever format, check if the first key is a supported language
+ if(count($stringParts) > 1 && in_array($stringParts[0], $supported_languages)){
+ $language = $stringParts[0];
+ }
+ // If in neither format, check the whole string
+ elseif(in_array($languageString, $supported_languages)){
+ $language = $languageString;
+ }
+ }
+
+ // If no language found, set the first one in the supported-list
+ if(empty($language)){
+ $language = $supported_languages[0];
+ }
+
+ return $language;
+ }
+}
+*/
+
+use Gettext\Scanner\PhpScanner;
+use Gettext\Translations;
+use Gettext\Generator\PoGenerator;
+use Gettext\Generator\MoGenerator;
+
+
+class TranslatorScanning {
+ public $scanner;
+
+ public function __construct($domain = "default"){
+
+ $phpScanner = new PhpScanner(
+ Translations::create($domain)
+ );
+
+ //Set a default domain, so any translations with no domain specified, will be added to that domain
+ $phpScanner->setDefaultDomain($domain);
+
+ //Extract all comments starting with 'i18n:' and 'Translators:'
+ $phpScanner->extractCommentsStartingWith('i18n:', 'Translators:');
+
+ //Scan files
+ foreach (glob(__DIR__.'/../www/{*,*/*,*/*/*}.php', GLOB_BRACE) as $file) {
+// echo $file."
\n";
+ $phpScanner->scanFile($file);
+ }
+
+ $this->scanner = $phpScanner;
+ }
+
+ public function save(){
+ //Save the translations in .po files
+ $generator = new PoGenerator();
+
+ foreach ($this->phpScanner->getTranslations() as $domain => $translations) {
+ echo "Generate file: ".__DIR__."/../www/{$domain}.po
\n";
+ $generator->generateFile($translations, __DIR__."/../www/{$domain}.po");
+ }
+ }
+
+
+ public static function compiler(){
+ $loader = new PoLoader();
+
+ //From a file
+ $translations = $loader->loadFile(__DIR__."/../www/domain1.po");
+
+ $generator = new MoGenerator();
+ $generator->generateFile($translations, __DIR__."/../www/domain1.mo");
+
+
+
+// From a string
+// $string = file_get_contents('locales2/en.po');
+// $translations = $loader->loadString($string);
+ }
+}
+
+// Resources: https://packagist.org/packages/gettext/gettext
+// Resources: https://www.sitepoint.com/easy-multi-language-twig-apps-with-gettext/
+
+
+function __($string, ...$values): string {
+ if(DEBUG){
+ logTranslationKey($string);
+ }
+ return sprintf( _($string), $values);
+}
+
+function logTranslationKey($string){
+ file_put_contents("translationkeys.txt", file_get_contents("translationkeys.txt") . "\n" . $string);
+}
\ No newline at end of file
diff --git a/application/Utils.php b/application/Utils.php
index f2ebe84..9349599 100644
--- a/application/Utils.php
+++ b/application/Utils.php
@@ -18,5 +18,16 @@ class Utils {
}
}
-
+ public static function debug(...$args){
+ foreach($args as $arg){
+ echo "
"; + if(is_bool($arg)){ + var_dump($arg); + } + else { + echo print_r($arg, true); + } + echo "\n"; + } + } } \ No newline at end of file diff --git a/application/WebPage.php b/application/WebPage.php index e74163e..e87da69 100644 --- a/application/WebPage.php +++ b/application/WebPage.php @@ -17,6 +17,8 @@ class WebPage { public function __construct(){ + new Translator(); + $this->pr = Config::get('system', 'projectroot', ''); $this->loggedIn = Auth::checkLogin(); @@ -28,6 +30,11 @@ class WebPage { } try { + if(isset($_POST) && !empty($_POST)){ + $this->sanitizePost(); + $this->doPost(); + } + $this->prepareNavbar(); $this->load(); @@ -35,7 +42,7 @@ class WebPage { $this->template = str_replace('.php', '.html', $_SERVER['SCRIPT_NAME']); } - if($this->httpStatus == 200){ + if($this->httpStatus == 200 || $this instanceof ErrorPage){ global $twig; if(DEBUG){ @@ -43,7 +50,7 @@ class WebPage { header("X-Script-name: ".$_SERVER['SCRIPT_NAME']); } - http_response_code(200); + http_response_code($this->httpStatus); echo $twig->render($this->template, $this->vars()); } @@ -55,7 +62,9 @@ class WebPage { catch (\Twig\Error\LoaderError $e){ http_response_code(500); if( $e->getCode() == 0){ + echo "
"; print_r($e->getMessage()); + echo ""; } else { echo $e; @@ -63,12 +72,21 @@ class WebPage { } catch(\Twig\Error\RuntimeError | \Twig\Error\SyntaxError | Exception $e) { http_response_code(500); - echo $e; - print_r( $this->vars() ); + + if(!$this instanceof ErrorPage){ + $e = new ErrorPage(500, $e, print_r( $this->vars(), true )); + } + else { + echo "
".$e.""; + } + } } - function load(){ throw new Exception("Incomplete implementation"); } + function load(){ throw new Exception("Incomplete implementation"); } + + function doPost(){ throw new Exception("Incomplete implementation"); } function vars(): array { return (array) $this; @@ -83,6 +101,20 @@ class WebPage { ["href"=>"/review/", "name"=>"Review", "active"=>$this->activePage=="/review/"?'active':''] ], $this->navbar ?? []); } + + protected function sanitizePost(){ + $data = []; + + foreach($_POST as $key => $value){ + if(($data[$key] = Utils::filter($value)) === false){ + $err[] = __("Failed to sanitize: `%s`: %s \t-\t type: %s\n", $key, $value, gettype($value)); + } + } + + if(!empty($err)){ return $err; } + $this->data = $data; + return true; + } } class WebPageAuth extends WebPage { @@ -91,4 +123,38 @@ class WebPageAuth extends WebPage { parent::__construct(); } } +} + +class ErrorPage extends WebPage { + + public $errCode = 500; + public $errormessage = "An unexpected error occurred"; + public $errorDescription = ""; + public $template = "errors/500.html"; + + public function load(){ + if($this->errCode != 500){ + $this->template = "errors/$this->errCode.html"; + } + } + + public function __construct($errCode = 500, $e = "", $extra = ""){ + $this->errCode = $errCode; + if($e instanceof Exception){ + $this->errormessage = $e->getMessage(); + } + $this->errorDescription = $e; + $this->errorDescription .= "\n".$extra; + + parent::__construct(); + } +} + +function isExecutingPage($file) : bool { + $file1 = explode("/", $file); + $request = str_replace('/','', $_SERVER['REQUEST_URI']); + if(strstr($request, '?')){ + $request = explode('?', $request)[0]; + } + return $request == $file1[ array_key_last($file1) ]; } \ No newline at end of file diff --git a/composer.json b/composer.json index 4777dcc..5d6ce76 100644 --- a/composer.json +++ b/composer.json @@ -2,6 +2,9 @@ "require": { "twbs/bootstrap": "5.1.3", "twig/twig": "^3.3", - "ext-mysqli": "*" + "ext-mysqli": "*", + "gettext/gettext": "^5.6", + "ext-gettext": "*", + "gettext/php-scanner": "^1.3" } } diff --git a/composer.lock b/composer.lock index 215e281..ea81e02 100644 --- a/composer.lock +++ b/composer.lock @@ -4,8 +4,271 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "0daa3afffc94d70bd593d310cef916f8", + "content-hash": "6111dbb0fd97c255b22341c3bbfb4f67", "packages": [ + { + "name": "gettext/gettext", + "version": "v5.6.1", + "source": { + "type": "git", + "url": "https://github.com/php-gettext/Gettext.git", + "reference": "017e249601d32b9a88c2eb4c10eac89bf582a7d3" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-gettext/Gettext/zipball/017e249601d32b9a88c2eb4c10eac89bf582a7d3", + "reference": "017e249601d32b9a88c2eb4c10eac89bf582a7d3", + "shasum": "" + }, + "require": { + "gettext/languages": "^2.3", + "php": "^7.2|^8.0" + }, + "require-dev": { + "brick/varexporter": "^0.3.5", + "friendsofphp/php-cs-fixer": "^3.2", + "oscarotero/php-cs-fixer-config": "^2.0", + "phpunit/phpunit": "^8.0|^9.0", + "squizlabs/php_codesniffer": "^3.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Gettext\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Oscar Otero", + "email": "oom@oscarotero.com", + "homepage": "http://oscarotero.com", + "role": "Developer" + } + ], + "description": "PHP gettext manager", + "homepage": "https://github.com/php-gettext/Gettext", + "keywords": [ + "JS", + "gettext", + "i18n", + "mo", + "po", + "translation" + ], + "support": { + "email": "oom@oscarotero.com", + "issues": "https://github.com/php-gettext/Gettext/issues", + "source": "https://github.com/php-gettext/Gettext/tree/v5.6.1" + }, + "funding": [ + { + "url": "https://paypal.me/oscarotero", + "type": "custom" + }, + { + "url": "https://github.com/oscarotero", + "type": "github" + }, + { + "url": "https://www.patreon.com/misteroom", + "type": "patreon" + } + ], + "time": "2021-12-04T11:33:21+00:00" + }, + { + "name": "gettext/languages", + "version": "2.9.0", + "source": { + "type": "git", + "url": "https://github.com/php-gettext/Languages.git", + "reference": "ed56dd2c7f4024cc953ed180d25f02f2640e3ffa" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-gettext/Languages/zipball/ed56dd2c7f4024cc953ed180d25f02f2640e3ffa", + "reference": "ed56dd2c7f4024cc953ed180d25f02f2640e3ffa", + "shasum": "" + }, + "require": { + "php": ">=5.3" + }, + "require-dev": { + "phpunit/phpunit": "^4.8 || ^5.7 || ^6.5 || ^7.5 || ^8.4" + }, + "bin": [ + "bin/export-plural-rules" + ], + "type": "library", + "autoload": { + "psr-4": { + "Gettext\\Languages\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Michele Locati", + "email": "mlocati@gmail.com", + "role": "Developer" + } + ], + "description": "gettext languages with plural rules", + "homepage": "https://github.com/php-gettext/Languages", + "keywords": [ + "cldr", + "i18n", + "internationalization", + "l10n", + "language", + "languages", + "localization", + "php", + "plural", + "plural rules", + "plurals", + "translate", + "translations", + "unicode" + ], + "support": { + "issues": "https://github.com/php-gettext/Languages/issues", + "source": "https://github.com/php-gettext/Languages/tree/2.9.0" + }, + "funding": [ + { + "url": "https://paypal.me/mlocati", + "type": "custom" + }, + { + "url": "https://github.com/mlocati", + "type": "github" + } + ], + "time": "2021-11-11T17:30:39+00:00" + }, + { + "name": "gettext/php-scanner", + "version": "v1.3.1", + "source": { + "type": "git", + "url": "https://github.com/php-gettext/PHP-Scanner.git", + "reference": "989a2cffa1d0f43d13b14c83a50429119b5eb8e4" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-gettext/PHP-Scanner/zipball/989a2cffa1d0f43d13b14c83a50429119b5eb8e4", + "reference": "989a2cffa1d0f43d13b14c83a50429119b5eb8e4", + "shasum": "" + }, + "require": { + "gettext/gettext": "^5.5.0", + "nikic/php-parser": "^4.2", + "php": ">=7.2" + }, + "require-dev": { + "friendsofphp/php-cs-fixer": "^2.15", + "oscarotero/php-cs-fixer-config": "^1.0", + "phpunit/phpunit": "^8.0", + "squizlabs/php_codesniffer": "^3.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Gettext\\Scanner\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Oscar Otero", + "email": "oom@oscarotero.com", + "homepage": "http://oscarotero.com", + "role": "Developer" + } + ], + "description": "PHP scanner for gettext", + "homepage": "https://github.com/php-gettext/PHP-Scanner", + "keywords": [ + "gettext", + "i18n", + "php", + "scanner", + "translation" + ], + "support": { + "email": "oom@oscarotero.com", + "issues": "https://github.com/php-gettext/PHP-Scanner/issues", + "source": "https://github.com/php-gettext/PHP-Scanner/tree/v1.3.1" + }, + "time": "2022-03-18T11:47:55+00:00" + }, + { + "name": "nikic/php-parser", + "version": "v4.13.2", + "source": { + "type": "git", + "url": "https://github.com/nikic/PHP-Parser.git", + "reference": "210577fe3cf7badcc5814d99455df46564f3c077" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/210577fe3cf7badcc5814d99455df46564f3c077", + "reference": "210577fe3cf7badcc5814d99455df46564f3c077", + "shasum": "" + }, + "require": { + "ext-tokenizer": "*", + "php": ">=7.0" + }, + "require-dev": { + "ircmaxell/php-yacc": "^0.0.7", + "phpunit/phpunit": "^6.5 || ^7.0 || ^8.0 || ^9.0" + }, + "bin": [ + "bin/php-parse" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "4.9-dev" + } + }, + "autoload": { + "psr-4": { + "PhpParser\\": "lib/PhpParser" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Nikita Popov" + } + ], + "description": "A PHP parser written in PHP", + "keywords": [ + "parser", + "php" + ], + "support": { + "issues": "https://github.com/nikic/PHP-Parser/issues", + "source": "https://github.com/nikic/PHP-Parser/tree/v4.13.2" + }, + "time": "2021-11-30T19:35:32+00:00" + }, { "name": "symfony/polyfill-ctype", "version": "v1.25.0", @@ -304,7 +567,10 @@ "stability-flags": [], "prefer-stable": false, "prefer-lowest": false, - "platform": [], + "platform": { + "ext-mysqli": "*", + "ext-gettext": "*" + }, "platform-dev": [], - "plugin-api-version": "2.2.0" + "plugin-api-version": "2.3.0" } diff --git a/scripts/cron.php b/scripts/cron.php new file mode 100644 index 0000000..9b91928 --- /dev/null +++ b/scripts/cron.php @@ -0,0 +1,7 @@ + @@ -71,10 +71,11 @@ {% endif %} {% block content %}{% endblock %} + +{% block endContent %}{% endblock %} +