@@ -47,6 +47,7 @@ Thumbs.db
|
||||
|
||||
/coverage/
|
||||
/build/
|
||||
/build.tar.gz
|
||||
/builds
|
||||
/phpunit.xml.cache
|
||||
/.phpunit.result.cache
|
||||
|
||||
@@ -62,7 +62,7 @@ Problems with it can be raised on our forum, or as issues in the main repository
|
||||
|
||||
## Server Requirements
|
||||
|
||||
PHP version 8.1 or higher is required, with the following extensions installed:
|
||||
PHP version 8.5 or higher is required, with the following extensions installed:
|
||||
|
||||
- [intl](http://php.net/manual/en/intl.requirements.php)
|
||||
- [mbstring](http://php.net/manual/en/mbstring.installation.php)
|
||||
@@ -71,7 +71,7 @@ PHP version 8.1 or higher is required, with the following extensions installed:
|
||||
> - The end of life date for PHP 7.4 was November 28, 2022.
|
||||
> - The end of life date for PHP 8.0 was November 26, 2023.
|
||||
> - If you are still using PHP 7.4 or 8.0, you should upgrade immediately.
|
||||
> - The end of life date for PHP 8.1 will be December 31, 2025.
|
||||
> - The end of life date for PHP 8.1 was December 31, 2025.
|
||||
|
||||
Additionally, make sure that the following extensions are enabled in your PHP:
|
||||
|
||||
|
||||
-32
@@ -1,32 +0,0 @@
|
||||
Al Rahma Sunday School — API Docs Pack
|
||||
=================================================
|
||||
Generated: 2025-11-07T21:01:43.815073
|
||||
|
||||
WHAT'S INCLUDED
|
||||
---------------
|
||||
- app/Controllers/Api/*.php (API controllers)
|
||||
- app/Controllers/{DocsController, ApiDocsController}
|
||||
- app/Filters/ApiDocsAuthFilter.php
|
||||
- app/Views/docs/{index.php, swagger_ui.php, swagger_ui_public.php}
|
||||
- public/docs/openapi/*.yaml (separate specs + master.yaml)
|
||||
- ROUTES_SNIPPET.php (add to app/Config/Routes.php)
|
||||
- FILTERS_SNIPPET.php (add alias to app/Config/Filters.php)
|
||||
|
||||
INSTALL
|
||||
-------
|
||||
1) Unzip into your project root.
|
||||
2) Merge routes:
|
||||
- Open app/Config/Routes.php and paste the contents of ROUTES_SNIPPET.php
|
||||
3) Add filter alias:
|
||||
- Open app/Config/Filters.php and add the 'apiDocsAuth' alias from FILTERS_SNIPPET.php
|
||||
4) Ensure your Models exist and match the controller expectations.
|
||||
5) Set JWT secret in .env:
|
||||
JWT_SECRET=your_super_secret_key
|
||||
6) Visit:
|
||||
- /docs (landing)
|
||||
- /docs/api (secured, admin only)
|
||||
- /docs/api/public (public read-only)
|
||||
7) API base path:
|
||||
- /api/v1/... (see YAMLs for full paths)
|
||||
|
||||
Enjoy!
|
||||
@@ -47,28 +47,63 @@ class ContentSecurityPolicy extends BaseConfig
|
||||
*
|
||||
* @var list<string>|string|null
|
||||
*/
|
||||
public $defaultSrc;
|
||||
public $defaultSrc = 'self';
|
||||
|
||||
/**
|
||||
* Lists allowed scripts' URLs.
|
||||
*
|
||||
* @var list<string>|string
|
||||
*/
|
||||
public $scriptSrc = 'self';
|
||||
public $scriptSrc = [
|
||||
'self',
|
||||
'unsafe-inline',
|
||||
'https://cdn.jsdelivr.net',
|
||||
'https://code.jquery.com',
|
||||
'https://cdnjs.cloudflare.com',
|
||||
];
|
||||
|
||||
/**
|
||||
* Lists allowed stylesheets' URLs.
|
||||
*
|
||||
* @var list<string>|string
|
||||
*/
|
||||
public $styleSrc = 'self';
|
||||
public $styleSrc = [
|
||||
'self',
|
||||
'unsafe-inline',
|
||||
'https://cdn.jsdelivr.net',
|
||||
'https://fonts.googleapis.com',
|
||||
'https://cdnjs.cloudflare.com',
|
||||
];
|
||||
|
||||
/**
|
||||
* Lists allowed stylesheet element URLs.
|
||||
*
|
||||
* @var list<string>|string
|
||||
*/
|
||||
public $styleSrcElem = [
|
||||
'self',
|
||||
'unsafe-inline',
|
||||
'https://cdn.jsdelivr.net',
|
||||
'https://fonts.googleapis.com',
|
||||
'https://cdnjs.cloudflare.com',
|
||||
];
|
||||
|
||||
/**
|
||||
* Allows inline style attributes used by legacy views.
|
||||
*
|
||||
* @var list<string>|string
|
||||
*/
|
||||
public $styleSrcAttr = [
|
||||
'self',
|
||||
'unsafe-inline',
|
||||
];
|
||||
|
||||
/**
|
||||
* Defines the origins from which images can be loaded.
|
||||
*
|
||||
* @var list<string>|string
|
||||
*/
|
||||
public $imageSrc = 'self';
|
||||
public $imageSrc = ['self', 'data:', 'https:'];
|
||||
|
||||
/**
|
||||
* Restricts the URLs that can appear in a page's `<base>` element.
|
||||
@@ -99,7 +134,13 @@ class ContentSecurityPolicy extends BaseConfig
|
||||
*
|
||||
* @var list<string>|string
|
||||
*/
|
||||
public $fontSrc;
|
||||
public $fontSrc = [
|
||||
'self',
|
||||
'data:',
|
||||
'https://fonts.gstatic.com',
|
||||
'https://cdn.jsdelivr.net',
|
||||
'https://cdnjs.cloudflare.com',
|
||||
];
|
||||
|
||||
/**
|
||||
* Lists valid endpoints for submission from `<form>` tags.
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<meta http-equiv="Content-Security-Policy"
|
||||
content="default-src 'self' https://cdnjs.cloudflare.com; connect-src 'self' http://localhost:8080 <?= base_url() ?> https://cdnjs.cloudflare.com; script-src 'self' https://cdnjs.cloudflare.com; style-src 'self' 'unsafe-inline' https://cdnjs.cloudflare.com; img-src 'self' data:; font-src https://cdnjs.cloudflare.com;" />
|
||||
content="default-src 'self'; connect-src 'self' http://localhost:8080 <?= base_url() ?> https://cdnjs.cloudflare.com; script-src 'self' 'unsafe-inline' https://cdn.jsdelivr.net https://cdnjs.cloudflare.com; style-src 'self' 'unsafe-inline' https://cdn.jsdelivr.net https://fonts.googleapis.com https://cdnjs.cloudflare.com; img-src 'self' data:; font-src 'self' https://fonts.gstatic.com https://cdnjs.cloudflare.com;" />
|
||||
<title>Al Rahma Sunday School — API Docs</title>
|
||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/swagger-ui/5.10.5/swagger-ui.min.css" />
|
||||
<style>
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
<meta content="" name="description">
|
||||
|
||||
<!-- Bootstrap CSS -->
|
||||
<link href="<?= base_url('assets/boot_css/bootstrap.min.css') ?>" rel="stylesheet">
|
||||
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.2.3/dist/css/bootstrap.min.css" rel="stylesheet">
|
||||
|
||||
<!-- Google Web Fonts -->
|
||||
|
||||
Executable
+709
@@ -0,0 +1,709 @@
|
||||
#!/usr/bin/env bash
|
||||
#
|
||||
# build_production.sh
|
||||
#
|
||||
# Build a deployable CodeIgniter 4 production directory from a complete
|
||||
# project checkout. The script deliberately refuses incomplete projects,
|
||||
# development Composer dependencies, and non-production environment files.
|
||||
#
|
||||
# Usage:
|
||||
# ./build_production.sh
|
||||
#
|
||||
# Optional:
|
||||
# --skip-composer Use the existing vendor directory. The script rejects
|
||||
# known development packages such as PHPUnit.
|
||||
# --skip-lint Skip PHP syntax checks.
|
||||
# --no-archive Do not create alrahma.zip.
|
||||
# --no-force Refuse to replace an existing output directory.
|
||||
# --help Show help.
|
||||
#
|
||||
# The web-server document root must be <production-folder>/public.
|
||||
#
|
||||
|
||||
set -Eeuo pipefail
|
||||
IFS=$'\n\t'
|
||||
umask 027
|
||||
|
||||
SCRIPT_NAME="$(basename "$0")"
|
||||
SOURCE_DIR="$(pwd)"
|
||||
OUTPUT_DIR="$(pwd)/build"
|
||||
ENV_FILE="$(pwd)/env.production"
|
||||
SKIP_COMPOSER=0
|
||||
SKIP_LINT=0
|
||||
MAKE_ARCHIVE=1
|
||||
FORCE=1
|
||||
STAGE_ROOT=""
|
||||
|
||||
log() {
|
||||
printf '[%s] %s\n' "$SCRIPT_NAME" "$*"
|
||||
}
|
||||
|
||||
warn() {
|
||||
printf '[%s] WARNING: %s\n' "$SCRIPT_NAME" "$*" >&2
|
||||
}
|
||||
|
||||
die() {
|
||||
printf '[%s] ERROR: %s\n' "$SCRIPT_NAME" "$*" >&2
|
||||
exit 1
|
||||
}
|
||||
|
||||
usage() {
|
||||
cat <<'USAGE'
|
||||
Build a CodeIgniter 4 production directory.
|
||||
|
||||
Default:
|
||||
./build_production.sh builds the current project into ./build, copies
|
||||
./env.production as .env, replaces any existing ./build directory, and
|
||||
creates ./alrahma.zip.
|
||||
|
||||
Options:
|
||||
--source DIR Complete project root. Default: current directory.
|
||||
--output DIR Destination. Default: ./build.
|
||||
--env FILE Production environment file. Default: ./env.production.
|
||||
--skip-composer Keep the existing vendor directory instead of running
|
||||
Composer. Known development dependencies are rejected.
|
||||
--skip-lint Skip PHP syntax checks.
|
||||
--archive Create ./alrahma.zip after building. Enabled by
|
||||
default. The archive contains .env and must be
|
||||
protected.
|
||||
--no-archive Do not create ./alrahma.zip.
|
||||
--force Replace an existing output directory. Enabled by
|
||||
default.
|
||||
--no-force Refuse to replace an existing output directory.
|
||||
-h, --help Show this help.
|
||||
|
||||
Example:
|
||||
./build_production.sh
|
||||
USAGE
|
||||
}
|
||||
|
||||
cleanup() {
|
||||
if [[ -n "${STAGE_ROOT:-}" && -d "$STAGE_ROOT" ]]; then
|
||||
rm -rf -- "$STAGE_ROOT"
|
||||
fi
|
||||
}
|
||||
trap cleanup EXIT
|
||||
trap 'die "Build failed near line $LINENO."' ERR
|
||||
|
||||
while [[ $# -gt 0 ]]; do
|
||||
case "$1" in
|
||||
--source)
|
||||
[[ $# -ge 2 ]] || die "--source requires a directory."
|
||||
SOURCE_DIR="$2"
|
||||
shift 2
|
||||
;;
|
||||
--output)
|
||||
[[ $# -ge 2 ]] || die "--output requires a directory."
|
||||
OUTPUT_DIR="$2"
|
||||
shift 2
|
||||
;;
|
||||
--env)
|
||||
[[ $# -ge 2 ]] || die "--env requires a file."
|
||||
ENV_FILE="$2"
|
||||
shift 2
|
||||
;;
|
||||
--skip-composer)
|
||||
SKIP_COMPOSER=1
|
||||
shift
|
||||
;;
|
||||
--skip-lint)
|
||||
SKIP_LINT=1
|
||||
shift
|
||||
;;
|
||||
--archive)
|
||||
MAKE_ARCHIVE=1
|
||||
shift
|
||||
;;
|
||||
--no-archive)
|
||||
MAKE_ARCHIVE=0
|
||||
shift
|
||||
;;
|
||||
--force)
|
||||
FORCE=1
|
||||
shift
|
||||
;;
|
||||
--no-force)
|
||||
FORCE=0
|
||||
shift
|
||||
;;
|
||||
-h|--help)
|
||||
usage
|
||||
exit 0
|
||||
;;
|
||||
*)
|
||||
die "Unknown option: $1"
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
for command_name in php rsync find awk sed grep sort xargs sha256sum zip; do
|
||||
command -v "$command_name" >/dev/null 2>&1 \
|
||||
|| die "Required command is missing: $command_name"
|
||||
done
|
||||
|
||||
[[ -d "$SOURCE_DIR" ]] || die "Source directory does not exist: $SOURCE_DIR"
|
||||
SOURCE_DIR="$(cd "$SOURCE_DIR" && pwd -P)"
|
||||
|
||||
OUTPUT_PARENT="$(dirname "$OUTPUT_DIR")"
|
||||
mkdir -p -- "$OUTPUT_PARENT"
|
||||
OUTPUT_PARENT="$(cd "$OUTPUT_PARENT" && pwd -P)"
|
||||
OUTPUT_DIR="$OUTPUT_PARENT/$(basename "$OUTPUT_DIR")"
|
||||
|
||||
[[ "$OUTPUT_DIR" != "/" ]] || die "Refusing to use / as the output directory."
|
||||
[[ "$OUTPUT_DIR" != "$SOURCE_DIR" ]] \
|
||||
|| die "Output directory must not be the project root."
|
||||
|
||||
[[ -n "$ENV_FILE" ]] \
|
||||
|| die "Pass a production environment file with --env."
|
||||
[[ -f "$ENV_FILE" ]] \
|
||||
|| die "Environment file does not exist: $ENV_FILE"
|
||||
ENV_FILE="$(cd "$(dirname "$ENV_FILE")" && pwd -P)/$(basename "$ENV_FILE")"
|
||||
|
||||
required_paths=(
|
||||
"app"
|
||||
"app/Config/Paths.php"
|
||||
"public"
|
||||
"public/index.php"
|
||||
"writable"
|
||||
"spark"
|
||||
"composer.json"
|
||||
"composer.lock"
|
||||
)
|
||||
|
||||
missing_paths=()
|
||||
for required_path in "${required_paths[@]}"; do
|
||||
if [[ ! -e "$SOURCE_DIR/$required_path" ]]; then
|
||||
missing_paths+=("$required_path")
|
||||
fi
|
||||
done
|
||||
|
||||
if (( ${#missing_paths[@]} > 0 )); then
|
||||
printf '[%s] Missing required project items:\n' "$SCRIPT_NAME" >&2
|
||||
printf ' - %s\n' "${missing_paths[@]}" >&2
|
||||
die "The source is not a complete deployable CodeIgniter project."
|
||||
fi
|
||||
|
||||
[[ -f "$SOURCE_DIR/vendor/autoload.php" || "$SKIP_COMPOSER" -eq 0 ]] \
|
||||
|| die "--skip-composer requires an existing vendor/autoload.php."
|
||||
|
||||
if [[ -e "$OUTPUT_DIR" ]]; then
|
||||
(( FORCE == 1 )) \
|
||||
|| die "Output already exists. Use --force to replace it: $OUTPUT_DIR"
|
||||
log "Removing existing output: $OUTPUT_DIR"
|
||||
rm -rf -- "$OUTPUT_DIR"
|
||||
fi
|
||||
|
||||
env_value() {
|
||||
local key="$1"
|
||||
local file="$2"
|
||||
|
||||
awk -F '=' -v wanted="$key" '
|
||||
/^[[:space:]]*[#;]/ { next }
|
||||
NF < 2 { next }
|
||||
{
|
||||
key = $1
|
||||
gsub(/^[[:space:]]+|[[:space:]]+$/, "", key)
|
||||
if (key != wanted) {
|
||||
next
|
||||
}
|
||||
|
||||
value = substr($0, index($0, "=") + 1)
|
||||
sub(/[[:space:]]+[;#].*$/, "", value)
|
||||
gsub(/^[[:space:]]+|[[:space:]]+$/, "", value)
|
||||
gsub(/^["'\'']|["'\'']$/, "", value)
|
||||
print value
|
||||
exit
|
||||
}
|
||||
' "$file"
|
||||
}
|
||||
|
||||
CI_ENVIRONMENT_VALUE="$(env_value "CI_ENVIRONMENT" "$ENV_FILE" || true)"
|
||||
CI_ENVIRONMENT_VALUE="$(printf '%s' "$CI_ENVIRONMENT_VALUE" | tr '[:upper:]' '[:lower:]')"
|
||||
|
||||
[[ "$CI_ENVIRONMENT_VALUE" == "production" ]] \
|
||||
|| die "The supplied environment file must set CI_ENVIRONMENT = production."
|
||||
|
||||
for recommended_key in \
|
||||
"app.baseURL" \
|
||||
"database.default.hostname" \
|
||||
"database.default.database" \
|
||||
"database.default.username"
|
||||
do
|
||||
if [[ -z "$(env_value "$recommended_key" "$ENV_FILE" || true)" ]]; then
|
||||
warn "Production environment does not define '$recommended_key'. Verify that it is injected another way."
|
||||
fi
|
||||
done
|
||||
|
||||
STAGE_ROOT="$(mktemp -d "${TMPDIR:-/tmp}/ci4-production-build.XXXXXXXX")"
|
||||
STAGE_DIR="$STAGE_ROOT/release"
|
||||
mkdir -p -- "$STAGE_DIR"
|
||||
|
||||
log "Copying application files into a clean staging directory."
|
||||
|
||||
copy_production_path() {
|
||||
local relative_path="$1"
|
||||
local source_path="$SOURCE_DIR/$relative_path"
|
||||
local destination_path="$STAGE_DIR/$relative_path"
|
||||
|
||||
[[ -e "$source_path" ]] || return 0
|
||||
|
||||
mkdir -p -- "$(dirname "$destination_path")"
|
||||
|
||||
if [[ -d "$source_path" ]]; then
|
||||
mkdir -p -- "$destination_path"
|
||||
rsync -a \
|
||||
--exclude='.DS_Store' \
|
||||
--exclude='__MACOSX/' \
|
||||
"$source_path/" "$destination_path/"
|
||||
else
|
||||
install -m 640 "$source_path" "$destination_path"
|
||||
fi
|
||||
}
|
||||
|
||||
copy_public_files() {
|
||||
mkdir -p -- "$STAGE_DIR/public"
|
||||
rsync -a \
|
||||
--exclude='.DS_Store' \
|
||||
--exclude='__MACOSX/' \
|
||||
--exclude='/docs/***' \
|
||||
--exclude='/gitlab/***' \
|
||||
--exclude='/jenkins/***' \
|
||||
--exclude='/uploads/***' \
|
||||
"$SOURCE_DIR/public/" "$STAGE_DIR/public/"
|
||||
}
|
||||
|
||||
production_paths=(
|
||||
"app"
|
||||
"bootstrap"
|
||||
"routes"
|
||||
"composer.json"
|
||||
"composer.lock"
|
||||
"spark"
|
||||
"preload.php"
|
||||
"LICENSE"
|
||||
)
|
||||
|
||||
for production_path in "${production_paths[@]}"; do
|
||||
copy_production_path "$production_path"
|
||||
done
|
||||
|
||||
copy_public_files
|
||||
|
||||
mkdir -p -- "$STAGE_DIR/writable"
|
||||
copy_production_path "writable/.htaccess"
|
||||
copy_production_path "writable/index.html"
|
||||
copy_production_path "writable/.gitkeep"
|
||||
|
||||
if (( SKIP_COMPOSER == 1 )); then
|
||||
copy_production_path "vendor"
|
||||
fi
|
||||
|
||||
install -m 600 "$ENV_FILE" "$STAGE_DIR/.env"
|
||||
|
||||
# Remove platform debris even when it was nested in an unexpected directory.
|
||||
find "$STAGE_DIR" -name '.DS_Store' -type f -delete
|
||||
find "$STAGE_DIR" -name '__MACOSX' -type d -prune -exec rm -rf {} +
|
||||
|
||||
runtime_directories=(
|
||||
"writable/cache"
|
||||
"writable/debugbar"
|
||||
"writable/logs"
|
||||
"writable/session"
|
||||
"writable/tmp"
|
||||
"writable/uploads"
|
||||
"writable/uploads/class_material"
|
||||
"writable/uploads/early_dismissal_signatures"
|
||||
"writable/uploads/exams"
|
||||
"writable/uploads/print_requests"
|
||||
"writable/uploads/receipts"
|
||||
"writable/uploads/reimbursements"
|
||||
"writable/reports"
|
||||
"writable/report_assets"
|
||||
"public/uploads"
|
||||
"public/uploads/event_flyers"
|
||||
"public/uploads/print_requests"
|
||||
)
|
||||
|
||||
for runtime_directory in "${runtime_directories[@]}"; do
|
||||
mkdir -p -- "$STAGE_DIR/$runtime_directory"
|
||||
: > "$STAGE_DIR/$runtime_directory/.gitkeep"
|
||||
done
|
||||
|
||||
chmod 750 "$STAGE_DIR/spark"
|
||||
chmod 600 "$STAGE_DIR/.env"
|
||||
|
||||
if (( SKIP_COMPOSER == 0 )); then
|
||||
command -v composer >/dev/null 2>&1 \
|
||||
|| die "Composer is required. Install Composer or use --skip-composer with an already production-only vendor directory."
|
||||
|
||||
log "Installing production Composer dependencies."
|
||||
(
|
||||
cd "$STAGE_DIR"
|
||||
COMPOSER_ALLOW_SUPERUSER=1 composer install \
|
||||
--no-dev \
|
||||
--prefer-dist \
|
||||
--optimize-autoloader \
|
||||
--classmap-authoritative \
|
||||
--no-interaction \
|
||||
--no-progress
|
||||
|
||||
COMPOSER_ALLOW_SUPERUSER=1 composer check-platform-reqs --no-dev
|
||||
)
|
||||
else
|
||||
log "Using the existing vendor directory."
|
||||
|
||||
[[ -f "$STAGE_DIR/vendor/autoload.php" ]] \
|
||||
|| die "Existing vendor/autoload.php is missing."
|
||||
|
||||
forbidden_vendor_paths=(
|
||||
"vendor/phpunit"
|
||||
"vendor/fakerphp"
|
||||
"vendor/mockery"
|
||||
"vendor/myclabs/deep-copy"
|
||||
"vendor/phar-io"
|
||||
"vendor/sebastian"
|
||||
"vendor/theseer"
|
||||
)
|
||||
|
||||
found_dev_dependencies=()
|
||||
for vendor_path in "${forbidden_vendor_paths[@]}"; do
|
||||
if [[ -e "$STAGE_DIR/$vendor_path" ]]; then
|
||||
found_dev_dependencies+=("$vendor_path")
|
||||
fi
|
||||
done
|
||||
|
||||
if (( ${#found_dev_dependencies[@]} > 0 )); then
|
||||
printf '[%s] Development packages remain in vendor:\n' "$SCRIPT_NAME" >&2
|
||||
printf ' - %s\n' "${found_dev_dependencies[@]}" >&2
|
||||
die "Run Composer with --no-dev instead of using --skip-composer."
|
||||
fi
|
||||
fi
|
||||
|
||||
[[ -f "$STAGE_DIR/vendor/autoload.php" ]] \
|
||||
|| die "Production vendor/autoload.php was not created."
|
||||
|
||||
php -r '
|
||||
require $argv[1];
|
||||
fwrite(STDOUT, "Composer autoload check passed.\n");
|
||||
' "$STAGE_DIR/vendor/autoload.php"
|
||||
|
||||
if (( SKIP_LINT == 0 )); then
|
||||
log "Linting application PHP files."
|
||||
lint_failure_file="$STAGE_ROOT/php-lint-failures.txt"
|
||||
: > "$lint_failure_file"
|
||||
|
||||
while IFS= read -r -d '' php_file; do
|
||||
if ! php -l "$php_file" >/dev/null 2>&1; then
|
||||
printf '%s\n' "$php_file" >> "$lint_failure_file"
|
||||
fi
|
||||
done < <(
|
||||
find \
|
||||
"$STAGE_DIR/app" \
|
||||
"$STAGE_DIR/public" \
|
||||
-type f -name '*.php' -print0
|
||||
)
|
||||
|
||||
if [[ -s "$lint_failure_file" ]]; then
|
||||
cat "$lint_failure_file" >&2
|
||||
die "PHP lint failed."
|
||||
fi
|
||||
fi
|
||||
|
||||
# Apply conservative baseline permissions. The deployment user or PHP-FPM
|
||||
# group can be supplied later using chown/chgrp on the server.
|
||||
find "$STAGE_DIR" -type d -exec chmod 750 {} +
|
||||
find "$STAGE_DIR" -type f -exec chmod 640 {} +
|
||||
chmod 750 "$STAGE_DIR/spark"
|
||||
chmod 600 "$STAGE_DIR/.env"
|
||||
|
||||
for runtime_directory in "${runtime_directories[@]}"; do
|
||||
chmod 770 "$STAGE_DIR/$runtime_directory"
|
||||
done
|
||||
|
||||
mkdir -p "$STAGE_DIR/.deployment"
|
||||
|
||||
cat > "$STAGE_DIR/.deployment/release-check.sh" <<'RELEASE_CHECK'
|
||||
#!/usr/bin/env bash
|
||||
set -Eeuo pipefail
|
||||
IFS=$'\n\t'
|
||||
|
||||
ROOT="$(cd "$(dirname "$0")/.." && pwd -P)"
|
||||
errors=0
|
||||
|
||||
fail() {
|
||||
printf 'FAIL: %s\n' "$*" >&2
|
||||
errors=$((errors + 1))
|
||||
}
|
||||
|
||||
pass() {
|
||||
printf 'PASS: %s\n' "$*"
|
||||
}
|
||||
|
||||
[[ -f "$ROOT/.env" ]] \
|
||||
&& pass ".env exists" \
|
||||
|| fail ".env is missing"
|
||||
|
||||
if grep -Eq '^[[:space:]]*CI_ENVIRONMENT[[:space:]]*=[[:space:]]*["'\'']?production["'\'']?[[:space:]]*$' "$ROOT/.env"; then
|
||||
pass "CI_ENVIRONMENT is production"
|
||||
else
|
||||
fail "CI_ENVIRONMENT is not production"
|
||||
fi
|
||||
|
||||
for required in \
|
||||
"app/Config/Paths.php" \
|
||||
"public/index.php" \
|
||||
"vendor/autoload.php" \
|
||||
"spark"
|
||||
do
|
||||
[[ -e "$ROOT/$required" ]] \
|
||||
&& pass "$required exists" \
|
||||
|| fail "$required is missing"
|
||||
done
|
||||
|
||||
for writable_path in \
|
||||
"writable/cache" \
|
||||
"writable/logs" \
|
||||
"writable/session" \
|
||||
"writable/tmp" \
|
||||
"writable/uploads"
|
||||
do
|
||||
if [[ -d "$ROOT/$writable_path" && -w "$ROOT/$writable_path" ]]; then
|
||||
pass "$writable_path is writable"
|
||||
else
|
||||
fail "$writable_path is not writable"
|
||||
fi
|
||||
done
|
||||
|
||||
for forbidden in \
|
||||
".git" \
|
||||
"tests" \
|
||||
"node_modules" \
|
||||
"vendor/phpunit" \
|
||||
"phpunit.xml"
|
||||
do
|
||||
[[ ! -e "$ROOT/$forbidden" ]] \
|
||||
&& pass "$forbidden is absent" \
|
||||
|| fail "$forbidden must not be deployed"
|
||||
done
|
||||
|
||||
php -r 'require $argv[1];' "$ROOT/vendor/autoload.php" \
|
||||
&& pass "Composer autoload works" \
|
||||
|| fail "Composer autoload failed"
|
||||
|
||||
printf '\nRequired web document root:\n %s/public\n' "$ROOT"
|
||||
printf '\nBefore traffic is enabled, run database migration status and the application financial smoke tests.\n'
|
||||
|
||||
if (( errors > 0 )); then
|
||||
printf '\nRelease check failed with %d error(s).\n' "$errors" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
printf '\nRelease check passed.\n'
|
||||
RELEASE_CHECK
|
||||
|
||||
chmod 750 "$STAGE_DIR/.deployment/release-check.sh"
|
||||
|
||||
cat > "$STAGE_DIR/.deployment/nginx.conf.example" <<'NGINX'
|
||||
server {
|
||||
listen 80;
|
||||
server_name __DOMAIN__;
|
||||
|
||||
# Point the web root only at public. Exposing the project root is unsafe.
|
||||
root __APP_ROOT__/public;
|
||||
index index.php;
|
||||
|
||||
client_max_body_size 25m;
|
||||
|
||||
location / {
|
||||
try_files $uri $uri/ /index.php?$query_string;
|
||||
}
|
||||
|
||||
location ~ \.php$ {
|
||||
try_files $uri =404;
|
||||
|
||||
include fastcgi_params;
|
||||
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
|
||||
fastcgi_param HTTP_PROXY "";
|
||||
fastcgi_pass __PHP_FPM_SOCKET__;
|
||||
}
|
||||
|
||||
location ~ /\.(?!well-known).* {
|
||||
deny all;
|
||||
}
|
||||
|
||||
location ~* \.(?:css|js|jpg|jpeg|gif|png|svg|ico|webp|woff|woff2|ttf)$ {
|
||||
expires 7d;
|
||||
access_log off;
|
||||
try_files $uri =404;
|
||||
}
|
||||
}
|
||||
NGINX
|
||||
|
||||
cat > "$STAGE_DIR/.deployment/apache-vhost.conf.example" <<'APACHE'
|
||||
<VirtualHost *:80>
|
||||
ServerName __DOMAIN__
|
||||
DocumentRoot "__APP_ROOT__/public"
|
||||
|
||||
<Directory "__APP_ROOT__/public">
|
||||
AllowOverride All
|
||||
Options FollowSymLinks
|
||||
Require all granted
|
||||
</Directory>
|
||||
|
||||
ErrorLog ${APACHE_LOG_DIR}/school-app-error.log
|
||||
CustomLog ${APACHE_LOG_DIR}/school-app-access.log combined
|
||||
</VirtualHost>
|
||||
APACHE
|
||||
|
||||
cat > "$STAGE_DIR/.deployment/DEPLOYMENT.md" <<'DEPLOYMENT'
|
||||
# Production Deployment Notes
|
||||
|
||||
## Required document root
|
||||
|
||||
Configure the web server document root as:
|
||||
|
||||
```text
|
||||
<release-directory>/public
|
||||
```
|
||||
|
||||
Never expose the project root, `app`, `vendor`, `writable`, or `.env`.
|
||||
|
||||
## Server preparation
|
||||
|
||||
1. Install the PHP version and extensions accepted by:
|
||||
|
||||
```bash
|
||||
composer check-platform-reqs --no-dev
|
||||
```
|
||||
|
||||
2. Set the release owner and PHP-FPM group:
|
||||
|
||||
```bash
|
||||
sudo chown -R DEPLOY_USER:PHP_FPM_GROUP /path/to/release
|
||||
```
|
||||
|
||||
3. Keep application code read-only for PHP-FPM. Grant write access only to runtime locations:
|
||||
|
||||
```bash
|
||||
sudo find /path/to/release -type d -exec chmod 750 {} \;
|
||||
sudo find /path/to/release -type f -exec chmod 640 {} \;
|
||||
sudo chmod 600 /path/to/release/.env
|
||||
sudo chmod 750 /path/to/release/spark
|
||||
sudo chmod -R 770 /path/to/release/writable
|
||||
sudo chmod -R 770 /path/to/release/public/uploads
|
||||
```
|
||||
|
||||
4. Run the packaged release check:
|
||||
|
||||
```bash
|
||||
/path/to/release/.deployment/release-check.sh
|
||||
```
|
||||
|
||||
## Database deployment
|
||||
|
||||
Back up the production database first.
|
||||
|
||||
Review migration status before executing migrations:
|
||||
|
||||
```bash
|
||||
cd /path/to/release
|
||||
php spark migrate:status
|
||||
php spark migrate --all
|
||||
```
|
||||
|
||||
Financial hardening migrations must fail if required constraints cannot be installed. Do not accept warning-only deployment of uniqueness or check constraints.
|
||||
|
||||
## Persistent data
|
||||
|
||||
Do not overwrite persistent production data during release replacement:
|
||||
|
||||
- `.env`
|
||||
- `writable/uploads`
|
||||
- `writable/reports`
|
||||
- `writable/report_assets`
|
||||
- `public/uploads`
|
||||
|
||||
Mount or copy these from controlled persistent storage.
|
||||
|
||||
## Traffic activation
|
||||
|
||||
Before enabling traffic:
|
||||
|
||||
1. Run migrations.
|
||||
2. Run release checks.
|
||||
3. Run financial smoke tests for invoice, payment, refund, discount, expense, reimbursement, and purchase-order workflows.
|
||||
4. Confirm scheduled jobs and workers point at the new release.
|
||||
5. Confirm writable ownership.
|
||||
6. Confirm the web server points to `public`.
|
||||
7. Confirm `CI_ENVIRONMENT = production`.
|
||||
8. Confirm debug output is disabled.
|
||||
9. Confirm database backups and rollback instructions exist.
|
||||
|
||||
Use an atomic `current` symlink switch where possible rather than copying files over a live release.
|
||||
DEPLOYMENT
|
||||
|
||||
SOURCE_COMMIT="not-a-git-checkout"
|
||||
if command -v git >/dev/null 2>&1 && git -C "$SOURCE_DIR" rev-parse --is-inside-work-tree >/dev/null 2>&1; then
|
||||
SOURCE_COMMIT="$(git -C "$SOURCE_DIR" rev-parse HEAD)"
|
||||
fi
|
||||
|
||||
cat > "$STAGE_DIR/.deployment/BUILD_INFO" <<BUILD_INFO
|
||||
built_at_utc=$(date -u +'%Y-%m-%dT%H:%M:%SZ')
|
||||
source_directory=$SOURCE_DIR
|
||||
source_commit=$SOURCE_COMMIT
|
||||
php_version=$(php -r 'echo PHP_VERSION;')
|
||||
composer_mode=$([[ "$SKIP_COMPOSER" -eq 1 ]] && printf 'existing-vendor' || printf 'composer-no-dev')
|
||||
BUILD_INFO
|
||||
|
||||
(
|
||||
cd "$STAGE_DIR"
|
||||
find . -type f \
|
||||
! -path './.env' \
|
||||
! -path './.deployment/MANIFEST.sha256' \
|
||||
-print0 \
|
||||
| sort -z \
|
||||
| xargs -0 sha256sum \
|
||||
> .deployment/MANIFEST.sha256
|
||||
)
|
||||
|
||||
log "Running packaged release verification."
|
||||
"$STAGE_DIR/.deployment/release-check.sh"
|
||||
|
||||
log "Publishing production directory: $OUTPUT_DIR"
|
||||
mkdir -p -- "$(dirname "$OUTPUT_DIR")"
|
||||
mv -- "$STAGE_DIR" "$OUTPUT_DIR"
|
||||
|
||||
if (( MAKE_ARCHIVE == 1 )); then
|
||||
ARCHIVE_PATH="$(dirname "$OUTPUT_DIR")/alrahma.zip"
|
||||
rm -f -- "$ARCHIVE_PATH"
|
||||
|
||||
warn "The archive contains the production .env. Store and transfer it as a secret."
|
||||
(
|
||||
cd "$(dirname "$OUTPUT_DIR")"
|
||||
zip -qr "$ARCHIVE_PATH" "$(basename "$OUTPUT_DIR")"
|
||||
)
|
||||
|
||||
chmod 600 "$ARCHIVE_PATH"
|
||||
log "Created protected archive: $ARCHIVE_PATH"
|
||||
fi
|
||||
|
||||
cat <<SUMMARY
|
||||
|
||||
Production build completed.
|
||||
|
||||
Directory:
|
||||
$OUTPUT_DIR
|
||||
|
||||
Web document root:
|
||||
$OUTPUT_DIR/public
|
||||
|
||||
Verification:
|
||||
$OUTPUT_DIR/.deployment/release-check.sh
|
||||
|
||||
Deployment notes:
|
||||
$OUTPUT_DIR/.deployment/DEPLOYMENT.md
|
||||
|
||||
The build excludes runtime logs, sessions, caches, existing uploads, tests,
|
||||
development metadata, and local environment files. The supplied production
|
||||
environment file was copied as .env with mode 600.
|
||||
SUMMARY
|
||||
+1
-1
@@ -10,7 +10,7 @@
|
||||
"slack": "https://codeigniterchat.slack.com"
|
||||
},
|
||||
"require": {
|
||||
"php": "^8.1",
|
||||
"php": "^8.5",
|
||||
"codeigniter4/framework": "^4.6",
|
||||
"doctrine/migrations": "^3.8",
|
||||
"dompdf/dompdf": "^3.0",
|
||||
|
||||
Generated
+640
-308
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,230 @@
|
||||
#!/bin/bash
|
||||
|
||||
# ===============================
|
||||
# Al Rahma Sunday School Deployment Script
|
||||
# ===============================
|
||||
|
||||
# ----- Domain and App Info -----
|
||||
DM_NAME="home.alrahmaisgl.org"
|
||||
domain_app="alrahma"
|
||||
|
||||
# ----- Database credentials -----
|
||||
DB_HOST="localhost"
|
||||
DB_NAME="u280815660_school"
|
||||
DB_USER="u280815660_melabidi"
|
||||
DB_PASS=">tNxlRzP/W8"
|
||||
|
||||
# ----- Directories -----
|
||||
ZIP_FILE="$1"
|
||||
BASE_DIR="/home/u280815660/domains"
|
||||
DEPLOY_DIR="$BASE_DIR/$domain_app"
|
||||
APP_DIR="$BASE_DIR/$DM_NAME/$domain_app"
|
||||
PUBLIC_DIR="$BASE_DIR/$DM_NAME/public_html"
|
||||
BACKUP_DIR="$BASE_DIR/archive"
|
||||
SECRETS_DIR="$BASE_DIR/deploy_secrets"
|
||||
TIMESTAMP=$(date +"%Y%m%d_%H%M%S")
|
||||
|
||||
# ----- Runtime binaries -----
|
||||
# Hostinger/CloudLinux exposes PHP 8.5 here. The default CLI `php` may still be
|
||||
# PHP 8.2, which cannot install this project's PHP 8.5 lock file.
|
||||
PHP_BIN="${PHP_BIN:-/opt/alt/php85/usr/bin/php}"
|
||||
COMPOSER_BIN="${COMPOSER_BIN:-$(command -v composer2 || command -v composer || true)}"
|
||||
|
||||
|
||||
# ===============================
|
||||
# 1. VALIDATE INPUT
|
||||
# ===============================
|
||||
if [ -z "$ZIP_FILE" ] || [ ! -f "$ZIP_FILE" ]; then
|
||||
echo "? Please provide the ZIP file: ./deploy_home.sh alrahma_deploy.zip"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "?? Starting deployment of $ZIP_FILE..."
|
||||
|
||||
if [ ! -x "$PHP_BIN" ]; then
|
||||
echo "? PHP 8.5 binary was not found or is not executable: $PHP_BIN"
|
||||
echo " Set PHP_BIN=/path/to/php85 and rerun the deployment."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [ -z "$COMPOSER_BIN" ] || [ ! -e "$COMPOSER_BIN" ]; then
|
||||
echo "? composer2/composer was not found in PATH."
|
||||
echo " Set COMPOSER_BIN=/path/to/composer2 and rerun the deployment."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "?? Using PHP: $("$PHP_BIN" -v | head -n 1)"
|
||||
echo "?? Using Composer: $COMPOSER_BIN"
|
||||
|
||||
# ===============================
|
||||
# 2. BACKUP EXISTING SITE
|
||||
# ===============================
|
||||
echo "??? Backing up current site..."
|
||||
mkdir -p "$BACKUP_DIR"
|
||||
cp -r "$PUBLIC_DIR" "$BACKUP_DIR/public_html_$TIMESTAMP"
|
||||
cp -r "$APP_DIR" "$BACKUP_DIR/$domain_app_$TIMESTAMP"
|
||||
|
||||
# ===============================
|
||||
# 3. BACKUP PERSISTENT FILES
|
||||
# ===============================
|
||||
echo "?? Backing up persistent files..."
|
||||
mkdir -p "$SECRETS_DIR"
|
||||
PERSIST_BACKUP="$BASE_DIR/persist_backup_$TIMESTAMP"
|
||||
mkdir -p "$PERSIST_BACKUP"
|
||||
|
||||
# writable/
|
||||
if [ -d "$APP_DIR/writable" ]; then
|
||||
echo " ? Backing up writable/"
|
||||
cp -r "$APP_DIR/writable" "$PERSIST_BACKUP/writable"
|
||||
fi
|
||||
|
||||
# .env
|
||||
if [ -f "$APP_DIR/.env" ]; then
|
||||
echo " ? Backing up .env"
|
||||
cp "$APP_DIR/.env" "$PERSIST_BACKUP/.env"
|
||||
fi
|
||||
|
||||
# public/.htaccess backup is kept for rollback only. The new release file is
|
||||
# deployed below so stale CSP/rewrite rules do not survive deployments.
|
||||
if [ -f "$PUBLIC_DIR/.htaccess" ]; then
|
||||
echo " ? Backing up public .htaccess"
|
||||
cp "$PUBLIC_DIR/.htaccess" "$PERSIST_BACKUP/.htaccess"
|
||||
fi
|
||||
|
||||
# public/index.php backup is kept for rollback only. The new release file is
|
||||
# deployed below and then patched to point at the sibling app directory.
|
||||
if [ -f "$PUBLIC_DIR/index.php" ]; then
|
||||
echo " ? Backing up public index.php"
|
||||
cp "$PUBLIC_DIR/index.php" "$PERSIST_BACKUP/index.php"
|
||||
fi
|
||||
|
||||
# ===============================
|
||||
# 4. CLEAN & EXTRACT NEW DEPLOY
|
||||
# ===============================
|
||||
echo "?? Cleaning old deployment..."
|
||||
rm -rf "$DEPLOY_DIR"
|
||||
unzip "$ZIP_FILE" -d "$BASE_DIR"
|
||||
|
||||
# ===============================
|
||||
# 5. DEPLOY APP FILES
|
||||
# ===============================
|
||||
echo "?? Deploying new app files..."
|
||||
rm -rf "$APP_DIR"
|
||||
mkdir -p "$APP_DIR"
|
||||
cp -r "$DEPLOY_DIR"/. "$APP_DIR"
|
||||
|
||||
# ===============================
|
||||
# 6. RESTORE PERSISTENT FILES
|
||||
# ===============================
|
||||
echo "?? Restoring persistent files..."
|
||||
|
||||
# writable/
|
||||
if [ -d "$PERSIST_BACKUP/writable" ]; then
|
||||
echo " ? Restoring writable/"
|
||||
rm -rf "$APP_DIR/writable"
|
||||
cp -r "$PERSIST_BACKUP/writable" "$APP_DIR/writable"
|
||||
fi
|
||||
|
||||
# .env
|
||||
if [ -f "$PERSIST_BACKUP/.env" ]; then
|
||||
echo " ? Restoring .env"
|
||||
cp "$PERSIST_BACKUP/.env" "$APP_DIR/.env"
|
||||
fi
|
||||
|
||||
# ===============================
|
||||
# 7. DEPLOY PUBLIC FILES
|
||||
# ===============================
|
||||
echo "?? Deploying public files..."
|
||||
mkdir -p "$PUBLIC_DIR"
|
||||
find "$PUBLIC_DIR" -mindepth 1 -maxdepth 1 -exec rm -rf {} +
|
||||
cp -a "$APP_DIR/public/." "$PUBLIC_DIR/"
|
||||
|
||||
# ===============================
|
||||
# 8. FIX index.php PATH
|
||||
# ===============================
|
||||
echo "?? Fixing index.php path..."
|
||||
sed -i "s|require FCPATH . '../app/Config/Paths.php';|require FCPATH . '../alrahma/app/Config/Paths.php';|" "$PUBLIC_DIR/index.php"
|
||||
|
||||
# ===============================
|
||||
# 9. UPDATE BASE URL & DB CONFIG
|
||||
# ===============================
|
||||
APP_CONFIG="$APP_DIR/app/Config/App.php"
|
||||
DB_CONFIG="$APP_DIR/app/Config/Database.php"
|
||||
echo "?? Updating configuration files..."
|
||||
|
||||
# Base URL
|
||||
sed -i "s|public string \$baseURL = .*|public string \$baseURL = 'https://$DM_NAME/';|" "$APP_CONFIG"
|
||||
|
||||
# Database.php
|
||||
sed -i "s|'hostname' => .*|'hostname' => '$DB_HOST',|" "$DB_CONFIG"
|
||||
sed -i "s|'username' => .*|'username' => '$DB_USER',|" "$DB_CONFIG"
|
||||
sed -i "s|'password' => .*|'password' => '$DB_PASS',|" "$DB_CONFIG"
|
||||
sed -i "s|'database' => .*|'database' => '$DB_NAME',|" "$DB_CONFIG"
|
||||
|
||||
# ===============================
|
||||
# 10. UPDATE db_connection.php
|
||||
# ===============================
|
||||
DB_CONN_FILE="$APP_DIR/app/db_connection.php"
|
||||
if [ -f "$DB_CONN_FILE" ]; then
|
||||
echo "??? Updating db_connection.php..."
|
||||
sed -i "s|\$host = '.*';|\$host = '$DB_HOST';|" "$DB_CONN_FILE"
|
||||
sed -i "s|\$dbname = '.*';|\$dbname = '$DB_NAME';|" "$DB_CONN_FILE"
|
||||
sed -i "s|\$username = '.*';|\$username = '$DB_USER';|" "$DB_CONN_FILE"
|
||||
sed -i "s|\$password = '.*';|\$password = '$DB_PASS';|" "$DB_CONN_FILE"
|
||||
fi
|
||||
|
||||
# ===============================
|
||||
# 11. FIX FPDF PATHS
|
||||
# ===============================
|
||||
echo "?? Fixing FPDF paths..."
|
||||
grep -rl "ThirdParty\\\\fpdf\\\\fpdf.php" "$APP_DIR/app" | while read -r file; do
|
||||
sed -i "s|ThirdParty\\\\fpdf\\\\fpdf.php|ThirdParty/fpdf/fpdf.php|g" "$file"
|
||||
echo "? Fixed path in: $file"
|
||||
done
|
||||
|
||||
# ===============================
|
||||
# 12. COMPOSER INSTALL
|
||||
# ===============================
|
||||
echo "?? Installing production Composer dependencies with PHP 8.5..."
|
||||
cd "$APP_DIR" || exit
|
||||
|
||||
export PATH="$(dirname "$PHP_BIN"):$PATH"
|
||||
export COMPOSER_ALLOW_SUPERUSER=1
|
||||
|
||||
if "$PHP_BIN" "$COMPOSER_BIN" --version >/dev/null 2>&1; then
|
||||
"$PHP_BIN" "$COMPOSER_BIN" install \
|
||||
--no-dev \
|
||||
--prefer-dist \
|
||||
--optimize-autoloader \
|
||||
--classmap-authoritative \
|
||||
--no-interaction \
|
||||
--no-progress
|
||||
else
|
||||
"$COMPOSER_BIN" install \
|
||||
--no-dev \
|
||||
--prefer-dist \
|
||||
--optimize-autoloader \
|
||||
--classmap-authoritative \
|
||||
--no-interaction \
|
||||
--no-progress
|
||||
fi
|
||||
|
||||
"$PHP_BIN" spark --version
|
||||
|
||||
# ===============================
|
||||
# 13. RESTORE PERMISSIONS
|
||||
# ===============================
|
||||
echo "?? Setting permissions..."
|
||||
chmod 644 "$PUBLIC_DIR/.htaccess"
|
||||
chmod 644 "$PUBLIC_DIR/index.php"
|
||||
chmod -R 755 "$APP_DIR"
|
||||
chmod 644 "$APP_DIR/app/db_connection.php"
|
||||
chmod -R 777 "$APP_DIR/writable"
|
||||
|
||||
# ===============================
|
||||
# ? DONE
|
||||
# ===============================
|
||||
echo "? Deployment completed successfully at $TIMESTAMP"
|
||||
echo "?? Backup stored in: $BACKUP_DIR"
|
||||
echo "?? Persistent files restored from: $PERSIST_BACKUP"
|
||||
echo "?? Writable and .env restored; public .htaccess and index.php deployed from the new release."
|
||||
+242
@@ -0,0 +1,242 @@
|
||||
#--------------------------------------------------------------------
|
||||
# Example Environment Configuration file
|
||||
#
|
||||
# This file can be used as a starting point for your own
|
||||
# custom .env files, and contains most of the possible settings
|
||||
# available in a default install.
|
||||
#
|
||||
# By default, all of the settings are commented out. If you want
|
||||
# to override the setting, you must un-comment it by removing the '#'
|
||||
# at the beginning of the line.
|
||||
#--------------------------------------------------------------------
|
||||
|
||||
#--------------------------------------------------------------------
|
||||
# ENVIRONMENT
|
||||
#--------------------------------------------------------------------
|
||||
|
||||
CI_ENVIRONMENT = production
|
||||
|
||||
# Which profile to use if none is supplied to sendEmail()
|
||||
# Options: default, communication, payment, ...
|
||||
MAIL_PROFILE_DEFAULT=default
|
||||
|
||||
# ===== DEFAULT (system) =====
|
||||
MAIL_DEFAULT_HOST=smtp.gmail.com
|
||||
MAIL_DEFAULT_USER=alrahma.sunday.school@gmail.com
|
||||
MAIL_DEFAULT_PASS="psnp emdq dykw ypul"
|
||||
MAIL_DEFAULT_PORT=587
|
||||
MAIL_DEFAULT_ENCRYPTION=tls
|
||||
MAIL_DEFAULT_FROM_EMAIL=alrahma.sunday.school@gmail.com
|
||||
MAIL_DEFAULT_FROM_NAME="Al Rahma Sunday School"
|
||||
# Optional Reply-To (uncomment if you want a replyable inbox)
|
||||
# MAIL_DEFAULT_REPLY_TO=communications@alrahmaisgl.org
|
||||
MAIL_DEFAULT_REPLY_TO_NAME="No-Reply"
|
||||
# Optional bounce address (envelope sender)
|
||||
# MAIL_DEFAULT_RETURN_PATH=bounces@alrahmaisgl.org
|
||||
# Optional DKIM
|
||||
# MAIL_DEFAULT_DKIM_DOMAIN=alrahmaisgl.org
|
||||
# MAIL_DEFAULT_DKIM_SELECTOR=default
|
||||
# MAIL_DEFAULT_DKIM_PRIVATE=/path/to/private.key
|
||||
|
||||
# ===== COMMUNICATION =====
|
||||
MAIL_COMMUNICATION_HOST=smtp.gmail.com
|
||||
MAIL_COMMUNICATION_USER=alrahma.sunday.school@gmail.com
|
||||
MAIL_COMMUNICATION_PASS="psnp emdq dykw ypul"
|
||||
MAIL_COMMUNICATION_PORT=587
|
||||
MAIL_COMMUNICATION_ENCRYPTION=tls
|
||||
MAIL_COMMUNICATION_FROM_EMAIL=alrahma.sunday.school@gmail.com
|
||||
MAIL_COMMUNICATION_FROM_NAME="School Communications"
|
||||
MAIL_COMMUNICATION_REPLY_TO=alrahma.sunday.school@gmail.com
|
||||
MAIL_COMMUNICATION_REPLY_TO_NAME="School Communications"
|
||||
# MAIL_COMMUNICATION_RETURN_PATH=bounces@alrahmaisgl.org
|
||||
|
||||
# ===== PAYMENT =====
|
||||
MAIL_PAYMENT_HOST=smtp.gmail.com
|
||||
MAIL_PAYMENT_USER=alrahma.sunday.school@gmail.com
|
||||
MAIL_PAYMENT_PASS="psnp emdq dykw ypul"
|
||||
MAIL_PAYMENT_PORT=587
|
||||
MAIL_PAYMENT_ENCRYPTION=tls
|
||||
MAIL_PAYMENT_FROM_EMAIL=alrahma.sunday.school@gmail.com
|
||||
MAIL_PAYMENT_FROM_NAME="School Payments"
|
||||
|
||||
# MAIL_PAYMENT_RETURN_PATH=bounces@alrahmaisgl.org
|
||||
|
||||
# ---- Legacy fallbacks (kept for compatibility) ----
|
||||
SMTP_HOST = smtp.gmail.com
|
||||
SMTP_USER = alrahma.sunday.school@gmail.com
|
||||
SMTP_PASS = "psnp emdq dykw ypul"
|
||||
SMTP_PORT = 587
|
||||
SMTP_ENCRYPTION = tls # was SSL; set to ssl to match 587
|
||||
|
||||
# Reply-To for all mail profiles
|
||||
MAIL_DEFAULT_REPLY_TO=alrahma.isgl@gmail.com
|
||||
MAIL_DEFAULT_REPLY_TO_NAME="Al Rahma Sunday School"
|
||||
MAIL_COMMUNICATION_REPLY_TO=alrahma.isgl@gmail.com
|
||||
MAIL_COMMUNICATION_REPLY_TO_NAME="School Communications"
|
||||
MAIL_PAYMENT_REPLY_TO=alrahma.isgl@gmail.com
|
||||
MAIL_PAYMENT_REPLY_TO_NAME="School Payments"
|
||||
|
||||
# Optional sender directory you already use elsewhere
|
||||
MAIL_SENDERS="{\"general\":{\"email\":\"alrahma.isgl@gmail.com\",\"name\":\"Al Rahma No-Reply\"},\
|
||||
\"registration\":{\"email\":\"alrahma.isgl@gmail.com\",\"name\":\"Al Rahma Register Office\"},\
|
||||
\"notifications\":{\"email\":\"alrahma.isgl@gmail.com\",\"name\":\"Al Rahma Notifications\"},\
|
||||
\"finance\":{\"email\":\"alrahma.isgl@gmail.com\",\"name\":\"Al Rahma Finance Office\"}}"
|
||||
|
||||
# Principal notification recipient for TimeOff requests (comma-separated allowed)
|
||||
|
||||
PRINCIPAL_EMAIL="principal@alrahmaisgl.org"
|
||||
MAIL_PARENT_REPORT_TO="alrahma.isgl@gmail.com"
|
||||
MAIL_FROM_ADDRESS = "alrahma.sunday.school@gmail.com"
|
||||
session.expiration = 43200
|
||||
#--------------------------------------------------------------------
|
||||
# APP
|
||||
#--------------------------------------------------------------------
|
||||
|
||||
# If you have trouble with `.`, you could also use `_`.
|
||||
app.baseURL = 'https://test.alrahmaisgl.org/'
|
||||
# app.forceGlobalSecureRequests = true
|
||||
# app.CSPEnabled = false
|
||||
|
||||
#--------------------------------------------------------------------
|
||||
# DATABASE
|
||||
#--------------------------------------------------------------------
|
||||
database.default.hostname = localhost
|
||||
database.default.database = u280815660_school
|
||||
database.default.username = u280815660_melabidi
|
||||
database.default.password = >tNxlRzP/W8
|
||||
database.default.DBDriver = MySQLi
|
||||
database.default.DBPrefix =
|
||||
database.default.port = 3306
|
||||
|
||||
|
||||
#--------------------------------------------------------------------
|
||||
# CONTENT SECURITY POLICY
|
||||
#--------------------------------------------------------------------
|
||||
|
||||
# contentsecuritypolicy.reportOnly = false
|
||||
# contentsecuritypolicy.defaultSrc = 'none'
|
||||
# contentsecuritypolicy.scriptSrc = 'self'
|
||||
# contentsecuritypolicy.styleSrc = 'self'
|
||||
# contentsecuritypolicy.imageSrc = 'self'
|
||||
# contentsecuritypolicy.baseURI = null
|
||||
# contentsecuritypolicy.childSrc = null
|
||||
# contentsecuritypolicy.connectSrc = 'self'
|
||||
# contentsecuritypolicy.fontSrc = null
|
||||
# contentsecuritypolicy.formAction = null
|
||||
# contentsecuritypolicy.frameAncestors = null
|
||||
# contentsecuritypolicy.frameSrc = null
|
||||
# contentsecuritypolicy.mediaSrc = null
|
||||
# contentsecuritypolicy.objectSrc = null
|
||||
# contentsecuritypolicy.pluginTypes = null
|
||||
# contentsecuritypolicy.reportURI = null
|
||||
# contentsecuritypolicy.sandbox = false
|
||||
# contentsecuritypolicy.upgradeInsecureRequests = false
|
||||
# contentsecuritypolicy.styleNonceTag = '{csp-style-nonce}'
|
||||
# contentsecuritypolicy.scriptNonceTag = '{csp-script-nonce}'
|
||||
# contentsecuritypolicy.autoNonce = true
|
||||
|
||||
#--------------------------------------------------------------------
|
||||
# COOKIE
|
||||
#--------------------------------------------------------------------
|
||||
|
||||
# cookie.prefix = ''
|
||||
# cookie.expires = 0
|
||||
# cookie.path = '/'
|
||||
# cookie.domain = ''
|
||||
# cookie.secure = false
|
||||
# cookie.httponly = false
|
||||
# cookie.samesite = 'Lax'
|
||||
# cookie.raw = false
|
||||
|
||||
#--------------------------------------------------------------------
|
||||
# ENCRYPTION
|
||||
#--------------------------------------------------------------------
|
||||
|
||||
# encryption.key =
|
||||
# encryption.driver = OpenSSL
|
||||
# encryption.blockSize = 16
|
||||
# encryption.digest = SHA512
|
||||
|
||||
#--------------------------------------------------------------------
|
||||
# HONEYPOT
|
||||
#--------------------------------------------------------------------
|
||||
|
||||
# honeypot.hidden = 'true'
|
||||
# honeypot.label = 'Fill This Field'
|
||||
# honeypot.name = 'honeypot'
|
||||
# honeypot.template = '<label>{label}</label><input type="text" name="{name}" value=""/>'
|
||||
# honeypot.container = '<div style="display:none">{template}</div>'
|
||||
|
||||
#--------------------------------------------------------------------
|
||||
# SECURITY
|
||||
#--------------------------------------------------------------------
|
||||
|
||||
# security.csrfProtection = 'cookie'
|
||||
# security.tokenRandomize = false
|
||||
# security.tokenName = 'csrf_token_name'
|
||||
# security.headerName = 'X-CSRF-TOKEN'
|
||||
# security.cookieName = 'csrf_cookie_name'
|
||||
# security.expires = 7200
|
||||
# security.regenerate = true
|
||||
# security.redirect = false
|
||||
# security.samesite = 'Lax'
|
||||
|
||||
#--------------------------------------------------------------------
|
||||
# SESSION
|
||||
#--------------------------------------------------------------------
|
||||
|
||||
# session.driver = 'CodeIgniter\Session\Handlers\FileHandler'
|
||||
# session.cookieName = 'ci_session'
|
||||
# session.expiration = 7200
|
||||
# session.savePath = null
|
||||
# session.matchIP = false
|
||||
# session.timeToUpdate = 300
|
||||
# session.regenerateDestroy = false
|
||||
|
||||
#--------------------------------------------------------------------
|
||||
# LOGGER
|
||||
#--------------------------------------------------------------------
|
||||
|
||||
# logger.threshold = 4
|
||||
|
||||
#--------------------------------------------------------------------
|
||||
# CURLRequest
|
||||
#--------------------------------------------------------------------
|
||||
|
||||
# curlrequest.shareOptions = false
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
mail.protocol = smtp
|
||||
mail.SMTPHost = ${SMTP_HOST}
|
||||
mail.SMTPUser = ${SMTP_USER}
|
||||
mail.SMTPPass = ${SMTP_PASS}
|
||||
mail.SMTPPort = ${SMTP_PORT}
|
||||
mail.SMTPCrypto = tls
|
||||
|
||||
mail.fromEmail = ${SMTP_USER}
|
||||
mail.fromName = "Al Rahma Sunday School"
|
||||
mail.mailType = text
|
||||
mail.charset = UTF-8
|
||||
|
||||
|
||||
|
||||
|
||||
# ---- Printer Mode (network, usb, windows, or disabled)
|
||||
PRINTER_MODE=disabled
|
||||
|
||||
# ---- Network printer settings
|
||||
PRINTER_HOST=192.168.1.100
|
||||
PRINTER_PORT=9100
|
||||
|
||||
# ---- USB printer settings (used when PRINTER_MODE=usb)
|
||||
PRINTER_USB_VID=0x0dd4
|
||||
PRINTER_USB_PID=0x0285
|
||||
PRINTER_WINDOWS_NAME="CUSTOM P3L"
|
||||
|
||||
# ---- Formatting
|
||||
# 80mm paper ~ 48-56 characters per line
|
||||
PRINTER_CHARS_PER_LINE=48
|
||||
PRINTER_FEED_LINES=3
|
||||
+1
-1
@@ -24,7 +24,7 @@ RedirectMatch 404 /\.git
|
||||
Header always set X-Frame-Options "SAMEORIGIN"
|
||||
Header always set Referrer-Policy "strict-origin-when-cross-origin"
|
||||
Header always set Permissions-Policy "geolocation=(), microphone=()"
|
||||
Header always set Content-Security-Policy "default-src 'self';"
|
||||
Header always set Content-Security-Policy "default-src 'self'; script-src 'self' 'unsafe-inline' https://cdn.jsdelivr.net https://code.jquery.com https://cdnjs.cloudflare.com; style-src 'self' 'unsafe-inline' https://cdn.jsdelivr.net https://fonts.googleapis.com https://cdnjs.cloudflare.com; style-src-elem 'self' 'unsafe-inline' https://cdn.jsdelivr.net https://fonts.googleapis.com https://cdnjs.cloudflare.com; style-src-attr 'self' 'unsafe-inline'; font-src 'self' data: https://fonts.gstatic.com https://cdn.jsdelivr.net https://cdnjs.cloudflare.com; img-src 'self' data: https:; connect-src 'self'; object-src 'none'; base-uri 'self'; frame-ancestors 'self';"
|
||||
</IfModule>
|
||||
|
||||
# Disable Apache signature
|
||||
|
||||
+1
-1
@@ -6,7 +6,7 @@
|
||||
*---------------------------------------------------------------
|
||||
*/
|
||||
|
||||
$minPhpVersion = '8.1'; // If you update this, don't forget to update `spark`.
|
||||
$minPhpVersion = '8.5'; // If you update this, don't forget to update `spark`.
|
||||
if (version_compare(PHP_VERSION, $minPhpVersion, '<')) {
|
||||
$message = sprintf(
|
||||
'Your PHP version must be %s or higher to run CodeIgniter. Current version: %s',
|
||||
|
||||
@@ -35,7 +35,7 @@ if (str_starts_with(PHP_SAPI, 'cgi')) {
|
||||
*---------------------------------------------------------------
|
||||
*/
|
||||
|
||||
$minPhpVersion = '8.1'; // If you update this, don't forget to update `public/index.php`.
|
||||
$minPhpVersion = '8.5'; // If you update this, don't forget to update `public/index.php`.
|
||||
if (version_compare(PHP_VERSION, $minPhpVersion, '<')) {
|
||||
$message = sprintf(
|
||||
'Your PHP version must be %s or higher to run CodeIgniter. Current version: %s',
|
||||
|
||||
@@ -1,66 +0,0 @@
|
||||
|
||||
Cron ideas:
|
||||
|
||||
Run every night (safe):
|
||||
0 2 * * * php /path/to/spark attendance:auto-publish >> /var/log/attendance-autopublish.log 2>&1
|
||||
|
||||
Or run every Sunday at 23:59 school time.
|
||||
|
||||
|
||||
//attendance tracking for non reported only,
|
||||
ignore all breaks and school cancelation from weeks calculation analysis
|
||||
1 not reported absence => send email automated without involving team intervention
|
||||
2 absences in row or in 3 consecutive weeks, notify the team to call parent and send email, yellow color code
|
||||
3 absences in row or in 4 consecutive weeks, notify the team to call parent and send email, orange color code
|
||||
4 absences in row or in 5 consecutive weeks, notify the team to notify parent about student expeled send email, red color code.
|
||||
|
||||
2 lates in row or in 3 consecutive weeks, send email automated to parent, color code light Blue
|
||||
3 lates in row or in 4 consecutive weeks or 2 Lates and 1 Absence, notify the team to call parent and send email, color code dark Blue.
|
||||
4 lates in row or in all passed weeks whole school-year, notify the team to call parent and send email, color code Red.
|
||||
|
||||
|
||||
//attendance emails:
|
||||
Assalamu Alaikum parents Br. Talha Kamran and Sr. Zaheen Asia,
|
||||
|
||||
Insha Allah this email will find you well. ahmed ahmed to you and your family. Thank you very much for taking the time to speak with us. This is just a reminder that ahmed ahmed was absent 2 times in a row without notice. Any student that has to be absent due to illness or family commitment, that's OK as long as the school is given prior notice. Please let us know ahead of time. You can always email us/call us/text us at 978-364-0219 to let us know in advance if your child will be absent/late.
|
||||
|
||||
Kind Regards,
|
||||
-Al Rahma Admin
|
||||
|
||||
///////////////////////////////////
|
||||
Assalamu Alaikum parents Br. Elias Fahfouhi and Sr. Angelica Fahfouhi,
|
||||
|
||||
Insha Allah this email will find you well. Thank you very much for taking the time to speak with us. This is just a reminder that ahmed ahmed were absent 2 times in the last 3 weeks without notice. If your child has to be absent due to other commitments or illness, that's OK as long as the school is given prior notice. Please let us know ahead of time. You can always email us or call us/text us at 978-364-0219 to let us know in advance if your child will be absent/late.
|
||||
|
||||
Kind Regards,
|
||||
-Al Rahma Admin
|
||||
|
||||
////////////////////////
|
||||
Assalamu Alaikum parents Br. Talha Kamran and Sr. Asia Zaheen,
|
||||
|
||||
Insha Allah this email will find you well. We tried getting in touch with you but unfortunately we couldn't reach you, and left a voice message at 978-893-9703. This is just a reminder that ahmed ahmed was absent once and late twice over the last three school days without notice. Students have to be in class by 10 AM. If your child has to be absent or late due to illness or family commitment, that's OK as long as the school is given prior notice. Please let us know ahead of time. You can always email us/call us/text us at 978-364-0219 to let us know in advance if your child will be absent/late.
|
||||
|
||||
Kind Regards,
|
||||
-Al Rahma Admin
|
||||
|
||||
////////////////////////
|
||||
Assalamu Alaikum parents Br. Mubiru Sheba and Sr. Sylbert Jackson,
|
||||
|
||||
Insha Allah this email will find you well. Thank you very much for taking the time to speak with us. This is just a reminder that ahmed ahmed was absent 2 times in a row without notice. If your child has to be absent due to illness or family commitment, that's OK as long as the school is given prior notice. Please let us know ahead of time. You can always email us/call us/text us at 978-364-0219 to let us know in advance if your child will be absent/late.
|
||||
|
||||
Kind Regards,
|
||||
-Al Rahma Admin
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
Christy Wyatt
|
||||
259
|
||||
STU2500259
|
||||
Zaineb
|
||||
Elmerse
|
||||
christy.lee.wyatt@gmail.com
|
||||
Reference in New Issue
Block a user