diff --git a/.coderabbit.yaml b/.coderabbit.yaml index e2922eb39..f6b37ee52 100644 --- a/.coderabbit.yaml +++ b/.coderabbit.yaml @@ -5,3 +5,40 @@ reviews: base_branches: - next drafts: true + path_instructions: + - path: "**/*.php" + instructions: | + Flag any usage of error_log() as it should be replaced with proper logging mechanisms: + 1. For error handling: Use a proper logging framework with error levels (PSR-3 LoggerInterface) + 2. For debugging: Use a dedicated debug logger or remove debug statements + 3. For CLI output: Use a CLI output handler or symfony/console + 4. For application events: Use structured logging with proper log levels + + Example replacement: + ```php + // Instead of: + error_log("Import failed - " . $e->getMessage()); + + // Use: + $logger->error("Import failed", ['error' => $e->getMessage()]); + ``` + - path: "**/*.{js,jsx,ts,tsx}" + instructions: | + Flag any usage of console.log() as it should be replaced with proper logging: + 1. For development: Use proper debug tools or logging libraries + 2. For production: Remove console.log() statements or use structured logging + 3. For errors: Use error tracking services (e.g., Sentry) + 4. For debugging: Consider using debug libraries that can be enabled/disabled + + Example replacement: + ```javascript + // Instead of: + console.log('User data:', userData); + + // Use: + logger.debug('Processing user data', { userData }); + // or for development only: + if (process.env.NODE_ENV === 'development') { + debug('User data:', userData); + } + ``` \ No newline at end of file diff --git a/.ddev/.env.template b/.ddev/.env.template index 7996b4c86..b0085cad6 100644 --- a/.ddev/.env.template +++ b/.ddev/.env.template @@ -1,4 +1,4 @@ -# zms +# ZMS Core ZMS_API_URL=https://zms.ddev.site/terminvereinbarung/api/2 ZMS_CRONROOT=1 ZMS_ENV=dev @@ -8,5 +8,57 @@ MYSQL_DATABASE=db MYSQL_USER=db MYSQL_PASSWORD=db -# xdebug -PHP_IDE_CONFIG="serverName=zms.ddev.site" \ No newline at end of file +# Cache Configuration +CACHE_DIR=/var/www/html/zmscitizenapi/cache +SOURCE_CACHE_TTL=3600 + +# Maintenance Mode +MAINTENANCE_ENABLED=false + +# Logger Configuration +LOGGER_MAX_REQUESTS=1000 +LOGGER_RESPONSE_LENGTH=1048576 # 1MB +LOGGER_STACK_LINES=20 +LOGGER_MESSAGE_SIZE=8192 # 8KB +LOGGER_CACHE_TTL=60 +LOGGER_MAX_RETRIES=3 +LOGGER_BACKOFF_MIN=100 +LOGGER_BACKOFF_MAX=1000 +LOGGER_LOCK_TIMEOUT=5 + +# Captcha Configuration +CAPTCHA_ENABLED=false +FRIENDLY_CAPTCHA_SECRET_KEY= +FRIENDLY_CAPTCHA_SITE_KEY= +FRIENDLY_CAPTCHA_ENDPOINT=https://eu-api.friendlycaptcha.eu/api/v1/siteverify +FRIENDLY_CAPTCHA_ENDPOINT_PUZZLE=https://eu-api.friendlycaptcha.eu/api/v1/puzzle +ALTCHA_CAPTCHA_SECRET_KEY= +ALTCHA_CAPTCHA_SITE_KEY= +ALTCHA_CAPTCHA_ENDPOINT=https://eu.altcha.org/form/ +ALTCHA_CAPTCHA_ENDPOINT_PUZZLE=https://eu.altcha.org/ + +# Rate Limiting +RATE_LIMIT_MAX_REQUESTS=60 +RATE_LIMIT_CACHE_TTL=60 +RATE_LIMIT_MAX_RETRIES=3 +RATE_LIMIT_BACKOFF_MIN=10 +RATE_LIMIT_BACKOFF_MAX=50 +RATE_LIMIT_LOCK_TIMEOUT=1 + +# Request Limits +MAX_REQUEST_SIZE=10485760 # 10MB +MAX_STRING_LENGTH=32768 # 32KB +MAX_RECURSION_DEPTH=10 + +# CSRF Configuration +CSRF_TOKEN_LENGTH=32 +CSRF_SESSION_KEY=csrf_token + +# CORS Configuration +CORS=http://localhost:8082,http://localhost:8080,http://localhost:8090,https://localhost:8091,http://localhost:3000,http://127.0.0.1:3000,https://zms.ddev.site,http://zms.ddev.site + +# IP Filter Configuration +IP_BLACKLIST= + +# Debug Configuration +PHP_IDE_CONFIG="serverName=zms.ddev.site" diff --git a/.ddev/config.yaml b/.ddev/config.yaml index fff88a9bd..918511c3f 100644 --- a/.ddev/config.yaml +++ b/.ddev/config.yaml @@ -143,13 +143,13 @@ timezone: Europe/Berlin # fail_on_hook_fail: False # Decide whether 'ddev start' should be interrupted by a failing hook -# host_https_port: "59002" +host_https_port: "8091" # The host port binding for https can be explicitly specified. It is # dynamic unless otherwise specified. # This is not used by most people, most people use the *router* instead # of the localhost port. -# host_webserver_port: "59001" +host_webserver_port: "8090" # The host port binding for the ddev-webserver can be explicitly specified. It is # dynamic unless otherwise specified. # This is not used by most people, most people use the *router* instead diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md new file mode 100644 index 000000000..accdc0bc1 --- /dev/null +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -0,0 +1,5 @@ +### Pull Request Checklist (Feature Branch to `next`): + +- [ ] Ich habe die neuesten Änderungen aus dem `next` Branch in meinen Feature-Branch gemergt. +- [ ] Das Code-Review wurde abgeschlossen. +- [ ] Fachliche Tests wurden durchgeführt und sind abgeschlossen. diff --git a/.github/workflows/build-api-docs.yaml b/.github/workflows/build-api-docs.yaml new file mode 100644 index 000000000..edaf381bf --- /dev/null +++ b/.github/workflows/build-api-docs.yaml @@ -0,0 +1,78 @@ +name: Build API Documentation + +on: + workflow_call: + outputs: + result: + description: "Build result" + value: ${{ jobs.build.outputs.result }} + +permissions: + contents: read + +jobs: + build: + runs-on: ubuntu-latest + outputs: + result: ${{ steps.set-result.outputs.result }} + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Set up PHP + uses: shivammathur/setup-php@v2 + with: + php-version: '8.0' + extensions: mbstring, json + tools: composer + + - name: Install PHP dependencies + run: | + composer install --working-dir=zmsapi + composer install --working-dir=zmscitizenapi + + - name: Run PHP post-install scripts + run: | + cd zmsapi && bin/configure && composer run-script post-install-cmd && cd .. + cd zmscitizenapi && bin/configure && composer run-script post-install-cmd && cd .. + + - name: Set up Node.js + uses: actions/setup-node@v4 + with: + node-version: '16' + + - name: Install Node.js dependencies + run: | + cd zmsapi && npm install && cd .. + cd zmscitizenapi && npm install && cd .. + + - name: Build projects + run: | + cd zmsapi && npm run build && cd .. + cd zmscitizenapi && npm run build && cd .. + + - name: Generate Swagger documentation + run: | + cd zmsapi + npm run doc + npx swagger-cli bundle -o public/doc/swagger.json public/doc/swagger.yaml + cd .. + + cd zmscitizenapi + npm run doc + npx swagger-cli bundle -o public/doc/swagger.json public/doc/swagger.yaml + cd .. + + - name: Upload API docs artifact + uses: actions/upload-artifact@v4 + with: + name: api-docs + retention-days: 7 + path: | + zmsapi/public/doc/ + zmscitizenapi/public/doc/ + + - name: Set job result + id: set-result + if: always() + run: echo "result=${{ job.status }}" >> $GITHUB_OUTPUT \ No newline at end of file diff --git a/.github/workflows/combined-workflow-with-docs.yaml b/.github/workflows/combined-workflow-with-docs.yaml new file mode 100644 index 000000000..1c1ab075e --- /dev/null +++ b/.github/workflows/combined-workflow-with-docs.yaml @@ -0,0 +1,106 @@ +name: Combined Workflow with Documentation + +on: + push: + branches: [next] + +permissions: + contents: read + packages: write + pages: write + id-token: write + +jobs: + call-php-code-quality: + uses: ./.github/workflows/php-code-quality.yaml + + call-php-unit-tests: + uses: ./.github/workflows/php-unit-tests.yaml + + call-owasp-security-checks: + uses: ./.github/workflows/owasp-security-checks.yaml + + aggregate-reports: + needs: [call-php-unit-tests, call-owasp-security-checks] + runs-on: ubuntu-latest + steps: + - name: Create directories + run: | + mkdir -p public/coverage + mkdir -p public/security + + - name: Download coverage reports + uses: actions/download-artifact@v4 + with: + pattern: 'coverage-*' + path: public/coverage-temp + merge-multiple: false + + - name: Download security reports + uses: actions/download-artifact@v4 + with: + pattern: 'security-report-*' + path: public/security-temp + merge-multiple: false + + - name: Fix directory structure + run: | + # Fix coverage reports + for module in zmsadmin zmscalldisplay zmscitizenapi zmsdldb zmsentities zmsmessaging zmsslim zmsstatistic zmsticketprinter zmsapi zmsdb zmsclient; do + if [ -d "public/coverage-temp/coverage-$module" ]; then + mkdir -p "public/coverage/coverage-$module" + mv "public/coverage-temp/coverage-$module"/* "public/coverage/coverage-$module/" + fi + done + rm -rf public/coverage-temp + + # Fix security reports + for module in zmsadmin zmscalldisplay zmscitizenapi zmsdldb zmsentities zmsmessaging zmsslim zmsstatistic zmsticketprinter zmsapi zmsdb zmsclient; do + if [ -f "public/security-temp/security-report-$module/dependency-check-report.html" ]; then + mkdir -p "public/security/security-report-$module" + mv "public/security-temp/security-report-$module/dependency-check-report.html" "public/security/security-report-$module/" + fi + done + rm -rf public/security-temp + + - name: Debug - List final structure + run: | + echo "=== Final Coverage Structure ===" + ls -R public/coverage/ + echo "=== Final Security Structure ===" + ls -R public/security/ + + - name: Upload aggregated reports + uses: actions/upload-artifact@v4 + with: + name: aggregated-reports + path: public/ + retention-days: 7 + + call-build-api-docs: + uses: ./.github/workflows/build-api-docs.yaml + + generate-schema-diagrams: + uses: ./.github/workflows/generate-schema-diagrams.yaml + + deploy-to-pages: + needs: [aggregate-reports, call-build-api-docs, generate-schema-diagrams] + if: | + needs.call-build-api-docs.result == 'success' && + needs.aggregate-reports.result == 'success' && + needs.generate-schema-diagrams.result == 'success' + uses: ./.github/workflows/deploy-pages.yaml + with: + coverage_artifact: aggregated-reports + api_docs_artifact: api-docs + security_artifact: aggregated-reports + git_commit: ${{ github.sha }} + git_branch: ${{ github.ref_name }} + + call-php-build-images: + needs: [call-php-code-quality, call-php-unit-tests] + if: | + always() && + needs.call-php-code-quality.result == 'success' && + needs.call-php-unit-tests.result == 'success' + uses: ./.github/workflows/php-build-images.yaml \ No newline at end of file diff --git a/.github/workflows/combined-workflow.yaml b/.github/workflows/combined-workflow.yaml index 8524121c4..bf184b26d 100644 --- a/.github/workflows/combined-workflow.yaml +++ b/.github/workflows/combined-workflow.yaml @@ -1,11 +1,28 @@ name: Combined Workflow -on: [push] +on: + push: + branches-ignore: [next] + workflow_call: + +permissions: + contents: read + packages: write jobs: - call-unit-tests: - uses: ./.github/workflows/unit-tests.yaml + call-php-code-quality: + uses: ./.github/workflows/php-code-quality.yaml + + call-php-unit-tests: + uses: ./.github/workflows/php-unit-tests.yaml + + call-owasp-security-checks: + uses: ./.github/workflows/owasp-security-checks.yaml - call-build-images: - needs: call-unit-tests - uses: ./.github/workflows/build-images.yaml + call-php-build-images: + needs: [call-php-code-quality, call-php-unit-tests] + if: | + always() && + needs.call-php-code-quality.result == 'success' && + needs.call-php-unit-tests.result == 'success' + uses: ./.github/workflows/php-build-images.yaml \ No newline at end of file diff --git a/.github/workflows/deploy-pages.yaml b/.github/workflows/deploy-pages.yaml new file mode 100644 index 000000000..3d646d5fb --- /dev/null +++ b/.github/workflows/deploy-pages.yaml @@ -0,0 +1,227 @@ +name: Deploy to GitHub Pages + +on: + workflow_call: + inputs: + coverage_artifact: + description: 'Name of the coverage reports artifact' + required: true + type: string + default: 'coverage-reports' + api_docs_artifact: + description: 'Name of the API documentation artifact' + required: true + type: string + default: 'api-docs' + security_artifact: + description: 'Name of the security reports artifact' + required: true + type: string + default: 'security-reports' + git_commit: + description: 'Git commit hash' + required: true + type: string + git_branch: + description: 'Git branch name' + required: true + type: string + +permissions: + pages: write + id-token: write + contents: read + +concurrency: + group: "pages" + cancel-in-progress: false + +jobs: + deploy: + runs-on: ubuntu-latest + environment: + name: github-pages + url: ${{ steps.deployment.outputs.page_url }} + steps: + - name: Create public directory + run: mkdir -p public + + - name: Download schema diagrams + uses: actions/download-artifact@v4 + with: + name: schema-diagrams + path: public/diagrams + + - name: Download coverage reports + uses: actions/download-artifact@v4 + with: + name: ${{ inputs.coverage_artifact }} + path: public + + - name: Download API docs + uses: actions/download-artifact@v4 + with: + name: ${{ inputs.api_docs_artifact }} + path: public + + - name: Download security reports + uses: actions/download-artifact@v4 + with: + name: ${{ inputs.security_artifact }} + path: public + + - name: Create index page + run: | + cat > public/index.html <<'EOL' + + + + eAppointment Documentation + + + +

