From 4d48bda2c7e4249f07ec4fd82caf2f4e1d9aa25c Mon Sep 17 00:00:00 2001 From: Ibrahim BinAlshikh Date: Mon, 4 Aug 2025 17:00:03 +0300 Subject: [PATCH 01/12] Update composer.json --- composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.json b/composer.json index eed39d6f7..8b43235fa 100644 --- a/composer.json +++ b/composer.json @@ -2,7 +2,7 @@ "name": "webfiori/framework", "description": "WebFiori framework. Made to make the web bloom.", "homepage": "https://webfiori.com", - "version": "3.0.0-Beta.27", + "version": "3.0.0-Beta.28", "type": "library", "support": { "issues": "https://github.com/webfiori/framework/issues", From bd05e130fe5a964fdea1f2f29a08cf4100ba8c9a Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Thu, 9 Oct 2025 12:19:46 +0000 Subject: [PATCH 02/12] chore(main): release 3.0.0-Beta.29 --- CHANGELOG.md | 33 +++++++++++++++++++++++++++++++++ composer.json | 2 +- 2 files changed, 34 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8fa8d739f..81b513f08 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,38 @@ # Changelog +## [3.0.0-Beta.29](https://github.com/WebFiori/framework/compare/v3.0.0-Beta.28...v3.0.0-Beta.29) (2025-10-09) + + +### Bug Fixes + +* API Creation ([d3c384f](https://github.com/WebFiori/framework/commit/d3c384f48f61c828cf9358cbb1675e26be25b0ee)) +* Command Writer ([338e8d2](https://github.com/WebFiori/framework/commit/338e8d2d7e72e66463bb8d57a6b34f20aa38ff3b)) +* Fix Test Case ([6b26e2f](https://github.com/WebFiori/framework/commit/6b26e2f023cea94eb7fdf2a665f1a721e5e190eb)) +* Fix Test Cases ([1086336](https://github.com/WebFiori/framework/commit/108633622e1938699360b45d65d3565bcf611e85)) +* Fix Test Cases ([0c914de](https://github.com/WebFiori/framework/commit/0c914de97f37eef1bf862c4b37f323470dfcf5ef)) +* Fixes and Tests Refactoring ([5ff31ab](https://github.com/WebFiori/framework/commit/5ff31ab5dc2c33b8a9389f3bf33001a525f57a7d)) +* Getting Arg Value in CLI ([c56b9bf](https://github.com/WebFiori/framework/commit/c56b9bf6f34617336ad1f750943f705d84357ba0)) +* Migrations Command ([ce54748](https://github.com/WebFiori/framework/commit/ce5474803a26352b75eaea66604b630378b98aa9)) +* Namespaces ([e51d354](https://github.com/WebFiori/framework/commit/e51d354a2be0843f55e70ced529c45341988ba2e)) +* References to Classes ([a140646](https://github.com/WebFiori/framework/commit/a1406462197a32a8bd16a6d12168c8c2a4fda016)) +* Tasks Names Check ([3c80893](https://github.com/WebFiori/framework/commit/3c80893d4c793e330f571205a702b853540d9a36)) +* Test Cases ([f78c6d5](https://github.com/WebFiori/framework/commit/f78c6d5a2b9fa10baa98ce9c229295d95f5b0fef)) +* Test Classes ([cd68b49](https://github.com/WebFiori/framework/commit/cd68b49acbc7dab3d8abc25fa3df21b129d19bf4)) +* Theme Creation ([6864c23](https://github.com/WebFiori/framework/commit/6864c236e7174113a4b4dc26e8289ed2645bb278)) +* Theme Resources Creation ([e9f1025](https://github.com/WebFiori/framework/commit/e9f10258f4433982e922cac494dec69b06f34e8b)) +* Writing Classes ([2fcb0c5](https://github.com/WebFiori/framework/commit/2fcb0c5993d7eb47c270d8113f58a2cf13f76046)) + + +### Miscellaneous Chores + +* Merge pull request [#266](https://github.com/WebFiori/framework/issues/266) from WebFiori/dev ([19fc94a](https://github.com/WebFiori/framework/commit/19fc94a9166ecafb2572e0926f9992b93a170341)) +* Merge pull request [#268](https://github.com/WebFiori/framework/issues/268) from WebFiori/dev ([fb1e6a3](https://github.com/WebFiori/framework/commit/fb1e6a3bb3d5b69642641fd323e82785f28c72f9)) +* Updated Database Library ([7f853fd](https://github.com/WebFiori/framework/commit/7f853fd25fd9f0b1211fb18fc807a2436f403946)) +* Updated Database to v1.0.0 ([c35b109](https://github.com/WebFiori/framework/commit/c35b109d292df38b32965ceed0bd7e0a90c8cc08)) +* Updated Dependencies ([ff05d95](https://github.com/WebFiori/framework/commit/ff05d95b8923ee631649c4610bf7dd348dcd4869)) +* Updated HTTP to v4 ([ab525fc](https://github.com/WebFiori/framework/commit/ab525fc9a37db07ae454b53e961cddba628b82e3)) +* Updated Version Number ([c2bac79](https://github.com/WebFiori/framework/commit/c2bac791aa6aca1bd9e8742c017d75e8574fd38d)) + ## [3.0.0-Beta.26](https://github.com/WebFiori/framework/compare/v3.0.0-Beta.26...v3.0.0-Beta.26) (2025-04-07) diff --git a/composer.json b/composer.json index ef7411354..e16eff99e 100644 --- a/composer.json +++ b/composer.json @@ -2,7 +2,7 @@ "name": "webfiori/framework", "description": "WebFiori framework. Made to make the web bloom.", "homepage": "https://webfiori.com", - "version": "3.0.0-Beta.28", + "version": "3.0.0-Beta.29", "type": "library", "support": { "issues": "https://github.com/webfiori/framework/issues", From bd9a3bd297eab02b71cefc359bb4ceffba00de61 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Tue, 21 Oct 2025 21:26:47 +0000 Subject: [PATCH 03/12] chore(main): release 3.0.0-Beta.30 --- CHANGELOG.md | 17 +++++++++++++++++ composer.json | 2 +- 2 files changed, 18 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 81b513f08..357d999a0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,22 @@ # Changelog +## [3.0.0-Beta.30](https://github.com/WebFiori/framework/compare/v3.0.0-Beta.29...v3.0.0-Beta.30) (2025-10-21) + + +### Features + +* Added a Script to Update Version ([e998c47](https://github.com/WebFiori/framework/commit/e998c476b2629b6b8307d6ad52813cdc1a63eb05)) + + +### Bug Fixes + +* Metadata ([84facc3](https://github.com/WebFiori/framework/commit/84facc3638ce6989d9b3efcf6dc05f9b7f87944b)) + + +### Miscellaneous Chores + +* Merge pull request [#269](https://github.com/WebFiori/framework/issues/269) from WebFiori/dev ([8ffb523](https://github.com/WebFiori/framework/commit/8ffb523559ab609095da446b2d592607dac1f20e)) + ## [3.0.0-Beta.29](https://github.com/WebFiori/framework/compare/v3.0.0-Beta.28...v3.0.0-Beta.29) (2025-10-09) diff --git a/composer.json b/composer.json index 1bb80c644..360e8d439 100644 --- a/composer.json +++ b/composer.json @@ -2,7 +2,7 @@ "name": "webfiori/framework", "description": "WebFiori framework. Made to make the web bloom.", "homepage": "https://webfiori.com", - "version": "3.0.0-Beta.29", + "version": "3.0.0-Beta.30", "type": "library", "support": { "issues": "https://github.com/webfiori/framework/issues", From f08d491c2803b16ecd64486596ccc5727d37a1ce Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Mon, 27 Oct 2025 22:01:38 +0000 Subject: [PATCH 04/12] chore(main): release 3.0.0-beta.31 --- CHANGELOG.md | 15 +++++++++++++++ composer.json | 2 +- 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 357d999a0..9e24c82e7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,20 @@ # Changelog +## [3.0.0-beta.31](https://github.com/WebFiori/framework/compare/v3.0.0-Beta.30...v3.0.0-beta.31) (2025-10-27) + + +### Bug Fixes + +* Bug in Finding Class Loader Path ([6831a96](https://github.com/WebFiori/framework/commit/6831a96d91962480b097561b559e2ecf07af1c0b)) +* Show Framework Logo ([e47f1a5](https://github.com/WebFiori/framework/commit/e47f1a5d70b5d5982ff87247de3feb98ce75d558)) + + +### Miscellaneous Chores + +* Merge pull request [#271](https://github.com/WebFiori/framework/issues/271) from WebFiori/dev ([6bf9c26](https://github.com/WebFiori/framework/commit/6bf9c26ab448e60bcdfee5f6f91b2cdc97304006)) +* Updated Framework Version ([3d719ea](https://github.com/WebFiori/framework/commit/3d719ea32aea00d973dbb61e5ddf98194a5e7e0f)) +* Updated Index ([0b128ec](https://github.com/WebFiori/framework/commit/0b128ec53339304b351df9d1953e192dc233a24a)) + ## [3.0.0-Beta.30](https://github.com/WebFiori/framework/compare/v3.0.0-Beta.29...v3.0.0-Beta.30) (2025-10-21) diff --git a/composer.json b/composer.json index 360e8d439..077831b4e 100644 --- a/composer.json +++ b/composer.json @@ -2,7 +2,7 @@ "name": "webfiori/framework", "description": "WebFiori framework. Made to make the web bloom.", "homepage": "https://webfiori.com", - "version": "3.0.0-Beta.30", + "version": "3.0.0-beta.31", "type": "library", "support": { "issues": "https://github.com/webfiori/framework/issues", From f28f9776e7b19d91b352ef1e3fb4a1cc7a793674 Mon Sep 17 00:00:00 2001 From: Ibrahim BinAlshikh Date: Tue, 17 Feb 2026 23:41:30 +0300 Subject: [PATCH 05/12] feat: Env Vars for Json Driver --- WebFiori/Framework/Config/Controller.php | 25 ++++++++++++ WebFiori/Framework/Config/JsonDriver.php | 40 ++++++++++++------- .../Framework/Tests/Config/JsonDriverTest.php | 34 ++++++++++++++++ 3 files changed, 85 insertions(+), 14 deletions(-) diff --git a/WebFiori/Framework/Config/Controller.php b/WebFiori/Framework/Config/Controller.php index 367a44b5d..fc04303d6 100644 --- a/WebFiori/Framework/Config/Controller.php +++ b/WebFiori/Framework/Config/Controller.php @@ -109,6 +109,25 @@ public static function setDriver(ConfigurationDriver $driver) { self::get()->driver = $driver; self::init($driver); } + /** + * Resolves environment variable references in configuration values. + * + * If value starts with 'env:', attempts to read from environment. + * Falls back to original value if env var doesn't exist. + * + * @param mixed $value The value to resolve + * + * @return mixed The resolved value + */ + public static function resolveEnvValue(string $value) { + + if (str_starts_with($value, 'env:')) { + $envVar = substr($value, 4); + + return getenv($envVar) ?: ($_ENV[$envVar] ?? $value); + } + return $value; + } /** * Reads application environment variables and updates the class which holds * application environment variables. @@ -117,6 +136,12 @@ public static function setDriver(ConfigurationDriver $driver) { */ public static function updateEnv() { foreach (self::getDriver()->getEnvVars() as $name => $envVar) { + if (is_string($envVar) && str_starts_with($envVar, 'env:')) { + $envVar = self::resolveEnvValue($envVar); + } else if (is_array($envVar) && isset($envVar['value']) && str_starts_with($envVar['value'], 'env:')) { + $envVar['value'] = self::resolveEnvValue($envVar['value']); + } + if (!defined($name)) { if (isset($envVar['value'])) { define($name, $envVar['value']); diff --git a/WebFiori/Framework/Config/JsonDriver.php b/WebFiori/Framework/Config/JsonDriver.php index 831a0594d..487882c9a 100644 --- a/WebFiori/Framework/Config/JsonDriver.php +++ b/WebFiori/Framework/Config/JsonDriver.php @@ -117,7 +117,7 @@ public function addOrUpdateSMTPAccount(SMTPAccount $emailAccount) { 'password' => $emailAccount->getPassword(), 'address' => $emailAccount->getAddress(), 'sender-name' => $emailAccount->getSenderName(), - + 'access-token' => $emailAccount->getAccessToken() ], 'none', 'same'); $this->json->get('smtp-connections')->add($emailAccount->getAccountName(), $connectionAsJson); $this->writeJson(); @@ -206,12 +206,12 @@ public function getDBConnection(string $conName) { } return new ConnectionInfo( - $jsonObj->get('type'), - $jsonObj->get('username'), - $jsonObj->get('password'), - $jsonObj->get('database'), - $jsonObj->get('host'), - $jsonObj->get('port'), + $this->getProp($jsonObj, 'type', $conName), + $this->getProp($jsonObj, 'username', $conName), + $this->getProp($jsonObj, 'password', $conName), + $this->getProp($jsonObj, 'database', $conName), + $this->getProp($jsonObj, 'host', $conName), + $this->getProp($jsonObj, 'port', $conName), $extrasArr); } } @@ -291,9 +291,19 @@ public function getEnvVars(): array { $vars = $this->json->get('env-vars'); foreach ($vars->getPropsNames() as $name) { + $value = ''; + $desc = null; + $val = $this->json->get('env-vars')->get($name); + + if (gettype($val) == 'object') { + $value = $val->get('value'); + $desc = $val->get('description'); + } else { + $value = $val; + } $retVal[$name] = [ - 'value' => $this->json->get('env-vars')->get($name)->get('value'), - 'description' => $this->json->get('env-vars')->get($name)->get('description') + 'value' => Controller::resolveEnvValue($value), + 'description' => $desc ]; } @@ -332,13 +342,13 @@ public function getPrimaryLanguage(): string { * password. */ public function getSchedulerPassword(): string { - $pass = $this->json->get('scheduler-password') ?? 'NO_PASSWORD'; + $pass = ''.$this->json->get('scheduler-password') ?? 'NO_PASSWORD'; - if (strlen($pass.'') == 0 || $pass == 'NO_PASSWORD') { + if (strlen($pass) == 0 || $pass == 'NO_PASSWORD') { return 'NO_PASSWORD'; } - return $pass; + return Controller::resolveEnvValue($pass); } /** * Returns SMTP connection given its name. @@ -365,7 +375,9 @@ public function getSMTPConnection(string $name) { 'sender-name' => $this->getProp($jsonObj, 'sender-name', $name), 'server-address' => $this->getProp($jsonObj, 'host', $name), 'user' => $this->getProp($jsonObj, 'username', $name), - 'account-name' => $name + 'user' => $this->getProp($jsonObj, 'username', $name), + 'account-name' => $name, + 'access-token' => $this->getProp($jsonObj, 'access-token', $name) ]); } } @@ -735,7 +747,7 @@ private function getProp(Json $j, $name, string $connName) { throw new InitializationException('The property "'.$name.'" of the connection "'.$connName.'" is missing.'); } - return $val; + return Controller::resolveEnvValue($val); } private function isValidLangCode($langCode) { $code = strtoupper(trim($langCode)); diff --git a/tests/WebFiori/Framework/Tests/Config/JsonDriverTest.php b/tests/WebFiori/Framework/Tests/Config/JsonDriverTest.php index e714dbb02..eb658c56e 100644 --- a/tests/WebFiori/Framework/Tests/Config/JsonDriverTest.php +++ b/tests/WebFiori/Framework/Tests/Config/JsonDriverTest.php @@ -628,4 +628,38 @@ public function testSMTPConnections04() { $account = $driver->getSMTPConnection('Cool2'); $this->assertNotNull($account); } + public function testEnvVars00() { + $driver = new JsonDriver(); + $driver->setConfigFileName('config-with-env.json'); + putenv('HOST=22.22.22.22'); + putenv('VERBOSE=1'); + putenv('SMTP_00_USER=test@example.com'); + putenv('SMTP_00_PASS=3241'); + putenv('SMTP_00_ADDRESS=test2@example.com'); + putenv('SMTP_00_NAME=Ibrahim'); + putenv('SMTP_TOKEN=some_tkn'); + + putenv('DB_HOST_2=122.76.76.87'); + putenv('DB_NAME_2=Ibrahim'); + putenv('DB_PASS_2=some_pass'); + + + $driver->initialize(); + $vars = $driver->getEnvVars(); + + $this->assertEquals('22.22.22.22', $vars['HOST']['value']); + $this->assertEquals('1', $vars['WF_VERBOSE_2']['value']); + + $smtp = $driver->getSMTPConnection('conn00'); + $this->assertEquals('test2@example.com', $smtp->getAddress()); + $this->assertEquals('some_tkn', $smtp->getAccessToken()); + $this->assertEquals('3241', $smtp->getPassword()); + $this->assertEquals('test@example.com', $smtp->getUsername()); + $this->assertEquals('Ibrahim', $smtp->getSenderName()); + + $dbConn00 = $driver->getDBConnection('New_Connection_2'); + $this->assertEquals('122.76.76.87', $dbConn00->getHost()); + $this->assertEquals('Ibrahim', $dbConn00->getDBName()); + $this->assertEquals('some_pass', $dbConn00->getPassword()); + } } From e9ad0f40a4cc211a00e4d6881d963c1e2af47fd9 Mon Sep 17 00:00:00 2001 From: Ibrahim BinAlshikh Date: Wed, 18 Feb 2026 00:06:13 +0300 Subject: [PATCH 06/12] test: Added Env Vars Test Config --- App/Config/config-with-env.json | 69 +++++++++++++++++++++++++++++++++ 1 file changed, 69 insertions(+) create mode 100644 App/Config/config-with-env.json diff --git a/App/Config/config-with-env.json b/App/Config/config-with-env.json new file mode 100644 index 000000000..f4624fd38 --- /dev/null +++ b/App/Config/config-with-env.json @@ -0,0 +1,69 @@ +{ + "base-url":"DYNAMIC", + "theme":null, + "home-page":"BASE_URL", + "primary-lang":"env:LANG", + "titles":{ + "AR":"افتراضي", + "EN":"Default" + }, + "name-separator":"|", + "scheduler-password":"env:SCHEDULER_PASS", + "app-names":{ + "AR":"تطبيق", + "EN":"Application" + }, + "app-descriptions":{ + "AR":"", + "EN":"" + }, + "version-info":{ + "version":"1.0", + "version-type":"Stable", + "release-date":"2023-10-30" + }, + "env-vars":{ + "WF_VERBOSE_2":{ + "value":"env:VERBOSE", + "description":"Configure the verbosity of error messsages at run-time. This should be set to true in testing and false in production." + }, + "CLI_HTTP_HOST_2":{ + "value":"example.com", + "description":"Host name that will be used when runing the application as command line utility." + }, + "HOST":"env:HOST" + }, + "smtp-connections":{ + "conn00": { + "host":"smtp.outlook.com", + "port":"244", + "username":"env:SMTP_00_USER", + "password":"env:SMTP_00_PASS", + "address":"env:SMTP_00_ADDRESS", + "sender-name":"env:SMTP_00_NAME", + "access-token":"env:SMTP_TOKEN" + } + }, + "database-connections":{ + "New_Connection":{ + "type":"mysql", + "host":"env:DB_HOST_1", + "port":3306, + "username":"cool", + "database":"env:DB_NAME_1", + "password":"env:DB_PASS_1", + "extras":{ + } + }, + "New_Connection_2":{ + "type":"mysql", + "host":"env:DB_HOST_2", + "port":3306, + "database":"env:DB_NAME_2", + "password":"env:DB_PASS_2", + "username":"cool", + "extras":{ + } + } + } +} \ No newline at end of file From f0ee1a715a3cc1cb4920ad3c779a87f1813748f3 Mon Sep 17 00:00:00 2001 From: Ibrahim BinAlshikh Date: Wed, 18 Feb 2026 00:06:47 +0300 Subject: [PATCH 07/12] test: Added Scheduler Password Test --- tests/WebFiori/Framework/Tests/Config/JsonDriverTest.php | 3 +++ 1 file changed, 3 insertions(+) diff --git a/tests/WebFiori/Framework/Tests/Config/JsonDriverTest.php b/tests/WebFiori/Framework/Tests/Config/JsonDriverTest.php index eb658c56e..4919d2ba6 100644 --- a/tests/WebFiori/Framework/Tests/Config/JsonDriverTest.php +++ b/tests/WebFiori/Framework/Tests/Config/JsonDriverTest.php @@ -661,5 +661,8 @@ public function testEnvVars00() { $this->assertEquals('122.76.76.87', $dbConn00->getHost()); $this->assertEquals('Ibrahim', $dbConn00->getDBName()); $this->assertEquals('some_pass', $dbConn00->getPassword()); + + putenv('SCHEDULER_PASS=my_secure_hash'); + $this->assertEquals('my_secure_hash', $driver->getSchedulerPassword()); } } From b34cd182ae731d9b814b3362da1332b103e08277 Mon Sep 17 00:00:00 2001 From: Ibrahim BinAlshikh Date: Wed, 18 Feb 2026 00:07:18 +0300 Subject: [PATCH 08/12] feat: Optional Config Var --- WebFiori/Framework/Config/JsonDriver.php | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/WebFiori/Framework/Config/JsonDriver.php b/WebFiori/Framework/Config/JsonDriver.php index 487882c9a..440c907fb 100644 --- a/WebFiori/Framework/Config/JsonDriver.php +++ b/WebFiori/Framework/Config/JsonDriver.php @@ -375,9 +375,8 @@ public function getSMTPConnection(string $name) { 'sender-name' => $this->getProp($jsonObj, 'sender-name', $name), 'server-address' => $this->getProp($jsonObj, 'host', $name), 'user' => $this->getProp($jsonObj, 'username', $name), - 'user' => $this->getProp($jsonObj, 'username', $name), 'account-name' => $name, - 'access-token' => $this->getProp($jsonObj, 'access-token', $name) + 'access-token' => $this->getProp($jsonObj, 'access-token', $name, false) ]); } } @@ -402,6 +401,7 @@ public function getSMTPConnections(): array { $acc->setSenderName($this->getProp($jsonObj, 'sender-name', $name)); $acc->setServerAddress($this->getProp($jsonObj, 'host', $name)); $acc->setUsername($this->getProp($jsonObj, 'username', $name)); + $acc->setAccessToken($this->getProp($jsonObj, 'access-token', $name, false)); $retVal[$name] = $acc; } @@ -740,10 +740,10 @@ public function setTitleSeparator(string $separator) { public function toJSON() : Json { return $this->json; } - private function getProp(Json $j, $name, string $connName) { + private function getProp(Json $j, $name, string $connName, bool $requred = true) { $val = $j->get($name); - if ($val === null) { + if ($val === null && $requred) { throw new InitializationException('The property "'.$name.'" of the connection "'.$connName.'" is missing.'); } From 32da35f1252461aa659392ec75b68efae1122370 Mon Sep 17 00:00:00 2001 From: Ibrahim BinAlshikh Date: Wed, 18 Feb 2026 01:06:49 +0300 Subject: [PATCH 09/12] docs: Updated PHP Docs For Config --- .../Framework/Config/ConfigurationDriver.php | 28 +++++++++++ WebFiori/Framework/Config/Controller.php | 34 +++++++++++-- WebFiori/Framework/Config/JsonDriver.php | 50 +++++++++++++++++-- 3 files changed, 104 insertions(+), 8 deletions(-) diff --git a/WebFiori/Framework/Config/ConfigurationDriver.php b/WebFiori/Framework/Config/ConfigurationDriver.php index 93382a08b..9c68f8229 100644 --- a/WebFiori/Framework/Config/ConfigurationDriver.php +++ b/WebFiori/Framework/Config/ConfigurationDriver.php @@ -20,6 +20,9 @@ interface ConfigurationDriver { * the constant will be accesaable anywhere within the application's environment. * Additionally, it will be added as environment variable using 'putenv()'. * + * Note: The value parameter supports the 'env:' prefix for referencing + * system environment variables (e.g., "env:MY_VAR"). + * * @param string $name The name of the named constant such as 'MY_CONSTANT'. * * @param mixed|null $value The value of the constant. @@ -31,6 +34,9 @@ public function addEnvVar(string $name, mixed $value = null, ?string $descriptio /** * Adds new database connections information or update existing connections. * + * Note: When using JsonDriver, connection properties support environment variable + * substitution using the 'env:' prefix. For example, you can set host to "env:DB_HOST" + * in the JSON configuration file, and it will be resolved at runtime. * * @param ConnectionInfo $dbConnectionsInfo An object which holds connection information. */ @@ -38,6 +44,10 @@ public function addOrUpdateDBConnection(ConnectionInfo $dbConnectionsInfo); /** * Adds new SMTP account or Updates an existing one. * + * Note: When using JsonDriver, SMTP account properties support environment variable + * substitution using the 'env:' prefix. For example, you can set password to "env:SMTP_PASS" + * in the JSON configuration file, and it will be resolved at runtime. + * * @param SMTPAccount $emailAccount An instance of 'SMTPAccount'. */ public function addOrUpdateSMTPAccount(SMTPAccount $emailAccount); @@ -92,6 +102,9 @@ public function getBaseURL() : string; /** * Returns database connection information given connection name. * + * Note: Connection properties that use the 'env:' prefix in configuration + * will be automatically resolved to their environment variable values. + * * @param string $conName The name of the connection. * * @return ConnectionInfo|null The method should return an object of type @@ -107,6 +120,9 @@ public function getDBConnection(string $conName); * The keys of the array should be the name of database connection and the * value of each key should be an object of type ConnectionInfo. * + * Note: Connection properties that use the 'env:' prefix in configuration + * will be automatically resolved to their environment variable values. + * * @return array An associative array. */ public function getDBConnections() : array; @@ -133,6 +149,9 @@ public function getDescriptions() : array; /** * Returns an associative array of application constants. * + * Note: Environment variable values that use the 'env:' prefix in configuration + * will be automatically resolved to their system environment variable values. + * * @return array The indices of the array are names of the constants and * values are sub-associative arrays. Each sub-array must have two indices, * 'value' and 'description'. @@ -158,6 +177,9 @@ public function getPrimaryLanguage() : string; * return the hashed value. If no password is set, this method should return the * string 'NO_PASSWORD'. * + * Note: The scheduler password value supports environment variable substitution + * using the 'env:' prefix (e.g., "env:SCHEDULER_PASS" in configuration). + * * @return string Password hash or the string 'NO_PASSWORD' if there is no * password. */ @@ -165,6 +187,9 @@ public function getSchedulerPassword() : string; /** * Returns SMTP connection given its name. * + * Note: SMTP account properties that use the 'env:' prefix in configuration + * will be automatically resolved to their environment variable values. + * * The method should be implemented in a way that it searches * for an account with the given name in the set * of added accounts. If no account was found, null is returned. @@ -184,6 +209,9 @@ public function getSMTPConnection(string $name); * The indices of the array should act as the names of the accounts, * and the value of the index should be an object of type SMTPAccount. * + * Note: SMTP account properties that use the 'env:' prefix in configuration + * will be automatically resolved to their environment variable values. + * * @return array An associative array that contains all added SMTP accounts. * */ diff --git a/WebFiori/Framework/Config/Controller.php b/WebFiori/Framework/Config/Controller.php index fc04303d6..9ba8fbb62 100644 --- a/WebFiori/Framework/Config/Controller.php +++ b/WebFiori/Framework/Config/Controller.php @@ -112,14 +112,38 @@ public static function setDriver(ConfigurationDriver $driver) { /** * Resolves environment variable references in configuration values. * - * If value starts with 'env:', attempts to read from environment. - * Falls back to original value if env var doesn't exist. + * This method enables the use of environment variables in configuration files + * by using the 'env:' prefix. When a configuration value starts with 'env:', + * the method attempts to read the corresponding environment variable. + * + * Example usage in JSON configuration: + * + * { + * "database-connections": { + * "production": { + * "host": "env:DB_HOST", + * "password": "env:DB_PASS" + * } + * } + * } + * + * + * The method will: + * - Check if the value starts with 'env:' + * - Extract the environment variable name (e.g., 'DB_HOST' from 'env:DB_HOST') + * - Attempt to read from getenv() first, then $_ENV + * - Fall back to the original value if the environment variable doesn't exist * - * @param mixed $value The value to resolve + * @param mixed $value The value to resolve. Can be any type, but only strings + * starting with 'env:' will be processed. * - * @return mixed The resolved value + * @return mixed The resolved value. Returns the environment variable value if found, + * otherwise returns the original value unchanged. */ - public static function resolveEnvValue(string $value) { + public static function resolveEnvValue($value) { + if (!is_string($value)) { + return $value; + } if (str_starts_with($value, 'env:')) { $envVar = substr($value, 4); diff --git a/WebFiori/Framework/Config/JsonDriver.php b/WebFiori/Framework/Config/JsonDriver.php index 440c907fb..272b46d91 100644 --- a/WebFiori/Framework/Config/JsonDriver.php +++ b/WebFiori/Framework/Config/JsonDriver.php @@ -81,6 +81,9 @@ public function __construct() { * a named constant at run time using the function 'define'. This means * the constant will be accessable anywhere within the application's environment. * + * Note: The value parameter supports the 'env:' prefix for referencing + * system environment variables (e.g., "env:MY_VAR"). + * * @param string $name The name of the named constant such as 'MY_CONSTANT'. * * @param mixed $value The value of the constant. @@ -95,6 +98,15 @@ public function addEnvVar(string $name, mixed $value = null, ?string $descriptio ], 'none', 'same')); $this->writeJson(); } + /** + * Adds new database connections information or update existing connection. + * + * Note: When using this driver, connection properties support environment variable + * substitution using the 'env:' prefix. For example, you can set host to "env:DB_HOST" + * in the JSON configuration file, and it will be resolved at runtime. + * + * @param ConnectionInfo $dbConnectionsInfo An object which holds connection information. + */ public function addOrUpdateDBConnection(ConnectionInfo $dbConnectionsInfo) { $connectionJAsJson = new Json([ 'type' => $dbConnectionsInfo->getDatabaseType(), @@ -108,7 +120,15 @@ public function addOrUpdateDBConnection(ConnectionInfo $dbConnectionsInfo) { $this->json->get('database-connections')->add($dbConnectionsInfo->getName(), $connectionJAsJson); $this->writeJson(); } - + /** + * Adds new SMTP account or Updates an existing one. + * + * Note: When using this driver, SMTP account properties support environment variable + * substitution using the 'env:' prefix. For example, you can set password to "env:SMTP_PASS" + * in the JSON configuration file, and it will be resolved at runtime. + * + * @param SMTPAccount $emailAccount An instance of 'SMTPAccount'. + */ public function addOrUpdateSMTPAccount(SMTPAccount $emailAccount) { $connectionAsJson = new Json([ 'host' => $emailAccount->getServerAddress(), @@ -190,6 +210,16 @@ public static function getConfigFileName() : string { return self::$configFileName; } + /** + * Returns database connection information given connection name. + * + * Note: Connection properties that use the 'env:' prefix in configuration + * will be automatically resolved to their environment variable values. + * + * @param string $conName The name of the connection. + * + * @return ConnectionInfo|null Returns connection info if found, null otherwise. + */ public function getDBConnection(string $conName) { $jsonObj = $this->json->get('database-connections')->get($conName); @@ -218,6 +248,9 @@ public function getDBConnection(string $conName) { /** * Returns an associative array that contain the information of database connections. * + * Note: Connection properties that use the 'env:' prefix in configuration + * will be automatically resolved to their environment variable values. + * * @return array An associative array. The indices are connections names and * values are objects of type 'ConnectionInfo'. */ @@ -281,6 +314,9 @@ public function getDescriptions(): array { * Returns an array that holds the information of defined application environment * variables. * + * Note: Environment variable values that use the 'env:' prefix in configuration + * will be automatically resolved to their system environment variable values. + * * @return array The returned array will be associative. The key will represent * the name of the variable and its value is a sub-associative array with * two indices, 'description' and 'value'. The description index is a text that describes @@ -338,6 +374,9 @@ public function getPrimaryLanguage(): string { * return the hashed value. If no password is set, this method will return the * string 'NO_PASSWORD'. * + * Note: The scheduler password value supports environment variable substitution + * using the 'env:' prefix (e.g., "env:SCHEDULER_PASS" in configuration). + * * @return string Password hash or the string 'NO_PASSWORD' if there is no * password. */ @@ -353,10 +392,12 @@ public function getSchedulerPassword(): string { /** * Returns SMTP connection given its name. * - * The method will search - * for an account with the given name in the set + * The method will search for an account with the given name in the set * of added accounts. If no account was found, null is returned. * + * Note: SMTP account properties that use the 'env:' prefix in configuration + * will be automatically resolved to their environment variable values. + * * @param string $name The name of the account. * * @return SMTPAccount|null If the account is found, The method @@ -383,6 +424,9 @@ public function getSMTPConnection(string $name) { /** * Returns an array that contains all added SMTP accounts. * + * Note: SMTP account properties that use the 'env:' prefix in configuration + * will be automatically resolved to their environment variable values. + * * @return array An array that contains all added SMTP accounts. */ public function getSMTPConnections(): array { From 89cd39d0f48c558a471a6351218539a8c72f30b2 Mon Sep 17 00:00:00 2001 From: Ibrahim BinAlshikh Date: Wed, 18 Feb 2026 01:07:38 +0300 Subject: [PATCH 10/12] feat: Add `env:` to Class Driver --- WebFiori/Framework/Config/ClassDriver.php | 112 ++++++++++++++- .../Tests/Config/ClassDriverEnvTest.php | 128 ++++++++++++++++++ 2 files changed, 234 insertions(+), 6 deletions(-) create mode 100644 tests/WebFiori/Framework/Tests/Config/ClassDriverEnvTest.php diff --git a/WebFiori/Framework/Config/ClassDriver.php b/WebFiori/Framework/Config/ClassDriver.php index 3c4647836..437d0c793 100644 --- a/WebFiori/Framework/Config/ClassDriver.php +++ b/WebFiori/Framework/Config/ClassDriver.php @@ -15,6 +15,18 @@ * create a class called 'AppConfig' on the directory APP_DIR/Config and * use it to read and write configurations. * + * ## Environment Variable Support + * + * This driver supports environment variable substitution using the 'env:' prefix. + * The following configuration sections support this feature: + * - Database connections (all properties) + * - SMTP connections (all properties) + * - Environment variables (value field) + * - Scheduler password + * + * Values stored in the generated PHP class can use the 'env:' prefix, and they + * will be resolved to their environment variable values when read. + * * @author Ibrahim */ class ClassDriver implements ConfigurationDriver { @@ -60,6 +72,9 @@ public static function a($file, $str, $tabSize = 0) { * a named constant at run time using the function 'define'. This means * the constant will be accesaable anywhere within the appllication's environment. * + * Note: The value parameter supports the 'env:' prefix for referencing + * system environment variables (e.g., "env:MY_VAR"). + * * @param string $name The name of the named constant such as 'MY_CONSTANT'. * * @param mixed $value The value of the constant. @@ -77,6 +92,10 @@ public function addEnvVar(string $name, mixed $value = null, ?string $descriptio /** * Adds new database connections information or update existing connection. * + * Note: When using this driver, connection properties support environment variable + * substitution using the 'env:' prefix. Values will be stored in the generated PHP class + * and resolved when read. + * * @param ConnectionInfo $dbConnectionsInfo An object which holds connection information. */ public function addOrUpdateDBConnection(ConnectionInfo $dbConnectionsInfo) { @@ -86,6 +105,10 @@ public function addOrUpdateDBConnection(ConnectionInfo $dbConnectionsInfo) { /** * Adds new SMTP account or Updates an existing one. * + * Note: When using this driver, SMTP account properties support environment variable + * substitution using the 'env:' prefix. Values will be stored in the generated PHP class + * and resolved when read. + * * @param SMTPAccount $emailAccount An instance of 'SMTPAccount'. */ public function addOrUpdateSMTPAccount(SMTPAccount $emailAccount) { @@ -157,6 +180,9 @@ public function getBaseURL(): string { /** * Returns database connection information given connection name. * + * Note: Connection properties that use the 'env:' prefix will be + * automatically resolved to their environment variable values. + * * @param string $conName The name of the connection. * * @return ConnectionInfo|null The method will return an object of type @@ -174,10 +200,30 @@ public function getDBConnection(string $conName) { /** * Returns an associative array that contain the information of database connections. * + * Note: Connection properties that use the 'env:' prefix will be + * automatically resolved to their environment variable values. + * * @return array An associative array of objects of type ConnectionInfo. */ public function getDBConnections(): array { - return $this->configVars['database-connections']; + $connections = $this->configVars['database-connections']; + + foreach ($connections as $name => $connObj) { + if ($connObj instanceof ConnectionInfo) { + $connObj->setHost(Controller::resolveEnvValue($connObj->getHost())); + $connObj->setUsername(Controller::resolveEnvValue($connObj->getUsername())); + $connObj->setPassword(Controller::resolveEnvValue($connObj->getPassword())); + $connObj->setDBName(Controller::resolveEnvValue($connObj->getDBName())); + + $extras = $connObj->getExtars(); + foreach ($extras as $key => $value) { + $extras[$key] = Controller::resolveEnvValue($value); + } + $connObj->setExtras($extras); + } + } + + return $connections; } public function getDescription(string $langCode) { @@ -198,12 +244,25 @@ public function getDescriptions(): array { /** * Returns an associative array of application constants. * + * Note: Environment variable values that use the 'env:' prefix will be + * automatically resolved to their system environment variable values. + * * @return array The indices of the array are names of the constants and * values are sub-associative arrays. Each sub-array will have two indices, * 'value' and 'description'. */ public function getEnvVars(): array { - return $this->configVars['env-vars']; + $vars = $this->configVars['env-vars']; + + foreach ($vars as $name => $varData) { + if (is_array($varData) && isset($varData['value'])) { + $vars[$name]['value'] = Controller::resolveEnvValue($varData['value']); + } else { + $vars[$name] = Controller::resolveEnvValue($varData); + } + } + + return $vars; } /** * Returns a string that represents the URL of home page of the application. @@ -221,9 +280,17 @@ public function getHomePage() : string { public function getPrimaryLanguage() : string { return $this->configVars['site']['primary-lang']; } - + /** + * Returns sha256 hash of the password which is used to prevent unauthorized + * access to run the tasks or access scheduler web interface. + * + * Note: The scheduler password value supports environment variable substitution + * using the 'env:' prefix (e.g., "env:SCHEDULER_PASS"). + * + * @return string Password hash or the string 'NO_PASSWORD' if there is no password. + */ public function getSchedulerPassword(): string { - return $this->configVars['scheduler-password']; + return Controller::resolveEnvValue($this->configVars['scheduler-password']); } public function getSMTPAccount(string $name) { @@ -242,15 +309,48 @@ public function getSMTPAccount(string $name) { * will return an object of type SMTPAccount. Else, the * method will return null. * + */ /** + * Returns SMTP connection given its name. + * + * Note: SMTP account properties that use the 'env:' prefix will be + * automatically resolved to their environment variable values. + * + * @param string $name The name of the account. + * + * @return SMTPAccount|null If the account is found, The method + * will return an object of type SMTPAccount. Else, the + * method will return null. + * */ public function getSMTPConnection(string $name) { if (isset($this->getSMTPConnections()[$name])) { return $this->getSMTPConnections()[$name]; } } - + /** + * Returns an array that contains all added SMTP accounts. + * + * Note: SMTP account properties that use the 'env:' prefix will be + * automatically resolved to their environment variable values. + * + * @return array An associative array of SMTPAccount objects. + */ public function getSMTPConnections(): array { - return $this->configVars['smtp-connections']; + $connections = $this->configVars['smtp-connections']; + + foreach ($connections as $name => $smtpObj) { + if ($smtpObj instanceof SMTPAccount) { + $smtpObj->setServerAddress(Controller::resolveEnvValue($smtpObj->getServerAddress())); + $smtpObj->setPort(Controller::resolveEnvValue($smtpObj->getPort())); + $smtpObj->setUsername(Controller::resolveEnvValue($smtpObj->getUsername())); + $smtpObj->setPassword(Controller::resolveEnvValue($smtpObj->getPassword())); + $smtpObj->setAddress(Controller::resolveEnvValue($smtpObj->getAddress())); + $smtpObj->setSenderName(Controller::resolveEnvValue($smtpObj->getSenderName())); + $smtpObj->setAccessToken(Controller::resolveEnvValue($smtpObj->getAccessToken())); + } + } + + return $connections; } public function getTheme(): string { diff --git a/tests/WebFiori/Framework/Tests/Config/ClassDriverEnvTest.php b/tests/WebFiori/Framework/Tests/Config/ClassDriverEnvTest.php new file mode 100644 index 000000000..51e579948 --- /dev/null +++ b/tests/WebFiori/Framework/Tests/Config/ClassDriverEnvTest.php @@ -0,0 +1,128 @@ +initialize(true); + + // Add connection with env: values + $conn = new ConnectionInfo('mysql', 'env:TEST_DB_USER', 'env:TEST_DB_PASS', 'env:TEST_DB_NAME'); + $conn->setHost('env:TEST_DB_HOST'); + $conn->setName('test_conn'); + $driver->addOrUpdateDBConnection($conn); + + // Retrieve and verify resolution + $retrieved = $driver->getDBConnection('test_conn'); + $this->assertEquals('192.168.1.100', $retrieved->getHost()); + $this->assertEquals('testuser', $retrieved->getUsername()); + $this->assertEquals('testpass123', $retrieved->getPassword()); + $this->assertEquals('testdb', $retrieved->getDBName()); + + $driver->remove(); + } + + /** + * @test + */ + public function testEnvVarsInSMTPConnection() { + putenv('TEST_SMTP_HOST=smtp.test.com'); + putenv('TEST_SMTP_USER=test@example.com'); + putenv('TEST_SMTP_PASS=smtppass'); + putenv('TEST_SMTP_FROM=noreply@test.com'); + + $driver = new ClassDriver(); + $driver->initialize(true); + + // Add SMTP with env: values + $smtp = new SMTPAccount(); + $smtp->setAccountName('test_smtp'); + $smtp->setServerAddress('env:TEST_SMTP_HOST'); + $smtp->setUsername('env:TEST_SMTP_USER'); + $smtp->setPassword('env:TEST_SMTP_PASS'); + $smtp->setAddress('env:TEST_SMTP_FROM'); + $smtp->setSenderName('Test Sender'); + $smtp->setPort(587); + $driver->addOrUpdateSMTPAccount($smtp); + + // Retrieve and verify resolution + $retrieved = $driver->getSMTPConnection('test_smtp'); + $this->assertEquals('smtp.test.com', $retrieved->getServerAddress()); + $this->assertEquals('test@example.com', $retrieved->getUsername()); + $this->assertEquals('smtppass', $retrieved->getPassword()); + $this->assertEquals('noreply@test.com', $retrieved->getAddress()); + + $driver->remove(); + } + + /** + * @test + */ + public function testEnvVarsInEnvVars() { + putenv('TEST_API_KEY=secret_key_123'); + + $driver = new ClassDriver(); + $driver->initialize(true); + + $driver->addEnvVar('MY_API_KEY', 'env:TEST_API_KEY', 'API Key from environment'); + + $vars = $driver->getEnvVars(); + $this->assertEquals('secret_key_123', $vars['MY_API_KEY']['value']); + + $driver->remove(); + } + + /** + * @test + */ + public function testEnvVarsInSchedulerPassword() { + putenv('TEST_SCHEDULER_PASS=hashed_password_123'); + + $driver = new ClassDriver(); + $driver->initialize(true); + + $driver->setSchedulerPassword('env:TEST_SCHEDULER_PASS'); + + $this->assertEquals('hashed_password_123', $driver->getSchedulerPassword()); + + $driver->remove(); + } + + /** + * @test + */ + public function testFallbackWhenEnvNotSet() { + $driver = new ClassDriver(); + $driver->initialize(true); + + // Add connection with env: that doesn't exist + $conn = new ConnectionInfo('mysql', 'env:NONEXISTENT_USER', 'pass', 'db'); + $conn->setHost('env:NONEXISTENT_HOST'); + $conn->setName('fallback_test'); + $driver->addOrUpdateDBConnection($conn); + + // Should fallback to original value + $retrieved = $driver->getDBConnection('fallback_test'); + $this->assertEquals('env:NONEXISTENT_HOST', $retrieved->getHost()); + $this->assertEquals('env:NONEXISTENT_USER', $retrieved->getUsername()); + + $driver->remove(); + } +} From e7429c16cbea0999f2c2f07a333ec1f54e0bd971 Mon Sep 17 00:00:00 2001 From: Ibrahim BinAlshikh Date: Wed, 18 Feb 2026 01:57:01 +0300 Subject: [PATCH 11/12] Update JsonDriverTest.php --- tests/WebFiori/Framework/Tests/Config/JsonDriverTest.php | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/WebFiori/Framework/Tests/Config/JsonDriverTest.php b/tests/WebFiori/Framework/Tests/Config/JsonDriverTest.php index 4919d2ba6..e8ce8ebf6 100644 --- a/tests/WebFiori/Framework/Tests/Config/JsonDriverTest.php +++ b/tests/WebFiori/Framework/Tests/Config/JsonDriverTest.php @@ -664,5 +664,6 @@ public function testEnvVars00() { putenv('SCHEDULER_PASS=my_secure_hash'); $this->assertEquals('my_secure_hash', $driver->getSchedulerPassword()); + $driver->setConfigFileName('app-config.json'); } } From a8ee6672e2758e919dc7b2bfcc6e8d3718cab82f Mon Sep 17 00:00:00 2001 From: Ibrahim BinAlshikh Date: Wed, 18 Feb 2026 02:24:36 +0300 Subject: [PATCH 12/12] fix: App Directories Creation --- WebFiori/Framework/App.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/WebFiori/Framework/App.php b/WebFiori/Framework/App.php index 0962a778b..b3f62091b 100644 --- a/WebFiori/Framework/App.php +++ b/WebFiori/Framework/App.php @@ -726,6 +726,7 @@ private static function getRoot() { * @throws Exception */ private static function initAutoLoader() { + Ini::createAppDirs(); /** * Initialize autoloader. */ @@ -744,7 +745,6 @@ private static function initAutoLoader() { } if (!class_exists(APP_DIR.'\\Ini\\AutoLoad')) { - Ini::createAppDirs(); Ini::get()->createIniClass('AutoLoad', 'Add user-defined directories to the set of directories at which the framework will search for classes.'); } self::call(APP_DIR.'\\Ini\\AutoLoad::initialize');