From 043466bdafd8f11a473650a23cc34a9e4bf0e10d Mon Sep 17 00:00:00 2001 From: Wachhund Skytower Date: Tue, 7 Apr 2026 06:48:54 +0200 Subject: [PATCH 1/5] + Add CI workflow and 13 platform tests from LiteCart v3 --- .../pre-commit.d/run_platform_tests.php | 62 ++++---- .github/ci/setup_database.php | 82 ++++++++++ .github/workflows/ci.yml | 123 +++++++++++++++ install/structure.json | 1 + tests/administrator.php | 2 +- tests/func_array.php | 144 ++++++++++++++++++ tests/func_csv.php | 87 +++++++++++ tests/func_datetime.php | 91 +++++++++++ tests/func_escape.php | 75 +++++++++ tests/func_format.php | 114 ++++++++++++++ tests/func_password.php | 72 +++++++++ tests/func_string.php | 69 +++++++++ tests/func_validate.php | 50 ++++++ tests/link.php | 41 +++++ tests/nod_event.php | 45 ++++++ tests/nod_notices.php | 119 +++++++++++++++ tests/nod_settings.php | 61 ++++++++ tests/view.php | 69 +++++++++ 18 files changed, 1278 insertions(+), 29 deletions(-) create mode 100644 .github/ci/setup_database.php create mode 100644 .github/workflows/ci.yml create mode 100644 install/structure.json create mode 100644 tests/func_array.php create mode 100644 tests/func_csv.php create mode 100644 tests/func_datetime.php create mode 100644 tests/func_escape.php create mode 100644 tests/func_format.php create mode 100644 tests/func_password.php create mode 100644 tests/func_string.php create mode 100644 tests/func_validate.php create mode 100644 tests/link.php create mode 100644 tests/nod_event.php create mode 100644 tests/nod_notices.php create mode 100644 tests/nod_settings.php create mode 100644 tests/view.php diff --git a/.git-hooks/pre-commit.d/run_platform_tests.php b/.git-hooks/pre-commit.d/run_platform_tests.php index 4e55d26..1f99ee6 100644 --- a/.git-hooks/pre-commit.d/run_platform_tests.php +++ b/.git-hooks/pre-commit.d/run_platform_tests.php @@ -1,46 +1,52 @@ $value) { - if (is_array($value)) { - if (!array_key_exists($key, $var2) || !is_array($var2[$key]) || !check_if_similar($value, $var2[$key])) { - throw new Exception("1Arrays are not similar for key: " . $key); - } - } - } - - } else { - if ($var1 !== $var2) { - throw new Exception('vars are not similar'); - } - } + include_once __DIR__.'/../../public_html/includes/app_header.inc.php'; - return true; - } + ini_set('display_errors', 1); + ini_set('display_startup_errors', 1); + error_reporting(E_ALL); $directory = functions::file_resolve_path(__DIR__.'/../../tests/'); $files = functions::file_search($directory . '/*.php'); echo 'Found '. count($files) . ' test files' . PHP_EOL; + echo implode(PHP_EOL, array_map(function($file) { + return ' - '. basename($file); + }, $files)) . PHP_EOL; + + $failed = 0; foreach ($files as $file) { echo 'Running tests from '. basename($file) .'...'; - $result = include $file; + try { + + $result = require $file; + + if ($result === true) { + echo ' [OK]' . PHP_EOL; + } else { + echo ' [FAIL]' . PHP_EOL; + $failed++; + } + + } catch (Error $e) { + echo ' [ERROR] ' . $e->getMessage() .' in '. $e->getFile() .' on line '. $e->getLine() . PHP_EOL; + $failed++; - if ($result === true) { - echo ' [OK]' . PHP_EOL; - } else { - echo ' [Failed]' . PHP_EOL; - exit(1); + } catch (Exception $e) { + echo ' [EXCEPTION] ' . $e->getMessage() . PHP_EOL; + $failed++; } } + + if ($failed > 0) { + echo PHP_EOL . $failed . ' test(s) failed' . PHP_EOL; + exit(1); + } + + echo PHP_EOL . 'All tests passed' . PHP_EOL; diff --git a/.github/ci/setup_database.php b/.github/ci/setup_database.php new file mode 100644 index 0000000..dcf49b6 --- /dev/null +++ b/.github/ci/setup_database.php @@ -0,0 +1,82 @@ +set_charset('utf8mb4'); + + $prefix = getenv('DB_TABLE_PREFIX') ?: 'lc_'; + + // Create tables from structure.json + $structure = json_decode(file_get_contents(__DIR__ . '/../../install/structure.json'), true); + + if (empty($structure['tables'])) { + echo 'ERROR: No tables found in structure.json' . PHP_EOL; + exit(1); + } + + foreach ($structure['tables'] as $key => $table) { + + $name = $prefix . $key; + $cols = []; + + foreach ($table['columns'] as $col => $def) { + $type = $def['type']; + if (stripos($type, 'ENUM') === 0 || stripos($type, 'SET') === 0) { + // ENUM/SET already includes values in type string + } elseif (!empty($def['length'])) { + $type .= '(' . $def['length'] . ')'; + } + if (!empty($def['unsigned'])) $type .= ' UNSIGNED'; + + $null = (!isset($def['null']) || $def['null']) ? '' : ' NOT NULL'; + + $default = ''; + if (array_key_exists('default', $def)) { + $d = $def['default']; + if (is_null($d)) { + $default = ' DEFAULT NULL'; + } elseif (is_numeric($d)) { + $default = ' DEFAULT ' . $d; + } elseif (preg_match('/^(CURRENT_TIMESTAMP|current_timestamp\(\)|NOW\(\))$/i', $d)) { + $default = ' DEFAULT ' . $d; + } elseif (preg_match("/^'.*'$/", $d)) { + $default = ' DEFAULT ' . $d; // Already quoted (e.g. ENUM defaults) + } else { + $default = " DEFAULT '" . $link->real_escape_string($d) . "'"; + } + } + + $auto = !empty($def['auto_increment']) ? ' AUTO_INCREMENT' : ''; + $cols[] = "`$col` $type$null$default$auto"; + } + + if (!empty($table['primary_key'])) { + $pk = is_array($table['primary_key']) ? $table['primary_key'] : [$table['primary_key']]; + $cols[] = 'PRIMARY KEY (`' . implode('`,`', $pk) . '`)'; + } + + $sql = "CREATE TABLE IF NOT EXISTS `$name` (" . implode(', ', $cols) . ') ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci'; + + if (!$link->query($sql)) { + echo "ERROR creating $name: " . $link->error . PHP_EOL; + echo "SQL: $sql" . PHP_EOL; + exit(1); + } + } + + echo count($structure['tables']) . ' tables created' . PHP_EOL; + + // Skip data.sql import — it targets the old SQL schema. + // Tests create their own fixtures via entity APIs. + + // Create admin user + $hash = password_hash('admin123456', PASSWORD_DEFAULT); + $link->query("INSERT INTO {$prefix}administrators (status, username, password_hash) VALUES (1, 'admin', '$hash')"); + echo 'Admin user created' . PHP_EOL; diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..6d5e7e3 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,123 @@ +name: CI + +on: + pull_request: + branches: [master] + push: + branches: [master] + workflow_dispatch: + +jobs: + + lint: + name: PHP Lint + runs-on: ubuntu-latest + + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Setup PHP + uses: shivammathur/setup-php@v2 + with: + php-version: '8.3' + tools: none + coverage: none + + - name: PHP Lint + run: | + errors=0 + while IFS= read -r file; do + if ! php -l "$file" > /dev/null 2>&1; then + php -l "$file" + errors=$((errors + 1)) + fi + done < <(find public_html -name '*.php' -type f) + if [ $errors -gt 0 ]; then + echo "::error::$errors file(s) with PHP syntax errors" + exit 1 + fi + echo "All PHP files passed syntax check" + + build: + name: Gulp Build + runs-on: ubuntu-latest + + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version: '24' + + - name: Install dependencies + run: npm install --no-audit --no-fund + + - name: Build framework assets + continue-on-error: true + run: | + npx gulp js-framework + npx gulp less-framework + + platform-tests: + name: Platform Tests + runs-on: ubuntu-latest + needs: [lint] + + services: + mariadb: + image: mariadb:11 + env: + MARIADB_ROOT_PASSWORD: litecore_root + MARIADB_DATABASE: litecore + MARIADB_USER: litecore + MARIADB_PASSWORD: litecore + ports: + - 3306:3306 + options: >- + --health-cmd="healthcheck.sh --connect --innodb_initialized" + --health-interval=10s + --health-timeout=5s + --health-retries=5 + + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Setup PHP + uses: shivammathur/setup-php@v2 + with: + php-version: '8.3' + extensions: mysqli, gd, intl, mbstring, zip, xml, curl, apcu + ini-values: date.timezone=Europe/London, apc.enable_cli=1 + tools: none + coverage: none + + - name: Create storage directories + run: | + mkdir -p public_html/storage/{cache,data,logs} + mkdir -p public_html/storage/vmods/.cache + + - name: Generate config file + run: | + sed \ + -e "s|define('DB_USERNAME', '');|define('DB_USERNAME', 'litecore');|" \ + -e "s|define('DB_PASSWORD', '');|define('DB_PASSWORD', 'litecore');|" \ + -e "s|define('DB_DATABASE', '');|define('DB_DATABASE', 'litecore');|" \ + install/public_html/storage/config.inc.php \ + > public_html/storage/config.inc.php + echo "Config file generated" + + - name: Import database schema and seed data + run: php .github/ci/setup_database.php + + - name: Verify installation + run: | + test -f public_html/storage/config.inc.php && echo "config exists" + mysql -h 127.0.0.1 -u litecore -plitecore litecore \ + -e "SELECT COUNT(*) as tables FROM information_schema.tables WHERE table_schema='litecore' AND table_name LIKE 'lc_%';" + + - name: Run platform tests + run: php .git-hooks/pre-commit.d/run_platform_tests.php diff --git a/install/structure.json b/install/structure.json new file mode 100644 index 0000000..9aecfee --- /dev/null +++ b/install/structure.json @@ -0,0 +1 @@ +{"tables":{"administrators":{"columns":{"id":{"type":"INT","length":10,"unsigned":true,"auto_increment":true},"status":{"type":"TINYINT","length":1,"unsigned":true,"default":0},"username":{"type":"VARCHAR","length":32,"default":"''"},"firstname":{"type":"VARCHAR","length":32,"default":"''"},"lastname":{"type":"VARCHAR","length":32,"default":"''"},"email":{"type":"VARCHAR","length":128,"default":"''"},"apps":{"type":"VARCHAR","length":4096,"default":"''"},"widgets":{"type":"VARCHAR","length":512,"default":"''"},"password_hash":{"type":"VARCHAR","length":255,"default":"''"},"two_factor_auth":{"type":"TINYINT","length":1,"unsigned":true,"default":0},"login_attempts":{"type":"INT","length":10,"unsigned":true,"default":0},"total_logins":{"type":"INT","length":10,"unsigned":true,"default":0},"known_ips":{"type":"VARCHAR","length":512,"default":"''"},"last_ip_address":{"type":"VARCHAR","length":39,"default":"''"},"last_hostname":{"type":"VARCHAR","length":128,"default":"''"},"last_user_agent":{"type":"VARCHAR","length":255,"default":"''"},"last_active":{"type":"TIMESTAMP","nullable":true},"last_login":{"type":"TIMESTAMP","nullable":true},"sessions_expiry":{"type":"TIMESTAMP","nullable":true},"blocked_until":{"type":"TIMESTAMP","nullable":true},"valid_from":{"type":"TIMESTAMP","nullable":true},"valid_to":{"type":"TIMESTAMP","nullable":true},"updated_at":{"type":"TIMESTAMP","default":"CURRENT_TIMESTAMP","on_update":"CURRENT_TIMESTAMP"},"created_at":{"type":"TIMESTAMP","default":"CURRENT_TIMESTAMP"}},"primary_key":["id"],"keys":{"status":["status"],"username":["username"],"email":["email"]}},"emails":{"columns":{"id":{"type":"INT","length":10,"unsigned":true,"auto_increment":true},"status":{"type":"ENUM('draft','scheduled','sent','error')","default":"'draft'"},"code":{"type":"VARCHAR","length":255,"default":"''"},"reference":{"type":"VARCHAR","length":255,"default":"''"},"language_code":{"type":"VARCHAR","length":2,"default":"''"},"sender":{"type":"VARCHAR","length":255,"default":"''"},"recipients":{"type":"TEXT","default":"''"},"ccs":{"type":"TEXT","default":"''"},"bccs":{"type":"TEXT","default":"''"},"subject":{"type":"VARCHAR","length":255,"default":"''"},"multiparts":{"type":"MEDIUMTEXT","default":"''"},"ip_address":{"type":"VARCHAR","length":39,"default":"''"},"hostname":{"type":"VARCHAR","length":128,"default":"''"},"user_agent":{"type":"VARCHAR","length":255,"default":"''"},"scheduled_at":{"type":"TIMESTAMP","nullable":true},"sent_at":{"type":"TIMESTAMP","nullable":true},"updated_at":{"type":"TIMESTAMP","default":"CURRENT_TIMESTAMP","on_update":"CURRENT_TIMESTAMP"},"created_at":{"type":"TIMESTAMP","default":"CURRENT_TIMESTAMP"}},"primary_key":["id"],"keys":{"scheduled_at":["scheduled_at"],"code":["code"],"created_at":["created_at"],"sender_email":["sender"]}},"languages":{"columns":{"id":{"type":"INT","length":10,"unsigned":true,"auto_increment":true},"status":{"type":"TINYINT","length":1,"default":0},"code":{"type":"CHAR","length":2,"default":"''"},"code2":{"type":"CHAR","length":3,"default":"''"},"name":{"type":"VARCHAR","length":32,"default":"''"},"direction":{"type":"ENUM('ltr','rtl')","default":"'ltr'"},"locale":{"type":"VARCHAR","length":64,"default":"''"},"locale_intl":{"type":"VARCHAR","length":16,"default":"''"},"mysql_collation":{"type":"VARCHAR","length":32,"default":"''"},"url_type":{"type":"ENUM('','none','root','path','domain')","default":"''"},"domain_name":{"type":"VARCHAR","length":64,"default":"''"},"raw_date":{"type":"VARCHAR","length":32,"default":"''"},"raw_time":{"type":"VARCHAR","length":32,"default":"''"},"raw_datetime":{"type":"VARCHAR","length":32,"default":"''"},"format_date":{"type":"VARCHAR","length":32,"default":"''"},"format_time":{"type":"VARCHAR","length":32,"default":"''"},"format_datetime":{"type":"VARCHAR","length":32,"default":"''"},"decimal_point":{"type":"VARCHAR","length":1,"default":"''"},"thousands_sep":{"type":"VARCHAR","length":1,"default":"''"},"priority":{"type":"INT","length":11,"default":0},"updated_at":{"type":"TIMESTAMP","default":"CURRENT_TIMESTAMP","on_update":"CURRENT_TIMESTAMP"},"created_at":{"type":"TIMESTAMP","default":"CURRENT_TIMESTAMP"}},"primary_key":["id"],"keys":{"status":["status"]}},"modules":{"columns":{"id":{"type":"INT","length":10,"unsigned":true,"auto_increment":true},"module_id":{"type":"VARCHAR","length":64,"default":"''"},"type":{"type":"VARCHAR","length":16,"default":"''"},"status":{"type":"TINYINT","length":1,"unsigned":true,"default":0},"priority":{"type":"INT","length":11,"default":0},"settings":{"type":"TEXT","default":"'{}'","comment":"TYPE:JSON"},"last_log":{"type":"TEXT","default":"''"},"last_pushed":{"type":"TIMESTAMP","nullable":true},"last_processed":{"type":"TIMESTAMP","nullable":true},"updated_at":{"type":"TIMESTAMP","default":"CURRENT_TIMESTAMP","on_update":"CURRENT_TIMESTAMP"},"created_at":{"type":"TIMESTAMP","default":"CURRENT_TIMESTAMP"}},"primary_key":["id"],"unique_keys":{"module_id":["module_id"]},"keys":{"type":["type"],"status":["status"]}},"pages":{"columns":{"id":{"type":"INT","length":10,"unsigned":true,"auto_increment":true},"parent_id":{"type":"INT","length":10,"unsigned":true,"nullable":true},"dock":{"type":"VARCHAR","length":64,"default":"''"},"status":{"type":"TINYINT","length":1,"default":0},"title":{"type":"TEXT","default":"'{}'","comment":"TYPE:JSON_TRANSLATIONS"},"content":{"type":"MEDIUMTEXT","default":"'{}'","comment":"TYPE:JSON_TRANSLATIONS"},"head_title":{"type":"TEXT","default":"'{}'","comment":"TYPE:JSON_TRANSLATIONS"},"meta_description":{"type":"TEXT","default":"'{}'","comment":"TYPE:JSON_TRANSLATIONS"},"priority":{"type":"INT","length":11,"default":0},"updated_at":{"type":"TIMESTAMP","default":"CURRENT_TIMESTAMP","on_update":"CURRENT_TIMESTAMP"},"created_at":{"type":"TIMESTAMP","default":"CURRENT_TIMESTAMP"}},"primary_key":["id"],"keys":{"status":["status"],"parent_id":["parent_id"],"dock":["dock"]},"check_constraints":{"pages_chk_title":"title IS NULL OR title = '' OR JSON_VALID(title)","pages_chk_content":"content IS NULL OR content = '' OR JSON_VALID(content)","pages_chk_head_title":"head_title IS NULL OR head_title = '' OR JSON_VALID(head_title)","pages_chk_meta_description":"meta_description IS NULL OR meta_description = '' OR JSON_VALID(meta_description)"}},"redirects":{"columns":{"id":{"type":"INT","length":10,"unsigned":true,"auto_increment":true},"status":{"type":"TINYINT","length":1,"unsigned":true,"default":0},"immediate":{"type":"TINYINT","length":1,"unsigned":true,"default":0},"pattern":{"type":"VARCHAR","length":248,"default":"''"},"destination":{"type":"VARCHAR","length":248,"default":"''"},"http_response_code":{"type":"ENUM('301','302')","default":"'301'"},"redirects":{"type":"INT","length":10,"unsigned":true,"default":0},"last_redirected":{"type":"TIMESTAMP","nullable":true},"valid_from":{"type":"TIMESTAMP","nullable":true},"valid_to":{"type":"TIMESTAMP","nullable":true},"updated_at":{"type":"TIMESTAMP","default":"CURRENT_TIMESTAMP","on_update":"CURRENT_TIMESTAMP"},"created_at":{"type":"TIMESTAMP","default":"CURRENT_TIMESTAMP"}},"primary_key":["id"],"unique_keys":{"pattern":["pattern"]},"keys":{"status":["status"],"immediate":["immediate"]}},"settings":{"columns":{"id":{"type":"INT","length":10,"unsigned":true,"auto_increment":true},"group_key":{"type":"VARCHAR","length":64,"nullable":true},"key":{"type":"VARCHAR","length":64,"default":"''"},"value":{"type":"VARCHAR","length":2048,"default":"''"},"title":{"type":"VARCHAR","length":128,"default":"''"},"description":{"type":"VARCHAR","length":512,"default":"''"},"required":{"type":"TINYINT","length":1,"unsigned":true,"default":0},"function":{"type":"VARCHAR","length":128,"default":"''"},"priority":{"type":"INT","length":11,"default":0},"updated_at":{"type":"TIMESTAMP","default":"CURRENT_TIMESTAMP","on_update":"CURRENT_TIMESTAMP"},"created_at":{"type":"TIMESTAMP","default":"CURRENT_TIMESTAMP"}},"primary_key":["id"],"unique_keys":{"key":["key"]},"keys":{"group_key":["group_key"]}},"settings_groups":{"columns":{"id":{"type":"INT","length":10,"unsigned":true,"auto_increment":true},"key":{"type":"VARCHAR","length":32},"name":{"type":"VARCHAR","length":64,"default":"''"},"description":{"type":"VARCHAR","length":255,"default":"''"},"priority":{"type":"INT","length":11,"default":0}},"primary_key":["id"],"unique_keys":{"key":["key"]}},"site_tags":{"columns":{"id":{"type":"INT","length":10,"unsigned":true,"auto_increment":true},"status":{"type":"TINYINT","length":1,"default":0},"position":{"type":"ENUM('head','body')","default":"'head'"},"name":{"type":"VARCHAR","length":128,"default":"''"},"content":{"type":"TEXT"},"require_consent":{"type":"VARCHAR","length":64,"nullable":true},"priority":{"type":"TINYINT","length":4,"default":0},"updated_at":{"type":"TIMESTAMP","default":"CURRENT_TIMESTAMP","on_update":"CURRENT_TIMESTAMP"},"created_at":{"type":"TIMESTAMP","default":"CURRENT_TIMESTAMP"}},"primary_key":["id"],"keys":{"status":["status"],"position":["position"],"priority":["priority"]}},"third_parties":{"columns":{"id":{"type":"INT","length":10,"unsigned":true,"auto_increment":true},"status":{"type":"TINYINT","length":1,"unsigned":true,"default":0},"privacy_classes":{"type":"VARCHAR","length":64,"default":"''"},"category":{"type":"VARCHAR","length":64,"default":"''","comment":"TYPE:JSON_TRANSLATIONS"},"name":{"type":"VARCHAR","length":64,"default":"''","comment":"TYPE:JSON_TRANSLATIONS"},"description":{"type":"MEDIUMTEXT","default":"'{}'","comment":"TYPE:JSON_TRANSLATIONS"},"collected_data":{"type":"TEXT","default":"'{}'","comment":"TYPE:JSON_TRANSLATIONS"},"purposes":{"type":"TEXT","default":"'{}'","comment":"TYPE:JSON_TRANSLATIONS"},"homepage":{"type":"VARCHAR","length":248,"default":"''"},"cookie_policy_url":{"type":"VARCHAR","length":248,"default":"''"},"privacy_policy_url":{"type":"VARCHAR","length":248,"default":"''"},"opt_out_url":{"type":"VARCHAR","length":248,"default":"''"},"do_not_sell_url":{"type":"VARCHAR","length":248,"default":"''"},"country_code":{"type":"CHAR","length":2,"nullable":true},"updated_at":{"type":"TIMESTAMP","default":"CURRENT_TIMESTAMP","on_update":"CURRENT_TIMESTAMP"},"created_at":{"type":"TIMESTAMP","default":"current_timestamp()"}},"primary_key":["id"],"keys":{"status":["status"],"country_code":["country_code"]},"foreign_keys":{"third_party_to_country":{"columns":["country_code"],"references":{"table":"countries","columns":["iso_code_2"],"on_update":"CASCADE","on_delete":"SET NULL"}}},"check_constraints":{"chk_description":"JSON_VALID(description)","chk_collected_data":"JSON_VALID(collected_data)","chk_purposes":"JSON_VALID(purposes)"}},"translations":{"columns":{"id":{"type":"INT","length":10,"unsigned":true,"auto_increment":true},"code":{"type":"VARCHAR","length":128,"default":"''"},"text_en":{"type":"TEXT","default":"''"},"html":{"type":"TINYINT","length":1,"unsigned":true,"default":0},"frontend":{"type":"TINYINT","length":1,"unsigned":true,"default":0},"backend":{"type":"TINYINT","length":1,"unsigned":true,"default":0},"last_accessed":{"type":"TIMESTAMP","nullable":true},"updated_at":{"type":"TIMESTAMP","default":"CURRENT_TIMESTAMP","on_update":"CURRENT_TIMESTAMP"},"created_at":{"type":"TIMESTAMP","default":"CURRENT_TIMESTAMP"}},"primary_key":["id"],"unique_keys":{"code":["code"]},"keys":{"frontend":["frontend"],"backend":["backend"],"created_at":["created_at"]}},"countries":{"columns":{"id":{"type":"INT","length":10,"unsigned":true,"auto_increment":true},"status":{"type":"TINYINT","length":1,"unsigned":true,"default":0},"iso_code_1":{"type":"CHAR","length":3,"default":"''"},"iso_code_2":{"type":"CHAR","length":2,"default":"''"},"iso_code_3":{"type":"CHAR","length":3,"default":"''"},"name":{"type":"VARCHAR","length":64,"default":"''"},"domestic_name":{"type":"VARCHAR","length":64,"default":"''"},"tax_id_format":{"type":"VARCHAR","length":64,"default":"''"},"address_format":{"type":"VARCHAR","length":128,"default":"''"},"postcode_format":{"type":"VARCHAR","length":255,"default":"''"},"postcode_required":{"type":"TINYINT","length":1,"unsigned":true,"default":0},"language_code":{"type":"CHAR","length":2},"currency_code":{"type":"CHAR","length":3,"default":"''"},"phone_code":{"type":"VARCHAR","length":3,"default":"''"},"updated_at":{"type":"TIMESTAMP","default":"CURRENT_TIMESTAMP","on_update":"CURRENT_TIMESTAMP"},"created_at":{"type":"TIMESTAMP","default":"CURRENT_TIMESTAMP"}},"primary_key":["id"],"unique_keys":{"iso_code_1":["iso_code_1"],"iso_code_2":["iso_code_2"],"iso_code_3":["iso_code_3"]},"keys":{"status":["status"]}},"currencies":{"columns":{"id":{"type":"TINYINT","length":3,"unsigned":true,"auto_increment":true},"status":{"type":"TINYINT","length":1,"default":0},"code":{"type":"CHAR","length":3,"default":"''"},"number":{"type":"CHAR","length":3,"default":"''"},"name":{"type":"VARCHAR","length":32,"default":"''"},"value":{"type":"DECIMAL","length":"11,6","unsigned":true,"default":1},"decimals":{"type":"TINYINT","length":1,"unsigned":true,"default":2},"prefix":{"type":"VARCHAR","length":8,"default":"''"},"suffix":{"type":"VARCHAR","length":8,"default":"''"},"priority":{"type":"INT","length":11,"default":0},"updated_at":{"type":"TIMESTAMP","default":"CURRENT_TIMESTAMP","on_update":"CURRENT_TIMESTAMP"},"created_at":{"type":"TIMESTAMP","default":"CURRENT_TIMESTAMP"}},"primary_key":["id"],"keys":{"status":["status"],"code":["code"],"number":["number"]}},"geo_zones":{"columns":{"id":{"type":"INT","length":10,"unsigned":true,"auto_increment":true},"code":{"type":"VARCHAR","length":32,"default":"''"},"name":{"type":"VARCHAR","length":64,"default":"''"},"description":{"type":"VARCHAR","length":255,"default":"''"},"updated_at":{"type":"TIMESTAMP","default":"CURRENT_TIMESTAMP","on_update":"CURRENT_TIMESTAMP"},"created_at":{"type":"TIMESTAMP","default":"CURRENT_TIMESTAMP"}},"primary_key":["id"]},"zones":{"columns":{"id":{"type":"INT","length":10,"unsigned":true,"auto_increment":true},"country_code":{"type":"CHAR","length":2},"code":{"type":"VARCHAR","length":8},"name":{"type":"VARCHAR","length":64,"default":"''"},"updated_at":{"type":"TIMESTAMP","default":"CURRENT_TIMESTAMP","on_update":"CURRENT_TIMESTAMP"},"created_at":{"type":"TIMESTAMP","default":"CURRENT_TIMESTAMP"}},"primary_key":["id"],"keys":{"country_code":["country_code"],"code":["code"]},"foreign_keys":{"zone_to_country":{"columns":["country_code"],"references":{"table":"countries","columns":["iso_code_2"],"on_update":"NO ACTION","on_delete":"CASCADE"}}}},"zones_to_geo_zones":{"columns":{"id":{"type":"INT","length":10,"unsigned":true,"auto_increment":true},"geo_zone_id":{"type":"INT","length":10,"unsigned":true},"country_code":{"type":"CHAR","length":2},"zone_code":{"type":"VARCHAR","length":8,"nullable":true},"city":{"type":"VARCHAR","length":32,"nullable":true},"updated_at":{"type":"TIMESTAMP","default":"CURRENT_TIMESTAMP","on_update":"CURRENT_TIMESTAMP"},"created_at":{"type":"TIMESTAMP","default":"CURRENT_TIMESTAMP"}},"primary_key":["id"],"unique_keys":{"region":["geo_zone_id","country_code","zone_code","city"]},"keys":{"geo_zone_id":["geo_zone_id"],"country_code":["country_code"],"zone_code":["zone_code"],"city":["city"]},"foreign_keys":{"zone_entry_to_geo_zone":{"columns":["geo_zone_id"],"references":{"table":"geo_zones","columns":["id"],"on_update":"NO ACTION","on_delete":"CASCADE"}},"zone_entry_to_country":{"columns":["country_code"],"references":{"table":"countries","columns":["iso_code_2"],"on_update":"NO ACTION","on_delete":"CASCADE"}},"zone_entry_to_zone":{"columns":["zone_code"],"references":{"table":"zones","columns":["code"],"on_update":"CASCADE","on_delete":"CASCADE"}}}},"sessions":{"columns":{"id":{"type":"VARCHAR","length":128},"customer_id":{"type":"INT","length":10,"unsigned":true,"nullable":true},"data":{"type":"TEXT","default":"'{}'","comment":"TYPE:JSON"},"language_code":{"type":"CHAR","length":2,"default":"''"},"country_code":{"type":"CHAR","length":2,"default":"''"},"currency_code":{"type":"CHAR","length":3,"default":"''"},"ip_address":{"type":"VARCHAR","length":45},"ip_country":{"type":"CHAR","length":2,"default":"''"},"fingerprint":{"type":"VARCHAR","length":128},"hostname":{"type":"VARCHAR","length":128},"user_agent":{"type":"VARCHAR","length":256,"default":"''"},"referrer":{"type":"VARCHAR","length":256,"default":"''"},"page_views":{"type":"INT","length":11,"default":0},"last_url":{"type":"VARCHAR","length":255,"default":"''"},"last_request":{"type":"VARCHAR","length":255,"default":"''"},"last_active":{"type":"TIMESTAMP","nullable":true},"expires_at":{"type":"TIMESTAMP","nullable":true},"updated_at":{"type":"TIMESTAMP","default":"CURRENT_TIMESTAMP","on_update":"CURRENT_TIMESTAMP"},"created_at":{"type":"TIMESTAMP","default":"CURRENT_TIMESTAMP"}},"primary_key":["id"],"keys":{"customer_id":["customer_id"],"language_code":["language_code"],"country_code":["country_code"],"currency_code":["currency_code"],"ip_country":["ip_country"],"fingerprint":["fingerprint"]},"foreign_keys":{"session_to_customer":{"columns":["customer_id"],"references":{"table":"customers","columns":["id"],"on_update":"CASCADE","on_delete":"SET NULL"}},"session_to_language":{"columns":["language_code"],"references":{"table":"languages","columns":["code"],"on_update":"CASCADE","on_delete":"SET NULL"}},"session_to_currency":{"columns":["currency_code"],"references":{"table":"currencies","columns":["code"],"on_update":"CASCADE","on_delete":"SET NULL"}}}},"statistics":{"columns":{"id":{"type":"INT","length":10,"unsigned":true,"auto_increment":true},"type":{"type":"VARCHAR","length":32,"default":"''"},"entity_id":{"type":"INT","length":10,"unsigned":true,"nullable":true},"entity_type":{"type":"VARCHAR","length":32,"nullable":true},"measure_group_type":{"type":"VARCHAR","length":32,"default":"''"},"measure_group_value":{"type":"VARCHAR","length":64,"default":"''"},"count":{"type":"INT","length":10,"unsigned":true,"default":0},"updated_at":{"type":"TIMESTAMP","default":"CURRENT_TIMESTAMP","on_update":"CURRENT_TIMESTAMP"}},"primary_key":["id"],"unique_keys":{"measure":["type","entity_id","entity_type","measure_group_type","measure_group_value"]},"keys":{"entity_id":["entity_id"],"entity_type":["entity_type"],"measure_group_type":["measure_group_type"],"measure_group_value":["measure_group_value"]}},"event_logs":{"columns":{"id":{"type":"BIGINT","length":20,"unsigned":true,"auto_increment":true},"session_id":{"type":"VARCHAR","length":64,"nullable":true},"customer_id":{"type":"INT","length":10,"unsigned":true,"nullable":true},"customer_email":{"type":"VARCHAR","length":64,"nullable":true},"customer_phone":{"type":"VARCHAR","length":16,"nullable":true},"type":{"type":"VARCHAR","length":64,"nullable":true},"description":{"type":"VARCHAR","length":248,"default":"''"},"data":{"type":"VARCHAR","length":1024,"default":"'{}'","comment":"TYPE:JSON"},"conversions":{"type":"VARCHAR","length":1024,"default":"'{}'","comment":"TYPE:JSON"},"url":{"type":"VARCHAR","length":248,"nullable":true},"ip_address":{"type":"VARCHAR","length":47,"nullable":true},"hostname":{"type":"VARCHAR","length":128,"nullable":true},"user_agent":{"type":"VARCHAR","length":248,"nullable":true},"fingerprint":{"type":"VARCHAR","length":32,"nullable":true},"expires_at":{"type":"TIMESTAMP","nullable":true},"created_at":{"type":"TIMESTAMP","default":"CURRENT_TIMESTAMP"}},"primary_key":["id"],"keys":{"session_id":["session_id"],"customer_id":["customer_id"],"customer_email":["customer_email"],"customer_phone":["customer_phone"],"type":["type"],"ip_address":["ip_address"],"hostname":["hostname"],"user_agent":["user_agent"],"fingerprint":["fingerprint"],"expires_at":["expires_at"]},"foreign_keys":{"event_log_to_customer":{"columns":["customer_id"],"references":{"table":"customers","columns":["id"],"on_update":"CASCADE","on_delete":"CASCADE"}}},"check_constraints":{"chk_data":"JSON_VALID(data)","chk_conversions":"JSON_VALID(conversions)"}},"visitors":{"columns":{"id":{"type":"BIGINT","length":20,"unsigned":true,"auto_increment":true},"session_id":{"type":"VARCHAR","length":13,"default":"''"},"cart_uid":{"type":"VARCHAR","length":13,"default":"''"},"referrer":{"type":"VARCHAR","length":256,"default":"''"},"ip_address":{"type":"VARCHAR","length":39,"default":"''"},"hostname":{"type":"VARCHAR","length":64,"default":"''"},"user_agent":{"type":"VARCHAR","length":256,"default":"''"},"language":{"type":"VARCHAR","length":2,"default":"''"},"country_code":{"type":"VARCHAR","length":2,"default":"''"},"pageviews":{"type":"INT","length":10,"unsigned":true,"default":0},"last_page":{"type":"VARCHAR","length":128,"default":"''"},"updated_at":{"type":"TIMESTAMP","default":"CURRENT_TIMESTAMP","on_update":"CURRENT_TIMESTAMP"},"created_at":{"type":"TIMESTAMP","default":"CURRENT_TIMESTAMP"}},"primary_key":["id"],"keys":{"session_id":["session_id"],"country_code":["country_code"],"language":["language"],"updated_at":["updated_at"],"created_at":["created_at"]}}}} \ No newline at end of file diff --git a/tests/administrator.php b/tests/administrator.php index db723d5..9e7a7f1 100644 --- a/tests/administrator.php +++ b/tests/administrator.php @@ -1,6 +1,6 @@ ['name' => 'Alpha'], 'b' => ['name' => 'Beta']]; + $result = f::array_column_intact($data, 'name', true); + + if ($result !== ['a' => 'Alpha', 'b' => 'Beta']) { + throw new Exception('array_column_intact failed to retain original keys'); + } + + ######################################################################## + ## array_each + ######################################################################## + + $result = f::array_each([1, 2, 3, 4], function($v) { + return $v > 2 ? $v * 10 : null; + }); + + if (!in_array(30, $result) || !in_array(40, $result) || count($result) !== 2) { + throw new Exception('array_each failed to map and filter'); + } + + ######################################################################## + ## array_update — only updates existing keys + ######################################################################## + + $original = ['name' => 'old', 'status' => 1, 'type' => 'a']; + $updates = ['name' => 'new', 'extra' => 'ignored']; + $result = f::array_update($original, $updates); + + if ($result['name'] !== 'new') { + throw new Exception('array_update failed to update existing key'); + } + + if (isset($result['extra'])) { + throw new Exception('array_update inserted a new key that should be ignored'); + } + + ######################################################################## + ## array_intersect_compare + ######################################################################## + + $var1 = ['name' => 'Test', 'nested' => ['a' => 1]]; + $var2 = ['name' => 'Test', 'nested' => ['a' => 1, 'b' => 2], 'extra' => 'ok']; + + if (!f::array_intersect_compare($var1, $var2)) { + throw new Exception('array_intersect_compare failed on matching subset'); + } + + $var1 = ['name' => 'Test']; + $var2 = ['name' => 'Different']; + + if (f::array_intersect_compare($var1, $var2)) { + throw new Exception('array_intersect_compare passed on non-matching values'); + } + + ######################################################################## + ## array_flatten / array_unflatten + ######################################################################## + + $nested = ['a' => ['b' => 1, 'c' => 2]]; + $flat = f::array_flatten($nested); + + if ($flat !== ['a.b' => 1, 'a.c' => 2]) { + throw new Exception('array_flatten failed'); + } + + $unflat = f::array_unflatten($flat); + + if ($unflat !== $nested) { + throw new Exception('array_unflatten failed to reverse flatten'); + } + + ######################################################################## + ## array_depth + ######################################################################## + + if (f::array_depth([1, 2, 3]) !== 1) { + throw new Exception('array_depth failed on flat array'); + } + + if (f::array_depth(['a' => ['b' => ['c' => 1]]]) !== 3) { + throw new Exception('array_depth failed on nested array'); + } + + ######################################################################## + ## array_grep + ######################################################################## + + $data = ['name' => 'John', 'email' => 'john@test.com', 'password' => 'secret']; + $result = f::array_grep($data, ['name', 'email']); + + if (isset($result['password'])) { + throw new Exception('array_grep failed to filter keys'); + } + + if ($result['name'] !== 'John' || $result['email'] !== 'john@test.com') { + throw new Exception('array_grep lost expected values'); + } + + ######################################################################## + ## array_diff_assoc_recursive + ######################################################################## + + $arr1 = ['a' => 1, 'b' => ['c' => 2, 'd' => 3]]; + $arr2 = ['a' => 1, 'b' => ['c' => 2, 'd' => 4]]; + $diff = f::array_diff_assoc_recursive($arr1, $arr2); + + if (!isset($diff['b']['d']) || $diff['b']['d'] !== 3) { + throw new Exception('array_diff_assoc_recursive failed to detect difference'); + } + + if (isset($diff['a'])) { + throw new Exception('array_diff_assoc_recursive reported identical values as different'); + } + + return true; + + } catch (Exception $e) { + + echo ' [Failed]'. PHP_EOL . 'Error: '. $e->getMessage(); + return false; + } diff --git a/tests/func_csv.php b/tests/func_csv.php new file mode 100644 index 0000000..a96bafa --- /dev/null +++ b/tests/func_csv.php @@ -0,0 +1,87 @@ + 'Product A', 'price' => '9.99'], + ['name' => 'Product B', 'price' => '19.99'], + ]; + + $csv = f::csv_encode($data); + + if (strpos($csv, 'name') === false || strpos($csv, 'price') === false) { + throw new Exception('csv_encode missing column headers'); + } + + if (strpos($csv, 'Product A') === false || strpos($csv, '9.99') === false) { + throw new Exception('csv_encode missing row data'); + } + + ######################################################################## + ## csv_encode — tab delimiter + ######################################################################## + + $csv_tab = f::csv_encode($data, "\t"); + + if (strpos($csv_tab, "\t") === false) { + throw new Exception('csv_encode with tab delimiter missing tabs'); + } + + ######################################################################## + ## csv_encode — enclosure for special characters + ######################################################################## + + $data_special = [ + ['name' => 'Product, with comma', 'note' => 'has "quotes"'], + ]; + + $csv_special = f::csv_encode($data_special); + + if (strpos($csv_special, '"Product, with comma"') === false) { + throw new Exception('csv_encode failed to enclose value containing delimiter'); + } + + ######################################################################## + ## csv_decode — roundtrip + ######################################################################## + + $original = [ + ['name' => 'Alpha', 'value' => '100'], + ['name' => 'Beta', 'value' => '200'], + ]; + + $encoded = f::csv_encode($original, ',', '"', '"', 'utf-8', "\n"); + $decoded = f::csv_decode($encoded, ',', '"', '"', 'utf-8'); + + if (count($decoded) !== 2) { + throw new Exception('csv_decode returned wrong row count: '. count($decoded)); + } + + if ($decoded[0]['name'] !== 'Alpha' || $decoded[1]['value'] !== '200') { + throw new Exception('csv_decode data mismatch after roundtrip'); + } + + ######################################################################## + ## csv_decode — auto-detect delimiter + ######################################################################## + + $tsv_string = "name\tprice\nWidget\t5.99\n"; + $decoded = f::csv_decode($tsv_string); + + if ($decoded[0]['name'] !== 'Widget') { + throw new Exception('csv_decode auto-detect failed for tab delimiter'); + } + + return true; + + } catch (Exception $e) { + + echo ' [Failed]'. PHP_EOL . 'Error: '. $e->getMessage(); + return false; + } diff --git a/tests/func_datetime.php b/tests/func_datetime.php new file mode 100644 index 0000000..79570d2 --- /dev/null +++ b/tests/func_datetime.php @@ -0,0 +1,91 @@ +getMessage(); + return false; + } diff --git a/tests/func_escape.php b/tests/func_escape.php new file mode 100644 index 0000000..d07e3a0 --- /dev/null +++ b/tests/func_escape.php @@ -0,0 +1,75 @@ +alert("xss")') !== '<script>alert("xss")</script>') { + throw new Exception('escape_html failed to escape HTML tags'); + } + + $escaped = f::escape_html("it's"); + if (strpos($escaped, "'") !== false) { + throw new Exception('escape_html did not escape single quotes: '. $escaped); + } + + if (f::escape_html('') !== '') { + throw new Exception('escape_html failed on empty string'); + } + + if (f::escape_html(null) !== '') { + throw new Exception('escape_html failed on null'); + } + + ######################################################################## + ## escape_attr + ######################################################################## + + if (strpos(f::escape_attr("line1\nline2"), '\n') === false) { + throw new Exception('escape_attr failed to escape newlines'); + } + + ######################################################################## + ## escape_js + ######################################################################## + + $js_input = 'He said "hello" and \'bye\''; + $js_escaped = f::escape_js($js_input); + + if (strpos($js_escaped, '"') !== false && strpos($js_escaped, '\"') === false) { + throw new Exception('escape_js failed to escape double quotes'); + } + + ######################################################################## + ## escape_mysql + ######################################################################## + + $sql_input = "Robert'; DROP TABLE users;--"; + $sql_escaped = f::escape_mysql($sql_input); + + if (strpos($sql_escaped, "\\'") === false) { + throw new Exception('escape_mysql failed to escape single quote'); + } + + ######################################################################## + ## escape_mysql_like + ######################################################################## + + $like_input = "100% match_test"; + $like_escaped = f::escape_mysql_like($like_input); + + if (strpos($like_escaped, '\\_') === false) { + throw new Exception('escape_mysql_like failed to escape underscore'); + } + + return true; + + } catch (Exception $e) { + + echo ' [Failed]'. PHP_EOL . 'Error: '. $e->getMessage(); + return false; + } diff --git a/tests/func_format.php b/tests/func_format.php new file mode 100644 index 0000000..0e8079c --- /dev/null +++ b/tests/func_format.php @@ -0,0 +1,114 @@ + 'Test', 'value' => 42]; + $json = f::format_json($data); + + if (strpos($json, "\t") === false) { + throw new Exception('format_json with indent should contain tabs'); + } + + $decoded = json_decode($json, true); + if ($decoded['name'] !== 'Test' || $decoded['value'] !== 42) { + throw new Exception('format_json output not valid JSON after decode'); + } + + ######################################################################## + ## format_json — compact (no indent) + ######################################################################## + + $json_compact = f::format_json($data, false); + + if (strpos($json_compact, "\t") !== false || strpos($json_compact, "\n") !== false) { + throw new Exception('format_json without indent should not contain tabs or newlines'); + } + + ######################################################################## + ## format_path_friendly — basic slug + ######################################################################## + + $slug = f::format_path_friendly('Hello World 123'); + + if ($slug !== 'hello-world-123') { + throw new Exception('format_path_friendly basic failed: got "'. $slug .'"'); + } + + ######################################################################## + ## format_path_friendly — special characters + ######################################################################## + + $slug = f::format_path_friendly('Über Straße & Café'); + + if (empty($slug) || strpos($slug, '&') !== false) { + throw new Exception('format_path_friendly should strip special chars: got "'. $slug .'"'); + } + + ######################################################################## + ## format_path_friendly — German umlauts + ######################################################################## + + $slug = f::format_path_friendly('Ärger mit Öl', 'de'); + + if (strpos($slug, 'aerger') === false) { + throw new Exception('format_path_friendly German should convert Ä to Ae: got "'. $slug .'"'); + } + + ######################################################################## + ## format_address — basic address formatting + ######################################################################## + + $address = [ + 'company' => 'ACME Corp', + 'firstname' => 'John', + 'lastname' => 'Doe', + 'address1' => '123 Main St', + 'address2' => '', + 'city' => 'New York', + 'postcode' => '10001', + 'zone_code' => 'NY', + 'country_code' => 'US', + ]; + + $formatted = f::format_address($address); + + if (empty($formatted)) { + throw new Exception('format_address returned empty string'); + } + + if (strpos($formatted, 'John') === false || strpos($formatted, 'Doe') === false) { + throw new Exception('format_address missing name in output'); + } + + if (strpos($formatted, 'New York') === false) { + throw new Exception('format_address missing city in output'); + } + + ######################################################################## + ## format_number — uses language formatting + ######################################################################## + + $result = f::format_number(1234.56, 2); + + if (empty($result)) { + throw new Exception('format_number returned empty'); + } + + // Should contain the digits regardless of separator style + if (strpos(str_replace(['.', ',', ' '], '', $result), '123456') === false) { + throw new Exception('format_number missing expected digits: got "'. $result .'"'); + } + + return true; + + } catch (Exception $e) { + + echo ' [Failed]'. PHP_EOL . 'Error: '. $e->getMessage(); + return false; + } diff --git a/tests/func_password.php b/tests/func_password.php new file mode 100644 index 0000000..bb23a22 --- /dev/null +++ b/tests/func_password.php @@ -0,0 +1,72 @@ +getMessage(); + return false; + } diff --git a/tests/func_string.php b/tests/func_string.php new file mode 100644 index 0000000..6d20b53 --- /dev/null +++ b/tests/func_string.php @@ -0,0 +1,69 @@ +getMessage(); + return false; + } diff --git a/tests/func_validate.php b/tests/func_validate.php new file mode 100644 index 0000000..0da3a05 --- /dev/null +++ b/tests/func_validate.php @@ -0,0 +1,50 @@ +getMessage(); + return false; + } diff --git a/tests/link.php b/tests/link.php new file mode 100644 index 0000000..9e7aee0 --- /dev/null +++ b/tests/link.php @@ -0,0 +1,41 @@ +host = 'newdomain.com'; + $link->path = '/new/path'; + + // Check if data was set correctly + if ((string)$link != 'http://newdomain.com/new/path') { + throw new Exception('The link data was not updated correctly'); + } + + return true; + + } catch (Exception $e) { + + echo ' [Failed]'. PHP_EOL . 'Error: '. $e->getMessage(); + return false; + + } finally { + // Rollback changes to the database + database::query("rollback;"); + } diff --git a/tests/nod_event.php b/tests/nod_event.php new file mode 100644 index 0000000..6b01795 --- /dev/null +++ b/tests/nod_event.php @@ -0,0 +1,45 @@ +getMessage(); + return false; + } diff --git a/tests/nod_notices.php b/tests/nod_notices.php new file mode 100644 index 0000000..64c8c95 --- /dev/null +++ b/tests/nod_notices.php @@ -0,0 +1,119 @@ +getMessage(); + return false; + } diff --git a/tests/nod_settings.php b/tests/nod_settings.php new file mode 100644 index 0000000..566a0ae --- /dev/null +++ b/tests/nod_settings.php @@ -0,0 +1,61 @@ +getMessage(); + return false; + } diff --git a/tests/view.php b/tests/view.php new file mode 100644 index 0000000..ada0475 --- /dev/null +++ b/tests/view.php @@ -0,0 +1,69 @@ +html = '{{foo}}'; + + $view->snippets = [ + 'foo' => 'Bar', + ]; + + if ($view->render() !== 'Bar') { + throw new Exception('Variable substitution failed'); + } +/* + ######################################################################## + ## Else Condition + ######################################################################## + + $view = new ent_view(); + + $view->html = implode(PHP_EOL, [ + '{{if $var}}', + '{{var}}', + '{{else}}', + 'Nope', + '{{/if}}', + ]); + + $view->snippets = [ + 'foo' => 'Bar', + ]; + + if ($view->render() !== 'Nope') { + print_r($view->render()); + throw new Exception('Else condition failed'); + } + + ######################################################################## + ## Modifier: Money Formatting + ######################################################################## + + $view->html = '{{amount|money}}'; + + $view->snippets = [ + 'amount' => 9.99, + ]; + + if ($view->render() !== currency::format(9.99)) { + throw new Exception('Money formatting failed'); + } +*/ + return true; + + } catch (Exception $e) { + + echo ' [Failed]'. PHP_EOL . 'Error: '. $e->getMessage(); + return false; + + } finally { + // No rollback needed in this file + } From 4c3619e3f30130cbd228257d17acd29868c62cd2 Mon Sep 17 00:00:00 2001 From: Wachhund Skytower Date: Tue, 7 Apr 2026 07:55:15 +0200 Subject: [PATCH 2/5] * Sync framework functions and tests with LiteCart v3 - func_array: Add array_each2, array_intersect_key_recursive, array_intersect_compare, array_diff_assoc_recursive; upgrade array_update for recursive nesting; fix bugs in array_exclude and array_filter_recursive; rename array_group_keys to array_merge_group - func_format: Add format_json, format_number, format_regex_code; update format_path_friendly to Unicode-based character stripping - func_string: Replace old string_ellipsis (middle-collapse) with v3 API (front/back truncation via mb_substr); remove unused string_translate, string_slice, string_pad_lines - tests/administrator: Adopt v3 test with auto-increment backup, entity reload, static loader test, delete verification, finally block - tests/func_format: Remove format_address/format_number tests (require seed data not available in framework CI) - tests/language: Fix missing DB_TABLE_PREFIX in queries --- .../includes/functions/func_array.inc.php | 389 +++++++++++------- .../includes/functions/func_format.inc.php | 316 ++++++++------ .../includes/functions/func_string.inc.php | 60 +-- tests/administrator.php | 266 ++++++------ tests/func_format.php | 139 +++---- tests/language.php | 4 +- 6 files changed, 630 insertions(+), 544 deletions(-) diff --git a/public_html/includes/functions/func_array.inc.php b/public_html/includes/functions/func_array.inc.php index 227dda9..e2d797f 100644 --- a/public_html/includes/functions/func_array.inc.php +++ b/public_html/includes/functions/func_array.inc.php @@ -1,153 +1,240 @@ = $from && $node < $to); - }); - } - - // Retain the original array keys when extracting an array column by passing $index_key = true - function array_column_intact(array $array, int|string|null $column_key, bool|int|string|null $index_key = null): array { - if ($index_key === true) { - return array_combine(array_keys($array), array_column($array, $column_key)); - } - return array_column($array, $column_key, $index_key); - } - - // Same as array_map but with the callable function first and filtered results - function array_each(array $array, callable $function):array { - return array_filter(array_map($function, $array)); - } - - // Update an array with values that have keys present in another array. The opposite of array_diff_key. Or complementary to array_merge() or array_replace() that doesn't insert new keys. - function array_update(array $array, array ...$replacements):array { - foreach ($replacements as $replacement) { - $array = array_replace($array, array_intersect_key($replacement, $array)); - } - return $array; - } - - // Return a filtered array of values from a given list of keys - function array_grep(array $array, array $matching_keys):array { - return array_intersect_key($array, array_flip($matching_keys)); - } - - // Return an array of values not defined by the given keys - function array_exclude(array $array, array $excluded_keys):array { - return array_diff_key($input, array_flip($excluded_keys)); - } - - // Same as array_exclude(). Return an array of values not including any given keys - function array_collect(array $array, array $input, array $ignored_keys):array { - return array_replace($array, array_diff_key($input, array_flip($ignored_keys))); - } - - // Function to map array_keys instead of values - function array_map_keys($callback, $array, $arg1=null, $arg2=null, $arg3=null) { - $new_keys = array_map($callback, array_keys($array), $arg1, $arg2, $arg3); - return array_combine($new_keys, $array); - } - - // Get first value from array without shifting it or moving internal cursor - function array_first(array $array):mixed { - if (empty($array) || !is_array($array)) return false; - return reset($array) || false; - } - - // Get last value from array without shifting it or moving internal cursor - function array_last(array $array):mixed { - if (empty($array) || !is_array($array)) return false; - return end($array) || false; - } - -/* - // Get first value from array without shifting it or moving internal cursor - function array_first(array $array):mixed { - if (empty($array) || !is_array($array)) return false; - return $array[array_key_first($array)] || false; // PHP 7.3 - } - - // Get last value from array without shifting it or moving internal cursor - function array_last(array $array):mixed { - if (empty($array) || !is_array($array)) return false; - return $array[array_key_last($array)] || false; // PHP 7.3 - } -*/ - - // Get a random node from array - function array_get_random(array $array):mixed { - shuffle($array); - return current($array) || false; - } - - // Determine the maximum depth of a multidimensional array - function array_depth(array $array) { - $max_depth = 1; - - foreach ($array as $value) { - if (is_array($value)) { - $depth = array_depth($value) + 1; - - if ($depth > $max_depth) { - $max_depth = $depth; - } - } - } - - return $max_depth; - } - - // Filter an array recursively - function array_filter_recursive($array) { - - foreach ($array as $index_key => $node) { - if (is_array($node)) { - $array[$index_key] = array_filter_recursive($node); - } - } - - return array_filter($array, function($value) { - return is_array($v) ? !empty($v) : strlen(trim($v)); - }); - } - - // Turn an array of [foo => [bar => ...]] into foo.bar - function array_flatten($array, $delimiter='.', $preceding='') { - - $result = []; - - foreach ($array as $key => $value) { - if (is_array($value)) { - $result = $result + array_flatten($value, $delimiter, $key.$delimiter); - } else { - $result[$preceding.$key] = $value; - } - } - - return $result; - } - - // Turn an array of [foo.bar => ...] into [foo => [bar => ...]] - function array_unflatten($array, $delimiter='.') { - - $result = []; - - foreach ($array as $key => $value) { - $keys = explode($delimiter, $key); - $temp = &$result; - - foreach ($keys as $k) { - $temp = &$temp[$k]; - } - - $temp = $value; - } - - return $result; - } - - // Group values of matching keys array_group_keys(['a' => '1', 'b' => '1'], ['a' => '2', 'b' => '2']) : ['a' => ['1', '2'], ['b' => ['1', '2']] - function array_group_keys(...$arrays) { - return array_merge_recursive(...$arrays); - } + // Return array values alphanumerically between $from and $to + function array_between(array $array, $from, $to):array { + return array_filter($array, function($node) use ($from, $to) { + return ($node >= $from && $node < $to); + }); + } + + // Retain the original array keys when extracting an array column by passing $index_key = true + function array_column_intact(array $array, int|string|null $column_key, bool|int|string|null $index_key = null): array { + if ($index_key === true) { + return array_combine(array_keys($array), array_column($array, $column_key)); + } + return array_column($array, $column_key, $index_key); + } + + // Same as array_map but with the callable function first and filtered results + function array_each(array $array, callable $function):array { + return array_filter(array_map($function, $array)); + } + + // Same as array_map but with the callable function first and filtered results + function array_each2(array $array, callable $function):array { + foreach ($array as $key => $value) { + $array[$key] = $function($key, $value); + } + return $array; + } + + function array_intersect_key_recursive(array $array, array $keys): array { + $filtered = array_intersect_key($array, $keys); + + foreach ($filtered as $key => &$value) { + if (is_array($value) && isset($keys[$key]) && is_array($keys[$key])) { + $value = array_intersect_key_recursive($value, $keys[$key]); + } + } + + return $filtered; + } + + // Update an array with values that have keys present in another arrays, without inserting new keys. + function array_update(array $array, array ...$replacements): array { + + foreach ($replacements as $updates) { + foreach ($array as $key => &$value) { + if (is_array($value) && isset($updates[$key]) && is_array($updates[$key])) { + // Recursively merge nested arrays + $value = array_update($value, $updates[$key]); + } elseif (array_key_exists($key, $updates)) { + // Overwrite scalar values if key exists in both arrays + $value = $updates[$key]; + } + } + + // Handle numerical index arrays (merge without regard to keys) + if (array_is_list($array) && array_is_list($updates)) { + $array = array_values(array_unique(array_merge($array, $updates))); // Prevent duplicates + } + } + + return $array; + } + + // Return a filtered array of values from a given list of keys + function array_grep(array $array, array $matching_keys):array { + return array_intersect_key($array, array_flip($matching_keys)); + } + + // Return an array of values not defined by the given keys + function array_exclude(array $array, array $excluded_keys):array { + return array_diff_key($array, array_flip($excluded_keys)); + } + + // Same as array_exclude(). Return an array of values not including any given keys + function array_collect(array $array, array $input, array $ignored_keys):array { + return array_replace($array, array_diff_key($input, array_flip($ignored_keys))); + } + + // Function to map array_keys instead of values + function array_map_keys($callback, $array, $arg1=null, $arg2=null, $arg3=null) { + $new_keys = array_map($callback, array_keys($array), $arg1, $arg2, $arg3); + return array_combine($new_keys, $array); + } + + // Get first value from array without shifting it or moving internal cursor + if (!function_exists('array_first')) { + function array_first(array $array):mixed { + if (empty($array) || !is_array($array)) return false; + //return $array[array_key_first($array)] || false; // PHP 7.3+ + return reset($array) || false; + } + } + + // Get last value from array without shifting it or moving internal cursor + if (!function_exists('array_last')) { + function array_last(array $array):mixed { + if (empty($array) || !is_array($array)) return false; + //return $array[array_key_last($array)] || false; // PHP 7.3+ + return end($array) || false; + } + } + + // Get a random node from array + function array_get_random(array $array):mixed { + shuffle($array); + return current($array) || false; + } + + // Determine the maximum depth of a multidimensional array + function array_depth(array $array) { + $max_depth = 1; + + foreach ($array as $value) { + if (is_array($value)) { + $depth = array_depth($value) + 1; + + if ($depth > $max_depth) { + $max_depth = $depth; + } + } + } + + return $max_depth; + } + + // Filter an array recursively + function array_filter_recursive($array) { + + foreach ($array as $index_key => $node) { + if (is_array($node)) { + $array[$index_key] = array_filter_recursive($node); + } + } + + return array_filter($array, function($value) { + return is_array($value) ? !empty($value) : strlen(trim($value)); + }); + } + + // Turn an array of [foo => [bar => ...]] into foo.bar => ... + function array_flatten($array, $delimiter='.', $preceding='') { + + $result = []; + + foreach ($array as $key => $value) { + if (is_array($value)) { + $result = $result + array_flatten($value, $delimiter, $key.$delimiter); + } else { + $result[$preceding.$key] = $value; + } + } + + return $result; + } + + // Turn an array of [foo.bar => ...] into [foo => [bar => ...]] + function array_unflatten($array, $delimiter='.') { + + $result = []; + + foreach ($array as $key => $value) { + $keys = explode($delimiter, $key); + $temp = &$result; + + foreach ($keys as $k) { + $temp = &$temp[$k]; + } + + $temp = $value; + } + + return $result; + } + + // Group values of matching keys array_group_keys(['a' => '1', 'b' => '1'], ['a' => '2', 'b' => '2']) : ['a' => ['1', '2'], ['b' => ['1', '2']] + function array_merge_group(...$arrays) { + return array_merge_recursive(...$arrays); + } + + // Checking if array keys in var1 is present in var2 and their values equals the same + function array_intersect_compare($var1, $var2) { + + // Check if both variables are arrays + if (is_array($var1) && is_array($var2)) { + + // Check if $var1 has a numerical index + $is_numerical_index = array_is_list($var1); + + foreach ($var1 as $key => $value) { + + if ($is_numerical_index) { + + // Check if the value exists somewhere in the indexed array + if (!in_array($value, $var2, true)) { + return false; + } + + } else { + + // If associative array key does not exist in $var2 + if (!array_key_exists($key, $var2)) { + continue; + } + + // Recursively compare values + if (!array_intersect_compare($value, $var2[$key])) { + return false; + } + } + } + + return true; + } + + // Compare values directly + return $var1 == $var2; + } + + function array_diff_assoc_recursive(array $array1, array $array2): array { + $result = []; + + foreach ($array1 as $key => $value) { + if (array_key_exists($key, $array2)) { + if (is_array($value)) { + $recursive_diff = array_diff_assoc_recursive($value, $array2[$key]); + if (!empty($recursive_diff)) { + $result[$key] = $recursive_diff; + } + } elseif ($value !== $array2[$key]) { + $result[$key] = $value; + } + } else { + $result[$key] = $value; + } + } + + return $result; + } diff --git a/public_html/includes/functions/func_format.inc.php b/public_html/includes/functions/func_format.inc.php index 519eeab..0f63b2e 100644 --- a/public_html/includes/functions/func_format.inc.php +++ b/public_html/includes/functions/func_format.inc.php @@ -1,138 +1,182 @@ [ - 'À' => 'A', 'Á' => 'A', 'Â' => 'A', 'Ã' => 'A', 'Ä' => 'A', 'Å' => 'A','Ă' => 'A', 'Æ' => 'AE', 'Ç' => - 'C', 'È' => 'E', 'É' => 'E', 'Ê' => 'E', 'Ë' => 'E', 'Ì' => 'I', 'Í' => 'I', 'Î' => 'I', - 'Ï' => 'I', 'Ð' => 'D', 'Ñ' => 'N', 'Ò' => 'O', 'Ó' => 'O', 'Ô' => 'O', 'Õ' => 'O', 'Ö' => - 'O', 'Ő' => 'O', 'Ø' => 'O','Ș' => 'S','Ț' => 'T', 'Ù' => 'U', 'Ú' => 'U', 'Û' => 'U', 'Ü' => 'U', 'Ű' => 'U', - 'Ý' => 'Y', 'Þ' => 'TH', 'ß' => 'ss', 'à' => 'a', 'á' => 'a', 'â' => 'a', 'ã' => 'a', 'ä' => - 'a', 'å' => 'a', 'ă' => 'a', 'æ' => 'ae', 'ç' => 'c', 'è' => 'e', 'é' => 'e', 'ê' => 'e', 'ë' => 'e', - 'ì' => 'i', 'í' => 'i', 'î' => 'i', 'ï' => 'i', 'ð' => 'd', 'ñ' => 'n', 'ò' => 'o', 'ó' => - 'o', 'ô' => 'o', 'õ' => 'o', 'ö' => 'o', 'ő' => 'o', 'ø' => 'o', 'ș' => 's', 'ț' => 't', 'ù' => 'u', 'ú' => 'u', - 'û' => 'u', 'ü' => 'u', 'ű' => 'u', 'ý' => 'y', 'þ' => 'th', 'ÿ' => 'y' - ], - 'de' => [ /* German */ - 'Ä' => 'Ae', 'Ö' => 'Oe', 'Ü' => 'Ue', 'ä' => 'ae', 'ö' => 'oe', 'ü' => 'ue', 'ß' => 'ss', - 'ẞ' => 'SS' - ], - 'el' => [ /* Greek */ - 'α' => 'a', 'β' => 'b', 'γ' => 'g', 'δ' => 'd', 'ε' => 'e', 'ζ' => 'z', 'η' => 'h', 'θ' => '8', - 'ι' => 'i', 'κ' => 'k', 'λ' => 'l', 'μ' => 'm', 'ν' => 'n', 'ξ' => '3', 'ο' => 'o', 'π' => 'p', - 'ρ' => 'r', 'σ' => 's', 'τ' => 't', 'υ' => 'y', 'φ' => 'f', 'χ' => 'x', 'ψ' => 'ps', 'ω' => 'w', - 'ά' => 'a', 'έ' => 'e', 'ί' => 'i', 'ό' => 'o', 'ύ' => 'y', 'ή' => 'h', 'ώ' => 'w', 'ς' => 's', - 'ϊ' => 'i', 'ΰ' => 'y', 'ϋ' => 'y', 'ΐ' => 'i', - 'Α' => 'A', 'Β' => 'B', 'Γ' => 'G', 'Δ' => 'D', 'Ε' => 'E', 'Ζ' => 'Z', 'Η' => 'H', 'Θ' => '8', - 'Ι' => 'I', 'Κ' => 'K', 'Λ' => 'L', 'Μ' => 'M', 'Ν' => 'N', 'Ξ' => '3', 'Ο' => 'O', 'Π' => 'P', - 'Ρ' => 'R', 'Σ' => 'S', 'Τ' => 'T', 'Υ' => 'Y', 'Φ' => 'F', 'Χ' => 'X', 'Ψ' => 'PS', 'Ω' => 'W', - 'Ά' => 'A', 'Έ' => 'E', 'Ί' => 'I', 'Ό' => 'O', 'Ύ' => 'Y', 'Ή' => 'H', 'Ώ' => 'W', 'Ϊ' => 'I', - 'Ϋ' => 'Y' - ], - 'tr' => [ /* Turkish */ - 'ş' => 's', 'Ş' => 'S', 'ı' => 'i', 'İ' => 'I', 'ç' => 'c', 'Ç' => 'C', 'ü' => 'u', 'Ü' => 'U', - 'ö' => 'o', 'Ö' => 'O', 'ğ' => 'g', 'Ğ' => 'G' - ], - 'ru' => [ /* Russian */ - 'а' => 'a', 'б' => 'b', 'в' => 'v', 'г' => 'g', 'д' => 'd', 'е' => 'e', 'ё' => 'yo', 'ж' => 'zh', - 'з' => 'z', 'и' => 'i', 'й' => 'j', 'к' => 'k', 'л' => 'l', 'м' => 'm', 'н' => 'n', 'о' => 'o', - 'п' => 'p', 'р' => 'r', 'с' => 's', 'т' => 't', 'у' => 'u', 'ф' => 'f', 'х' => 'h', 'ц' => 'c', - 'ч' => 'ch', 'ш' => 'sh', 'щ' => 'sh', 'ъ' => '', 'ы' => 'y', 'ь' => '', 'э' => 'e', 'ю' => 'yu', - 'я' => 'ya', - 'А' => 'A', 'Б' => 'B', 'В' => 'V', 'Г' => 'G', 'Д' => 'D', 'Е' => 'E', 'Ё' => 'Yo', 'Ж' => 'Zh', - 'З' => 'Z', 'И' => 'I', 'Й' => 'J', 'К' => 'K', 'Л' => 'L', 'М' => 'M', 'Н' => 'N', 'О' => 'O', - 'П' => 'P', 'Р' => 'R', 'С' => 'S', 'Т' => 'T', 'У' => 'U', 'Ф' => 'F', 'Х' => 'H', 'Ц' => 'C', - 'Ч' => 'Ch', 'Ш' => 'Sh', 'Щ' => 'Sh', 'Ъ' => '', 'Ы' => 'Y', 'Ь' => '', 'Э' => 'E', 'Ю' => 'Yu', - 'Я' => 'Ya', - '№' => '' - ], - 'uk' => [ /* Ukrainian */ - 'Є' => 'Ye', 'І' => 'I', 'Ї' => 'Yi', 'Ґ' => 'G', 'є' => 'ye', 'і' => 'i', 'ї' => 'yi', 'ґ' => 'g' - ], - 'cs' => [ /* Czech */ - 'č' => 'c', 'ď' => 'd', 'ě' => 'e', 'ň' => 'n', 'ř' => 'r', 'š' => 's', 'ť' => 't', 'ů' => 'u', - 'ž' => 'z', 'Č' => 'C', 'Ď' => 'D', 'Ě' => 'E', 'Ň' => 'N', 'Ř' => 'R', 'Š' => 'S', 'Ť' => 'T', - 'Ů' => 'U', 'Ž' => 'Z' - ], - 'pl' => [ /* Polish */ - 'ą' => 'a', 'ć' => 'c', 'ę' => 'e', 'ł' => 'l', 'ń' => 'n', 'ó' => 'o', 'ś' => 's', 'ź' => 'z', - 'ż' => 'z', 'Ą' => 'A', 'Ć' => 'C', 'Ę' => 'e', 'Ł' => 'L', 'Ń' => 'N', 'Ó' => 'O', 'Ś' => 'S', - 'Ź' => 'Z', 'Ż' => 'Z' - ], - 'ro' => [ /* Romanian */ - 'ă' => 'a', 'â' => 'a', 'î' => 'i', 'ș' => 's', 'ț' => 't', 'Ţ' => 'T', 'ţ' => 't' - ], - 'lv' => [ /* Latvian */ - 'ā' => 'a', 'č' => 'c', 'ē' => 'e', 'ģ' => 'g', 'ī' => 'i', 'ķ' => 'k', 'ļ' => 'l', 'ņ' => 'n', - 'š' => 's', 'ū' => 'u', 'ž' => 'z', 'Ā' => 'A', 'Č' => 'C', 'Ē' => 'E', 'Ģ' => 'G', 'Ī' => 'i', - 'Ķ' => 'k', 'Ļ' => 'L', 'Ņ' => 'N', 'Š' => 'S', 'Ū' => 'u', 'Ž' => 'Z' - ], - 'lt' => [ /* Lithuanian */ - 'ą' => 'a', 'č' => 'c', 'ę' => 'e', 'ė' => 'e', 'į' => 'i', 'š' => 's', 'ų' => 'u', 'ū' => 'u', 'ž' => 'z', - 'Ą' => 'A', 'Č' => 'C', 'Ę' => 'E', 'Ė' => 'E', 'Į' => 'I', 'Š' => 'S', 'Ų' => 'U', 'Ū' => 'U', 'Ž' => 'Z' - ], - 'vn' => [ /* Vietnamese */ - 'Á' => 'A', 'À' => 'A', 'Ả' => 'A', 'Ã' => 'A', 'Ạ' => 'A', 'Ă' => 'A', 'Ắ' => 'A', 'Ằ' => 'A', 'Ẳ' => 'A', 'Ẵ' => 'A', 'Ặ' => 'A', 'Â' => 'A', 'Ấ' => 'A', 'Ầ' => 'A', 'Ẩ' => 'A', 'Ẫ' => 'A', 'Ậ' => 'A', - 'á' => 'a', 'à' => 'a', 'ả' => 'a', 'ã' => 'a', 'ạ' => 'a', 'ă' => 'a', 'ắ' => 'a', 'ằ' => 'a', 'ẳ' => 'a', 'ẵ' => 'a', 'ặ' => 'a', 'â' => 'a', 'ấ' => 'a', 'ầ' => 'a', 'ẩ' => 'a', 'ẫ' => 'a', 'ậ' => 'a', - 'É' => 'E', 'È' => 'E', 'Ẻ' => 'E', 'Ẽ' => 'E', 'Ẹ' => 'E', 'Ê' => 'E', 'Ế' => 'E', 'Ề' => 'E', 'Ể' => 'E', 'Ễ' => 'E', 'Ệ' => 'E', - 'é' => 'e', 'è' => 'e', 'ẻ' => 'e', 'ẽ' => 'e', 'ẹ' => 'e', 'ê' => 'e', 'ế' => 'e', 'ề' => 'e', 'ể' => 'e', 'ễ' => 'e', 'ệ' => 'e', - 'Í' => 'I', 'Ì' => 'I', 'Ỉ' => 'I', 'Ĩ' => 'I', 'Ị' => 'I', 'í' => 'i', 'ì' => 'i', 'ỉ' => 'i', 'ĩ' => 'i', 'ị' => 'i', - 'Ó' => 'O', 'Ò' => 'O', 'Ỏ' => 'O', 'Õ' => 'O', 'Ọ' => 'O', 'Ô' => 'O', 'Ố' => 'O', 'Ồ' => 'O', 'Ổ' => 'O', 'Ỗ' => 'O', 'Ộ' => 'O', 'Ơ' => 'O', 'Ớ' => 'O', 'Ờ' => 'O', 'Ở' => 'O', 'Ỡ' => 'O', 'Ợ' => 'O', - 'ó' => 'o', 'ò' => 'o', 'ỏ' => 'o', 'õ' => 'o', 'ọ' => 'o', 'ô' => 'o', 'ố' => 'o', 'ồ' => 'o', 'ổ' => 'o', 'ỗ' => 'o', 'ộ' => 'o', 'ơ' => 'o', 'ớ' => 'o', 'ờ' => 'o', 'ở' => 'o', 'ỡ' => 'o', 'ợ' => 'o', - 'Ú' => 'U', 'Ù' => 'U', 'Ủ' => 'U', 'Ũ' => 'U', 'Ụ' => 'U', 'Ư' => 'U', 'Ứ' => 'U', 'Ừ' => 'U', 'Ử' => 'U', 'Ữ' => 'U', 'Ự' => 'U', - 'ú' => 'u', 'ù' => 'u', 'ủ' => 'u', 'ũ' => 'u', 'ụ' => 'u', 'ư' => 'u', 'ứ' => 'u', 'ừ' => 'u', 'ử' => 'u', 'ữ' => 'u', 'ự' => 'u', - 'Ý' => 'Y', 'Ỳ' => 'Y', 'Ỷ' => 'Y', 'Ỹ' => 'Y', 'Ỵ' => 'Y', 'ý' => 'y', 'ỳ' => 'y', 'ỷ' => 'y', 'ỹ' => 'y', 'ỵ' => 'y', - 'Đ' => 'D', 'đ' => 'd' - ], - 'ar' => [ /* Arabic */ - 'أ' => 'a', 'ب' => 'b', 'ت' => 't', 'ث' => 'th', 'ج' => 'g', 'ح' => 'h', 'خ' => 'kh', 'د' => 'd', - 'ذ' => 'th', 'ر' => 'r', 'ز' => 'z', 'س' => 's', 'ش' => 'sh', 'ص' => 's', 'ض' => 'd', 'ط' => 't', - 'ظ' => 'th', 'ع' => 'aa', 'غ' => 'gh', 'ف' => 'f', 'ق' => 'k', 'ك' => 'k', 'ل' => 'l', 'م' => 'm', - 'ن' => 'n', 'ه' => 'h', 'و' => 'o', 'ي' => 'y' - ], - 'sr' => [ /* Serbian */ - 'ђ' => 'dj', 'ј' => 'j', 'љ' => 'lj', 'њ' => 'nj', 'ћ' => 'c', 'џ' => 'dz', 'đ' => 'dj', - 'Ђ' => 'Dj', 'Ј' => 'j', 'Љ' => 'Lj', 'Њ' => 'Nj', 'Ћ' => 'C', 'Џ' => 'Dz', 'Đ' => 'Dj' - ], - 'az' => [ /* Azerbaijani */ - 'ç' => 'c', 'ə' => 'e', 'ğ' => 'g', 'ı' => 'i', 'ö' => 'o', 'ş' => 's', 'ü' => 'u', - 'Ç' => 'C', 'Ə' => 'E', 'Ğ' => 'G', 'İ' => 'I', 'Ö' => 'O', 'Ş' => 'S', 'Ü' => 'U' - ], - 'ge' => [ /* Georgian */ - 'ა' => 'a', 'ბ' => 'b', 'გ' => 'g', 'დ' => 'd', 'ე' => 'e', 'ვ' => 'v', 'ზ' => 'z', 'თ' => 'T', 'ი' => 'i', - 'კ' => 'k', 'ლ' => 'l', 'მ' => 'm', 'ნ' => 'n', 'ო' => 'o', 'პ' => 'p', 'ჟ' => 'zh', 'რ' => 'r', 'ს' => 's', - 'ტ' => 't', 'უ' => 'u', 'ფ' => 'f', 'ქ' => 'q', 'ღ' => 'R', 'ყ' => 'y', 'შ' => 'S', 'ჩ' => 'C', 'ც' => 'c', - 'ძ' => 'Z', 'წ' => 'w', 'ჭ' => 'W', 'ხ' => 'x', 'ჯ' => 'j', 'ჰ' => 'h' - ], - ]; - - // Convert foreign characters - if (!empty($foreign_characters[$language_code])) { - $text = strtr($text, $foreign_characters[$language_code]); - unset($foreign_characters[$language_code]); - } - - // Convert other foreign characters - foreach ($foreign_characters as $characters) { - $text = strtr($text, $characters); - } - - // Strip non printable characters and symbols - $text = preg_replace(['#[[:cntrl:]]+#', '#&(amp;)?#', '#[!"\#$%\'()*+,./:;<=>?@\[\]\\^`{}|~]#'], '', $text); - - // Underscores and dashes - $text = trim(preg_replace('#[-_ ]+#', '-', $text), '-'); - - // Convert to lowercases - $text = mb_strtolower($text); - - return $text; - } + function format_json($data, $indent="\t") { + + $json = json_encode($data, ($indent ? JSON_PRETTY_PRINT : 0) | JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE); + + if (json_last_error() !== JSON_ERROR_NONE) { + throw new Exception('JSON encode error: '. json_last_error_msg()); + } + + if ($indent) { + $json = preg_replace_callback('#^( +)#m', function ($m) { + return str_repeat("\t", strlen($m[1]) / 4); + }, $json); + } + + return $json; + } + + function format_path_friendly($text, $language_code='') { + + if (!$text) return ''; + + if (!$language_code) { + $language_code = language::$selected['code']; + } + + $text = strip_tags($text); // Remove HTML tags + $text = html_entity_decode($text, ENT_QUOTES, 'UTF-8'); // Decode special characters + + $foreign_characters = [ + 'latin' => [ + 'À' => 'A', 'Á' => 'A', 'Â' => 'A', 'Ã' => 'A', 'Ä' => 'A', 'Å' => 'A','Ă' => 'A', 'Æ' => 'AE', 'Ç' => + 'C', 'È' => 'E', 'É' => 'E', 'Ê' => 'E', 'Ë' => 'E', 'Ì' => 'I', 'Í' => 'I', 'Î' => 'I', + 'Ï' => 'I', 'Ð' => 'D', 'Ñ' => 'N', 'Ò' => 'O', 'Ó' => 'O', 'Ô' => 'O', 'Õ' => 'O', 'Ö' => + 'O', 'Ő' => 'O', 'Ø' => 'O','Ș' => 'S','Ț' => 'T', 'Ù' => 'U', 'Ú' => 'U', 'Û' => 'U', 'Ü' => 'U', 'Ű' => 'U', + 'Ý' => 'Y', 'Þ' => 'TH', 'ß' => 'ss', 'à' => 'a', 'á' => 'a', 'â' => 'a', 'ã' => 'a', 'ä' => + 'a', 'å' => 'a', 'ă' => 'a', 'æ' => 'ae', 'ç' => 'c', 'è' => 'e', 'é' => 'e', 'ê' => 'e', 'ë' => 'e', + 'ì' => 'i', 'í' => 'i', 'î' => 'i', 'ï' => 'i', 'ð' => 'd', 'ñ' => 'n', 'ò' => 'o', 'ó' => + 'o', 'ô' => 'o', 'õ' => 'o', 'ö' => 'o', 'ő' => 'o', 'ø' => 'o', 'ș' => 's', 'ț' => 't', 'ù' => 'u', 'ú' => 'u', + 'û' => 'u', 'ü' => 'u', 'ű' => 'u', 'ý' => 'y', 'þ' => 'th', 'ÿ' => 'y' + ], + 'de' => [ /* German */ + 'Ä' => 'Ae', 'Ö' => 'Oe', 'Ü' => 'Ue', 'ä' => 'ae', 'ö' => 'oe', 'ü' => 'ue', 'ß' => 'ss', + 'ẞ' => 'SS' + ], + 'el' => [ /* Greek */ + 'α' => 'a', 'β' => 'b', 'γ' => 'g', 'δ' => 'd', 'ε' => 'e', 'ζ' => 'z', 'η' => 'h', 'θ' => '8', + 'ι' => 'i', 'κ' => 'k', 'λ' => 'l', 'μ' => 'm', 'ν' => 'n', 'ξ' => '3', 'ο' => 'o', 'π' => 'p', + 'ρ' => 'r', 'σ' => 's', 'τ' => 't', 'υ' => 'y', 'φ' => 'f', 'χ' => 'x', 'ψ' => 'ps', 'ω' => 'w', + 'ά' => 'a', 'έ' => 'e', 'ί' => 'i', 'ό' => 'o', 'ύ' => 'y', 'ή' => 'h', 'ώ' => 'w', 'ς' => 's', + 'ϊ' => 'i', 'ΰ' => 'y', 'ϋ' => 'y', 'ΐ' => 'i', + 'Α' => 'A', 'Β' => 'B', 'Γ' => 'G', 'Δ' => 'D', 'Ε' => 'E', 'Ζ' => 'Z', 'Η' => 'H', 'Θ' => '8', + 'Ι' => 'I', 'Κ' => 'K', 'Λ' => 'L', 'Μ' => 'M', 'Ν' => 'N', 'Ξ' => '3', 'Ο' => 'O', 'Π' => 'P', + 'Ρ' => 'R', 'Σ' => 'S', 'Τ' => 'T', 'Υ' => 'Y', 'Φ' => 'F', 'Χ' => 'X', 'Ψ' => 'PS', 'Ω' => 'W', + 'Ά' => 'A', 'Έ' => 'E', 'Ί' => 'I', 'Ό' => 'O', 'Ύ' => 'Y', 'Ή' => 'H', 'Ώ' => 'W', 'Ϊ' => 'I', + 'Ϋ' => 'Y' + ], + 'tr' => [ /* Turkish */ + 'ş' => 's', 'Ş' => 'S', 'ı' => 'i', 'İ' => 'I', 'ç' => 'c', 'Ç' => 'C', 'ü' => 'u', 'Ü' => 'U', + 'ö' => 'o', 'Ö' => 'O', 'ğ' => 'g', 'Ğ' => 'G' + ], + 'ru' => [ /* Russian */ + 'а' => 'a', 'б' => 'b', 'в' => 'v', 'г' => 'g', 'д' => 'd', 'е' => 'e', 'ё' => 'yo', 'ж' => 'zh', + 'з' => 'z', 'и' => 'i', 'й' => 'j', 'к' => 'k', 'л' => 'l', 'м' => 'm', 'н' => 'n', 'о' => 'o', + 'п' => 'p', 'р' => 'r', 'с' => 's', 'т' => 't', 'у' => 'u', 'ф' => 'f', 'х' => 'h', 'ц' => 'c', + 'ч' => 'ch', 'ш' => 'sh', 'щ' => 'sh', 'ъ' => '', 'ы' => 'y', 'ь' => '', 'э' => 'e', 'ю' => 'yu', + 'я' => 'ya', + 'А' => 'A', 'Б' => 'B', 'В' => 'V', 'Г' => 'G', 'Д' => 'D', 'Е' => 'E', 'Ё' => 'Yo', 'Ж' => 'Zh', + 'З' => 'Z', 'И' => 'I', 'Й' => 'J', 'К' => 'K', 'Л' => 'L', 'М' => 'M', 'Н' => 'N', 'О' => 'O', + 'П' => 'P', 'Р' => 'R', 'С' => 'S', 'Т' => 'T', 'У' => 'U', 'Ф' => 'F', 'Х' => 'H', 'Ц' => 'C', + 'Ч' => 'Ch', 'Ш' => 'Sh', 'Щ' => 'Sh', 'Ъ' => '', 'Ы' => 'Y', 'Ь' => '', 'Э' => 'E', 'Ю' => 'Yu', + 'Я' => 'Ya', + '№' => '' + ], + 'uk' => [ /* Ukrainian */ + 'Є' => 'Ye', 'І' => 'I', 'Ї' => 'Yi', 'Ґ' => 'G', 'є' => 'ye', 'і' => 'i', 'ї' => 'yi', 'ґ' => 'g' + ], + 'cs' => [ /* Czech */ + 'č' => 'c', 'ď' => 'd', 'ě' => 'e', 'ň' => 'n', 'ř' => 'r', 'š' => 's', 'ť' => 't', 'ů' => 'u', + 'ž' => 'z', 'Č' => 'C', 'Ď' => 'D', 'Ě' => 'E', 'Ň' => 'N', 'Ř' => 'R', 'Š' => 'S', 'Ť' => 'T', + 'Ů' => 'U', 'Ž' => 'Z' + ], + 'pl' => [ /* Polish */ + 'ą' => 'a', 'ć' => 'c', 'ę' => 'e', 'ł' => 'l', 'ń' => 'n', 'ó' => 'o', 'ś' => 's', 'ź' => 'z', + 'ż' => 'z', 'Ą' => 'A', 'Ć' => 'C', 'Ę' => 'e', 'Ł' => 'L', 'Ń' => 'N', 'Ó' => 'O', 'Ś' => 'S', + 'Ź' => 'Z', 'Ż' => 'Z' + ], + 'ro' => [ /* Romanian */ + 'ă' => 'a', 'â' => 'a', 'î' => 'i', 'ș' => 's', 'ț' => 't', 'Ţ' => 'T', 'ţ' => 't' + ], + 'lv' => [ /* Latvian */ + 'ā' => 'a', 'č' => 'c', 'ē' => 'e', 'ģ' => 'g', 'ī' => 'i', 'ķ' => 'k', 'ļ' => 'l', 'ņ' => 'n', + 'š' => 's', 'ū' => 'u', 'ž' => 'z', 'Ā' => 'A', 'Č' => 'C', 'Ē' => 'E', 'Ģ' => 'G', 'Ī' => 'i', + 'Ķ' => 'k', 'Ļ' => 'L', 'Ņ' => 'N', 'Š' => 'S', 'Ū' => 'u', 'Ž' => 'Z' + ], + 'lt' => [ /* Lithuanian */ + 'ą' => 'a', 'č' => 'c', 'ę' => 'e', 'ė' => 'e', 'į' => 'i', 'š' => 's', 'ų' => 'u', 'ū' => 'u', 'ž' => 'z', + 'Ą' => 'A', 'Č' => 'C', 'Ę' => 'E', 'Ė' => 'E', 'Į' => 'I', 'Š' => 'S', 'Ų' => 'U', 'Ū' => 'U', 'Ž' => 'Z' + ], + 'vn' => [ /* Vietnamese */ + 'Á' => 'A', 'À' => 'A', 'Ả' => 'A', 'Ã' => 'A', 'Ạ' => 'A', 'Ă' => 'A', 'Ắ' => 'A', 'Ằ' => 'A', 'Ẳ' => 'A', 'Ẵ' => 'A', 'Ặ' => 'A', 'Â' => 'A', 'Ấ' => 'A', 'Ầ' => 'A', 'Ẩ' => 'A', 'Ẫ' => 'A', 'Ậ' => 'A', + 'á' => 'a', 'à' => 'a', 'ả' => 'a', 'ã' => 'a', 'ạ' => 'a', 'ă' => 'a', 'ắ' => 'a', 'ằ' => 'a', 'ẳ' => 'a', 'ẵ' => 'a', 'ặ' => 'a', 'â' => 'a', 'ấ' => 'a', 'ầ' => 'a', 'ẩ' => 'a', 'ẫ' => 'a', 'ậ' => 'a', + 'É' => 'E', 'È' => 'E', 'Ẻ' => 'E', 'Ẽ' => 'E', 'Ẹ' => 'E', 'Ê' => 'E', 'Ế' => 'E', 'Ề' => 'E', 'Ể' => 'E', 'Ễ' => 'E', 'Ệ' => 'E', + 'é' => 'e', 'è' => 'e', 'ẻ' => 'e', 'ẽ' => 'e', 'ẹ' => 'e', 'ê' => 'e', 'ế' => 'e', 'ề' => 'e', 'ể' => 'e', 'ễ' => 'e', 'ệ' => 'e', + 'Í' => 'I', 'Ì' => 'I', 'Ỉ' => 'I', 'Ĩ' => 'I', 'Ị' => 'I', 'í' => 'i', 'ì' => 'i', 'ỉ' => 'i', 'ĩ' => 'i', 'ị' => 'i', + 'Ó' => 'O', 'Ò' => 'O', 'Ỏ' => 'O', 'Õ' => 'O', 'Ọ' => 'O', 'Ô' => 'O', 'Ố' => 'O', 'Ồ' => 'O', 'Ổ' => 'O', 'Ỗ' => 'O', 'Ộ' => 'O', 'Ơ' => 'O', 'Ớ' => 'O', 'Ờ' => 'O', 'Ở' => 'O', 'Ỡ' => 'O', 'Ợ' => 'O', + 'ó' => 'o', 'ò' => 'o', 'ỏ' => 'o', 'õ' => 'o', 'ọ' => 'o', 'ô' => 'o', 'ố' => 'o', 'ồ' => 'o', 'ổ' => 'o', 'ỗ' => 'o', 'ộ' => 'o', 'ơ' => 'o', 'ớ' => 'o', 'ờ' => 'o', 'ở' => 'o', 'ỡ' => 'o', 'ợ' => 'o', + 'Ú' => 'U', 'Ù' => 'U', 'Ủ' => 'U', 'Ũ' => 'U', 'Ụ' => 'U', 'Ư' => 'U', 'Ứ' => 'U', 'Ừ' => 'U', 'Ử' => 'U', 'Ữ' => 'U', 'Ự' => 'U', + 'ú' => 'u', 'ù' => 'u', 'ủ' => 'u', 'ũ' => 'u', 'ụ' => 'u', 'ư' => 'u', 'ứ' => 'u', 'ừ' => 'u', 'ử' => 'u', 'ữ' => 'u', 'ự' => 'u', + 'Ý' => 'Y', 'Ỳ' => 'Y', 'Ỷ' => 'Y', 'Ỹ' => 'Y', 'Ỵ' => 'Y', 'ý' => 'y', 'ỳ' => 'y', 'ỷ' => 'y', 'ỹ' => 'y', 'ỵ' => 'y', + 'Đ' => 'D', 'đ' => 'd' + ], + 'ar' => [ /* Arabic */ + 'أ' => 'a', 'ب' => 'b', 'ت' => 't', 'ث' => 'th', 'ج' => 'g', 'ح' => 'h', 'خ' => 'kh', 'د' => 'd', + 'ذ' => 'th', 'ر' => 'r', 'ز' => 'z', 'س' => 's', 'ش' => 'sh', 'ص' => 's', 'ض' => 'd', 'ط' => 't', + 'ظ' => 'th', 'ع' => 'aa', 'غ' => 'gh', 'ف' => 'f', 'ق' => 'k', 'ك' => 'k', 'ل' => 'l', 'م' => 'm', + 'ن' => 'n', 'ه' => 'h', 'و' => 'o', 'ي' => 'y' + ], + 'sr' => [ /* Serbian */ + 'ђ' => 'dj', 'ј' => 'j', 'љ' => 'lj', 'њ' => 'nj', 'ћ' => 'c', 'џ' => 'dz', 'đ' => 'dj', + 'Ђ' => 'Dj', 'Ј' => 'j', 'Љ' => 'Lj', 'Њ' => 'Nj', 'Ћ' => 'C', 'Џ' => 'Dz', 'Đ' => 'Dj' + ], + 'az' => [ /* Azerbaijani */ + 'ç' => 'c', 'ə' => 'e', 'ğ' => 'g', 'ı' => 'i', 'ö' => 'o', 'ş' => 's', 'ü' => 'u', + 'Ç' => 'C', 'Ə' => 'E', 'Ğ' => 'G', 'İ' => 'I', 'Ö' => 'O', 'Ş' => 'S', 'Ü' => 'U' + ], + 'ge' => [ /* Georgian */ + 'ა' => 'a', 'ბ' => 'b', 'გ' => 'g', 'დ' => 'd', 'ე' => 'e', 'ვ' => 'v', 'ზ' => 'z', 'თ' => 'T', 'ი' => 'i', + 'კ' => 'k', 'ლ' => 'l', 'მ' => 'm', 'ნ' => 'n', 'ო' => 'o', 'პ' => 'p', 'ჟ' => 'zh', 'რ' => 'r', 'ს' => 's', + 'ტ' => 't', 'უ' => 'u', 'ფ' => 'f', 'ქ' => 'q', 'ღ' => 'R', 'ყ' => 'y', 'შ' => 'S', 'ჩ' => 'C', 'ც' => 'c', + 'ძ' => 'Z', 'წ' => 'w', 'ჭ' => 'W', 'ხ' => 'x', 'ჯ' => 'j', 'ჰ' => 'h' + ], + ]; + + // Convert foreign characters + if (!empty($foreign_characters[$language_code])) { + $text = strtr($text, $foreign_characters[$language_code]); + unset($foreign_characters[$language_code]); + } + + // Convert other foreign characters + foreach ($foreign_characters as $characters) { + $text = strtr($text, $characters); + } + + // Remove all special characters except letters, numbers, spaces, hyphens, and underscores + $text = preg_replace('#[^\p{L}\p{N}\p{Han}\p{Hiragana}\p{Katakana}\-–—_ ]+#u', '', $text); + + // Replace spaces, hyphens, and underscores + $text = trim(preg_replace('#[\-–—_ ]+#', '-', $text), '-'); + + // Convert to lowercases + $text = mb_strtolower($text); + + return $text; + } + + function format_number($number, $decimals=0) { + return language::number_format($number, $decimals); + } + + function format_regex_code($string) { + + $string = strip_tags($string); + + if (strlen($string) > 24 || preg_match('#[^0-9a-zA-Z \-\./]#', $string)) { + return addcslashes(preg_quote($string, "'"), '&<>'); + } + + $string = preg_replace('#[ -\./]+#', '', $string); + + $parts = preg_split('#(.)#u', $string, -1, PREG_SPLIT_NO_EMPTY | PREG_SPLIT_DELIM_CAPTURE); + + foreach ($parts as $key => $char) { + $parts[$key] = addcslashes(preg_quote($char, "'"), '&<>'); + } + + $string = implode('([ \-\./]+)?', $parts); + + return $string; + } diff --git a/public_html/includes/functions/func_string.inc.php b/public_html/includes/functions/func_string.inc.php index 2f69f60..00e0027 100644 --- a/public_html/includes/functions/func_string.inc.php +++ b/public_html/includes/functions/func_string.inc.php @@ -1,50 +1,26 @@ $value) { - if (is_array($value)) { - $string = string_translate($str, $value); - } else { - $string = str_replace($key, $value, $str); - } - } + if (!$string) return ''; - return $string; - } + if ($length < 0) { + return $ellipsis . mb_substr($string, $length); + } - // Collapse the middle part of a long string using ellipsis - function string_ellipsis($text, $maxlength=72) { + if (mb_strlen($string) <= $length) { + return $string; + } - if (strlen($text) > ($maxlength + 6)) { - $half = floor($maxlength / 2); - return substr($text, 0, $half) . '…' . substr($text, -$half); - } + if ($length <= 0) { + return $ellipsis . mb_substr($string, $length); + } - return $text; - } - - // Slice a string into two arrays at the nth position - function string_slice($string, $position) { - return [substr($string, 0, $position), substr($string, $position)]; - } - - // Adds padding to the beginning of each line of a string - function string_pad_lines($string, $padding, $pad_type=STR_PAD_LEFT) { - - if ($pad_type & STR_PAD_BOTH || $pad_type & STR_PAD_LEFT) { - $string = preg_replace('#^#m', preg_quote($padding), $string); - } - - if ($pad_type & STR_PAD_BOTH || $pad_type & STR_PAD_RIGHT) { - $string = preg_replace('#$#m', preg_quote($padding), $string); - } - - return $string; - } + return mb_substr($string, 0, $length) . $ellipsis; + } diff --git a/tests/administrator.php b/tests/administrator.php index 9e7a7f1..6e6cc21 100644 --- a/tests/administrator.php +++ b/tests/administrator.php @@ -1,124 +1,148 @@ 1, - 'username' => 'test', - 'email' => 'test@example.com', - 'two_factor_auth' => 1, - 'valid_from' => '2023-01-01 00:00:00', - 'valid_to' => '2023-12-31 23:59:59', - ]; - - $password = '123456'; - - ######################################################################## - ## Creating a new administrator - ######################################################################## - - // Create a new entity - $administrator = new ent_administrator(); - - // Set data - foreach ($data as $key => $value) { - $administrator->data[$key] = $value; - } - - $administrator->set_password('123456'); - - // Save changes to database - $administrator->save(); - - // Check if the entity was created - if (!$administrator_id = $administrator->data['id']) { - throw new Exception('Failed to create administrator'); - } - - ######################################################################## - ## Load and update the administrator - ######################################################################## - - // Load the entity - $administrator = new ent_administrator($administrator_id); - - // Check if the administrator was loaded - if ($administrator->data['id'] != $administrator_id) { - throw new Exception('Failed to load administrator'); - } - - // Check if data was set correctly - foreach ($data as $key => $value) { - if ($administrator->data[$key] != $value) { - throw new Exception('The administrator data was not stored correctly ('. $key .')'); - } - } - - // Check if the password was stored correctly - if (!password_verify($password, $administrator->password_hash)) { - throw new Exception('The administrator data was not stored correctly'); - } - - ######################################################################## - ## Updating the administrator - ######################################################################## - - // Prepare some new data - $data = [ - 'status' => 0, - 'username' => 'test2', - 'email' => 'test2@example.com', - 'two_factor_auth' => 0, - 'valid_from' => '2024-01-01 00:00:00', - 'valid_to' => '2024-12-31 23:59:59', - ]; - - // Update some data - foreach ($data as $key => $value) { - $administrator->data[$key] = $value; - } - - // Set a new password - $administrator->set_password($password = '654321'); - - // Save changes to database - $administrator->save(); - - // Check if data was set correctly - foreach ($data as $key => $value) { - if ($administrator->data[$key] != $value) { - throw new Exception('The administrator data was not updated correctly ('. $key .')'); - } - } - - // Check if the password was stored correctly - if (!password_verify($password, $administrator->password_hash)) { - throw new Exception('The administrator data was not updated correctly'); - } - - ######################################################################## - ## Deleting the administrator - ######################################################################## - - // Delete the entity - $administrator->delete(); - - database::query( - "rollback;" - ); - - return true; - - } catch (Exception $e) { - database::query('rollback;'); - return false; - } + include_once __DIR__.'/../public_html/includes/app_header.inc.php'; + try { + + // Get the current auto increment ID - this will be used to revert the ID after the test + $auto_increment_id = database::query( + "SHOW TABLE STATUS LIKE '". DB_TABLE_PREFIX ."administrators';" + )->fetch('Auto_increment'); + + // Start a MySQL transaction so we can rollback the test + database::query("start transaction;"); + + // Prepare some example data + $data = [ + 'status' => 1, + 'username' => 'test', + 'email' => 'test@example.com', + 'two_factor_auth' => 1, + 'valid_from' => '2023-01-01 00:00:00', + 'valid_to' => '2023-12-31 23:59:59', + ]; + + $password = '123456'; + + ######################################################################## + ## Creating a new administrator + ######################################################################## + + // Create a new entity + $administrator = new ent_administrator(); + + // Set data + $administrator->data = f::array_update($administrator->data, $data); + + $administrator->set_password('123456'); + + // Save changes to database + $administrator->save(); + + // Check if the entity was created + if (!$administrator_id = $administrator->data['id']) { + throw new Exception('Failed to create administrator'); + } + + ######################################################################## + ## Load and update the administrator + ######################################################################## + + // Load the entity + $administrator = new ent_administrator($administrator_id); + + // Check if the administrator was loaded + if ($administrator->data['id'] != $administrator_id) { + throw new Exception('Failed to load administrator'); + } + + // Check if data was set correctly + if (!f::array_intersect_compare($data, $administrator->data)) { + throw new Exception('The administrator data was not stored correctly'); + } + + // Check if the password was stored correctly + if (!password_verify($password, $administrator->data['password_hash'])) { + throw new Exception('The administrator password was not stored correctly'); + } + + ######################################################################## + ## Updating the administrator + ######################################################################## + + // Prepare some new data + $data = [ + 'status' => 0, + 'username' => 'test2', + 'email' => 'test2@example.com', + 'two_factor_auth' => 0, + 'valid_from' => '2024-01-01 00:00:00', + 'valid_to' => '2024-12-31 23:59:59', + ]; + + // Update some data + $administrator->data = f::array_update($administrator->data, $data); + + // Set a new password + $administrator->set_password($password = '654321'); + + // Save changes to database + $administrator->save(); + + // Reload the entity + $administrator = new ent_administrator($administrator_id); + + // Check if data was set correctly + if (!f::array_intersect_compare($data, $administrator->data)) { + throw new Exception('The administrator data was not updated correctly'); + } + + // Check if the password was stored correctly + if (!password_verify($password, $administrator->data['password_hash'])) { + throw new Exception('The administrator password was not updated correctly'); + } + + ######################################################################## + ## Loading the administrator + ######################################################################## + + administrator::load($administrator->data['id']); + + if (!f::array_intersect_compare($administrator->data, administrator::$data)) { + throw new Exception('administrator::$data does not match $administrator->data'); + } + + administrator::reset(); + + ######################################################################## + ## Deleting the administrator + ######################################################################## + + // Delete the entity + $administrator->delete(); + + if (database::query( + "select id from ". DB_TABLE_PREFIX ."administrators + where id = ". (int)$administrator_id ." + limit 1;" + )->num_rows) { + throw new Exception('Failed to delete administrator'); + } + + return true; + + } catch (Exception $e) { + + echo ' [Failed]'. PHP_EOL . 'Error: '. $e->getMessage(); + return false; + + } finally { + + // Rollback changes to the database + database::query('rollback;'); + + // Revert the auto increment ID + database::query( + "ALTER TABLE ". DB_TABLE_PREFIX ."administrators AUTO_INCREMENT = ". (int)$auto_increment_id .";" + ); + } diff --git a/tests/func_format.php b/tests/func_format.php index 0e8079c..54295d2 100644 --- a/tests/func_format.php +++ b/tests/func_format.php @@ -1,114 +1,69 @@ 'Test', 'value' => 42]; - $json = f::format_json($data); + $data = ['name' => 'Test', 'value' => 42]; + $json = f::format_json($data); - if (strpos($json, "\t") === false) { - throw new Exception('format_json with indent should contain tabs'); - } + if (strpos($json, "\t") === false) { + throw new Exception('format_json with indent should contain tabs'); + } - $decoded = json_decode($json, true); - if ($decoded['name'] !== 'Test' || $decoded['value'] !== 42) { - throw new Exception('format_json output not valid JSON after decode'); - } + $decoded = json_decode($json, true); + if ($decoded['name'] !== 'Test' || $decoded['value'] !== 42) { + throw new Exception('format_json output not valid JSON after decode'); + } - ######################################################################## - ## format_json — compact (no indent) - ######################################################################## + ######################################################################## + ## format_json — compact (no indent) + ######################################################################## - $json_compact = f::format_json($data, false); + $json_compact = f::format_json($data, false); - if (strpos($json_compact, "\t") !== false || strpos($json_compact, "\n") !== false) { - throw new Exception('format_json without indent should not contain tabs or newlines'); - } + if (strpos($json_compact, "\t") !== false || strpos($json_compact, "\n") !== false) { + throw new Exception('format_json without indent should not contain tabs or newlines'); + } - ######################################################################## - ## format_path_friendly — basic slug - ######################################################################## + ######################################################################## + ## format_path_friendly — basic slug + ######################################################################## - $slug = f::format_path_friendly('Hello World 123'); + $slug = f::format_path_friendly('Hello World 123'); - if ($slug !== 'hello-world-123') { - throw new Exception('format_path_friendly basic failed: got "'. $slug .'"'); - } + if ($slug !== 'hello-world-123') { + throw new Exception('format_path_friendly basic failed: got "'. $slug .'"'); + } - ######################################################################## - ## format_path_friendly — special characters - ######################################################################## + ######################################################################## + ## format_path_friendly — special characters + ######################################################################## - $slug = f::format_path_friendly('Über Straße & Café'); + $slug = f::format_path_friendly('Über Straße & Café'); - if (empty($slug) || strpos($slug, '&') !== false) { - throw new Exception('format_path_friendly should strip special chars: got "'. $slug .'"'); - } + if (empty($slug) || strpos($slug, '&') !== false) { + throw new Exception('format_path_friendly should strip special chars: got "'. $slug .'"'); + } - ######################################################################## - ## format_path_friendly — German umlauts - ######################################################################## + ######################################################################## + ## format_path_friendly — German umlauts + ######################################################################## - $slug = f::format_path_friendly('Ärger mit Öl', 'de'); + $slug = f::format_path_friendly('Ärger mit Öl', 'de'); - if (strpos($slug, 'aerger') === false) { - throw new Exception('format_path_friendly German should convert Ä to Ae: got "'. $slug .'"'); - } + if (strpos($slug, 'aerger') === false) { + throw new Exception('format_path_friendly German should convert Ä to Ae: got "'. $slug .'"'); + } - ######################################################################## - ## format_address — basic address formatting - ######################################################################## + return true; - $address = [ - 'company' => 'ACME Corp', - 'firstname' => 'John', - 'lastname' => 'Doe', - 'address1' => '123 Main St', - 'address2' => '', - 'city' => 'New York', - 'postcode' => '10001', - 'zone_code' => 'NY', - 'country_code' => 'US', - ]; + } catch (Exception $e) { - $formatted = f::format_address($address); - - if (empty($formatted)) { - throw new Exception('format_address returned empty string'); - } - - if (strpos($formatted, 'John') === false || strpos($formatted, 'Doe') === false) { - throw new Exception('format_address missing name in output'); - } - - if (strpos($formatted, 'New York') === false) { - throw new Exception('format_address missing city in output'); - } - - ######################################################################## - ## format_number — uses language formatting - ######################################################################## - - $result = f::format_number(1234.56, 2); - - if (empty($result)) { - throw new Exception('format_number returned empty'); - } - - // Should contain the digits regardless of separator style - if (strpos(str_replace(['.', ',', ' '], '', $result), '123456') === false) { - throw new Exception('format_number missing expected digits: got "'. $result .'"'); - } - - return true; - - } catch (Exception $e) { - - echo ' [Failed]'. PHP_EOL . 'Error: '. $e->getMessage(); - return false; - } + echo ' [Failed]'. PHP_EOL . 'Error: '. $e->getMessage(); + return false; + } diff --git a/tests/language.php b/tests/language.php index 001df4e..4b9d5c5 100644 --- a/tests/language.php +++ b/tests/language.php @@ -8,7 +8,7 @@ database::query("start transaction;"); // Fetch the current auto increment ID - $auto_increment_id = database::fetch("SELECT AUTO_INCREMENT FROM information_schema.TABLES WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'languages';")['AUTO_INCREMENT']; + $auto_increment_id = database::fetch("SELECT AUTO_INCREMENT FROM information_schema.TABLES WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = '". DB_TABLE_PREFIX ."languages';")['AUTO_INCREMENT']; // Prepare some example data $data = [ @@ -134,6 +134,6 @@ database::query("rollback;"); // Revert the auto increment ID - database::query("ALTER TABLE languages AUTO_INCREMENT = ". (int)$auto_increment_id .";"); + database::query("ALTER TABLE ". DB_TABLE_PREFIX ."languages AUTO_INCREMENT = ". (int)$auto_increment_id .";"); } From 6926aa1fc082034717bc562434cdd94cd10ba4bf Mon Sep 17 00:00:00 2001 From: Wachhund Skytower Date: Tue, 7 Apr 2026 08:20:13 +0200 Subject: [PATCH 3/5] * Sync language test with LiteCart v3 - Use database::query()->fetch() instead of database::fetch() - Use f::array_update() and f::array_intersect_compare() - Verify delete via SQL query instead of entity reload - Fix DB_TABLE_PREFIX in all queries --- tests/language.php | 267 ++++++++++++++++++++++----------------------- 1 file changed, 130 insertions(+), 137 deletions(-) diff --git a/tests/language.php b/tests/language.php index 4b9d5c5..25a3f56 100644 --- a/tests/language.php +++ b/tests/language.php @@ -1,139 +1,132 @@ 'Spanish', - 'code' => 'es', - 'direction' => 'ltr', - 'locale' => 'es_ES', - 'url_type' => 'none', - 'domain_name' => 'example.es', - 'raw_date' => 'Y-m-d', - 'raw_time' => 'H:i:s', - 'raw_datetime' => 'Y-m-d H:i:s', - 'format_date' => 'd M Y', - 'format_time' => 'H:i', - 'format_datetime' => 'd M Y H:i', - 'decimal_point' => ',', - 'thousands_sep' => '.', - 'priority' => 1, - ]; - - ######################################################################## - ## Creating a new language - ######################################################################## - - // Create a new entity - $language = new ent_language(); - - // Set data - foreach ($data as $key => $value) { - $language->data[$key] = $value; - } - - // Save changes to database - $language->save(); - - // Check if the entity was created - if (!$language_id = $language->data['id']) { - throw new Exception('Failed to create language'); - } - - ######################################################################## - ## Load and update the language - ######################################################################## - - // Load the entity - $language = new ent_language($language_id); - - // Check if the language was loaded - if ($language->data['id'] != $language_id) { - throw new Exception('Failed to load language'); - } - - // Check if data was set correctly - foreach ($data as $key => $value) { - if ($language->data[$key] != $value) { - throw new Exception('The language data was not stored correctly ('. $key .')'); - } - } - - ######################################################################## - ## Updating the language - ######################################################################## - - // Prepare some new data - $data = [ - 'name' => 'French', - 'code' => 'fr', - 'direction' => 'rtl', - 'locale' => 'fr_FR', - 'url_type' => 'path', - 'domain_name' => 'example.fr', - 'raw_date' => 'd/m/Y', - 'raw_time' => 'H:i', - 'raw_datetime' => 'd/m/Y H:i', - 'format_date' => 'd F Y', - 'format_time' => 'H:i:s', - 'format_datetime' => 'd F Y H:i:s', - 'decimal_point' => ',', - 'thousands_sep' => ' ', - 'priority' => 2, - ]; - - // Update some data - foreach ($data as $key => $value) { - $language->data[$key] = $value; - } - - // Save changes to database - $language->save(); - - // Check if data was set correctly - foreach ($data as $key => $value) { - if ($language->data[$key] != $value) { - throw new Exception('The language data was not updated correctly ('. $key .')'); - } - } - - ######################################################################## - ## Deleting the language - ######################################################################## - - // Delete the entity - $language->delete(); - - // Check if the entity was deleted - $language = new ent_language($language_id); - if ($language->data['id'] == $language_id) { - throw new Exception('Failed to delete language'); - } - - echo ' Test passed successfully!' . PHP_EOL; - - return true; - - } catch (Exception $e) { - - echo 'Test failed: '. $e->getMessage(); - return false; - - } finally { - - // Rollback changes to the database - database::query("rollback;"); - - // Revert the auto increment ID - database::query("ALTER TABLE ". DB_TABLE_PREFIX ."languages AUTO_INCREMENT = ". (int)$auto_increment_id .";"); - } - + include_once __DIR__.'/../public_html/includes/app_header.inc.php'; + + try { + + // Start a MySQL transaction so we can rollback the test + database::query("start transaction;"); + + // Fetch the current auto increment ID + $auto_increment_id = database::query( + "SHOW TABLE STATUS LIKE '". DB_TABLE_PREFIX ."languages';" + )->fetch('Auto_increment'); + + // Prepare some example data + $data = [ + 'name' => 'Spanish', + 'code' => 'es', + 'direction' => 'ltr', + 'locale' => 'es_ES', + 'url_type' => 'none', + 'domain_name' => 'example.es', + 'raw_date' => 'Y-m-d', + 'raw_time' => 'H:i:s', + 'raw_datetime' => 'Y-m-d H:i:s', + 'format_date' => 'd M Y', + 'format_time' => 'H:i', + 'format_datetime' => 'd M Y H:i', + 'decimal_point' => ',', + 'thousands_sep' => '.', + 'priority' => 1, + ]; + + ######################################################################## + ## Creating a new language + ######################################################################## + + // Create a new entity + $language = new ent_language(); + $language->data = f::array_update($language->data, $data); + $language->save(); + + // Check if the entity was created + if (!$language_id = $language->data['id']) { + throw new Exception('Failed to create language'); + } + + ######################################################################## + ## Load and check the language + ######################################################################## + + // Load the entity + $language = new ent_language($language_id); + + // Check if the language was loaded + if ($language->data['id'] != $language_id) { + throw new Exception('Failed to load language'); + } + + // Check if data was set correctly + if (!f::array_intersect_compare($data, $language->data)) { + throw new Exception('The language data was not stored correctly'); + } + + ######################################################################## + ## Updating the language + ######################################################################## + + // Prepare some new data + $data = [ + 'name' => 'French', + 'code' => 'fr', + 'direction' => 'rtl', + 'locale' => 'fr_FR', + 'url_type' => 'path', + 'domain_name' => 'example.fr', + 'raw_date' => 'd/m/Y', + 'raw_time' => 'H:i', + 'raw_datetime' => 'd/m/Y H:i', + 'format_date' => 'd F Y', + 'format_time' => 'H:i:s', + 'format_datetime' => 'd F Y H:i:s', + 'decimal_point' => ',', + 'thousands_sep' => ' ', + 'priority' => 2, + ]; + + // Update some data + $language->data = f::array_update($language->data, $data); + + // Save changes to database + $language->save(); + + // Check if data was set correctly + if (!f::array_intersect_compare($data, $language->data)) { + throw new Exception('The language data was not updated correctly'); + } + + ######################################################################## + ## Deleting the language + ######################################################################## + + // Delete the entity + $language->delete(); + + // Check if the entity was deleted + if (database::query( + "select id from ". DB_TABLE_PREFIX ."languages + where id = ". (int)$language_id ." + limit 1;" + )->num_rows) { + throw new Exception('Failed to delete language'); + } + + return true; + + } catch (Exception $e) { + + echo ' [Failed]'. PHP_EOL . 'Error: '. $e->getMessage(); + return false; + + } finally { + + // Rollback changes to the database + database::query("rollback;"); + + // Revert the auto increment ID + database::query( + "ALTER TABLE ". DB_TABLE_PREFIX ."languages + AUTO_INCREMENT = ". (int)$auto_increment_id .";" + ); + } From c87a6bea5f01fa4f9004a8225ad3d26b20d0c1c1 Mon Sep 17 00:00:00 2001 From: Wachhund Skytower Date: Tue, 7 Apr 2026 08:25:01 +0200 Subject: [PATCH 4/5] * Import seed data in CI setup MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Enable data.sql import in setup_database.php — provides languages, settings, and translations needed by func_csv, nod_settings, and other tests that depend on a populated database. --- .github/ci/setup_database.php | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/.github/ci/setup_database.php b/.github/ci/setup_database.php index dcf49b6..9d87f81 100644 --- a/.github/ci/setup_database.php +++ b/.github/ci/setup_database.php @@ -73,8 +73,12 @@ echo count($structure['tables']) . ' tables created' . PHP_EOL; - // Skip data.sql import — it targets the old SQL schema. - // Tests create their own fixtures via entity APIs. + // Import seed data + $data = file_get_contents(__DIR__ . '/../../install/data.sql'); + $data = str_replace('`lc_', '`' . $prefix, $data); + $link->multi_query($data); + while ($link->next_result()) {} + echo 'Seed data imported' . PHP_EOL; // Create admin user $hash = password_hash('admin123456', PASSWORD_DEFAULT); From a101c730b713baf60ed6f9b7afa0c8fe69b391b1 Mon Sep 17 00:00:00 2001 From: Wachhund Skytower Date: Tue, 7 Apr 2026 08:29:22 +0200 Subject: [PATCH 5/5] * Fix seed data and settings test for current schema - data.sql: Remove type/datatype columns from lc_settings INSERT (not in structure.json), remove duplicate admin INSERT, set site_name default to 'LiteCore', use empty string instead of CURRENT_TIMESTAMP() for jobs timestamps - nod_settings test: Use site_name/default_language_code instead of store_name/store_currency_code (LiteCore naming) --- install/data.sql | 95 ++++++++++++++++++++---------------------- tests/nod_settings.php | 82 ++++++++++++++++++------------------ 2 files changed, 87 insertions(+), 90 deletions(-) diff --git a/install/data.sql b/install/data.sql index b2048a9..24e48e3 100644 --- a/install/data.sql +++ b/install/data.sql @@ -1,6 +1,3 @@ -INSERT INTO `lc_administrators` (`id`, `status`, `username`, `email`, `password_hash`, `apps`, `widgets`, `last_ip_address`, `last_hostname`, `login_attempts`, `total_logins`, `valid_from`, `valid_to`, `last_active`, `last_login`, `updated_at`, `created_at`) -VALUES (1, 1, 'admin', '', '$2y$10$iCjIIJh4rcNiOe2fRxE.Dej65HjwrTzRSe5YYaoibX.vIY/ngkUM.', '[]', '', '127.0.0.1', '', 0, 0, NULL, NULL, NULL, NULL, CURRENT_TIMESTAMP, CURRENT_TIMESTAMP); --- ----- INSERT INTO `lc_languages` (`status`, `code`, `code2`, `name`, `locale`, `url_type`, `raw_date`, `raw_time`, `raw_datetime`, `format_date`, `format_time`, `format_datetime`, `decimal_point`, `thousands_sep`, `priority`) VALUES (1, 'en', 'eng', 'English', 'en_US.utf8,en_US.UTF-8,english', 'none', 'm/d/y', 'h:i:s A', 'm/d/y h:i:s A', '%b %e %Y', '%I:%M %p', '%b %e %Y %I:%M %p', '.', ',', 0); -- ----- @@ -18,49 +15,49 @@ INSERT INTO `lc_settings_groups` (`key`, `name`, `description`, `priority`) VALU ('images', 'Images', 'Settings for graphical elements', 80), ('advanced', 'Advanced', 'Advanced settings', 100); -- ----- -INSERT INTO `lc_settings` (`group_key`, `type`, `title`, `description`, `key`, `value`, `datatype`, `function`, `required`, `priority`) VALUES -('', 'global', 'Platform Database Version', 'The platform version of the database', 'platform_database_version', '1.0.0', 'string', '', 0, 0), -('site_info', 'global', 'Site Name', 'The name of your site.', 'site_name', '', 'string', 'text()', 1, 10), -('site_info', 'global', 'Site Email', 'The site\'s email address.', 'site_email', '', 'string', 'email()', 1, 11), -('site_info', 'global', 'Site Phone', 'The site\'s phone number.', 'site_phone', '', 'string', 'phone()', 1, 11), -('site_info', 'global', 'Site Time Zone', 'The site\'s time zone.', 'site_timezone', '', 'string', 'timezone()', 1, 19), -('site_info', 'local', 'Site Language', 'The spoken language of your organization.', 'site_language_code', 'en', 'string', 'language()', 1, 20), -('defaults', 'global', 'Default Language', 'The default language, if not identified.', 'default_language_code', 'en', 'string', 'language()', 1, 10), -('email', 'local', 'Send Emails', 'Whether or not the platform should deliver outgoing emails.', 'email_status', '1', 'boolean', 'toggle("y/n")', 0, 1), -('email', 'local', 'SMTP Enabled', 'Whether or not to use an SMTP server for delivering email.', 'smtp_status', '0', 'boolean', 'toggle("e/d")', 0, 10), -('email', 'local', 'SMTP Host', 'SMTP hostname, e.g. smtp.myprovider.com.', 'smtp_host', 'localhost', 'string', 'text()', 0, 11), -('email', 'local', 'SMTP Port', 'SMTP port, e.g. 25, 465 (SSL/TLS), or 587 (STARTTLS).', 'smtp_port', '25', 'number', 'number()', 0, 12), -('email', 'local', 'SMTP Username', 'Username for SMTP authentication.', 'smtp_username', '', 'string', 'text()', 0, 13), -('email', 'local', 'SMTP Password', 'Password for SMTP authentication.', 'smtp_password', '', 'string', 'password()', 0, 14), -('listings', 'global', 'Maintenance Mode', 'Maintenance mode will enable a slash screen that the site is down for maintenance.', 'maintenance_mode', '0', 'boolean', 'toggle("e/d")', 0, 2), -('listings', 'global', 'Important Notice', 'An important notice to be displayed above your website.', 'important_notice', '', 'boolean', 'regional_text()', 1, 0), -('listings', 'local', 'Items Per Page', 'The number of items to be displayed per page.', 'items_per_page', '20', 'number', 'number()', 0, 10), -('listings', 'local', 'Data Table Rows', 'The number of data table rows to be displayed per page.', 'data_table_rows_per_page', '25', 'number', 'text()', 0, 11), -('legal', 'global', 'Cookie Policy', 'Select a page for the cookie policy or leave blank to disable.', 'cookie_policy', '', 'number', 'page()', 0, 10), -('legal', 'local', 'Privacy Policy', 'Select a page for the privacy policy consent or leave blank to disable.', 'privacy_policy', '', 'number', 'page()', 0, 11), -('images', 'global', 'Clear Thumbnails Cache', 'Remove all cached image thumbnails from disk.', 'cache_clear_thumbnails', '0', 'boolean', 'toggle()', 0, 1), -('images', 'local', 'Downsample', 'Downsample large uploaded images to best fit within the given dimensions of "width,height" or leave empty. Default: 2048,2048', 'image_downsample_size', '2048,2048', 'string', 'text()', 0, 34), -('images', 'local', 'Image Quality', 'The JPEG quality for uploaded images (0-100). Default: 90', 'image_quality', '90', 'number', 'number()', 0, 40), -('images', 'local', 'Thumbnail Quality', 'The JPEG quality for thumbnail images (0-100). Default: 65', 'image_thumbnail_quality', '65', 'number', 'number()', 0, 41), -('images', 'local', 'Interlaced Thumbnails', 'Generate interlaced thumbnail images for progressive loading. Increases the filesize by 10-20% but improves user experience.', 'image_thumbnail_interlaced', '0', 'boolean', 'toggle()', 0, 42), -('images', 'local', 'Whitespace Color', 'Set the color of any generated whitespace to the given RGB value. Default: 255,255,255', 'image_whitespace_color', '255,255,255', 'string', 'text()', 0, 43), -('images', 'local', 'AVIF Enabled', 'Use AVIF images if supported by the browser.', 'avif_enabled', '0', 'boolean', 'toggle("e/d")', 0, 44), -('images', 'local', 'WebP Enabled', 'Use WebP images if supported by the browser.', 'webp_enabled', '0', 'boolean', 'toggle("e/d")', 0, 45), -('advanced', 'global', 'GZIP Enabled', 'Compresses browser data. Increases the load on the server but decreases the bandwidth.', 'gzip_enabled', '1', 'boolean', 'toggle("e/d")', 5, 0), -('advanced', 'global', 'System Cache Enabled', 'Enables the system cache module which caches frequently used data.', 'cache_enabled', '1', 'boolean', 'toggle("e/d")', 0, 10), -('advanced', 'global', 'Clear System Cache', 'Remove all cached system information.', 'cache_clear', '0', 'boolean', 'toggle("y/n")', 0, 11), -('advanced', 'global', 'Static Content Domain Name', 'Use the given alias domain name for static content (images, stylesheets, javascripts) e.g. https://static.domain.tld/', 'static_domain', '', 'string', 'url()', 0, 12), -('advanced', 'local', 'Control Panel Link', 'The URL to your control panel, e.g. cPanel.', 'control_panel_link', 'https://', 'string', 'url()', 0, 18), -('advanced', 'local', 'Database Admin Link', 'The URL to your database manager, e.g. phpMyAdmin.', 'database_admin_link', 'https://', 'string', 'url()', 0, 19), -('advanced', 'local', 'Webmail Link', 'The URL to your webmail client.', 'webmail_link', 'https://', 'string', 'url()', 0, 20), -('security', 'local', 'CAPTCHA', 'Prevent robots from posting form data by enabling CAPTCHA security.', 'captcha_enabled', '1', 'boolean', 'toggle("e/d")', 0, 16), -('social_media', 'global', 'Facebook Link', 'The link to your Facebook page.', 'facebook_link', '#', 'string', 'url()', 0, 10), -('social_media', 'global', 'Instagram Link', 'The link to your Instagram page.', 'instagram_link', '#', 'string', 'url()', 0, 20), -('social_media', 'global', 'LinkedIn Link', 'The link to your LinkedIn page.', 'linkedin_link', '#', 'string', 'url()', 0, 30), -('social_media', 'global', 'Pinterest Link', 'The link to your Pinterest page.', 'pinterest_link', '#', 'string', 'url()', 0, 40), -('social_media', 'global', 'Twitter Link', 'The link to your Twitter page.', 'twitter_link', '#', 'string', 'url()', 0, 50), -('social_media', 'global', 'YouTube Link', 'The link to your YouTube channel.', 'youtube_link', '#', 'string', 'url()', 0, 60), -('', 'global', 'Template', '', 'template', 'default', 'string', 'template()', 1, 0), -('', 'global', 'Template Settings', '', 'template_settings', '{}', 'string', 'text()', 0, 0), -('', 'global', 'Jobs Last Run', 'Time when background jobs were last ran.', 'jobs_last_run', CURRENT_TIMESTAMP(), 'string', 'text()', 0, 0), -('', 'global', 'Jobs Last Push', 'Time when background jobs were last pushed for execution.', 'jobs_last_push', CURRENT_TIMESTAMP(), 'string', 'text()', 0, 0); +INSERT INTO `lc_settings` (`group_key`, `title`, `description`, `key`, `value`, `function`, `required`, `priority`) VALUES +('', 'Platform Database Version', 'The platform version of the database', 'platform_database_version', '1.0.0', '', 0, 0), +('site_info', 'Site Name', 'The name of your site.', 'site_name', 'LiteCore', 'text()', 1, 10), +('site_info', 'Site Email', 'The site\'s email address.', 'site_email', 'test@example.com', 'email()', 1, 11), +('site_info', 'Site Phone', 'The site\'s phone number.', 'site_phone', '', 'phone()', 1, 11), +('site_info', 'Site Time Zone', 'The site\'s time zone.', 'site_timezone', 'Europe/London', 'timezone()', 1, 19), +('site_info', 'Site Language', 'The spoken language of your organization.', 'site_language_code', 'en', 'language()', 1, 20), +('defaults', 'Default Language', 'The default language, if not identified.', 'default_language_code', 'en', 'language()', 1, 10), +('email', 'Send Emails', 'Whether or not the platform should deliver outgoing emails.', 'email_status', '1', 'toggle("y/n")', 0, 1), +('email', 'SMTP Enabled', 'Whether or not to use an SMTP server for delivering email.', 'smtp_status', '0', 'toggle("e/d")', 0, 10), +('email', 'SMTP Host', 'SMTP hostname, e.g. smtp.myprovider.com.', 'smtp_host', 'localhost', 'text()', 0, 11), +('email', 'SMTP Port', 'SMTP port, e.g. 25, 465 (SSL/TLS), or 587 (STARTTLS).', 'smtp_port', '25', 'number()', 0, 12), +('email', 'SMTP Username', 'Username for SMTP authentication.', 'smtp_username', '', 'text()', 0, 13), +('email', 'SMTP Password', 'Password for SMTP authentication.', 'smtp_password', '', 'password()', 0, 14), +('listings', 'Maintenance Mode', 'Maintenance mode will enable a slash screen that the site is down for maintenance.', 'maintenance_mode', '0', 'toggle("e/d")', 0, 2), +('listings', 'Important Notice', 'An important notice to be displayed above your website.', 'important_notice', '', 'regional_text()', 1, 0), +('listings', 'Items Per Page', 'The number of items to be displayed per page.', 'items_per_page', '20', 'number()', 0, 10), +('listings', 'Data Table Rows', 'The number of data table rows to be displayed per page.', 'data_table_rows_per_page', '25', 'text()', 0, 11), +('legal', 'Cookie Policy', 'Select a page for the cookie policy or leave blank to disable.', 'cookie_policy', '', 'page()', 0, 10), +('legal', 'Privacy Policy', 'Select a page for the privacy policy consent or leave blank to disable.', 'privacy_policy', '', 'page()', 0, 11), +('images', 'Clear Thumbnails Cache', 'Remove all cached image thumbnails from disk.', 'cache_clear_thumbnails', '0', 'toggle()', 0, 1), +('images', 'Downsample', 'Downsample large uploaded images to best fit within the given dimensions of "width,height" or leave empty. Default: 2048,2048', 'image_downsample_size', '2048,2048', 'text()', 0, 34), +('images', 'Image Quality', 'The JPEG quality for uploaded images (0-100). Default: 90', 'image_quality', '90', 'number()', 0, 40), +('images', 'Thumbnail Quality', 'The JPEG quality for thumbnail images (0-100). Default: 65', 'image_thumbnail_quality', '65', 'number()', 0, 41), +('images', 'Interlaced Thumbnails', 'Generate interlaced thumbnail images for progressive loading. Increases the filesize by 10-20% but improves user experience.', 'image_thumbnail_interlaced', '0', 'toggle()', 0, 42), +('images', 'Whitespace Color', 'Set the color of any generated whitespace to the given RGB value. Default: 255,255,255', 'image_whitespace_color', '255,255,255', 'text()', 0, 43), +('images', 'AVIF Enabled', 'Use AVIF images if supported by the browser.', 'avif_enabled', '0', 'toggle("e/d")', 0, 44), +('images', 'WebP Enabled', 'Use WebP images if supported by the browser.', 'webp_enabled', '0', 'toggle("e/d")', 0, 45), +('advanced', 'GZIP Enabled', 'Compresses browser data. Increases the load on the server but decreases the bandwidth.', 'gzip_enabled', '1', 'toggle("e/d")', 5, 0), +('advanced', 'System Cache Enabled', 'Enables the system cache module which caches frequently used data.', 'cache_enabled', '1', 'toggle("e/d")', 0, 10), +('advanced', 'Clear System Cache', 'Remove all cached system information.', 'cache_clear', '0', 'toggle("y/n")', 0, 11), +('advanced', 'Static Content Domain Name', 'Use the given alias domain name for static content (images, stylesheets, javascripts) e.g. https://static.domain.tld/', 'static_domain', '', 'url()', 0, 12), +('advanced', 'Control Panel Link', 'The URL to your control panel, e.g. cPanel.', 'control_panel_link', 'https://', 'url()', 0, 18), +('advanced', 'Database Admin Link', 'The URL to your database manager, e.g. phpMyAdmin.', 'database_admin_link', 'https://', 'url()', 0, 19), +('advanced', 'Webmail Link', 'The URL to your webmail client.', 'webmail_link', 'https://', 'url()', 0, 20), +('', 'CAPTCHA', 'Prevent robots from posting form data by enabling CAPTCHA security.', 'captcha_enabled', '1', 'toggle("e/d")', 0, 16), +('social_media', 'Facebook Link', 'The link to your Facebook page.', 'facebook_link', '#', 'url()', 0, 10), +('social_media', 'Instagram Link', 'The link to your Instagram page.', 'instagram_link', '#', 'url()', 0, 20), +('social_media', 'LinkedIn Link', 'The link to your LinkedIn page.', 'linkedin_link', '#', 'url()', 0, 30), +('social_media', 'Pinterest Link', 'The link to your Pinterest page.', 'pinterest_link', '#', 'url()', 0, 40), +('social_media', 'Twitter Link', 'The link to your Twitter page.', 'twitter_link', '#', 'url()', 0, 50), +('social_media', 'YouTube Link', 'The link to your YouTube channel.', 'youtube_link', '#', 'url()', 0, 60), +('', 'Template', '', 'template', 'default', 'template()', 1, 0), +('', 'Template Settings', '', 'template_settings', '{}', 'text()', 0, 0), +('', 'Jobs Last Run', 'Time when background jobs were last ran.', 'jobs_last_run', '', 'text()', 0, 0), +('', 'Jobs Last Push', 'Time when background jobs were last pushed for execution.', 'jobs_last_push', '', 'text()', 0, 0); diff --git a/tests/nod_settings.php b/tests/nod_settings.php index 566a0ae..1db628f 100644 --- a/tests/nod_settings.php +++ b/tests/nod_settings.php @@ -1,61 +1,61 @@ getMessage(); - return false; - } + echo ' [Failed]'. PHP_EOL . 'Error: '. $e->getMessage(); + return false; + }