ZMS eAppointment Technical Documentation

+ +
+
+

API Documentation

+ +
+ +
+

Code Test Coverage Reports

+ +
+ +
+

OWASP Security Reports

+ +
+
+ + + + EOL + + - name: Download coverage reports + uses: actions/download-artifact@v4 + with: + name: ${{ inputs.coverage_artifact }} + path: public + + - name: Download API docs + uses: actions/download-artifact@v4 + with: + name: ${{ inputs.api_docs_artifact }} + path: public + + - name: Download security reports + uses: actions/download-artifact@v4 + with: + name: ${{ inputs.security_artifact }} + path: public + + - name: Debug - List directory structure + run: | + echo "=== Directory Structure ===" + ls -R public/ + + - name: Setup Pages + uses: actions/configure-pages@v5 + + - name: Upload Pages artifact + uses: actions/upload-pages-artifact@v3 + with: + path: public + + - name: Deploy to GitHub Pages + id: deployment + uses: actions/deploy-pages@v4 \ No newline at end of file diff --git a/.github/workflows/generate-schema-diagrams.yaml b/.github/workflows/generate-schema-diagrams.yaml new file mode 100644 index 000000000..72a5294fb --- /dev/null +++ b/.github/workflows/generate-schema-diagrams.yaml @@ -0,0 +1,410 @@ +name: Generate Schema Diagrams + +on: + workflow_call: + outputs: + diagrams_artifact: + description: 'Name of the generated diagrams artifact' + value: 'schema-diagrams' + +permissions: + contents: read + +jobs: + generate: + runs-on: ubuntu-latest + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Create public directory + run: mkdir -p public/diagrams + + - name: Setup Python + uses: actions/setup-python@v5 + with: + python-version: '3.12' + + - name: Install dependencies + run: | + pip install jsonschema jinja2 + + - name: Create schema converter script + run: | + cat > convert_schema.py <<'EOL' + import json + import os + from pathlib import Path + from jsonschema import validators + from typing import Dict, Any, Set, Union, List, Tuple + from jinja2 import Template + + def sanitize_name(name: str, prefix: str = '') -> str: + """Sanitize name for Mermaid compatibility.""" + name = name.replace('-', '_').replace(' ', '_') + return f"{prefix}_{name}" if prefix else f"c_{name}" + + def get_type_name(type_def: Union[str, list, dict]) -> str: + """Convert JSON Schema type to Mermaid type.""" + if isinstance(type_def, list): + return 'any' + if isinstance(type_def, dict): + return type_def.get('type', 'any') + return type_def + + def load_all_schemas() -> List[Tuple[str, Dict[str, Any], str]]: + """Load all schema files with their prefixes.""" + schemas = [] + + # Load main schemas + main_dir = Path('zmsentities/schema') + if main_dir.exists(): + for file in main_dir.glob('*.json'): + with open(file) as f: + schemas.append((file.stem, json.load(f), '')) + + # Load citizen schemas + citizen_dir = Path('zmsentities/schema/citizenapi') + if citizen_dir.exists(): + for file in citizen_dir.glob('*.json'): + with open(file) as f: + schemas.append((file.stem, json.load(f), 'citizen')) + + return schemas + + def convert_schemas_to_mermaid(schemas: List[Tuple[str, Dict[str, Any], str]], prefix_filter: str = None) -> str: + """Convert schemas to a Mermaid diagram, optionally filtering by prefix.""" + # Filter schemas if prefix is provided + if prefix_filter is not None: + schemas = [(name, schema, prefix) for name, schema, prefix in schemas if prefix == prefix_filter] + + classes = [] + relationships = [] + processed = set() + schema_classes = {} # Map schema names to their class names + + def process_schema(schema: Dict[str, Any], class_name: str) -> None: + if class_name in processed: + return + processed.add(class_name) + + class_def = [f'class {class_name} {{\n'] + properties = schema.get('properties', {}) + required = schema.get('required', []) + + if not properties: + class_def.append(' +any _empty\n') + + for prop_name, prop_schema in properties.items(): + prop_type = get_type_name(prop_schema.get('type', 'any')) + is_required = prop_name in required + + # Check for references to other schemas + ref = prop_schema.get('$ref', '') + if ref: + ref_name = ref.split('/')[-1].replace('.json', '') + if ref_name in schema_classes: + relationships.append(f'{class_name} --> {schema_classes[ref_name]}') + class_def.append(f' +{schema_classes[ref_name]} {prop_name}{"" if is_required else "?"}\n') + continue + + if prop_type == 'array': + items = prop_schema.get('items', {}) + item_ref = items.get('$ref', '') + if item_ref: + ref_name = item_ref.split('/')[-1].replace('.json', '') + if ref_name in schema_classes: + relationships.append(f'{class_name} --> "*" {schema_classes[ref_name]}') + class_def.append(f' +{schema_classes[ref_name]}[] {prop_name}{"" if is_required else "?"}\n') + continue + + if items.get('type') == 'object': + item_class_name = f'{class_name}_{sanitize_name(prop_name)}_Item' + relationships.append(f'{class_name} --> "*" {item_class_name}') + process_schema(items, item_class_name) + class_def.append(f' +{item_class_name}[] {prop_name}{"" if is_required else "?"}\n') + else: + item_type = get_type_name(items.get('type', 'any')) + class_def.append(f' +{item_type}[] {prop_name}{"" if is_required else "?"}\n') + elif prop_type == 'object': + nested_class_name = f'{class_name}_{sanitize_name(prop_name)}' + relationships.append(f'{class_name} --> {nested_class_name}') + process_schema(prop_schema, nested_class_name) + class_def.append(f' +{nested_class_name} {prop_name}{"" if is_required else "?"}\n') + else: + class_def.append(f' +{prop_type} {prop_name}{"" if is_required else "?"}\n') + + class_def.append('}\n\n') + classes.append(''.join(class_def)) + + # First pass: collect all schema class names + for name, schema, prefix in schemas: + class_name = sanitize_name(name, prefix) + schema_classes[name] = class_name + + # Second pass: process all schemas + for name, schema, prefix in schemas: + class_name = schema_classes[name] + process_schema(schema, class_name) + + # Combine all elements + mermaid = ['classDiagram\n'] + mermaid.extend(classes) + mermaid.extend(f'{r}\n' for r in relationships) + + return ''.join(mermaid) + + def create_diagram_page(title: str, mermaid: str) -> str: + """Create HTML page with diagram.""" + template = Template(""" + + + {{ title }} + + + + + +

{{ title }}

+ + +
+ + + + 90% +
+ +
+
+
{{ mermaid }}
+
+
+ + + + """) + return template.render(title=title, mermaid=mermaid) + + if __name__ == '__main__': + print("Starting schema conversion...") + diagrams_dir = Path('public/diagrams') + diagrams_dir.mkdir(parents=True, exist_ok=True) + + # Load all schemas + schemas = load_all_schemas() + + # Generate separate diagrams + zmsapi_mermaid = convert_schemas_to_mermaid(schemas, prefix_filter='') + zmscitizenapi_mermaid = convert_schemas_to_mermaid(schemas, prefix_filter='citizen') + combined_mermaid = convert_schemas_to_mermaid(schemas) + + # Create pages + diagrams_dir.joinpath('zmsapi.html').write_text( + create_diagram_page('ZMS API Schema', zmsapi_mermaid) + ) + diagrams_dir.joinpath('zmscitizenapi.html').write_text( + create_diagram_page('ZMS Citizen API Schema', zmscitizenapi_mermaid) + ) + diagrams_dir.joinpath('index.html').write_text( + create_diagram_page('Combined API Schemas', combined_mermaid) + ) + + print("Schema conversion complete") + EOL + + - name: Generate schema diagrams and pages + run: python convert_schema.py + + - name: Upload diagrams artifact + uses: actions/upload-artifact@v4 + with: + name: schema-diagrams + path: public/diagrams/ + retention-days: 1 \ No newline at end of file diff --git a/.github/workflows/owasp-security-checks.yaml b/.github/workflows/owasp-security-checks.yaml new file mode 100644 index 000000000..569dfc391 --- /dev/null +++ b/.github/workflows/owasp-security-checks.yaml @@ -0,0 +1,129 @@ +name: owasp security checks + +on: + workflow_call: + outputs: + security-scan-result: + description: "Security scan result" + value: ${{ jobs.security-scan.outputs.result }} + +jobs: + security-scan: + runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + include: + - module: zmsadmin + php_version: "8.0" + - module: zmsapi + php_version: "8.0" + - module: zmscalldisplay + php_version: "8.0" + - module: zmscitizenapi + php_version: "8.0" + - module: zmsmessaging + php_version: "8.0" + - module: zmsstatistic + php_version: "8.0" + - module: zmsticketprinter + php_version: "8.0" + - module: zmsdldb + php_version: "8.0" + - module: zmsentities + php_version: "8.0" + - module: zmsslim + php_version: "8.0" + - module: zmsdb + php_version: "8.0" + - module: zmsclient + php_version: "8.0" + container: + image: "ghcr.io/it-at-m/eappointment-php-base:${{ matrix.php_version }}-dev" + outputs: + result: ${{ steps.set-result.outputs.result }} + steps: + - uses: actions/checkout@v4 + + - name: Install python3 + run: | + apt update + apt install -y python3 python3-click python3-git + + - name: Reference local modules + run: python3 ./cli modules reference-libraries --no-symlink + + - name: Install Composer Dependencies + continue-on-error: true + run: | + cd "${{ matrix.module }}" + composer install --no-progress --prefer-dist --optimize-autoloader + + - name: PHP Security Checker + continue-on-error: true + uses: symfonycorp/security-checker-action@v5 + with: + lock: ${{ matrix.module }}/composer.lock + + - name: OWASP Dependency-Check + continue-on-error: true + uses: dependency-check/Dependency-Check_Action@main + with: + project: ${{ matrix.module }} + path: ${{ matrix.module }} + format: 'HTML' + out: 'reports/${{ matrix.module }}' + args: > + --scan ${{ matrix.module }}/composer.lock + --suppression .security/suppression.xml + --failOnCVSS 7 + + - name: Upload security report + if: always() + uses: actions/upload-artifact@v4 + with: + name: security-report-${{ matrix.module }} + path: reports/${{ matrix.module }} + retention-days: 1 + + - name: Set job result + id: set-result + if: always() + run: echo "result=${{ job.status }}" >> $GITHUB_OUTPUT + + zap-scan: + needs: security-scan + runs-on: ubuntu-latest + continue-on-error: true + strategy: + fail-fast: false + matrix: + include: + - module: zmsapi + url: http://localhost:8080/api + - module: zmscitizenapi + url: http://localhost:8080/citizen-api + steps: + - uses: actions/checkout@v4 + + - name: Run OWASP ZAP Scan + continue-on-error: true + uses: zaproxy/action-full-scan@v0.12.0 + with: + target: ${{ matrix.url }} + rules_file_name: '.zap/rules.tsv' + cmd_options: '-a' + allow_issue_writing: false + + - name: Security Headers Check + continue-on-error: true + run: | + curl -s -I "${{ matrix.url }}" | grep -iE '^(Strict-Transport-Security|X-Frame-Options|X-Content-Type-Options|Content-Security-Policy|X-XSS-Protection):' + + - name: Upload ZAP scan report + if: always() + uses: actions/upload-artifact@v4 + with: + name: zap-scan-${{ matrix.module }} + path: reports/ + retention-days: 1 \ No newline at end of file diff --git a/.github/workflows/build-images.yaml b/.github/workflows/php-build-images.yaml similarity index 96% rename from .github/workflows/build-images.yaml rename to .github/workflows/php-build-images.yaml index 3b913d38e..1fed03e70 100644 --- a/.github/workflows/build-images.yaml +++ b/.github/workflows/php-build-images.yaml @@ -45,6 +45,8 @@ jobs: php_version: "8.0" - module: zmscalldisplay php_version: "8.0" + - module: zmscitizenapi + php_version: "8.0" - module: zmsmessaging php_version: "8.0" - module: zmsstatistic diff --git a/.github/workflows/php-code-quality.yaml b/.github/workflows/php-code-quality.yaml new file mode 100644 index 000000000..9a6c0cd32 --- /dev/null +++ b/.github/workflows/php-code-quality.yaml @@ -0,0 +1,59 @@ +name: code quality + +on: + workflow_call: + +jobs: + module-code-quality: + runs-on: ubuntu-latest + container: + image: "ghcr.io/it-at-m/eappointment-php-base:${{ matrix.php_version }}-dev" + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Install Composer Dependencies + run: | + cd "${{ matrix.module }}" + composer install --no-progress --prefer-dist --optimize-autoloader + + - name: PHPCS / PSR-12 + run: | + cd "${{ matrix.module }}" + echo "Running phpcs (ignoring warnings)..." + vendor/bin/phpcs --standard=psr12 --warning-severity=0 src/ + + - name: PHPMD + run: | + cd "${{ matrix.module }}" + echo "Running phpmd..." + vendor/bin/phpmd src/ text ../phpmd.rules.xml + + strategy: + fail-fast: false + matrix: + include: + - module: zmsadmin + php_version: "8.0" + - module: zmsapi + php_version: "8.0" + - module: zmscalldisplay + php_version: "8.0" + - module: zmscitizenapi + php_version: "8.0" + - module: zmsclient + php_version: "8.0" + - module: zmsdb + php_version: "8.0" + - module: zmsdldb + php_version: "8.0" + - module: zmsentities + php_version: "8.0" + - module: zmsmessaging + php_version: "8.0" + - module: zmsslim + php_version: "8.0" + - module: zmsstatistic + php_version: "8.0" + - module: zmsticketprinter + php_version: "8.0" \ No newline at end of file diff --git a/.github/workflows/unit-tests.yaml b/.github/workflows/php-unit-tests.yaml similarity index 51% rename from .github/workflows/unit-tests.yaml rename to .github/workflows/php-unit-tests.yaml index 57f376e2c..bde14a4e2 100644 --- a/.github/workflows/unit-tests.yaml +++ b/.github/workflows/php-unit-tests.yaml @@ -2,23 +2,72 @@ name: unit tests on: workflow_call: + outputs: + module-test-result: + description: "Module test result" + value: ${{ jobs.module-test.outputs.result }} + zmsapi-test-result: + description: "ZMSAPI test result" + value: ${{ jobs.zmsapi-test.outputs.result }} + zmsdb-test-result: + description: "ZMSDB test result" + value: ${{ jobs.zmsdb-test.outputs.result }} + zmsclient-test-result: + description: "ZMSClient test result" + value: ${{ jobs.zmsclient-test.outputs.result }} jobs: module-test: runs-on: ubuntu-latest container: - image: "registry.gitlab.com/eappointment/php-base:${{ matrix.php_version }}-dev" + image: "ghcr.io/it-at-m/eappointment-php-base:${{ matrix.php_version }}-dev" + env: + XDEBUG_MODE: coverage + PHP_INI_SCAN_DIR: "/usr/local/etc/php/conf.d:/tmp/php/conf.d" + outputs: + result: ${{ steps.set-result.outputs.result }} steps: - - name: Checkout GitHub Action - uses: actions/checkout@main + - uses: actions/checkout@main + + - name: Configure PHP + run: | + mkdir -p /tmp/php/conf.d + echo "zend_extension=xdebug.so" > /tmp/php/conf.d/xdebug.ini + echo "xdebug.mode=coverage" >> /tmp/php/conf.d/xdebug.ini + echo "xdebug.start_with_request=yes" >> /tmp/php/conf.d/xdebug.ini + - name: Install Composer Dependencies run: | cd "${{ matrix.module }}" composer install --no-progress --prefer-dist --optimize-autoloader - - name: Run Unit Tests + + - name: Run Unit Tests with Coverage + env: + XDEBUG_MODE: coverage run: | cd "${{ matrix.module }}" - ./vendor/bin/phpunit + mkdir -p coverage + echo "Running tests with coverage for ${{ matrix.module }}" + php -dmemory_limit=-1 \ + ./vendor/bin/phpunit -c phpunit.xml \ + --coverage-html coverage/html \ + --coverage-clover coverage/clover.xml \ + --log-junit coverage/junit.xml || if [ $? -eq 2 ] && [ -f "coverage/clover.xml" ]; then exit 0; fi + echo "Coverage files generated:" + ls -la coverage/ + + - name: Upload Coverage + uses: actions/upload-artifact@v4 + with: + name: coverage-${{ matrix.module }} + path: ${{ matrix.module }}/coverage/ + retention-days: 1 + + - name: Set job result + id: set-result + if: always() + run: echo "result=${{ job.status }}" >> $GITHUB_OUTPUT + strategy: fail-fast: false matrix: @@ -27,6 +76,8 @@ jobs: php_version: "8.0" - module: zmscalldisplay php_version: "8.0" + - module: zmscitizenapi + php_version: "8.0" - module: zmsdldb php_version: "8.0" - module: zmsentities @@ -40,14 +91,13 @@ jobs: - module: zmsticketprinter php_version: "8.0" - zmsapi-test: runs-on: ubuntu-latest container: - image: "registry.gitlab.com/eappointment/php-base:8.0-dev" + image: "ghcr.io/it-at-m/eappointment-php-base:8.0-dev" services: mariadb: - image: mariadb:10.6 + image: mariadb:10.11 env: MYSQL_ROOT_PASSWORD: zmsapi MYSQL_DATABASE: zmsbo @@ -89,17 +139,33 @@ jobs: ln -s vendor/eappointment/zmsdb/tests/Zmsdb/fixtures data echo "Importing test data..." vendor/bin/importTestData --commit - php -dzend_extension=xdebug.so -dmemory_limit=-1 vendor/bin/phpunit \ - -v --colors=never --coverage-text --coverage-html public/_tests/coverage/ --log-junit \ - public/_tests/junit.xml + mkdir -p coverage + echo "Running tests with coverage" + php -dzend_extension=xdebug.so -dxdebug.mode=coverage -dmemory_limit=-1 \ + ./vendor/bin/phpunit -c phpunit.xml \ + --coverage-html coverage/html \ + --coverage-clover coverage/clover.xml \ + --log-junit coverage/junit.xml + echo "Coverage files generated:" + ls -la coverage/ + - name: Upload Coverage + uses: actions/upload-artifact@v4 + with: + name: coverage-zmsapi + path: zmsapi/coverage/ + retention-days: 1 + - name: Set job result + id: set-result + if: always() + run: echo "result=${{ job.status }}" >> $GITHUB_OUTPUT zmsdb-test: runs-on: ubuntu-latest container: - image: "registry.gitlab.com/eappointment/php-base:8.0-dev" + image: "ghcr.io/it-at-m/eappointment-php-base:8.0-dev" services: mariadb: - image: mariadb:10.6 + image: mariadb:10.11 env: MYSQL_ROOT_PASSWORD: zmsdb MYSQL_DATABASE: zmsbo @@ -134,18 +200,35 @@ jobs: MYSQL_PORT: "tcp://mariadb:3306" MYSQL_DATABASE: zmsbo MYSQL_ROOT_PASSWORD: zmsdb + XDEBUG_MODE: coverage run: | cd zmsdb bin/importTestData --commit - php -dzend_extension=xdebug.so -dmemory_limit=-1 vendor/bin/phpunit \ - -v --colors=never --coverage-text --coverage-html public/_tests/coverage/ --log-junit \ - public/_tests/junit.xml - - - + mkdir -p coverage + echo "Running tests with coverage" + php -i | grep -i xdebug + php -dzend_extension=xdebug.so -dxdebug.mode=coverage -dmemory_limit=-1 \ + ./vendor/bin/phpunit -c phpunit.xml \ + --coverage-html coverage/html \ + --coverage-clover coverage/clover.xml \ + --log-junit coverage/junit.xml + echo "Coverage files generated:" + ls -la coverage/ + - name: Upload Coverage + uses: actions/upload-artifact@v4 + with: + name: coverage-zmsdb + path: zmsdb/coverage/ + retention-days: 1 + - name: Set job result + id: set-result + if: always() + run: echo "result=${{ job.status }}" >> $GITHUB_OUTPUT zmsclient-test: runs-on: ubuntu-latest + outputs: + result: ${{ steps.set-result.outputs.result }} steps: - name: Checkout GitHub Action uses: actions/checkout@main @@ -164,11 +247,6 @@ jobs: echo "Installing Composer dependencies for $module" (cd "$module" && composer install --no-progress --prefer-dist --optimize-autoloader) done - - name: Install Docker Compose - run: | - sudo curl -L "https://github.com/docker/compose/releases/download/1.29.2/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose - sudo chmod +x /usr/local/bin/docker-compose - docker-compose --version - name: Install Docker Compose run: | @@ -178,4 +256,24 @@ jobs: - name: Run Unit Tests run: | - /bin/bash -c "cd ./zmsclient && docker-compose up -d && docker-compose exec -T test ./vendor/bin/phpunit" + cd ./zmsclient + mkdir -p coverage + docker-compose up -d + docker-compose exec -T test php -dzend_extension=xdebug.so -dmemory_limit=-1 \ + ./vendor/bin/phpunit \ + --coverage-html coverage/html \ + --coverage-clover coverage/clover.xml \ + --log-junit coverage/junit.xml + echo "Coverage files generated:" + ls -la coverage/ + - name: Upload Coverage + uses: actions/upload-artifact@v4 + with: + name: coverage-zmsclient + path: zmsclient/coverage/ + retention-days: 1 + + - name: Set job result + id: set-result + if: always() + run: echo "result=${{ job.status }}" >> $GITHUB_OUTPUT \ No newline at end of file diff --git a/.github/workflows/static.yaml b/.github/workflows/static.yaml deleted file mode 100644 index 22ce2b1ba..000000000 --- a/.github/workflows/static.yaml +++ /dev/null @@ -1,91 +0,0 @@ -# Workflow for deploying zmsapi PHP and Node.js static content to Pages -name: Deploy zmsapi static content to Pages - -on: - # Runs on pushes targeting the default branch (main) - push: - branches: ["main"] - - # Allows you to run this workflow manually from the Actions tab - workflow_dispatch: - -# Sets permissions of the GITHUB_TOKEN to allow deployment to GitHub Pages -permissions: - contents: read - pages: write - id-token: write - -# Allow only one concurrent deployment, skipping runs queued between the run in-progress and latest queued. -concurrency: - group: "pages" - cancel-in-progress: false - -jobs: - deploy: - environment: - name: github-pages - url: ${{ steps.deployment.outputs.page_url }} - runs-on: ubuntu-latest - - steps: - # Step 1: Checkout the repository - - name: Checkout - uses: actions/checkout@v4 - - # Step 2: Set up PHP environment - - name: Set up PHP - uses: shivammathur/setup-php@v2 - with: - php-version: '8.0' # Specify PHP version as per your requirement - extensions: mbstring, json - tools: composer - - # Step 3: Install PHP dependencies for zmsapi - - name: Install PHP dependencies - run: | - composer install --working-dir=zmsapi - - # Step 4: Run PHP post-install scripts for zmsapi - - name: Run PHP post-install scripts - run: | - cd zmsapi && bin/configure && composer run-script post-install-cmd && cd .. - - # Step 5: Set up Node.js environment - - name: Set up Node.js - uses: actions/setup-node@v3 - with: - node-version: '16' # Specify Node.js version as needed - - # Step 6: Install Node.js dependencies for zmsapi - - name: Install Node.js dependencies for zmsapi - run: | - cd zmsapi - npm install - - # Step 7: Build the project for zmsapi - - name: Build zmsapi - run: | - cd zmsapi - npm run build - - # Step 8: Generate Swagger documentation for zmsapi - - name: Generate Swagger documentation - run: | - cd zmsapi - npm run doc - npx swagger-cli bundle -o public/doc/swagger.json public/doc/swagger.yaml - - # Step 9: Set up GitHub Pages for deployment - - name: Setup Pages - uses: actions/configure-pages@v5 - - # Step 10: Upload the static content to GitHub Pages - - name: Upload artifact - uses: actions/upload-pages-artifact@v3 - with: - path: '.' # Upload the entire repository, including the generated docs - - # Step 11: Deploy to GitHub Pages - - name: Deploy to GitHub Pages - id: deployment - uses: actions/deploy-pages@v4 diff --git a/.gitignore b/.gitignore index b24773838..404a67129 100644 --- a/.gitignore +++ b/.gitignore @@ -7,3 +7,4 @@ .vscode/settings.json .vscode/tasks.json .vscode/terminals.json +coverage/ diff --git a/.htaccess b/.htaccess index 8826a3601..5d49aac60 100644 --- a/.htaccess +++ b/.htaccess @@ -39,6 +39,14 @@ RewriteRule ^terminvereinbarung/calldisplay(.*) /var/www/html/zmscalldisplay/pub RewriteRule ^terminvereinbarung/calldisplay/+_(.*) /var/www/html/zmscalldisplay/public/_$1 [QSA] +# zmscitizenapi +SetEnvIf Request_URI ^/zmscitizenapi ZMS_MODULE_BASEPATH=/terminvereinbarung/api/citizen +RewriteCond %{REQUEST_URI} !^/terminvereinbarung/api/citizen/+(_|doc) +RewriteCond %{REQUEST_FILENAME} !-f +RewriteRule ^terminvereinbarung/api/citizen(/.*)?$ /var/www/html/zmscitizenapi/public/index.php$1 [L] +RewriteRule ^terminvereinbarung/api/citizen/+(doc|_)(.*) /var/www/html/zmscitizenapi/public/$1$2 [QSA] + + # zmsstatistic SetEnvIf Request_URI ^/zmsstatistic ZMS_MODULE_BASEPATH=/terminvereinbarung/statistic RewriteCond %{REQUEST_URI} !^/terminvereinbarung/[^/]+/+_ diff --git a/.resources/Containerfile b/.resources/Containerfile index 93011d48e..48b04b778 100644 --- a/.resources/Containerfile +++ b/.resources/Containerfile @@ -1,5 +1,5 @@ ARG PHP_VERSION -FROM registry.gitlab.com/eappointment/php-base:${PHP_VERSION}-dev as build +FROM ghcr.io/it-at-m/eappointment-php-base:${PHP_VERSION}-dev as build RUN useradd --shell /bin/bash --create-home build COPY --chown=build:build . /build ARG MODULE @@ -8,8 +8,7 @@ USER build:build RUN make live ARG PHP_VERSION -FROM registry.gitlab.com/eappointment/php-base:${PHP_VERSION}-base +FROM ghcr.io/it-at-m/eappointment-php-base:${PHP_VERSION}-base RUN docker-apt-clean-install libfcgi-bin ARG MODULE COPY --from=build --chown=0:0 /build/${MODULE} /var/www/html - diff --git a/.security/suppression.xml b/.security/suppression.xml new file mode 100644 index 000000000..9a56c50b7 --- /dev/null +++ b/.security/suppression.xml @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/.zap/rules.tsv b/.zap/rules.tsv new file mode 100644 index 000000000..5fceff1a0 --- /dev/null +++ b/.zap/rules.tsv @@ -0,0 +1,2 @@ +10096 IGNORE (Timestamp Disclosure - Unix) +10038 IGNORE (Content Security Policy (CSP) Header Not Set) \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md index 61eab132a..3e69b75b8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,152 @@ +## Release Muc-20 (14.02.2025) + +

Bug +

+ + +

Aufgabe +

+ + +

Story +

+ + + + + +## Release Muc-19 (17.01.2025) + +

Bug +

+ + +

Story +

+ + +## Release Muc-18 (13.12.2024) + +

Bug +

+ + +

Story +

+ + +## Release Muc-17 (29.11.2024) + +

Aufgabe +

+ + +

Bug +

+ + +

Story +

+ + ## Release Muc-16 (08.11.2024)

Aufgabe

diff --git a/README.md b/README.md index c584f59e6..be1072713 100644 --- a/README.md +++ b/README.md @@ -32,8 +32,7 @@ The ZMS system is intended to manage human waiting queues. It has the following * collecting statistics like waiting time or served clients per day * emergency call for employees -[ZMSAPI Documentation](https://it-at-m.github.io/eappointment/zmsapi/public/doc/index.html) -[ZMSCITIZENAPI Documentation](https://it-at-m.github.io/eappointment/zmscitizenapi/public/doc/index.html) +[Code Coverage ZMSAPI and ZMSCITIZENAPI Documentation](https://it-at-m.github.io/eappointment/) ## Contact [Overview](https://opensource.muenchen.de/software/zeitmanagementsystem.html) @@ -43,14 +42,14 @@ Munich Contact: it@M - opensource@muenchen.de BerlinOnline Stadtportal GmbH & Co KG and it@M. - + --- @@ -79,8 +78,7 @@ Das ZMS-System dient zur Verwaltung von Warteschlangen für Menschen. Es bietet * Sammeln von Statistiken wie Wartezeiten oder bedienten Kunden pro Tag * Notruf für Mitarbeiter -[ZMSAPI-Dokumentation](https://it-at-m.github.io/eappointment/zmsapi/public/doc/index.html) -[ZMSCITIZENAPI-Dokumentation](https://it-at-m.github.io/eappointment/zmscitizenapi/public/doc/index.html) +[Code-Abdeckung ZMSAPI und ZMSCITIZENAPI Dokumentation](https://it-at-m.github.io/eappointment/) ## Kontakt BerlinOnline Stadtportal GmbH & Co KG Kontakt: @@ -89,14 +87,14 @@ Munich Kontakt: it@M - opensource@muenchen.de BerlinOnline Stadtportal GmbH & Co KG und it@M. - + ---- @@ -117,6 +115,31 @@ e.g. - `ddev exec ./cli modules check-upgrade 8.2` - `ddev exec ./cli modules check-upgrade 8.3` +## Code Quality Checks +We use PHPCS (following PSR-12 standards) and PHPMD to maintain code quality and detect possible issues early. These checks run automatically in our GitHub Actions pipeline but can also be executed locally. + +To run Checks locally in your local docker container: + +0. Run all at once: +- `ddev exec "./cli modules loop 'vendor/bin/phpcs --standard=psr12 src/'"` +- `ddev exec "./cli modules loop 'vendor/bin/phpcbf --standard=psr12 src/'"` + +1. **Enter the container** (if using DDEV or Docker): +- `ddev ssh` + +2. **Go to the desired module directory: +- `cd zmsadmin` +3. Run PHPCS (PSR-12 standard): +- `vendor/bin/phpcs --standard=psr12 src/` +- ``` + You can automatically fix many PHPCS formatting issues by running: + - vendor/bin/phpcbf --standard=psr12 src/ + or + - phpcs --standard=psr12 --fix src/ + ``` +4. Run PHPMD (using the phpmd.rules.xml in the project root): +- `vendor/bin/phpmd src/ text ../phpmd.rules.xml` + ## Unit Testing To run unit tests locally refer to the Github Workflows: https://github.com/it-at-m/eappointment/blob/main/.github/workflows/unit-tests.yaml and in your local docker container run: @@ -140,6 +163,7 @@ To keep our branch names organized and easily understandable, we follow a specif 2. **project**: The project identifier. This should be: - `zms` for the ZMS project. + - `zmskvr` for the ZMSKVR project. - `mpdzbs` for the MPDZBS project. 3. **issue number**: The ticket or issue number related to this branch (use digits only). This helps track the branch to a specific issue in the project management system. @@ -154,20 +178,27 @@ To keep our branch names organized and easily understandable, we follow a specif - **Feature Branch**: `feature-zms-12345-this-is-a-feature-in-the-zms-project` - **Bugfix Branch**: `bugfix-mpdzbs-67890-fix-crash-on-startup` -- **Hotfix Branch**: `hotfix-zms-98765-critical-fix-for-login` +- **Hotfix Branch**: `hotfix-zmskvr-98765-critical-fix-for-login` - **Cleanup Branch**: `cleanup-mpdzbs-11111-remove-unused-code` - **Chore Branch**: `chore-zms-2964-composer-update` -- **Docs Branch**: `docs-zms-0000-update-readme` `docs-zms-release-40-update-changelog` +- **Docs Branch**: `docs-zmskvr-0000-update-readme` `docs-zms-release-40-update-changelog` #### Regular Expression The branch name must match the following regular expression: -`^(feature|hotfix|bugfix|cleanup|maintenance|docs)-(zms|mpdzbs)-[0-9]+-[a-z0-9-]+$` +`^(feature|hotfix|bugfix|cleanup|maintenance|docs)-(zms|zmskvr|mpdzbs)-[0-9]+-[a-z0-9-]+$` **For further commit rules please refer to https://www.conventionalcommits.org/en/v1.0.0-beta.4/** +- **feat(ZMS-123): commit message** +- **fix(ZMSKVR-123): commit message** +- **clean(ZMS-123): commit message** +- **chore(ZMSKVR-123): commit message** +- **docs(ZMS-123): commit message** ## Screenshot ![screenshot](https://github.com/user-attachments/assets/54d360e9-c47b-4f3c-b849-5966a8766af9) +![combined_image](https://github.com/user-attachments/assets/87902e60-fe90-48a0-bf60-c5edec210dc9) +

(back to top)

@@ -184,4 +215,3 @@ The branch name must match the following regular expression: [license-shield]: https://img.shields.io/github/license/it-at-m/eappointment.svg?style=for-the-badge [license-url]: https://github.com/it-at-m/eappointment/blob/main/LICENSE [product-screenshot]: images/screenshot.png - diff --git a/SECURITY.md b/SECURITY.md new file mode 100644 index 000000000..e8a41e3e9 --- /dev/null +++ b/SECURITY.md @@ -0,0 +1,19 @@ +## Reporting a Vulnerability (EN) +If you believe you have discovered a security vulnerability, please do not open a public issue. Instead, report it directly to us at: +opensource@muenchen.de. + +### What to Expect: +Acknowledgement: You will receive an initial response acknowledging receipt of your report within [timeframe, e.g., 0-2 weeks] depending on the severity. +Assessment: We will review the reported vulnerability and determine its validity and severity. You can expect updates throughout this process, with a typical assessment period of [timeframe, e.g., 7-10 days]. +Outcome: If the vulnerability is confirmed, we will work on a fix and notify you once a patch is available. If the report is declined, we will explain why the issue is not considered a security risk. +Thank you for helping us keep the project secure! + +## Meldung einer Sicherheitslücke (DE) +Wenn Sie glauben, eine Sicherheitslücke entdeckt zu haben, öffnen Sie bitte kein öffentliches Issue. Melden Sie das Problem stattdessen direkt an uns unter: +opensource@muenchen.de. + +### Was Sie erwarten können: +Bestätigung: Sie erhalten innerhalb von [Zeitrahmen, z. B. 0-2 Wochen] eine erste Antwort, die den Eingang Ihrer Meldung bestätigt je nach Schweregrad. +Bewertung: Wir werden die gemeldete Sicherheitslücke überprüfen und ihre Gültigkeit sowie Schwere bewerten. Sie können während dieses Prozesses mit regelmäßigen Updates rechnen, wobei eine typische Bewertungsdauer [Zeitrahmen, z. B. 7-10 Tage] beträgt. +Ergebnis: Wenn die Sicherheitslücke bestätigt wird, arbeiten wir an einer Lösung und benachrichtigen Sie, sobald ein Patch verfügbar ist. Wird die Meldung abgelehnt, erklären wir, warum das Problem nicht als Sicherheitsrisiko eingestuft wird. +Vielen Dank, dass Sie uns dabei helfen, das Projekt sicher zu halten! diff --git a/cli b/cli index b9c91682d..4f0f349c3 100755 --- a/cli +++ b/cli @@ -22,6 +22,7 @@ modules = [ "zmsadmin", "zmsapi", "zmscalldisplay", + "zmscitizenapi", "zmsclient", "zmsdb", "zmsdldb", diff --git a/mellon/composer.lock b/mellon/composer.lock index 3c5043616..1e907525f 100644 --- a/mellon/composer.lock +++ b/mellon/composer.lock @@ -2929,16 +2929,16 @@ }, { "name": "squizlabs/php_codesniffer", - "version": "3.10.3", + "version": "3.11.2", "source": { "type": "git", "url": "https://github.com/PHPCSStandards/PHP_CodeSniffer.git", - "reference": "62d32998e820bddc40f99f8251958aed187a5c9c" + "reference": "1368f4a58c3c52114b86b1abe8f4098869cb0079" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/PHPCSStandards/PHP_CodeSniffer/zipball/62d32998e820bddc40f99f8251958aed187a5c9c", - "reference": "62d32998e820bddc40f99f8251958aed187a5c9c", + "url": "https://api.github.com/repos/PHPCSStandards/PHP_CodeSniffer/zipball/1368f4a58c3c52114b86b1abe8f4098869cb0079", + "reference": "1368f4a58c3c52114b86b1abe8f4098869cb0079", "shasum": "" }, "require": { @@ -3005,7 +3005,7 @@ "type": "open_collective" } ], - "time": "2024-09-18T10:38:58+00:00" + "time": "2024-12-11T16:04:26+00:00" }, { "name": "symfony/config", diff --git a/mellon/phpunit.xml.dist b/mellon/phpunit.xml.dist index 73763f0ef..928b65eaf 100644 --- a/mellon/phpunit.xml.dist +++ b/mellon/phpunit.xml.dist @@ -10,7 +10,7 @@ convertWarningsToExceptions="true" processIsolation="false" forceCoversAnnotation="false" - stopOnFailure="true" + stopOnFailure="false" bootstrap="tests/bootstrap.php" > @@ -22,5 +22,13 @@ ./src + + ./vendor + ./tests + + + + + diff --git a/mellon/src/Mellon/Collection.php b/mellon/src/Mellon/Collection.php index 19646fa79..73fd238b4 100644 --- a/mellon/src/Mellon/Collection.php +++ b/mellon/src/Mellon/Collection.php @@ -1,4 +1,5 @@ value) + if ( + is_array($this->value) || ( $this->value instanceof \Traversable && $this->value instanceof \Countable diff --git a/mellon/src/Mellon/ValidBool.php b/mellon/src/Mellon/ValidBool.php index 9be75c818..519b04752 100644 --- a/mellon/src/Mellon/ValidBool.php +++ b/mellon/src/Mellon/ValidBool.php @@ -1,4 +1,5 @@ fileName])) { - if ((! empty($_FILES[$this->fileName]["type"])) && + if ( + (! empty($_FILES[$this->fileName]["type"])) && ($_FILES[$this->fileName]['type'] != $this->acceptableTypes[$type]) ) { $this->setFailure($message); diff --git a/mellon/src/Mellon/ValidJson.php b/mellon/src/Mellon/ValidJson.php index 8929f0d95..8473a84df 100644 --- a/mellon/src/Mellon/ValidJson.php +++ b/mellon/src/Mellon/ValidJson.php @@ -1,4 +1,5 @@ + + Mein PHPMD-Konfigurations-File + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/zmsadmin/.gitignore b/zmsadmin/.gitignore index 6cba15bb5..eca95fd4c 100644 --- a/zmsadmin/.gitignore +++ b/zmsadmin/.gitignore @@ -21,3 +21,4 @@ public/_css/admin.css public/_css/admin.css.map .cache .phpunit.* +coverage diff --git a/zmsadmin/.gitlab-ci.yml b/zmsadmin/.gitlab-ci.yml index eafacbb2d..e2cc82aa8 100644 --- a/zmsadmin/.gitlab-ci.yml +++ b/zmsadmin/.gitlab-ci.yml @@ -1,10 +1,10 @@ include: remote: https://gitlab.com/eappointment/php-base/-/raw/main/template-php-ci.yml -test-php-73: +test-php-80: variables: - PHP_VERSION: 7.3 - image: registry.gitlab.com/eappointment/php-base:${PHP_VERSION}-dev + PHP_VERSION: 8.0 + image: ghcr.io/it-at-m/eappointment-php-base:${PHP_VERSION}-dev script: - $CLI_COMPOSER - $CLI_PHPCS @@ -19,7 +19,7 @@ test-php-73: - npm install --legacy-peer-deps - make css variables: - PHP_VERSION: "7.3" + PHP_VERSION: "8.0" only: changes: - .gitlab-ci.yml diff --git a/zmsadmin/Dockerfile b/zmsadmin/Dockerfile index 09c7a8859..485cc6232 100644 --- a/zmsadmin/Dockerfile +++ b/zmsadmin/Dockerfile @@ -1,9 +1,9 @@ ARG PHP_VERSION -FROM registry.gitlab.com/eappointment/php-base:${PHP_VERSION}-dev as build +FROM ghcr.io/it-at-m/eappointment-php-base:${PHP_VERSION}-dev as build COPY --chown=1000:1000 . /build WORKDIR /build USER 1000:1000 RUN make live -FROM registry.gitlab.com/eappointment/php-base:${PHP_VERSION}-base +FROM ghcr.io/it-at-m/eappointment-php-base:${PHP_VERSION}-base COPY --from=build --chown=0:0 /build /var/www/html \ No newline at end of file diff --git a/zmsadmin/composer.lock b/zmsadmin/composer.lock index 860efefe2..f6aa47805 100644 --- a/zmsadmin/composer.lock +++ b/zmsadmin/composer.lock @@ -158,7 +158,7 @@ }, { "name": "eappointment/mellon", - "version": "dev-main", + "version": "dev-next", "dist": { "type": "path", "url": "../mellon", @@ -206,7 +206,7 @@ }, { "name": "eappointment/zmsclient", - "version": "dev-main", + "version": "dev-next", "dist": { "type": "path", "url": "../zmsclient", @@ -264,7 +264,7 @@ }, { "name": "eappointment/zmsentities", - "version": "dev-main", + "version": "dev-next", "dist": { "type": "path", "url": "../zmsentities", @@ -322,7 +322,7 @@ }, { "name": "eappointment/zmsslim", - "version": "dev-main", + "version": "dev-next", "dist": { "type": "path", "url": "../zmsslim", @@ -2819,8 +2819,8 @@ "type": "library", "extra": { "thanks": { - "name": "symfony/polyfill", - "url": "https://github.com/symfony/polyfill" + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" } }, "autoload": { @@ -3932,29 +3932,27 @@ }, { "name": "doctrine/deprecations", - "version": "1.1.3", + "version": "1.1.4", "source": { "type": "git", "url": "https://github.com/doctrine/deprecations.git", - "reference": "dfbaa3c2d2e9a9df1118213f3b8b0c597bb99fab" + "reference": "31610dbb31faa98e6b5447b62340826f54fbc4e9" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/doctrine/deprecations/zipball/dfbaa3c2d2e9a9df1118213f3b8b0c597bb99fab", - "reference": "dfbaa3c2d2e9a9df1118213f3b8b0c597bb99fab", + "url": "https://api.github.com/repos/doctrine/deprecations/zipball/31610dbb31faa98e6b5447b62340826f54fbc4e9", + "reference": "31610dbb31faa98e6b5447b62340826f54fbc4e9", "shasum": "" }, "require": { "php": "^7.1 || ^8.0" }, "require-dev": { - "doctrine/coding-standard": "^9", - "phpstan/phpstan": "1.4.10 || 1.10.15", - "phpstan/phpstan-phpunit": "^1.0", + "doctrine/coding-standard": "^9 || ^12", + "phpstan/phpstan": "1.4.10 || 2.0.3", + "phpstan/phpstan-phpunit": "^1.0 || ^2", "phpunit/phpunit": "^7.5 || ^8.5 || ^9.5", - "psalm/plugin-phpunit": "0.18.4", - "psr/log": "^1 || ^2 || ^3", - "vimeo/psalm": "4.30.0 || 5.12.0" + "psr/log": "^1 || ^2 || ^3" }, "suggest": { "psr/log": "Allows logging deprecations via PSR-3 logger implementation" @@ -3962,7 +3960,7 @@ "type": "library", "autoload": { "psr-4": { - "Doctrine\\Deprecations\\": "lib/Doctrine/Deprecations" + "Doctrine\\Deprecations\\": "src" } }, "notification-url": "https://packagist.org/downloads/", @@ -3973,9 +3971,9 @@ "homepage": "https://www.doctrine-project.org/", "support": { "issues": "https://github.com/doctrine/deprecations/issues", - "source": "https://github.com/doctrine/deprecations/tree/1.1.3" + "source": "https://github.com/doctrine/deprecations/tree/1.1.4" }, - "time": "2024-01-30T19:34:25+00:00" + "time": "2024-12-07T21:18:45+00:00" }, { "name": "doctrine/instantiator", @@ -4584,16 +4582,16 @@ }, { "name": "phpdocumentor/reflection-docblock", - "version": "5.5.0", + "version": "5.6.1", "source": { "type": "git", "url": "https://github.com/phpDocumentor/ReflectionDocBlock.git", - "reference": "54e10d44fc1a84e2598d26f70d4f6f1f233e228a" + "reference": "e5e784149a09bd69d9a5e3b01c5cbd2e2bd653d8" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpDocumentor/ReflectionDocBlock/zipball/54e10d44fc1a84e2598d26f70d4f6f1f233e228a", - "reference": "54e10d44fc1a84e2598d26f70d4f6f1f233e228a", + "url": "https://api.github.com/repos/phpDocumentor/ReflectionDocBlock/zipball/e5e784149a09bd69d9a5e3b01c5cbd2e2bd653d8", + "reference": "e5e784149a09bd69d9a5e3b01c5cbd2e2bd653d8", "shasum": "" }, "require": { @@ -4602,7 +4600,7 @@ "php": "^7.4 || ^8.0", "phpdocumentor/reflection-common": "^2.2", "phpdocumentor/type-resolver": "^1.7", - "phpstan/phpdoc-parser": "^1.7", + "phpstan/phpdoc-parser": "^1.7|^2.0", "webmozart/assert": "^1.9.1" }, "require-dev": { @@ -4642,29 +4640,29 @@ "description": "With this component, a library can provide support for annotations via DocBlocks or otherwise retrieve information that is embedded in a DocBlock.", "support": { "issues": "https://github.com/phpDocumentor/ReflectionDocBlock/issues", - "source": "https://github.com/phpDocumentor/ReflectionDocBlock/tree/5.5.0" + "source": "https://github.com/phpDocumentor/ReflectionDocBlock/tree/5.6.1" }, - "time": "2024-11-04T21:26:31+00:00" + "time": "2024-12-07T09:39:29+00:00" }, { "name": "phpdocumentor/type-resolver", - "version": "1.9.0", + "version": "1.10.0", "source": { "type": "git", "url": "https://github.com/phpDocumentor/TypeResolver.git", - "reference": "1fb5ba8d045f5dd984ebded5b1cc66f29459422d" + "reference": "679e3ce485b99e84c775d28e2e96fade9a7fb50a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpDocumentor/TypeResolver/zipball/1fb5ba8d045f5dd984ebded5b1cc66f29459422d", - "reference": "1fb5ba8d045f5dd984ebded5b1cc66f29459422d", + "url": "https://api.github.com/repos/phpDocumentor/TypeResolver/zipball/679e3ce485b99e84c775d28e2e96fade9a7fb50a", + "reference": "679e3ce485b99e84c775d28e2e96fade9a7fb50a", "shasum": "" }, "require": { "doctrine/deprecations": "^1.0", "php": "^7.3 || ^8.0", "phpdocumentor/reflection-common": "^2.0", - "phpstan/phpdoc-parser": "^1.18" + "phpstan/phpdoc-parser": "^1.18|^2.0" }, "require-dev": { "ext-tokenizer": "*", @@ -4700,9 +4698,9 @@ "description": "A PSR-5 based resolver of Class names, Types and Structural Element Names", "support": { "issues": "https://github.com/phpDocumentor/TypeResolver/issues", - "source": "https://github.com/phpDocumentor/TypeResolver/tree/1.9.0" + "source": "https://github.com/phpDocumentor/TypeResolver/tree/1.10.0" }, - "time": "2024-11-03T20:11:34+00:00" + "time": "2024-11-09T15:12:26+00:00" }, { "name": "phpmd/phpmd", @@ -4789,26 +4787,27 @@ }, { "name": "phpspec/prophecy", - "version": "v1.19.0", + "version": "v1.20.0", "source": { "type": "git", "url": "https://github.com/phpspec/prophecy.git", - "reference": "67a759e7d8746d501c41536ba40cd9c0a07d6a87" + "reference": "a0165c648cab6a80311c74ffc708a07bb53ecc93" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpspec/prophecy/zipball/67a759e7d8746d501c41536ba40cd9c0a07d6a87", - "reference": "67a759e7d8746d501c41536ba40cd9c0a07d6a87", + "url": "https://api.github.com/repos/phpspec/prophecy/zipball/a0165c648cab6a80311c74ffc708a07bb53ecc93", + "reference": "a0165c648cab6a80311c74ffc708a07bb53ecc93", "shasum": "" }, "require": { "doctrine/instantiator": "^1.2 || ^2.0", - "php": "^7.2 || 8.0.* || 8.1.* || 8.2.* || 8.3.*", + "php": "^7.2 || 8.0.* || 8.1.* || 8.2.* || 8.3.* || 8.4.*", "phpdocumentor/reflection-docblock": "^5.2", "sebastian/comparator": "^3.0 || ^4.0 || ^5.0 || ^6.0", "sebastian/recursion-context": "^3.0 || ^4.0 || ^5.0 || ^6.0" }, "require-dev": { + "friendsofphp/php-cs-fixer": "^3.40", "phpspec/phpspec": "^6.0 || ^7.0", "phpstan/phpstan": "^1.9", "phpunit/phpunit": "^8.0 || ^9.0 || ^10.0" @@ -4852,22 +4851,22 @@ ], "support": { "issues": "https://github.com/phpspec/prophecy/issues", - "source": "https://github.com/phpspec/prophecy/tree/v1.19.0" + "source": "https://github.com/phpspec/prophecy/tree/v1.20.0" }, - "time": "2024-02-29T11:52:51+00:00" + "time": "2024-11-19T13:12:41+00:00" }, { "name": "phpspec/prophecy-phpunit", - "version": "v2.2.0", + "version": "v2.3.0", "source": { "type": "git", "url": "https://github.com/phpspec/prophecy-phpunit.git", - "reference": "16e1247e139434bce0bac09848bc5c8d882940fc" + "reference": "8819516c1b489ecee4c60db5f5432fac1ea8ac6f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpspec/prophecy-phpunit/zipball/16e1247e139434bce0bac09848bc5c8d882940fc", - "reference": "16e1247e139434bce0bac09848bc5c8d882940fc", + "url": "https://api.github.com/repos/phpspec/prophecy-phpunit/zipball/8819516c1b489ecee4c60db5f5432fac1ea8ac6f", + "reference": "8819516c1b489ecee4c60db5f5432fac1ea8ac6f", "shasum": "" }, "require": { @@ -4875,6 +4874,9 @@ "phpspec/prophecy": "^1.18", "phpunit/phpunit": "^9.1 || ^10.1 || ^11.0" }, + "require-dev": { + "phpstan/phpstan": "^1.10" + }, "type": "library", "extra": { "branch-alias": { @@ -4904,36 +4906,36 @@ ], "support": { "issues": "https://github.com/phpspec/prophecy-phpunit/issues", - "source": "https://github.com/phpspec/prophecy-phpunit/tree/v2.2.0" + "source": "https://github.com/phpspec/prophecy-phpunit/tree/v2.3.0" }, - "time": "2024-03-01T08:33:58+00:00" + "time": "2024-11-19T13:24:17+00:00" }, { "name": "phpstan/phpdoc-parser", - "version": "1.33.0", + "version": "2.0.0", "source": { "type": "git", "url": "https://github.com/phpstan/phpdoc-parser.git", - "reference": "82a311fd3690fb2bf7b64d5c98f912b3dd746140" + "reference": "c00d78fb6b29658347f9d37ebe104bffadf36299" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpdoc-parser/zipball/82a311fd3690fb2bf7b64d5c98f912b3dd746140", - "reference": "82a311fd3690fb2bf7b64d5c98f912b3dd746140", + "url": "https://api.github.com/repos/phpstan/phpdoc-parser/zipball/c00d78fb6b29658347f9d37ebe104bffadf36299", + "reference": "c00d78fb6b29658347f9d37ebe104bffadf36299", "shasum": "" }, "require": { - "php": "^7.2 || ^8.0" + "php": "^7.4 || ^8.0" }, "require-dev": { "doctrine/annotations": "^2.0", - "nikic/php-parser": "^4.15", + "nikic/php-parser": "^5.3.0", "php-parallel-lint/php-parallel-lint": "^1.2", "phpstan/extension-installer": "^1.0", - "phpstan/phpstan": "^1.5", - "phpstan/phpstan-phpunit": "^1.1", - "phpstan/phpstan-strict-rules": "^1.0", - "phpunit/phpunit": "^9.5", + "phpstan/phpstan": "^2.0", + "phpstan/phpstan-phpunit": "^2.0", + "phpstan/phpstan-strict-rules": "^2.0", + "phpunit/phpunit": "^9.6", "symfony/process": "^5.2" }, "type": "library", @@ -4951,9 +4953,9 @@ "description": "PHPDoc parser with support for nullable, intersection and generic types", "support": { "issues": "https://github.com/phpstan/phpdoc-parser/issues", - "source": "https://github.com/phpstan/phpdoc-parser/tree/1.33.0" + "source": "https://github.com/phpstan/phpdoc-parser/tree/2.0.0" }, - "time": "2024-10-13T11:25:22+00:00" + "time": "2024-10-13T11:29:49+00:00" }, { "name": "phpunit/php-code-coverage", @@ -6410,16 +6412,16 @@ }, { "name": "squizlabs/php_codesniffer", - "version": "3.10.3", + "version": "3.11.2", "source": { "type": "git", "url": "https://github.com/PHPCSStandards/PHP_CodeSniffer.git", - "reference": "62d32998e820bddc40f99f8251958aed187a5c9c" + "reference": "1368f4a58c3c52114b86b1abe8f4098869cb0079" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/PHPCSStandards/PHP_CodeSniffer/zipball/62d32998e820bddc40f99f8251958aed187a5c9c", - "reference": "62d32998e820bddc40f99f8251958aed187a5c9c", + "url": "https://api.github.com/repos/PHPCSStandards/PHP_CodeSniffer/zipball/1368f4a58c3c52114b86b1abe8f4098869cb0079", + "reference": "1368f4a58c3c52114b86b1abe8f4098869cb0079", "shasum": "" }, "require": { @@ -6486,7 +6488,7 @@ "type": "open_collective" } ], - "time": "2024-09-18T10:38:58+00:00" + "time": "2024-12-11T16:04:26+00:00" }, { "name": "symfony/config", diff --git a/zmsadmin/custom_templates/block/appointment/form.twig b/zmsadmin/custom_templates/block/appointment/form.twig deleted file mode 100644 index ce383aaaf..000000000 --- a/zmsadmin/custom_templates/block/appointment/form.twig +++ /dev/null @@ -1,404 +0,0 @@ -{% embed "block/scaffholding/board.twig" with {'class': 'appointment-form'} %} - - {% from 'element/helper/form.twig' import formgroup, formbutton, checkboxfield, selectfield, inputfield %} - - {% block board_title %} - {% if selectedProcess %} - Terminvereinbarung Aktualisieren - {% else %} - Terminvereinbarung Neu - {% endif %} - - - {% endblock %} - - {% block board_body %} - {% include "element/helper/messageHandler.twig" %} - - {% set addRequestAuralMessage = "Die folgende Dienstleistung wurde zur Liste der ausgewählten Dienstleistungen hinzugefügt" %} - {% set cleanupRequestAuralMessage = "Die Auswahl der Dienstleistungen wurde zurückgesetzt" %} - {% set removeRequestAuralMessage = "Die folgende Dienstleistung ist nicht mehr ausgewählt" %} - {% set chooseRequestFirst = "Wählen Sie bitte zuerst die Dienstleistungen und die Anzahl der Slots aus bevor Sie das Datum (Zugriffschlüssel m) und die Uhrzeit (Zugriffschlüssel z) auswählen." %} - -
-
- - {% include 'block/cluster/scopeSelect.twig' with {'source': 'appointmentForm'} %} - {% if not scope %} -
- {% trans %} - Clusteransicht aktiviert
- Für diesen Arbeitsplatz wurde "Alle Clusterstandorte anzeigen" ausgewählt. Wählen Sie für eine Terminvereinbarung vorab einen Standort aus. Formulareingaben werden beim Wechsel auf einen anderen Standort im Cluster zurückgesetzt.{% endtrans %} -
- {% endif %} -
-
-
-
- -
-
- {{ inputfield({ - "id":"process_selected_date", - "type":"hidden", - "name":"selecteddate", - "value":selectedDate|date('Y-m-d'), - }) }} -
-
-
-
- {% if scope.preferences.appointment.multipleSlotsEnabled or selectedProcess.scope.preferences.appointment.multipleSlotsEnabled %} - {% set slotCountOptions = [] %} - {% for slotCount in 1..20 %} - {% if selectedProcess.appointments|first.slotCount == slotCount %} - {% set selected = "selected" %} - {% else %} - {% set selected = "" %} - {% endif %} - {% set duration = slotTimeInMinutes * slotCount %} - {% set slotCountOptions = slotCountOptions|merge([{ "value": slotCount, "name": duration, "selected":selected}]) %} - {% endfor %} - {{ formgroup( - {"label": "Termindauer in Minuten"}, - [{ - "type":"select", - "parameter": { - "accesskey":"o", - "name": "slotCount", - "id": "appointmentForm_slotCount", - "width": 3, - "options": slotCountOptions, - } - }] - ) }} - {% endif %} -
-
- -
-
-
- {% include "block/appointment/freeProcessList.twig" %} -
- - {% set familyName = selectedProcess.clients|first.familyName|decodeEntities %} - {% if formData.familyName._unvalidated %} - {% set familyName = formData.familyName._unvalidated %} - {% endif %} - - {% set title = "" %} - {% if selectedProcess %} - {% set title = "Bestätigter Termin: " ~ selectedProcess.appointments|first.date|date("Y-m-d H:i") %} - {% endif %} - {{ formgroup( - {"label": "Name", "required": 1, "errors": formData.familyName.messages}, - [{ - "type": "input", - "parameter": { - "name": "familyName", - "title": title, - "placeholder": "Pflichtfeld", - "value": familyName, - "accesskey":"n" - } - }] - ) }} - - {% set telephone = selectedProcess.clients|first.telephone %} - {% if formData.telephone._unvalidated %} - {% set telephone = formData.telephone._unvalidated %} - {% endif %} - {{ formgroup( - {"label": "Telefonnummer", "required": scope.preferences.client.telephoneRequired, "errors": formData.telephone.messages}, - [{ - "type": "input", - "parameter":{ - "name": "telephone", - "value": telephone, - "accesskey":"t" - } - }] - ) }} - {% if department.preferences.notifications.enabled %} -
- {% if department.preferences.notifications.sendConfirmationEnabled %} - {{ formgroup( - {}, - [{ - "type": "checkbox", - "parameter": { - "name": "sendConfirmation", - "label": "Terminbestätigung per SMS", - "value": 1, - "accesskey":"b", - "checked": 0 - } - }] - ) }} - {% endif %} - {% if scope.preferences.appointment.notificationHeadsUpEnabled and department.preferences.notifications.sendReminderEnabled %} - - {% set selectedProcessHeadsUpTime = 0 %} - {% set minutesToAppointment = selectedDate|date('U') %} - {% if selectedProcess %} - {% set selectedProcessHeadsUpTime = selectedProcess.appointments|first.date - selectedProcess.reminderTimestamp %} - {% set minutesToAppointment = (selectedProcess.appointments|first.date - getNow()|date('U')) / 60 %} - {% endif %} - {% set allowedHeadsUpTime = [30,60,120,180,240,1440,2880] %} - {% set headsUpTimeOptions = [{"value": 0, "name": "ohne Erinnerung"}] %} - {% for headsUpTimeValue in allowedHeadsUpTime %} - {% set value = headsUpTimeValue / 60 %} - {% if value < 1 %} - {% set label = headsUpTimeValue ~ " Minuten" %} - {% elseif value == 1 %} - {% set label = "1 Stunde" %} - {% elseif value > 1 and value < 24 %} - {% set label = value ~ " Stunden" %} - {% elseif value == 24 %} - {% set label = "1 Tag" %} - {% elseif value > 24 %} - {% set label = value / 24 ~ " Tage" %} - {% endif %} - - {% if headsUpTimeValue * 60 == selectedProcessHeadsUpTime %}{% set selected = "selected"%}{% else %}{% set selected = ""%}{% endif %} - - {% if headsUpTimeValue <= minutesToAppointment %} - {% set headsUpTimeOptions = headsUpTimeOptions|merge([{ "value": headsUpTimeValue * 60, "name": label ~ " vorher", "selected": selected }]) %} - {% endif %} - {% endfor %} - - {{ formgroup( - {"label": "Terminerinnerung per SMS"}, - [{ - "type": "select", - "parameter": { - "name": "headsUpTime", - "options": headsUpTimeOptions, - "accesskey":"r" - } - }] - ) }} - {% endif %} -
- {% endif %} - - {% if scope.preferences.client.emailRequired or scope.preferences.client.emailFrom %} -
- {% set email = selectedProcess.clients|first.email %} - {% if formData.email._unvalidated %} - {% set email = formData.email._unvalidated %} - {% endif %} - {{ formgroup( - {"label": "E-Mail", "required": selectedProcess.preferences.client.emailRequired|default(scope.preferences.client.emailRequired), "errors": formData.email.messages}, - [{ - "type": "input", - "parameter": { - "name": "email", - "value": email, - "accesskey":"a" - } - }] - ) }} - - {% if formData.sendMailConfirmation._unvalidated %} - {% set mcChecked = formData.sendMailConfirmation._unvalidated %} - {% endif %} - {{ formgroup( - {}, - [{ - "type": "checkbox", - "parameter": { - "name": "sendMailConfirmation", - "label": "Mit E-Mail Bestätigung", - "value": 1, - "accesskey":"i", - "checked": (mcChecked) ? mcChecked : 1 - } - }] - ) }} -
- {% if scope.preferences.survey.enabled %} - {% set defaultLabel = 'Zustimmung Kundenbefragung' %} - {{ formgroup( - {}, - [{ - "type": "checkbox", - "parameter": { - "accesskey":"k", - "name": "surveyAccepted", - "label": scope.preferences.survey.label|default(defaultLabel|trans), - "value": 1, - "checked": selectedProcess.clients|first.surveyAccepted|default(0), - "disabled": (scope.preferences.survey.emailContent == '') - } - }] - ) }} - {% if scope.preferences.survey.emailContent == '' %} -
- {% trans %}Die Kundenbefragung wurde ohne E-Mail-Text erstellt! Prüfen Sie bitte die Standort-Einstellungen!{% endtrans %} -
- {% endif %} - {% endif %} - {% endif %} - {{ formgroup( - { - "label": "Anmerkung", - "checked": scope.preferences.client.amendmentActivated, - "class": "form-group--nomaxwidth", - "errors": formData.amendment.messages - }, - [{ - "type":"textarea", - "parameter": { - "name": "amendment", - "rows": "3", - "maxlength": 500, - "value": selectedProcess.amendment, - "accesskey":"w" - } - }] - ) }} - - {% if scope.preferences.client.customTextfieldActivated|default(0) == 1 %} - {{ formgroup( - { - "label": scope.preferences.client.customTextfieldLabel, - "required": selectedProcess.preferences.client.customTextfieldRequired|default(scope.preferences.client.customTextfieldRequired), - "errors": formData.customTextfield.messages, - "checked": scope.preferences.client.customTextfieldActivated, - }, - [{ - "type": "input", - "parameter": { - "name": "customTextfield", - "value": selectedProcess.customTextfield, - "accesskey":"a" - } - }] - ) }} - {% endif %} -
- -
-
- {% set selectedRequests = [] %} - {% if selectedProcess %} - {% for request in selectedProcess.requests %} - {% set selectedRequests = selectedRequests|merge([ request.id ]) %} - {% endfor %} - {% endif %} - - {% if formData.requests._unvalidated %} - {% set selectedRequests = formData.requests._unvalidated %} - {% endif %} - {% if requestList|length > 0 %} - {% set requestsOptions = [] %} - {% for request in requestList %} - {% if request.id in selectedRequests %} - {% set checked = "1" %} - {% else %} - {% set checked = "0" %} - {% endif %} - - {% set requestName = request.name %} - {% set duration = slotTimeInMinutes * request.timeSlotCount %} - {% if scope.preferences.appointment.multipleSlotsEnabled or selectedProcess.scope.preferences.appointment.multipleSlotsEnabled %} - {% set requestName = request.name ~ " (" ~ duration ~ " min)" %} - {% endif %} - {% set requestsOptions = requestsOptions|merge([{ "value": request.id, "name": requestName, "class": "service-checkbox", "data": {'slots': request.timeSlotCount}, "selected": checked }]) %} - {% endfor %} - {{ formgroup( - { - "groupTag": "fieldset", - "legend": "Dienstleistung", - "errors": formData.requests.messages, - "id": "select-requests" - }, - [{ - "type": "checkboxSelect", - "parameter": { - "ariaLabel": "Dienstleistungen Auswahlliste", - "accesskey":"g", - "height": "10em", - "options": requestsOptions - } - }] - ) }} -
-
- {% set requestsOptions = [] %} - {% for requestGroup in requestsByCount %} - {% set slotCount = requestGroup.request.timeSlotCount %} - {% if requestGroup.request.id in selectedRequests %} - {% set checked = "1" %} - {% else %} - {% set checked = "0" %} - {% endif %} - {% set requestsOptions = requestsOptions|merge([{ "value": requestGroup.request.id, "name": requestGroup.request.name, "data": {"count": requestGroup.count, "slots": slotCount}, "selected": checked }]) %} - {% endfor %} - -
- Ausgewählte Dienstleistungen - -
-
-
-
    - {% for option in requestsOptions %} -
  • -
    - -
    -
  • - {% endfor %} -
-
-
-
-
- - -
- {% else %} -
- Dem ausgewählten Standort sind keine Dienstleistungen zugeordnet. -
- {% endif %} -
-
- *) Felder mit einem Stern müssen ausgefüllt werden. Außer für Spontankunden. -
- {% include "block/appointment/formButtons.twig" %} -
-
-
- {#warningText if the number of slots is exceeded#} - - {% endblock %} -{% endembed %} diff --git a/zmsadmin/custom_templates/block/calldisplay/config.twig b/zmsadmin/custom_templates/block/calldisplay/config.twig deleted file mode 100644 index 7e40e6944..000000000 --- a/zmsadmin/custom_templates/block/calldisplay/config.twig +++ /dev/null @@ -1,19 +0,0 @@ -{% embed "block/scaffholding/board.twig" with {'class': 'calldisplay-config'} %} - {% from 'element/helper/form.twig' import formgroup, formbutton %} - - {% block board_title %} - {{ title }} Standortauswahl - München - {{ organisation.name }} - {% endblock %} - - {% block board_body %} -
-
- {% endblock %} - -{% endembed %} - diff --git a/zmsadmin/custom_templates/block/element/helper/messages.twig b/zmsadmin/custom_templates/block/element/helper/messages.twig deleted file mode 100644 index 438ac882f..000000000 --- a/zmsadmin/custom_templates/block/element/helper/messages.twig +++ /dev/null @@ -1,368 +0,0 @@ -{% block conflictsBlock %} - {% set errorTitle = "Es wurden Konflikte entdeckt" %} - {{ block('error_start') }} - - Ihre Anpassungen wurden übernommen. -
- -
- {{ block('error_end') }} -{% endblock %} - -{% block errors %} - {% if args.error or error %} - {% block login_failed %} - {{ block('error_start') }} - Die Anmeldung ist leider fehlgeschlagen. Versuchen Sie es bitte erneut. - {{ block('error_end') }} - {% endblock %} - {% block unknown_error %} - {{ block('error_start') }} - Es ist ein unbekannter Fehler aufgetreten.Fehler: - {{ block('error_end') }} - {% endblock %} - {% block mail_failed %} - {% set errorTitle = "E-Mail Versand gescheitert." %} - {{ block('error_start') }} - Beim Versand der E-Mail ist ein Fehler aufgetreten. Bitte prüfen Sie Ihre Eingaben und versuchen Sie es erneut. - {{ block('error_end') }} - {% endblock %} - {% block notification_failed %} - {% set errorTitle = "E-Mail Versand gescheitert." %} - {{ block('error_start') }} - Beim Versand der SMS ist ein Fehler aufgetreten. Bitte prüfen Sie Ihre Eingaben und versuchen Sie es erneut. - {{ block('error_end') }} - {% endblock %} - {% block has_called_process %} - {% set errorTitle = "Es ist schon ein Kunde in Bearbeitung" %} - {{ block('error_start') }} - Dieser Arbeitsplatz hat schon einen Vorgang aufgerufen. Dieser wird weiterhin verwendet. - {{ block('error_end') }} - {% endblock %} - {% block has_called_pickup %} - {% set errorTitle = "Es ist schon ein Abholer in Bearbeitung" %} - {{ block('error_start') }} - Dieser Arbeitsplatz hat schon einen Abholer aufgerufen. -
- -
- {{ block('error_end') }} - {% endblock %} - {% endif %} -{% endblock %} - -{% block notices %} - {% if args.notice or notice %} - {% block test_notice %} - {{ block('notice_start') }} - Test Notice - {{ block('notice_end') }} - {% endblock %} - {% endif %} -{% endblock %} - -{% block successes %} - {% if args.success or success %} - - {% block config_saved %} - {{ block('success_start') }} - Die Config-Einstellungen wurden {{ timeString }} erfolgreich gespeichert. - {{ block('success_end') }} - {% endblock %} - - - {% block cluster_deleted %} - {{ block('success_start') }} - Der Cluster wurde {{ timeString }} erfolgreich entfernt. - {{ block('success_end') }} - {% endblock %} - - {% block cluster_created %} - {{ block('success_start') }} - Der Cluster wurde {{ timeString }} erfolgreich angelegt. - {{ block('success_end') }} - {% endblock %} - - {% block cluster_saved %} - {{ block('success_start') }} - Der Cluster wurde {{ timeString }} erfolgreich aktualisiert. - {{ block('success_end') }} - {% endblock %} - - {% block department_deleted %} - {{ block('success_start') }} - Die Behörde wurde {{ timeString }} erfolgreich entfernt. - {{ block('success_end') }} - {% endblock %} - - {% block department_created %} - {{ block('success_start') }} - Die Behörde wurde {{ timeString }} erfolgreich angelegt. - {{ block('success_end') }} - {% endblock %} - - {% block department_saved %} - {{ block('success_start') }} - Die Behörde wurde {{ timeString }} erfolgreich aktualisiert. - {{ block('success_end') }} - {% endblock %} - - - {% block organisation_deleted %} - {{ block('success_start') }} - Das Referat wurde {{ timeString }} erfolgreich entfernt. - {{ block('success_end') }} - {% endblock %} - - {% block organisation_created %} - {{ block('success_start') }} - Das Referat wurde {{ timeString }} erfolgreich angelegt. - {{ block('success_end') }} - {% endblock %} - - {% block organisation_saved %} - {{ block('success_start') }} - Das Referat wurde {{ timeString }} erfolgreich aktualisiert. - {{ block('success_end') }} - {% endblock %} - - - {% block owner_deleted %} - {{ block('success_start') }} - Der Kunde wurde {{ timeString }} erfolgreich entfernt. - {{ block('success_end') }} - {% endblock %} - - {% block owner_created %} - {{ block('success_start') }} - Der Kunde wurde {{ timeString }} erfolgreich angelegt. - {{ block('success_end') }} - {% endblock %} - - {% block owner_saved %} - {{ block('success_start') }} - Der Kunde wurde {{ timeString }} erfolgreich aktualisiert. - {{ block('success_end') }} - {% endblock %} - - {% block password_changed %} - {{ block('success_start') }} - Sie können sich ab jetzt mit Ihren neuen Zugangdaten anmelden, bleiben aber noch angemeldet. - {{ block('success_end') }} - {% endblock %} - - {% block scope_deleted %} - {% set successTitle = "Löschen erfolgreich" %} - {{ block('success_start') }} - Der Standort wurde {{ timeString }} erfolgreich entfernt. - {{ block('success_end') }} - {% endblock %} - - {% block scope_created %} - {% set successTitle = "Anlegen erfolgreich" %} - {{ block('success_start') }} - Der Standort wurde {{ timeString }} erfolgreich angelegt. - {{ block('success_end') }} - {% endblock %} - - {% block scope_saved %} - {% set successTitle = "Speichern erfolgreich" %} - {{ block('success_start') }} - Der Standort wurde {{ timeString }} erfolgreich aktualisiert. - {{ block('success_end') }} - {% endblock %} - - {% block source_saved %} - {% set successTitle = "Speichern erfolgreich" %} - {{ block('success_start') }} - Der Mandant wurde {{ timeString }} erfolgreich aktualisiert. - {{ block('success_end') }} - {% endblock %} - - {% block ticketprinter_deactivated_0 %} - {{ block('success_start') }} - Die Kioskeinstellungen wurden {{ timeString }} erfolgreich aktualisiert. - Der Kiosk ist nun in Betrieb. - {{ block('success_end') }} - {% endblock %} - - {% block ticketprinter_deactivated_1 %} - {{ block('success_start') }} - Die Kioskeinstellungen wurden {{ timeString }} erfolgreich aktualisiert. - Der Kiosk ist nun gesperrt. - {{ block('success_end') }} - {% endblock %} - - {% block useraccount_deleted %} - {{ block('success_start') }} - Der Nutzer wurde {{ timeString }} erfolgreich entfernt. - {{ block('success_end') }} - {% endblock %} - - {% block useraccount_saved %} - {{ block('success_start') }} - Der Nutzer wurde {{ timeString }} erfolgreich aktualisiert. - {{ block('success_end') }} - {% endblock %} - - {% block useraccount_added %} - {{ block('success_start') }} - Der Nutzer wurde {{ timeString }} erfolgreich angelegt. - {{ block('success_end') }} - {% endblock %} - - {% block dayoff_saved %} - {{ block('success_start') }} - Feiertage wurden {{ timeString }} erfolgreich aktualisiert. - {{ block('success_end') }} - {% endblock %} - - {% block process_reserved %} - {% set successTitle = "Termin erfolgreich eingetragen" %} - {{ block('success_start') }} -
- {% if selectedprocess.queue.number < 10000 %}
Die Wartenummer {% else %}
Die Vorgangsnummer {% endif %}{% if selectedprocess.clients|first.familyName %}für "{{ selectedprocess.clients|first.familyName|raw }}"{% endif %} lautet: {{ selectedprocess.queue.number }} 
-
Standort: {{ selectedprocess.scope.contact.name }} {{ selectedprocess.scope.shortName }} 
- {% if selectedprocess.scope.hint %}
Info: {{ selectedprocess.scope.hint }} 
{% endif %} -
-
-
- - - -
-
- {{ block('success_end') }} - {% endblock %} - - {% block process_changed %} - {% set successTitle = "Vorgang wurde geändert." %} - {{ block('success_start') }} - Die Terminzeit des Vorgangs mit der Nummer {{ selectedprocess.queue.number }} wurde erfolgreich geändert. -
- -
- {{ block('success_end') }} - {% endblock %} - - {% block process_deleted %} - {% set successTitle = "Vorgang gelöscht" %} - {{ block('success_start') }} - Der Vorgang mit der Nummer {{ selectedprocess.queue.number }} wurde erfolgreich entfernt. -
- -
- {{ block('success_end') }} - {% endblock %} - - {% block process_updated %} - {% set successTitle = "Vorgang aktualisiert" %} - {{ block('success_start') }} - Der Vorgang mit der Nummer {{ selectedprocess.queue.number }} wurde erfolgreich aktualisiert. -
- -
- {{ block('success_end') }} - {% endblock %} - - {% block process_withoutappointment_updated %} - {% set successTitle = "Spontankunde aktualisiert" %} - {{ block('success_start') }} - Der Spontankunde mit der Wartenummer {{ selectedprocess.queue.number }} wurde erfolgreich aktualisiert. -
- -
- {{ block('success_end') }} - {% endblock %} - - {% block process_queued %} - {% set successTitle = "Wartenummer wurde hinzugefügt" %} - {{ block('success_start') }} -
-
Die Wartenummer {% if selectedprocess.clients|first.familyName %}für "{{ selectedprocess.clients|first.familyName|raw }}"{% endif %} lautet: {{ selectedprocess.queue.number }} 
-
Standort: {{ selectedprocess.scope.contact.name }} {{ selectedprocess.scope.shortName }} 
- {% if selectedprocess.scope.hint %}
Info: {{ selectedprocess.scope.hint }} 
{% endif %} -
-
-
- - -
-
- {{ block('success_end') }} - {% endblock %} - {% block process_reset_queued %} - {% set successTitle = "Erfolgreiche Wiederaufnahme in Warteschlange" %} - {{ block('success_start') }} -

- Der Vorgang mit der Nummer {{ changedProcess.queue.number }}{% if changedProcess.clients|first.familyName %} (Name: {{ changedProcess.clients|first.familyName|raw }}){% endif %} wurde erfolgreich zum Aufruf zurückgesetzt. -

- {{ block('success_end') }} - {% endblock %} - - - {% block mail_sent %} - {% set successTitle = "E-Mail versendet" %} - {{ block('success_start') }} - Der Versand der E-Mail war erfolgreich. -
- -
- {{ block('success_end') }} - {% endblock %} - - {% block notification_sent %} - {% set successTitle = "SMS versendet" %} - {{ block('success_start') }} - SMS-Versand an {{ process.clients|first.telephone }} initiiert. -
- -
- {{ block('success_end') }} - {% endblock %} - - {% endif %} -{% endblock %} - -{% block error_start %} - {% endblock %} - -{% block success_start %} -