mirror of
https://github.com/opensourcepos/opensourcepos.git
synced 2026-05-29 18:55:53 -04:00
Compare commits
59 Commits
feature/pa
...
review-pr-
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a55885de87 | ||
|
|
accc8c5911 | ||
|
|
a1fd3991b9 | ||
|
|
bd312e3e1d | ||
|
|
6e498aab42 | ||
|
|
ee5ed3c699 | ||
|
|
e70395bb85 | ||
|
|
7f9321eca0 | ||
|
|
71056d9b03 | ||
|
|
e17944d883 | ||
|
|
0ac427b2b1 | ||
|
|
3038f83a4a | ||
|
|
75f6ce3140 | ||
|
|
ce7a3ce341 | ||
|
|
d99d2855ec | ||
|
|
96b4b24d9b | ||
|
|
871231e406 | ||
|
|
e62477ed4e | ||
|
|
2a0997f267 | ||
|
|
1ca8effe08 | ||
|
|
ed2c975ad5 | ||
|
|
403feed3e5 | ||
|
|
7f6f36210c | ||
|
|
1121ced532 | ||
|
|
632a18212d | ||
|
|
3208f15244 | ||
|
|
079b809622 | ||
|
|
d685e09c29 | ||
|
|
149c27d60f | ||
|
|
57b7705cd4 | ||
|
|
e8951422c0 | ||
|
|
8afc57fcf4 | ||
|
|
7af64a9a21 | ||
|
|
46d5781498 | ||
|
|
66b61c0554 | ||
|
|
6b97131c48 | ||
|
|
a4c19a3c2c | ||
|
|
7ca8c9561a | ||
|
|
4fac5d9198 | ||
|
|
221995b6db | ||
|
|
91dbe5b869 | ||
|
|
afd908327b | ||
|
|
cfde66481d | ||
|
|
80f00c8552 | ||
|
|
dbdf4db4fb | ||
|
|
64004db271 | ||
|
|
7f20a5dd4c | ||
|
|
d7a276b488 | ||
|
|
57dbe43313 | ||
|
|
6f1c39d99e | ||
|
|
45902caa67 | ||
|
|
1fe865a100 | ||
|
|
90da63cb13 | ||
|
|
8da4aff262 | ||
|
|
0e9f4a998d | ||
|
|
85c7ce2da4 | ||
|
|
e723e2ddf4 | ||
|
|
71eb8de7fe | ||
|
|
9d5019e12e |
29
.env.example
29
.env.example
@@ -7,31 +7,20 @@ CI_ENVIRONMENT = production
|
||||
#--------------------------------------------------------------------
|
||||
# SECURITY: ALLOWED HOSTNAMES
|
||||
#--------------------------------------------------------------------
|
||||
# IMPORTANT: Whitelist of allowed hostnames to prevent Host Header
|
||||
# CRITICAL: Whitelist of allowed hostnames to prevent Host Header
|
||||
# Injection attacks (GHSA-jchf-7hr6-h4f3).
|
||||
#
|
||||
# If not configured, the application will default to 'localhost',
|
||||
# which may break functionality in production.
|
||||
# REQUIRED IN PRODUCTION: Application will fail to start if not configured.
|
||||
# In development, falls back to 'localhost' with an error log.
|
||||
#
|
||||
# Configure this with all domains/subdomains that host your application:
|
||||
# - Primary domain
|
||||
# - WWW subdomain (if used)
|
||||
# - Any alternative domains
|
||||
# Configure with comma-separated list of domains/subdomains:
|
||||
# app.allowedHostnames = 'yourdomain.com,www.yourdomain.com'
|
||||
#
|
||||
# Examples:
|
||||
# Single domain:
|
||||
# app.allowedHostnames.0 = 'example.com'
|
||||
# For local development:
|
||||
# app.allowedHostnames = 'localhost'
|
||||
#
|
||||
# Multiple domains:
|
||||
# app.allowedHostnames.0 = 'example.com'
|
||||
# app.allowedHostnames.1 = 'www.example.com'
|
||||
# app.allowedHostnames.2 = 'demo.opensourcepos.org'
|
||||
#
|
||||
# For localhost development:
|
||||
# app.allowedHostnames.0 = 'localhost'
|
||||
#
|
||||
# Note: Do not include the protocol (http/https) or port number.
|
||||
#app.allowedHostnames.0 = ''
|
||||
# Note: Do not include protocol (http/https) or port numbers.
|
||||
app.allowedHostnames = ''
|
||||
|
||||
#--------------------------------------------------------------------
|
||||
# DATABASE
|
||||
|
||||
309
.github/ISSUE_TEMPLATE/bug report.yml
vendored
309
.github/ISSUE_TEMPLATE/bug report.yml
vendored
@@ -1,121 +1,188 @@
|
||||
name: Bug Report
|
||||
description: File a bug report
|
||||
title: "[Bug]: "
|
||||
labels: ["bug", "triage"]
|
||||
projects: ["ospos/3", "ospos/4"]
|
||||
assignees:
|
||||
- none
|
||||
body:
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: |
|
||||
Bug reports indicate that something is not working as intended.
|
||||
Please include as much detail as possible and submit a separate bug report for each problem.
|
||||
Do not include personal identifying information such as email addresses or encryption keys.
|
||||
- type: textarea
|
||||
id: bug-description
|
||||
attributes:
|
||||
label: Bug Description?
|
||||
description: Describe the problem that you are seeing
|
||||
placeholder: "Describe the problem that you are seeing"
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
id: steps-reproduce
|
||||
attributes:
|
||||
label: Steps to Reproduce?
|
||||
description: List the steps to reproduce this issue
|
||||
placeholder: "Steps to Reproduce"
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
id: expected-behavior
|
||||
attributes:
|
||||
label: Expected Behavior?
|
||||
description: Tell us what did you expect to happen?
|
||||
placeholder: "Expected Behavior"
|
||||
validations:
|
||||
required: true
|
||||
- type: dropdown
|
||||
id: ospos-version
|
||||
attributes:
|
||||
label: OpensourcePOS Version
|
||||
description: What version of our software are you running?
|
||||
options:
|
||||
- development (unreleased)
|
||||
- opensourcepos 3.4.1
|
||||
- opensourcepos 3.4.0
|
||||
- opensourcepos 3.3.9
|
||||
- opensourcepos 3.3.8
|
||||
- opensourcepos 3.3.7
|
||||
default: 0
|
||||
validations:
|
||||
required: true
|
||||
- type: dropdown
|
||||
id: php-version
|
||||
attributes:
|
||||
label: Php version
|
||||
description: What version of Php?
|
||||
options:
|
||||
- Php 7.2
|
||||
- Php 7.3
|
||||
- Php 7.4
|
||||
- Php 8.1
|
||||
- Php 8.2
|
||||
- Php 8.3
|
||||
- Php 8.4
|
||||
default: 0
|
||||
validations:
|
||||
required: true
|
||||
- type: dropdown
|
||||
id: browsers
|
||||
attributes:
|
||||
label: What browsers are you seeing the problem on?
|
||||
multiple: true
|
||||
options:
|
||||
- Firefox
|
||||
- Chrome
|
||||
- Safari
|
||||
- Microsoft Edge
|
||||
- Other
|
||||
- type: input
|
||||
id: server
|
||||
attributes:
|
||||
label: Server Operating System and version
|
||||
description: "Server Operating System "
|
||||
placeholder: "Server Operating System "
|
||||
validations:
|
||||
required: true
|
||||
- type: input
|
||||
id: database
|
||||
attributes:
|
||||
label: Database Management System and version
|
||||
description: "Database Management System"
|
||||
placeholder: "Database Management"
|
||||
validations:
|
||||
required: true
|
||||
- type: input
|
||||
id: webserver
|
||||
attributes:
|
||||
label: Web Server and version
|
||||
description: "Web Server and version "
|
||||
placeholder: "Web Server and version "
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
id: servers
|
||||
attributes:
|
||||
label: System Information Report (optional)
|
||||
description: Copy and paste from OSPOS > Configuration > Setup & Conf > Setup & Conf?
|
||||
placeholder: System Information Report
|
||||
value: "System Information Report"
|
||||
validations:
|
||||
required: true
|
||||
- type: checkboxes
|
||||
id: terms
|
||||
attributes:
|
||||
label: Unmodified copy of OpensourcePOS
|
||||
description: By submitting this issue you agree this copy has not been modified
|
||||
options:
|
||||
- label: I agree this copy has not been modified
|
||||
required: true
|
||||
name: 🐛 Bug Report
|
||||
description: File a bug report to help us improve
|
||||
title: "[Bug]: "
|
||||
labels: ["bug", "triage"]
|
||||
projects: ["ospos/3", "ospos/4"]
|
||||
assignees: []
|
||||
body:
|
||||
# ─────────────────────────────────────────────────────────────────────────────
|
||||
# INTRODUCTION
|
||||
# ─────────────────────────────────────────────────────────────────────────────
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: |
|
||||
## Thanks for taking the time to fill out this bug report! 🐜
|
||||
|
||||
Bug reports help us identify and fix issues. Please provide as much detail as possible.
|
||||
|
||||
> ⚠️ **Important:** Submit a separate bug report for each problem you encounter.
|
||||
>
|
||||
> 🚫 Do not include personal identifying information such as email addresses or encryption keys.
|
||||
|
||||
# ─────────────────────────────────────────────────────────────────────────────
|
||||
# PROBLEM DESCRIPTION
|
||||
# ─────────────────────────────────────────────────────────────────────────────
|
||||
- type: textarea
|
||||
id: bug-description
|
||||
attributes:
|
||||
label: 🐛 Bug Description
|
||||
description: A clear and concise description of what the bug is.
|
||||
placeholder: |
|
||||
Example: When I try to print a receipt, the application crashes
|
||||
with an error message saying "Unable to connect to printer".
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
id: steps-reproduce
|
||||
attributes:
|
||||
label: 📋 Steps to Reproduce
|
||||
description: Detailed steps to reproduce the behavior.
|
||||
placeholder: |
|
||||
1. Go to '...'
|
||||
2. Click on '...'
|
||||
3. Scroll down to '...'
|
||||
4. See error
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
id: expected-behavior
|
||||
attributes:
|
||||
label: ✅ Expected Behavior
|
||||
description: A clear and concise description of what you expected to happen.
|
||||
placeholder: |
|
||||
Example: The receipt should print successfully without any errors.
|
||||
validations:
|
||||
required: true
|
||||
|
||||
# ─────────────────────────────────────────────────────────────────────────────
|
||||
# ENVIRONMENT DETAILS
|
||||
# ─────────────────────────────────────────────────────────────────────────────
|
||||
- type: dropdown
|
||||
id: ospos-version
|
||||
attributes:
|
||||
label: 📦 OpenSourcePOS Version
|
||||
description: What version of our software are you running?
|
||||
options:
|
||||
- development (unreleased)
|
||||
- OpenSourcePOS 3.4.2
|
||||
- OpenSourcePOS 3.4.1
|
||||
- OpenSourcePOS 3.4.0
|
||||
- OpenSourcePOS 3.3.9
|
||||
- OpenSourcePOS 3.3.8
|
||||
default: 0
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: dropdown
|
||||
id: php-version
|
||||
attributes:
|
||||
label: 🔧 PHP Version
|
||||
description: What version of PHP are you running?
|
||||
options:
|
||||
- PHP 8.4
|
||||
- PHP 8.3
|
||||
- PHP 8.2
|
||||
- PHP 8.1
|
||||
- PHP 7.4
|
||||
- PHP 7.3
|
||||
- PHP 7.2
|
||||
default: 0
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: dropdown
|
||||
id: browsers
|
||||
attributes:
|
||||
label: 🌐 Browser(s)
|
||||
description: What browser(s) are you seeing the problem on?
|
||||
multiple: true
|
||||
options:
|
||||
- Firefox
|
||||
- Chrome
|
||||
- Safari
|
||||
- Microsoft Edge
|
||||
- Other
|
||||
|
||||
- type: input
|
||||
id: server
|
||||
attributes:
|
||||
label: 🖥️ Server Operating System
|
||||
description: What server OS and version are you running?
|
||||
placeholder: "e.g., Ubuntu 22.04, CentOS 7, Windows Server 2022"
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: input
|
||||
id: database
|
||||
attributes:
|
||||
label: 🗄️ Database
|
||||
description: What database management system and version are you using?
|
||||
placeholder: "e.g., MySQL 8.0, MariaDB 10.11, Percona 8.0"
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: input
|
||||
id: webserver
|
||||
attributes:
|
||||
label: 🌍 Web Server
|
||||
description: What web server and version are you using?
|
||||
placeholder: "e.g., Apache 2.4, Nginx 1.24, Caddy 2.7"
|
||||
validations:
|
||||
required: true
|
||||
|
||||
# ─────────────────────────────────────────────────────────────────────────────
|
||||
# ADDITIONAL INFORMATION
|
||||
# ─────────────────────────────────────────────────────────────────────────────
|
||||
- type: textarea
|
||||
id: system-info
|
||||
attributes:
|
||||
label: 📊 System Information Report
|
||||
description: |
|
||||
Copy and paste the system information from OSPOS:
|
||||
|
||||
**Navigation:** Configuration → Setup & Conf → System Info
|
||||
placeholder: |
|
||||
Paste the System Information Report here...
|
||||
render: text
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
id: logs
|
||||
attributes:
|
||||
label: 📜 Relevant Log Output
|
||||
description: |
|
||||
Please copy and paste any relevant log output.
|
||||
|
||||
**Log locations:**
|
||||
- OSPOS logs: `writable/logs/`
|
||||
- Web server logs: `/var/log/apache2/` or `/var/log/nginx/`
|
||||
- PHP logs: Check your `php.ini` for `error_log` location
|
||||
placeholder: |
|
||||
Paste log output here...
|
||||
render: shell
|
||||
|
||||
- type: textarea
|
||||
id: screenshots
|
||||
attributes:
|
||||
label: 📸 Screenshots
|
||||
description: If applicable, add screenshots to help explain your problem.
|
||||
placeholder: Drag and drop images here...
|
||||
|
||||
# ─────────────────────────────────────────────────────────────────────────────
|
||||
# CONFIRMATION
|
||||
# ─────────────────────────────────────────────────────────────────────────────
|
||||
- type: checkboxes
|
||||
id: terms
|
||||
attributes:
|
||||
label: ✓ Confirmation
|
||||
description: Please confirm the following before submitting
|
||||
options:
|
||||
- label: I certify that this is an unmodified copy of OpenSourcePOS
|
||||
required: true
|
||||
- label: I have searched existing issues to ensure this bug has not already been reported
|
||||
required: true
|
||||
- label: I have provided all the information requested above
|
||||
required: true
|
||||
199
.github/ISSUE_TEMPLATE/feature_request.yml
vendored
199
.github/ISSUE_TEMPLATE/feature_request.yml
vendored
@@ -1,63 +1,136 @@
|
||||
name: ✨ Feature Request
|
||||
description: Suggest an idea for this project
|
||||
title: "[Feature]: "
|
||||
labels: ["enhancement"]
|
||||
assignees: ["none"]
|
||||
body:
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: |
|
||||
Thanks for taking the time to fill out this feature request! 🤗
|
||||
Please make sure this feature request hasn't been already submitted by someone by looking through other open/closed issues. 😃
|
||||
|
||||
- type: dropdown
|
||||
attributes:
|
||||
multiple: false
|
||||
label: Type of Feature
|
||||
description: Select the type of feature request.
|
||||
options:
|
||||
- "✨ New Feature"
|
||||
- "📝 Documentation"
|
||||
- "🎨 Style and UI"
|
||||
- "🔨 Code Refactor"
|
||||
- "⚡ Performance Improvements"
|
||||
- "✅ New Test"
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: dropdown
|
||||
id: ospos-version
|
||||
attributes:
|
||||
label: OpensourcePOS Version
|
||||
description: What version of our software are you running?
|
||||
options:
|
||||
- opensourcepos 3.3.9
|
||||
- opensourcepos 3.3.8
|
||||
- opensourcepos 3.3.7
|
||||
default: 0
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
id: description
|
||||
attributes:
|
||||
label: Description
|
||||
description: Give us a brief description of the feature or enhancement you would like
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
id: additional-information
|
||||
attributes:
|
||||
label: Additional Information
|
||||
description: Give us some additional information on the feature request like proposed solutions, links, screenshots, etc.
|
||||
|
||||
- type: checkboxes
|
||||
id: terms
|
||||
attributes:
|
||||
label: Verify you searched open requests in OpensourcePOS
|
||||
description: By submitting this request you agree that you have searched Open Requests in the Tracker
|
||||
options:
|
||||
- label: I agree I have searched Open Requests
|
||||
required: true
|
||||
|
||||
name: ✨ Feature Request
|
||||
description: Suggest an idea or enhancement for this project
|
||||
title: "[Feature]: "
|
||||
labels: ["enhancement"]
|
||||
assignees: []
|
||||
body:
|
||||
# ─────────────────────────────────────────────────────────────────────────────
|
||||
# INTRODUCTION
|
||||
# ─────────────────────────────────────────────────────────────────────────────
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: |
|
||||
## Thanks for suggesting a new feature! 💡
|
||||
|
||||
We appreciate you taking the time to help improve OpenSourcePOS.
|
||||
|
||||
> 📋 **Before submitting:** Please search [existing feature requests](https://github.com/opensourcepos/opensourcepos/issues?q=is%3Aissue+is%3Aopen+label%3Aenhancement) to ensure your idea hasn't already been suggested.
|
||||
|
||||
# ─────────────────────────────────────────────────────────────────────────────
|
||||
# FEATURE DETAILS
|
||||
# ─────────────────────────────────────────────────────────────────────────────
|
||||
- type: dropdown
|
||||
id: feature-type
|
||||
attributes:
|
||||
label: 🏷️ Feature Type
|
||||
description: What type of feature are you requesting?
|
||||
options:
|
||||
- "✨ New Feature"
|
||||
- "📝 Documentation Improvement"
|
||||
- "🎨 UI/UX Enhancement"
|
||||
- "🔨 Code Refactoring"
|
||||
- "⚡ Performance Improvement"
|
||||
- "✅ New Test Coverage"
|
||||
- "🔌 Plugin/Integration"
|
||||
default: 0
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: dropdown
|
||||
id: ospos-version
|
||||
attributes:
|
||||
label: 📦 OpenSourcePOS Version
|
||||
description: What version are you currently running?
|
||||
options:
|
||||
- development (unreleased)
|
||||
- OpenSourcePOS 3.4.2
|
||||
- OpenSourcePOS 3.4.1
|
||||
- OpenSourcePOS 3.4.0
|
||||
- OpenSourcePOS 3.3.9
|
||||
- OpenSourcePOS 3.3.8
|
||||
default: 0
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
id: problem-statement
|
||||
attributes:
|
||||
label: 🎯 Problem Statement
|
||||
description: |
|
||||
Is your feature request related to a problem? Please describe.
|
||||
|
||||
A clear description of what the problem is. Ex: I'm always frustrated when [...]
|
||||
placeholder: |
|
||||
Example: I always have to manually calculate taxes for different regions,
|
||||
which is time-consuming and error-prone.
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
id: proposed-solution
|
||||
attributes:
|
||||
label: 💡 Proposed Solution
|
||||
description: A clear and concise description of what you want to happen.
|
||||
placeholder: |
|
||||
Example: Add an automatic tax calculation feature that:
|
||||
- Detects the customer's region
|
||||
- Applies the correct tax rate
|
||||
- Generates a tax report automatically
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
id: alternatives
|
||||
attributes:
|
||||
label: 🔄 Alternatives Considered
|
||||
description: A clear description of any alternative solutions or features you've considered.
|
||||
placeholder: |
|
||||
Example: I considered using an external tax service, but it would be
|
||||
better to have this integrated directly into OpenSourcePOS.
|
||||
|
||||
# ─────────────────────────────────────────────────────────────────────────────
|
||||
# ADDITIONAL INFORMATION
|
||||
# ─────────────────────────────────────────────────────────────────────────────
|
||||
- type: textarea
|
||||
id: additional-context
|
||||
attributes:
|
||||
label: 📎 Additional Context
|
||||
description: |
|
||||
Add any other context, screenshots, mockups, or references about the feature request here.
|
||||
|
||||
**Helpful additions:**
|
||||
- Links to similar features in other software
|
||||
- Mockups or diagrams
|
||||
- Code examples
|
||||
- Documentation references
|
||||
placeholder: |
|
||||
Any other relevant information, links, or screenshots...
|
||||
|
||||
- type: textarea
|
||||
id: acceptance-criteria
|
||||
attributes:
|
||||
label: ✅ Acceptance Criteria
|
||||
description: |
|
||||
(Optional) Define what "done" looks like for this feature.
|
||||
|
||||
Format: **Given** [context], **When** [action], **Then** [outcome]
|
||||
placeholder: |
|
||||
Given a customer is selected from region X
|
||||
When the sale is completed
|
||||
Then the tax rate for region X is automatically applied
|
||||
And the tax amount is correctly calculated
|
||||
And a tax entry is logged in the report
|
||||
|
||||
# ─────────────────────────────────────────────────────────────────────────────
|
||||
# CONFIRMATION
|
||||
# ─────────────────────────────────────────────────────────────────────────────
|
||||
- type: checkboxes
|
||||
id: terms
|
||||
attributes:
|
||||
label: ✓ Confirmation
|
||||
description: Please confirm before submitting
|
||||
options:
|
||||
- label: I have searched existing feature requests to ensure this is not a duplicate
|
||||
required: true
|
||||
- label: I have provided a clear problem statement and proposed solution
|
||||
required: true
|
||||
12
.github/workflows/build-release.yml
vendored
12
.github/workflows/build-release.yml
vendored
@@ -2,10 +2,6 @@ name: Build and Release
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
tags:
|
||||
- '*'
|
||||
pull_request:
|
||||
branches:
|
||||
- master
|
||||
@@ -80,8 +76,8 @@ jobs:
|
||||
id: version
|
||||
run: |
|
||||
VERSION=$(grep "application_version" app/Config/App.php | sed "s/.*= '\(.*\)';/\1/g")
|
||||
BRANCH=$(echo "${GITHUB_REF#refs/heads/}" | sed 's/feature\///')
|
||||
TAG=$(echo "${GITHUB_TAG:-$BRANCH}" | tr '/' '-')
|
||||
BRANCH=$(echo "${GITHUB_REF#refs/heads/}" | sed 's/feature\///' | tr '/' '_')
|
||||
TAG=$(echo "${GITHUB_TAG:-$BRANCH}" | tr '/' '_')
|
||||
SHORT_SHA=$(git rev-parse --short=6 HEAD)
|
||||
echo "version=$VERSION" >> $GITHUB_OUTPUT
|
||||
echo "version-tag=$VERSION-$BRANCH-$SHORT_SHA" >> $GITHUB_OUTPUT
|
||||
@@ -157,7 +153,7 @@ jobs:
|
||||
- name: Determine Docker tags
|
||||
id: tags
|
||||
run: |
|
||||
BRANCH=$(echo "${GITHUB_REF#refs/heads/}" | tr '/' '-')
|
||||
BRANCH=$(echo "${GITHUB_REF#refs/heads/}" | tr '/' '_')
|
||||
if [ "$BRANCH" = "master" ]; then
|
||||
echo "tags=${{ secrets.DOCKER_USERNAME }}/opensourcepos:${{ needs.build.outputs.version-tag }},${{ secrets.DOCKER_USERNAME }}/opensourcepos:latest" >> $GITHUB_OUTPUT
|
||||
else
|
||||
@@ -215,4 +211,4 @@ jobs:
|
||||
prerelease: true
|
||||
draft: false
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
33
.github/workflows/opencode.yml
vendored
33
.github/workflows/opencode.yml
vendored
@@ -1,33 +0,0 @@
|
||||
name: opencode
|
||||
|
||||
on:
|
||||
issue_comment:
|
||||
types: [created]
|
||||
pull_request_review_comment:
|
||||
types: [created]
|
||||
|
||||
jobs:
|
||||
opencode:
|
||||
if: |
|
||||
contains(github.event.comment.body, ' /oc') ||
|
||||
startsWith(github.event.comment.body, '/oc') ||
|
||||
contains(github.event.comment.body, ' /opencode') ||
|
||||
startsWith(github.event.comment.body, '/opencode')
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
id-token: write
|
||||
contents: read
|
||||
pull-requests: read
|
||||
issues: read
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
- name: Run opencode
|
||||
uses: anomalyco/opencode/github@latest
|
||||
env:
|
||||
ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
|
||||
with:
|
||||
model: anthropic/claude-3-haiku-20240307
|
||||
172
.github/workflows/release.yml
vendored
Normal file
172
.github/workflows/release.yml
vendored
Normal file
@@ -0,0 +1,172 @@
|
||||
name: Release Version Bump
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
version_type:
|
||||
description: 'Version bump type'
|
||||
required: true
|
||||
type: choice
|
||||
options:
|
||||
- minor
|
||||
- major
|
||||
- patch
|
||||
default: 'minor'
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
|
||||
jobs:
|
||||
prepare-release:
|
||||
name: Prepare Release
|
||||
runs-on: ubuntu-22.04
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Get current version
|
||||
id: current_version
|
||||
run: |
|
||||
CURRENT_VERSION=$(grep "application_version" app/Config/App.php | sed "s/.*= '\(.*\)';/\1/g")
|
||||
echo "current_version=$CURRENT_VERSION" >> $GITHUB_OUTPUT
|
||||
echo "Current version: $CURRENT_VERSION"
|
||||
|
||||
- name: Calculate new version
|
||||
id: version
|
||||
run: |
|
||||
CURRENT_VERSION="${{ steps.current_version.outputs.current_version }}"
|
||||
VERSION_TYPE="${{ github.event.inputs.version_type }}"
|
||||
|
||||
# Parse current version
|
||||
MAJOR=$(echo $CURRENT_VERSION | cut -d. -f1)
|
||||
MINOR=$(echo $CURRENT_VERSION | cut -d. -f2)
|
||||
PATCH=$(echo $CURRENT_VERSION | cut -d. -f3)
|
||||
|
||||
# Bump version based on type
|
||||
case $VERSION_TYPE in
|
||||
major)
|
||||
MAJOR=$((MAJOR + 1))
|
||||
MINOR=0
|
||||
PATCH=0
|
||||
;;
|
||||
minor)
|
||||
MINOR=$((MINOR + 1))
|
||||
PATCH=0
|
||||
;;
|
||||
patch)
|
||||
PATCH=$((PATCH + 1))
|
||||
;;
|
||||
esac
|
||||
|
||||
NEW_VERSION="${MAJOR}.${MINOR}.${PATCH}"
|
||||
echo "new_version=$NEW_VERSION" >> $GITHUB_OUTPUT
|
||||
echo "previous_version=$CURRENT_VERSION" >> $GITHUB_OUTPUT
|
||||
echo "New version: $NEW_VERSION (was: $CURRENT_VERSION, type: $VERSION_TYPE)"
|
||||
|
||||
- name: Update version in App.php
|
||||
run: |
|
||||
NEW_VERSION="${{ steps.version.outputs.new_version }}"
|
||||
sed -i "s/public string \\\$application_version = '[^']*';/public string \\\$application_version = '$NEW_VERSION';/" app/Config/App.php
|
||||
echo "Updated app/Config/App.php"
|
||||
|
||||
- name: Update version in package.json
|
||||
run: |
|
||||
NEW_VERSION="${{ steps.version.outputs.new_version }}"
|
||||
sed -i "s/\"version\": \"[^\"]*\",/\"version\": \"$NEW_VERSION\",/" package.json
|
||||
echo "Updated package.json"
|
||||
|
||||
- name: Update version in docker-compose.nginx.yml
|
||||
run: |
|
||||
NEW_VERSION="${{ steps.version.outputs.new_version }}"
|
||||
sed -i "s/jekkos\/opensourcepos:[^ ]*/jekkos\/opensourcepos:$NEW_VERSION/" docker-compose.nginx.yml
|
||||
echo "Updated docker-compose.nginx.yml"
|
||||
|
||||
- name: Update version in README.md
|
||||
run: |
|
||||
NEW_VERSION="${{ steps.version.outputs.new_version }}"
|
||||
# Extract major.minor for the "latest X.Y version" text
|
||||
MAJOR_MINOR=$(echo "$NEW_VERSION" | cut -d. -f1,2)
|
||||
sed -i "s/The latest \`[0-9]*\.[0-9]*\` version/The latest \`${MAJOR_MINOR}\` version/" README.md
|
||||
echo "Updated README.md with version ${MAJOR_MINOR}"
|
||||
|
||||
- name: Generate changelog
|
||||
id: changelog
|
||||
run: |
|
||||
PREVIOUS_VERSION="${{ steps.version.outputs.previous_version }}"
|
||||
NEW_VERSION="${{ steps.version.outputs.new_version }}"
|
||||
|
||||
# Get commits since last version
|
||||
if git rev-parse "$PREVIOUS_VERSION" >/dev/null 2>&1; then
|
||||
COMMITS=$(git log "$PREVIOUS_VERSION"..HEAD --pretty=format:"- %s" --no-merges)
|
||||
else
|
||||
COMMITS=$(git log --pretty=format:"- %s" --no-merges -50)
|
||||
fi
|
||||
|
||||
# Create changelog entry
|
||||
CHANGELOG_FILE="CHANGELOG.md"
|
||||
|
||||
# Create the new version comparison link
|
||||
NEW_LINK="[${NEW_VERSION}]: https://github.com/opensourcepos/opensourcepos/compare/${PREVIOUS_VERSION}...${NEW_VERSION}"
|
||||
|
||||
# Insert new link after [unreleased] line
|
||||
sed -i "/^\[unreleased\]/a $NEW_LINK" "$CHANGELOG_FILE"
|
||||
|
||||
# Update [unreleased] link to start from new version
|
||||
sed -i "s|^\[unreleased\]: .*|\[unreleased\]: https://github.com/opensourcepos/opensourcepos/compare/${NEW_VERSION}...HEAD|" "$CHANGELOG_FILE"
|
||||
|
||||
# Create version header and content using temp file to avoid sed issues with special characters
|
||||
VERSION_DATE=$(date +%Y-%m-%d)
|
||||
VERSION_HEADER="## [$NEW_VERSION] - $VERSION_DATE"
|
||||
|
||||
# Create temp file with changelog entry
|
||||
TMP_FILE=$(mktemp)
|
||||
{
|
||||
echo ""
|
||||
echo "$VERSION_HEADER"
|
||||
echo ""
|
||||
echo "$COMMITS"
|
||||
} > "$TMP_FILE"
|
||||
|
||||
# Insert after Unreleased header
|
||||
sed -i "/^## \[Unreleased\]/r $TMP_FILE" "$CHANGELOG_FILE"
|
||||
rm "$TMP_FILE"
|
||||
|
||||
echo "Updated CHANGELOG.md"
|
||||
echo "Changelog entries:"
|
||||
echo "$COMMITS"
|
||||
|
||||
- name: Update version in issue templates
|
||||
run: |
|
||||
NEW_VERSION="${{ steps.version.outputs.new_version }}"
|
||||
|
||||
# Calculate version to remove (keep 5 versions)
|
||||
PREVIOUS_VERSION="${{ steps.version.outputs.previous_version }}"
|
||||
|
||||
# Bug report template - insert new version after development (unreleased)
|
||||
BUG_TEMPLATE=".github/ISSUE_TEMPLATE/bug report.yml"
|
||||
sed -i "/- development (unreleased)/a\\ - OpenSourcePOS ${NEW_VERSION}" "$BUG_TEMPLATE"
|
||||
# Remove the oldest version (5th version from the end)
|
||||
sed -i "/OpenSourcePOS 3\\.3\\.7/d" "$BUG_TEMPLATE"
|
||||
echo "Updated $BUG_TEMPLATE"
|
||||
|
||||
# Feature request template - insert new version after development (unreleased)
|
||||
FEATURE_TEMPLATE=".github/ISSUE_TEMPLATE/feature_request.yml"
|
||||
sed -i "/- development (unreleased)/a\\ - OpenSourcePOS ${NEW_VERSION}" "$FEATURE_TEMPLATE"
|
||||
# Remove the oldest version (5th version from the end)
|
||||
sed -i "/OpenSourcePOS 3\\.3\\.7/d" "$FEATURE_TEMPLATE"
|
||||
echo "Updated $FEATURE_TEMPLATE"
|
||||
|
||||
- name: Commit version bump
|
||||
run: |
|
||||
git config user.name "github-actions[bot]"
|
||||
git config user.email "github-actions[bot]@users.noreply.github.com"
|
||||
|
||||
NEW_VERSION="${{ steps.version.outputs.new_version }}"
|
||||
|
||||
git add app/Config/App.php package.json docker-compose.nginx.yml CHANGELOG.md README.md .github/ISSUE_TEMPLATE/
|
||||
git commit -m "chore: release version $NEW_VERSION"
|
||||
git push origin HEAD
|
||||
72
.github/workflows/update-issue-templates.yml
vendored
72
.github/workflows/update-issue-templates.yml
vendored
@@ -1,72 +0,0 @@
|
||||
name: Update Issue Templates
|
||||
|
||||
on:
|
||||
release:
|
||||
types: [published]
|
||||
workflow_dispatch:
|
||||
schedule:
|
||||
- cron: '0 0 * * 0'
|
||||
|
||||
jobs:
|
||||
update-templates:
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: write
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Fetch releases and update templates
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
run: |
|
||||
# Fetch releases from GitHub API
|
||||
RELEASES=$(gh api repos/${{ github.repository }}/releases --jq '.[].tag_name' | head -n 10)
|
||||
|
||||
# Create temporary file with options
|
||||
OPTIONS_FILE=$(mktemp)
|
||||
echo " - development (unreleased)" >> "$OPTIONS_FILE"
|
||||
while IFS= read -r release; do
|
||||
echo " - opensourcepos $release" >> "$OPTIONS_FILE"
|
||||
done <<< "$RELEASES"
|
||||
|
||||
update_template() {
|
||||
local template="$1"
|
||||
local template_path=".github/ISSUE_TEMPLATE/$template"
|
||||
|
||||
# Find the line numbers for the OpensourcePOS Version dropdown
|
||||
start_line=$(grep -n "label: OpensourcePOS Version" "$template_path" | cut -d: -f1)
|
||||
|
||||
if [ -z "$start_line" ]; then
|
||||
echo "Could not find OpensourcePOS Version in $template"
|
||||
return 1
|
||||
fi
|
||||
|
||||
# Find the options section and default line
|
||||
options_start=$((start_line + 3))
|
||||
default_line=$(grep -n "default:" "$template_path" | awk -F: -v opts="$options_start" '$1 > opts {print $1; exit}')
|
||||
|
||||
# Create new template file
|
||||
head -n $((options_start - 1)) "$template_path" > "${template_path}.new"
|
||||
cat "$OPTIONS_FILE" >> "${template_path}.new"
|
||||
tail -n +$default_line "$template_path" >> "${template_path}.new"
|
||||
mv "${template_path}.new" "$template_path"
|
||||
|
||||
echo "Updated $template"
|
||||
}
|
||||
|
||||
update_template "bug report.yml"
|
||||
update_template "feature_request.yml"
|
||||
|
||||
- name: Commit and push changes
|
||||
run: |
|
||||
git config user.name "github-actions[bot]"
|
||||
git config user.email "github-actions[bot]@users.noreply.github.com"
|
||||
git add .github/ISSUE_TEMPLATE/*.yml
|
||||
if git diff --staged --quiet; then
|
||||
echo "No changes to commit"
|
||||
else
|
||||
git commit -m "Update issue templates with latest releases [skip ci]"
|
||||
git push
|
||||
fi
|
||||
@@ -1,3 +0,0 @@
|
||||
FROM php:8.4-cli
|
||||
RUN apt-get update && apt-get install -y libicu-dev && docker-php-ext-install intl
|
||||
WORKDIR /app
|
||||
36
INSTALL.md
36
INSTALL.md
@@ -8,26 +8,36 @@
|
||||
|
||||
## Security Configuration
|
||||
|
||||
### Allowed Hostnames (Required for Production)
|
||||
### Allowed Hostnames (REQUIRED for Production)
|
||||
|
||||
OpenSourcePOS validates the Host header against a whitelist to prevent Host Header Injection attacks (GHSA-jchf-7hr6-h4f3). **You must configure this for production deployments.**
|
||||
⚠️ **CRITICAL**: OpenSourcePOS validates the Host header to prevent Host Header Injection attacks (GHSA-jchf-7hr6-h4f3). **You MUST configure `app.allowedHostnames` for production deployments. If not configured, the application will fail to start.**
|
||||
|
||||
Add the following to your `.env` file:
|
||||
**Add to your `.env` file:**
|
||||
|
||||
```
|
||||
app.allowedHostnames.0 = 'yourdomain.com'
|
||||
app.allowedHostnames.1 = 'www.yourdomain.com'
|
||||
```bash
|
||||
# Comma-separated list of allowed hostnames (no protocols or ports)
|
||||
app.allowedHostnames = 'yourdomain.com,www.yourdomain.com'
|
||||
```
|
||||
|
||||
**For local development**, use:
|
||||
```
|
||||
app.allowedHostnames.0 = 'localhost'
|
||||
**For local development:**
|
||||
|
||||
```bash
|
||||
app.allowedHostnames = 'localhost'
|
||||
```
|
||||
|
||||
If `allowedHostnames` is not configured:
|
||||
1. A security warning will be logged
|
||||
2. The application will fall back to 'localhost' as the hostname
|
||||
3. This means URLs generated by the application (links, redirects, etc.) will point to 'localhost'
|
||||
**If you see this error at startup:**
|
||||
|
||||
```text
|
||||
RuntimeException: Security: allowedHostnames is not configured.
|
||||
```
|
||||
|
||||
**Solution**: Add `app.allowedHostnames` to your `.env` file with your domain(s).
|
||||
|
||||
**Why this matters:**
|
||||
- Prevents Host Header Injection attacks (GHSA-jchf-7hr6-h4f3)
|
||||
- Ensures URLs are generated with the correct domain
|
||||
- Security advisory: https://github.com/opensourcepos/opensourcepos/security/advisories/GHSA-jchf-7hr6-h4f3
|
||||
- Fixes issue #4480: .env configuration now works via comma-separated values
|
||||
|
||||
### HTTPS Behind Proxy
|
||||
|
||||
|
||||
@@ -62,14 +62,14 @@ class App extends BaseConfig
|
||||
* an entry in this list, the request will use the first allowed hostname.
|
||||
*
|
||||
* IMPORTANT: This MUST be configured for production deployments.
|
||||
* If empty, the application will fall back to 'localhost'.
|
||||
* If empty in production, the application will fail to start.
|
||||
* In development, it will fall back to 'localhost' with a warning.
|
||||
*
|
||||
* Configure via .env file:
|
||||
* app.allowedHostnames.0 = 'example.com'
|
||||
* app.allowedHostnames.1 = 'www.example.com'
|
||||
* Configure via .env file (comma-separated list):
|
||||
* app.allowedHostnames = 'example.com,www.example.com'
|
||||
*
|
||||
* For local development:
|
||||
* app.allowedHostnames.0 = 'localhost'
|
||||
* app.allowedHostnames = 'localhost'
|
||||
*
|
||||
* @var list<string>
|
||||
*/
|
||||
@@ -291,6 +291,17 @@ class App extends BaseConfig
|
||||
public function __construct()
|
||||
{
|
||||
parent::__construct();
|
||||
|
||||
// Solution for CodeIgniter 4 limitation: arrays cannot be set from .env
|
||||
// See: https://github.com/codeigniter4/CodeIgniter4/issues/7311
|
||||
$envAllowedHostnames = getenv('app.allowedHostnames');
|
||||
if ($envAllowedHostnames !== false && trim($envAllowedHostnames) !== '') {
|
||||
$this->allowedHostnames = array_values(array_filter(
|
||||
array_map('trim', explode(',', $envAllowedHostnames)),
|
||||
static fn (string $hostname): bool => $hostname !== ''
|
||||
));
|
||||
}
|
||||
|
||||
$this->https_on = (isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] == 'on') || (isset($_ENV['FORCE_HTTPS']) && $_ENV['FORCE_HTTPS'] == 'true');
|
||||
|
||||
$host = $this->getValidHost();
|
||||
@@ -305,19 +316,36 @@ class App extends BaseConfig
|
||||
* Security: Prevents Host Header Injection attacks (GHSA-jchf-7hr6-h4f3)
|
||||
* by validating the HTTP_HOST against a whitelist of allowed hostnames.
|
||||
*
|
||||
* In production: Fails fast if allowedHostnames is not configured.
|
||||
* In development: Allows localhost fallback with an error log.
|
||||
*
|
||||
* @return string A validated hostname
|
||||
* @throws \RuntimeException If allowedHostnames is not configured in production
|
||||
*/
|
||||
private function getValidHost(): string
|
||||
{
|
||||
$httpHost = $_SERVER['HTTP_HOST'] ?? 'localhost';
|
||||
|
||||
// Determine environment
|
||||
// CodeIgniter's test bootstrap sets $_SERVER['CI_ENVIRONMENT'] = 'testing'
|
||||
// Check $_SERVER first, then $_ENV, then fall back to 'production'
|
||||
$environment = $_SERVER['CI_ENVIRONMENT'] ?? $_ENV['CI_ENVIRONMENT'] ?? getenv('CI_ENVIRONMENT') ?: 'production';
|
||||
|
||||
if (empty($this->allowedHostnames)) {
|
||||
log_message('warning',
|
||||
$errorMessage =
|
||||
'Security: allowedHostnames is not configured. ' .
|
||||
'Host header injection protection is disabled. ' .
|
||||
'Please set app.allowedHostnames in your .env file. ' .
|
||||
'Received Host: ' . $httpHost
|
||||
);
|
||||
'Set app.allowedHostnames in your .env file. ' .
|
||||
'Example: app.allowedHostnames = "example.com,www.example.com" ' .
|
||||
'Received Host: ' . $httpHost;
|
||||
|
||||
// Production: Fail explicitly to prevent silent security vulnerabilities
|
||||
// Testing and development: Allow localhost fallback
|
||||
if ($environment === 'production') {
|
||||
throw new \RuntimeException($errorMessage);
|
||||
}
|
||||
|
||||
log_message('error', $errorMessage . ' Using localhost fallback (development only).');
|
||||
return 'localhost';
|
||||
}
|
||||
|
||||
@@ -325,6 +353,7 @@ class App extends BaseConfig
|
||||
return $httpHost;
|
||||
}
|
||||
|
||||
// Host not in whitelist - use first configured hostname as fallback
|
||||
log_message('warning',
|
||||
'Security: Rejected HTTP_HOST "' . $httpHost . '" - not in allowedHostnames whitelist. ' .
|
||||
'Using fallback: ' . $this->allowedHostnames[0]
|
||||
|
||||
@@ -70,7 +70,7 @@ class Filters extends BaseFilters
|
||||
public array $globals = [
|
||||
'before' => [
|
||||
'honeypot',
|
||||
'csrf' => ['except' => 'login'],
|
||||
'csrf' => ['except' => 'login|migrate'],
|
||||
'invalidchars',
|
||||
],
|
||||
'after' => [
|
||||
|
||||
@@ -5,6 +5,7 @@ namespace Config;
|
||||
use App\Models\Appconfig;
|
||||
use CodeIgniter\Cache\CacheInterface;
|
||||
use CodeIgniter\Config\BaseConfig;
|
||||
use CodeIgniter\Database\Exceptions\DatabaseException;
|
||||
|
||||
/**
|
||||
* This class holds the configuration options stored from the database so that on launch those settings can be cached
|
||||
@@ -34,11 +35,21 @@ class OSPOS extends BaseConfig
|
||||
if ($cache) {
|
||||
$this->settings = decode_array($cache);
|
||||
} else {
|
||||
$appconfig = model(Appconfig::class);
|
||||
foreach ($appconfig->get_all()->getResult() as $app_config) {
|
||||
$this->settings[$app_config->key] = $app_config->value;
|
||||
try {
|
||||
$appconfig = model(Appconfig::class);
|
||||
foreach ($appconfig->get_all()->getResult() as $app_config) {
|
||||
$this->settings[$app_config->key] = $app_config->value;
|
||||
}
|
||||
$this->cache->save('settings', encode_array($this->settings));
|
||||
} catch (DatabaseException $e) {
|
||||
// Database table doesn't exist yet (migrations haven't run)
|
||||
// Return empty settings to allow migration page to display
|
||||
$this->settings = [
|
||||
'language' => 'english',
|
||||
'language_code' => 'en',
|
||||
'company' => 'Home'
|
||||
];
|
||||
}
|
||||
$this->cache->save('settings', encode_array($this->settings));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -50,4 +61,4 @@ class OSPOS extends BaseConfig
|
||||
$this->cache->delete('settings');
|
||||
$this->set_settings();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -10,6 +10,7 @@ $routes->setDefaultController('Login');
|
||||
$routes->get('/', 'Login::index');
|
||||
$routes->get('login', 'Login::index');
|
||||
$routes->post('login', 'Login::index');
|
||||
$routes->post('migrate', 'Login::migrate');
|
||||
|
||||
$routes->add('no_access/index/(:segment)', 'No_access::index/$1');
|
||||
$routes->add('no_access/index/(:segment)/(:segment)', 'No_access::index/$1/$2');
|
||||
|
||||
@@ -5,6 +5,8 @@ namespace Config;
|
||||
use CodeIgniter\Config\BaseConfig;
|
||||
use CodeIgniter\Session\Handlers\BaseHandler;
|
||||
use CodeIgniter\Session\Handlers\DatabaseHandler;
|
||||
use CodeIgniter\Session\Handlers\FileHandler;
|
||||
use Config\Database;
|
||||
|
||||
class Session extends BaseConfig
|
||||
{
|
||||
@@ -124,4 +126,23 @@ class Session extends BaseConfig
|
||||
* seconds.
|
||||
*/
|
||||
public int $lockMaxRetries = 300;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
parent::__construct();
|
||||
|
||||
if ($this->driver === DatabaseHandler::class) {
|
||||
try {
|
||||
$db = Database::connect();
|
||||
|
||||
if (!$db->tableExists($this->savePath)) {
|
||||
$this->driver = FileHandler::class;
|
||||
$this->savePath = WRITEPATH . 'session';
|
||||
}
|
||||
} catch (\CodeIgniter\Database\Exceptions\DatabaseException $e) {
|
||||
$this->driver = FileHandler::class;
|
||||
$this->savePath = WRITEPATH . 'session';
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -135,4 +135,19 @@ class OSPOSRules
|
||||
{
|
||||
return parse_decimals($candidate) !== false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates that a locale-aware decimal value is non-negative (>= 0).
|
||||
*
|
||||
* @param string $candidate
|
||||
* @param string|null $error
|
||||
* @return bool
|
||||
* @noinspection PhpUnused
|
||||
*/
|
||||
public function nonNegativeDecimal(string $candidate, ?string &$error = null): bool
|
||||
{
|
||||
$value = parse_decimals($candidate);
|
||||
|
||||
return $value !== false && $value >= 0;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
namespace App\Controllers;
|
||||
|
||||
use App\Libraries\Barcode_lib;
|
||||
use App\Libraries\Image_lib;
|
||||
use App\Libraries\Mailchimp_lib;
|
||||
use App\Libraries\Receiving_lib;
|
||||
use App\Libraries\Sale_lib;
|
||||
@@ -250,6 +251,10 @@ class Config extends Secure_Controller
|
||||
$data['image_allowed_types'] = array_combine($image_allowed_types, $image_allowed_types);
|
||||
$data['selected_image_allowed_types'] = explode(',', $this->config['image_allowed_types']);
|
||||
|
||||
$exif_fields = ['Make', 'Model', 'Orientation', 'Copyright', 'Software', 'DateTime', 'GPS'];
|
||||
$data['exif_fields'] = array_combine($exif_fields, $exif_fields);
|
||||
$data['selected_exif_fields'] = array_filter(explode(',', $this->config['exif_fields_to_keep'] ?? ''));
|
||||
|
||||
// Integrations Related fields
|
||||
$data['mailchimp'] = [];
|
||||
|
||||
@@ -355,6 +360,15 @@ class Config extends Secure_Controller
|
||||
|
||||
$file->move(FCPATH . 'uploads/', $file_info['raw_name'] . '.' . $file_info['file_ext'], true);
|
||||
|
||||
$exif_fields_to_keep = array_filter(explode(',', $this->appconfig->get_value('exif_fields_to_keep', 'Copyright,Orientation,Software')));
|
||||
if (!empty($exif_fields_to_keep)) {
|
||||
$image_lib = new Image_lib();
|
||||
$filepath = FCPATH . 'uploads/' . $file_info['raw_name'] . '.' . $file_info['file_ext'];
|
||||
if (!$image_lib->stripEXIF($filepath, $exif_fields_to_keep)) {
|
||||
log_message('warning', 'EXIF stripping failed for: ' . $filepath);
|
||||
}
|
||||
}
|
||||
|
||||
return ($file_info);
|
||||
}
|
||||
|
||||
@@ -382,7 +396,8 @@ class Config extends Secure_Controller
|
||||
'image_max_width' => $this->request->getPost('image_max_width', FILTER_SANITIZE_NUMBER_INT),
|
||||
'image_max_height' => $this->request->getPost('image_max_height', FILTER_SANITIZE_NUMBER_INT),
|
||||
'image_max_size' => $this->request->getPost('image_max_size', FILTER_SANITIZE_NUMBER_INT),
|
||||
'image_allowed_types' => implode(',', $this->request->getPost('image_allowed_types')),
|
||||
'image_allowed_types' => implode(',', $this->request->getPost('image_allowed_types') ?? []),
|
||||
'exif_fields_to_keep' => implode(',', $this->request->getPost('exif_fields_to_keep') ?? []),
|
||||
'gcaptcha_enable' => $this->request->getPost('gcaptcha_enable') != null,
|
||||
'gcaptcha_secret_key' => $this->request->getPost('gcaptcha_secret_key'),
|
||||
'gcaptcha_site_key' => $this->request->getPost('gcaptcha_site_key'),
|
||||
@@ -504,9 +519,24 @@ class Config extends Secure_Controller
|
||||
$password = $this->encrypter->encrypt($this->request->getPost('smtp_pass'));
|
||||
}
|
||||
|
||||
$protocol = $this->request->getPost('protocol');
|
||||
$mailpath = $this->request->getPost('mailpath');
|
||||
|
||||
// Validate mailpath: required for sendmail, optional for others but must be safe if provided
|
||||
$isMailpathRequired = ($protocol === 'sendmail');
|
||||
$isMailpathProvided = !empty($mailpath);
|
||||
$isMailpathValid = $isMailpathProvided && preg_match('/^[a-zA-Z0-9_\-\/.]+$/', $mailpath);
|
||||
|
||||
if (($isMailpathRequired && !$isMailpathProvided) || ($isMailpathProvided && !$isMailpathValid)) {
|
||||
return $this->response->setJSON([
|
||||
'success' => false,
|
||||
'message' => lang('Config.mailpath_invalid')
|
||||
]);
|
||||
}
|
||||
|
||||
$batch_save_data = [
|
||||
'protocol' => $this->request->getPost('protocol'),
|
||||
'mailpath' => $this->request->getPost('mailpath'),
|
||||
'protocol' => $protocol,
|
||||
'mailpath' => $mailpath,
|
||||
'smtp_host' => $this->request->getPost('smtp_host'),
|
||||
'smtp_user' => $this->request->getPost('smtp_user'),
|
||||
'smtp_pass' => $password,
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
namespace App\Controllers;
|
||||
|
||||
use App\Libraries\MY_Migration;
|
||||
use CodeIgniter\HTTP\RedirectResponse;
|
||||
use CodeIgniter\HTTP\ResponseInterface;
|
||||
|
||||
@@ -81,7 +82,7 @@ class Home extends Secure_Controller
|
||||
if ($this->employee->check_password($this->request->getPost('username', FILTER_SANITIZE_FULL_SPECIAL_CHARS), $this->request->getPost('current_password'))) {
|
||||
// Validate password length BEFORE hashing
|
||||
$new_password = $this->request->getPost('password');
|
||||
|
||||
|
||||
if (strlen($new_password) < 8) {
|
||||
return $this->response->setJSON([
|
||||
'success' => false,
|
||||
@@ -89,7 +90,7 @@ class Home extends Secure_Controller
|
||||
'id' => NEW_ENTRY
|
||||
]);
|
||||
}
|
||||
|
||||
|
||||
$employee_data = [
|
||||
'username' => $this->request->getPost('username', FILTER_SANITIZE_FULL_SPECIAL_CHARS),
|
||||
'password' => password_hash($new_password, PASSWORD_DEFAULT),
|
||||
@@ -124,4 +125,4 @@ class Home extends Secure_Controller
|
||||
]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,8 +3,10 @@
|
||||
namespace App\Controllers;
|
||||
|
||||
use App\Libraries\Barcode_lib;
|
||||
use App\Libraries\Image_lib;
|
||||
use App\Libraries\Item_lib;
|
||||
|
||||
use App\Models\Appconfig;
|
||||
use App\Models\Attribute;
|
||||
use App\Models\Inventory;
|
||||
use App\Models\Item;
|
||||
@@ -39,6 +41,7 @@ class Items extends Secure_Controller
|
||||
private Stock_location $stock_location;
|
||||
private Supplier $supplier;
|
||||
private Tax_category $tax_category;
|
||||
private Appconfig $appconfig;
|
||||
private array $config;
|
||||
|
||||
|
||||
@@ -62,6 +65,7 @@ class Items extends Secure_Controller
|
||||
$this->stock_location = model(Stock_location::class);
|
||||
$this->supplier = model(Supplier::class);
|
||||
$this->tax_category = model(Tax_category::class);
|
||||
$this->appconfig = model(Appconfig::class);
|
||||
$this->config = config(OSPOS::class)->settings;
|
||||
}
|
||||
|
||||
@@ -788,6 +792,16 @@ class Items extends Secure_Controller
|
||||
];
|
||||
|
||||
$file->move(FCPATH . 'uploads/item_pics/', $file_info['raw_name'] . '.' . $file_info['file_ext'], true);
|
||||
|
||||
$exif_fields_to_keep = array_filter(explode(',', $this->appconfig->get_value('exif_fields_to_keep', 'Copyright,Orientation,Software')));
|
||||
if (!empty($exif_fields_to_keep)) {
|
||||
$image_lib = new Image_lib();
|
||||
$filepath = FCPATH . 'uploads/item_pics/' . $file_info['raw_name'] . '.' . $file_info['file_ext'];
|
||||
if (!$image_lib->stripEXIF($filepath, $exif_fields_to_keep)) {
|
||||
log_message('warning', 'EXIF stripping failed for: ' . $filepath);
|
||||
}
|
||||
}
|
||||
|
||||
return ($file_info);
|
||||
}
|
||||
|
||||
|
||||
@@ -5,6 +5,7 @@ namespace App\Controllers;
|
||||
use App\Libraries\MY_Migration;
|
||||
use App\Models\Employee;
|
||||
use CodeIgniter\HTTP\RedirectResponse;
|
||||
use CodeIgniter\HTTP\ResponseInterface;
|
||||
use CodeIgniter\Model;
|
||||
use Config\OSPOS;
|
||||
use Config\Services;
|
||||
@@ -36,6 +37,7 @@ class Login extends BaseController
|
||||
|
||||
$data = [
|
||||
'has_errors' => false,
|
||||
'is_new_install' => !(MY_Migration::get_current_version()),
|
||||
'is_latest' => $migration->is_latest(),
|
||||
'latest_version' => $migration->get_latest_migration(),
|
||||
'gcaptcha_enabled' => $gcaptcha_enabled,
|
||||
@@ -71,4 +73,28 @@ class Login extends BaseController
|
||||
|
||||
return redirect()->to('home');
|
||||
}
|
||||
|
||||
public function migrate(): ResponseInterface
|
||||
{
|
||||
try {
|
||||
$migration = new MY_Migration(config('Migrations'));
|
||||
$migration->migrate_to_ci4();
|
||||
|
||||
set_time_limit(3600);
|
||||
$migration->setNamespace('App')->latest();
|
||||
|
||||
return $this->response->setJSON([
|
||||
'success' => true,
|
||||
'message' => 'Migration completed successfully'
|
||||
]);
|
||||
|
||||
} catch (\Exception $e) {
|
||||
log_message('error', 'Migration failed: ' . $e->getMessage());
|
||||
|
||||
return $this->response->setJSON([
|
||||
'success' => false,
|
||||
'message' => 'Migration failed: ' . $e->getMessage()
|
||||
])->setStatusCode(500);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -582,12 +582,21 @@ class Sales extends Secure_Controller
|
||||
$data = [];
|
||||
|
||||
$rules = [
|
||||
'price' => 'trim|required|decimal_locale',
|
||||
'price' => 'trim|required|decimal_locale|nonNegativeDecimal',
|
||||
'quantity' => 'trim|required|decimal_locale',
|
||||
'discount' => 'trim|permit_empty|decimal_locale',
|
||||
'discount' => 'trim|permit_empty|decimal_locale|nonNegativeDecimal',
|
||||
];
|
||||
|
||||
if ($this->validate($rules)) {
|
||||
$messages = [
|
||||
'price' => [
|
||||
'nonNegativeDecimal' => lang('Sales.negative_price_invalid'),
|
||||
],
|
||||
'discount' => [
|
||||
'nonNegativeDecimal' => lang('Sales.negative_discount_invalid'),
|
||||
],
|
||||
];
|
||||
|
||||
if ($this->validate($rules, $messages)) {
|
||||
$description = $this->request->getPost('description', FILTER_SANITIZE_FULL_SPECIAL_CHARS);
|
||||
$serialnumber = $this->request->getPost('serialnumber', FILTER_SANITIZE_FULL_SPECIAL_CHARS);
|
||||
$price = parse_decimals($this->request->getPost('price'));
|
||||
@@ -596,20 +605,38 @@ class Sales extends Secure_Controller
|
||||
$discount = $discount_type
|
||||
? parse_quantity($this->request->getPost('discount'))
|
||||
: parse_decimals($this->request->getPost('discount'));
|
||||
$discount = $discount ?: 0;
|
||||
|
||||
// Return mode legitimately uses negative quantities for refunds
|
||||
if ($this->sale_lib->get_mode() != 'return' && $quantity < 0) {
|
||||
$data['error'] = lang('Sales.negative_quantity_invalid');
|
||||
return $this->_reload($data);
|
||||
}
|
||||
|
||||
// Business logic: discount bounds depend on discount_type and item values
|
||||
if ($discount_type == PERCENT && $discount > 100) {
|
||||
$data['error'] = lang('Sales.discount_percent_exceeds_100');
|
||||
return $this->_reload($data);
|
||||
}
|
||||
|
||||
if ($discount_type == FIXED && bccomp((string)$discount, bcmul((string)abs($quantity), (string)$price, 2), 2) > 0) {
|
||||
$data['error'] = lang('Sales.discount_exceeds_item_total');
|
||||
return $this->_reload($data);
|
||||
}
|
||||
|
||||
$item_location = $this->request->getPost('location', FILTER_SANITIZE_NUMBER_INT);
|
||||
$discounted_total = $this->request->getPost('discounted_total') != ''
|
||||
? parse_decimals($this->request->getPost('discounted_total') ?? '')
|
||||
: null;
|
||||
|
||||
|
||||
$this->sale_lib->edit_item($line, $description, $serialnumber, $quantity, $discount, $discount_type, $price, $discounted_total);
|
||||
|
||||
$this->sale_lib->empty_payments();
|
||||
|
||||
$data['warning'] = $this->sale_lib->out_of_stock($this->sale_lib->get_item_id($line), $item_location);
|
||||
} else {
|
||||
$data['error'] = lang('Sales.error_editing_item');
|
||||
$errors = $this->validator->getErrors();
|
||||
$data['error'] = $errors ? reset($errors) : lang('Sales.error_editing_item');
|
||||
}
|
||||
|
||||
return $this->_reload($data);
|
||||
@@ -723,6 +750,12 @@ class Sales extends Secure_Controller
|
||||
$data['cash_amount_due'] = $totals['cash_amount_due'];
|
||||
$data['non_cash_amount_due'] = $totals['amount_due'];
|
||||
|
||||
// Prevent negative total sales (fraud/theft vector) - returns can have negative totals for legitimate refunds
|
||||
if ($this->sale_lib->get_mode() != 'return' && bccomp($totals['total'], '0') < 0) {
|
||||
$data['error'] = lang('Sales.negative_total_invalid');
|
||||
return $this->_reload($data);
|
||||
}
|
||||
|
||||
if ($data['cash_mode']) { // TODO: Convert this to ternary notation
|
||||
$data['amount_due'] = $totals['cash_amount_due'];
|
||||
} else {
|
||||
|
||||
@@ -40,7 +40,7 @@ class Tax_categories extends Secure_Controller
|
||||
$search = $this->request->getGet('search');
|
||||
$limit = $this->request->getGet('limit', FILTER_SANITIZE_NUMBER_INT);
|
||||
$offset = $this->request->getGet('offset', FILTER_SANITIZE_NUMBER_INT);
|
||||
$sort = $this->request->getGet('sort', FILTER_SANITIZE_FULL_SPECIAL_CHARS);
|
||||
$sort = $this->sanitizeSortColumn(get_tax_categories_table_headers(), $this->request->getGet('sort', FILTER_SANITIZE_FULL_SPECIAL_CHARS), 'tax_category_id');
|
||||
$order = $this->request->getGet('order', FILTER_SANITIZE_FULL_SPECIAL_CHARS);
|
||||
|
||||
$tax_categories = $this->tax_category->search($search, $limit, $offset, $sort, $order);
|
||||
|
||||
@@ -50,7 +50,7 @@ class Tax_codes extends Secure_Controller
|
||||
$search = $this->request->getGet('search');
|
||||
$limit = $this->request->getGet('limit', FILTER_SANITIZE_NUMBER_INT);
|
||||
$offset = $this->request->getGet('offset', FILTER_SANITIZE_NUMBER_INT);
|
||||
$sort = $this->request->getGet('sort', FILTER_SANITIZE_FULL_SPECIAL_CHARS);
|
||||
$sort = $this->sanitizeSortColumn(get_tax_code_table_headers(), $this->request->getGet('sort', FILTER_SANITIZE_FULL_SPECIAL_CHARS), 'tax_code');
|
||||
$order = $this->request->getGet('order', FILTER_SANITIZE_FULL_SPECIAL_CHARS);
|
||||
|
||||
$tax_codes = $this->tax_code->search($search, $limit, $offset, $sort, $order);
|
||||
|
||||
@@ -43,7 +43,7 @@ class Tax_jurisdictions extends Secure_Controller
|
||||
$search = $this->request->getGet('search');
|
||||
$limit = $this->request->getGet('limit', FILTER_SANITIZE_NUMBER_INT);
|
||||
$offset = $this->request->getGet('offset', FILTER_SANITIZE_NUMBER_INT);
|
||||
$sort = $this->request->getGet('sort', FILTER_SANITIZE_FULL_SPECIAL_CHARS);
|
||||
$sort = $this->sanitizeSortColumn(get_tax_jurisdictions_table_headers(), $this->request->getGet('sort', FILTER_SANITIZE_FULL_SPECIAL_CHARS), 'jurisdiction_id');
|
||||
$order = $this->request->getGet('order', FILTER_SANITIZE_FULL_SPECIAL_CHARS);
|
||||
|
||||
$tax_jurisdictions = $this->tax_jurisdiction->search($search, $limit, $offset, $sort, $order);
|
||||
|
||||
@@ -81,7 +81,7 @@ class Taxes extends Secure_Controller
|
||||
$search = $this->request->getGet('search');
|
||||
$limit = $this->request->getGet('limit', FILTER_SANITIZE_NUMBER_INT);
|
||||
$offset = $this->request->getGet('offset', FILTER_SANITIZE_NUMBER_INT);
|
||||
$sort = $this->request->getGet('sort', FILTER_SANITIZE_FULL_SPECIAL_CHARS);
|
||||
$sort = $this->sanitizeSortColumn(get_tax_rates_manage_table_headers(), $this->request->getGet('sort', FILTER_SANITIZE_FULL_SPECIAL_CHARS), 'tax_rate_id');
|
||||
$order = $this->request->getGet('order', FILTER_SANITIZE_FULL_SPECIAL_CHARS);
|
||||
|
||||
$tax_rates = $this->tax->search($search, $limit, $offset, $sort, $order);
|
||||
|
||||
@@ -0,0 +1,49 @@
|
||||
<?php
|
||||
|
||||
namespace App\Database\Migrations;
|
||||
|
||||
use CodeIgniter\Database\Migration;
|
||||
use Config\Database;
|
||||
|
||||
class MigrationEXIFStrippingOptions extends Migration
|
||||
{
|
||||
/**
|
||||
* Perform a migration step.
|
||||
*/
|
||||
public function up(): void
|
||||
{
|
||||
log_message('info', 'Migrating EXIF Stripping Options');
|
||||
|
||||
$db = Database::connect();
|
||||
|
||||
$configs = [
|
||||
[
|
||||
'key' => 'exif_fields_to_keep',
|
||||
'value' => 'Copyright,Orientation,Software'
|
||||
]
|
||||
];
|
||||
|
||||
foreach ($configs as $config) {
|
||||
$existing = $db->table('app_config')
|
||||
->where('key', $config['key'])
|
||||
->get()
|
||||
->getRow();
|
||||
|
||||
if ($existing === null) {
|
||||
$db->table('app_config')->insert($config);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Revert a migration step.
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
$db = Database::connect();
|
||||
|
||||
$db->table('app_config')
|
||||
->where('key', 'exif_fields_to_keep')
|
||||
->delete();
|
||||
}
|
||||
}
|
||||
@@ -4,6 +4,8 @@ namespace App\Events;
|
||||
|
||||
use App\Libraries\MY_Migration;
|
||||
use App\Models\Appconfig;
|
||||
use CodeIgniter\Session\Handlers\DatabaseHandler;
|
||||
use CodeIgniter\Session\Handlers\FileHandler;
|
||||
use CodeIgniter\Session\Session;
|
||||
use Config\OSPOS;
|
||||
use Config\Services;
|
||||
@@ -19,38 +21,47 @@ class Load_config
|
||||
{
|
||||
public Session $session;
|
||||
|
||||
/**
|
||||
* Loads configuration from database into App CI config and then applies those settings
|
||||
*/
|
||||
public function load_config(): void
|
||||
{
|
||||
// Migrations
|
||||
$migration_config = config('Migrations');
|
||||
$migration = new MY_Migration($migration_config);
|
||||
|
||||
$this->session = session();
|
||||
|
||||
// Database Configuration
|
||||
$config = config(OSPOS::class);
|
||||
|
||||
if (!$migration->is_latest()) {
|
||||
$this->session->destroy();
|
||||
}
|
||||
|
||||
// Language
|
||||
$language_exists = file_exists('../app/Language/' . current_language_code());
|
||||
|
||||
if (current_language_code() == null || current_language() == null || !$language_exists) { // TODO: current_language() is undefined
|
||||
$config->settings['language'] = 'english';
|
||||
$config->settings['language_code'] = 'en';
|
||||
}
|
||||
$this->setDefaultLanguage($config);
|
||||
|
||||
$language = Services::language();
|
||||
$language->setLocale($config->settings['language_code']);
|
||||
$language->setLocale(current_language_code());
|
||||
|
||||
// Time Zone
|
||||
date_default_timezone_set($config->settings['timezone'] ?? ini_get('date.timezone'));
|
||||
|
||||
bcscale(max(2, totals_decimals() + tax_decimals()));
|
||||
}
|
||||
|
||||
private function setDefaultLanguage(OSPOS $config): void
|
||||
{
|
||||
$languageCode = $config->settings['language_code'] ?? null;
|
||||
|
||||
if (empty($config->settings) || $languageCode === null) {
|
||||
$config->settings['language'] = 'english';
|
||||
$config->settings['language_code'] = 'en';
|
||||
return;
|
||||
}
|
||||
|
||||
if (!$this->languageExists($languageCode)) {
|
||||
$config->settings['language'] = 'english';
|
||||
$config->settings['language_code'] = 'en';
|
||||
}
|
||||
}
|
||||
|
||||
private function languageExists(string $languageCode): bool
|
||||
{
|
||||
return file_exists(APPPATH . 'Language/' . $languageCode);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -22,9 +22,7 @@ function current_language_code(bool $load_system_language = false): string
|
||||
}
|
||||
}
|
||||
|
||||
$language_code = $config['language_code'];
|
||||
|
||||
return empty($language_code) ? DEFAULT_LANGUAGE_CODE : $language_code;
|
||||
return $config->language_code ?? DEFAULT_LANGUAGE_CODE;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -45,9 +43,7 @@ function current_language(bool $load_system_language = false): string
|
||||
}
|
||||
}
|
||||
|
||||
$language = $config['language'];
|
||||
|
||||
return empty($language) ? DEFAULT_LANGUAGE : $language;
|
||||
return $config->language ?? DEFAULT_LANGUAGE_CODE;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -11,56 +11,54 @@ function check_encryption(): bool
|
||||
$old_key = config('Encryption')->key;
|
||||
|
||||
if ((empty($old_key)) || (strlen($old_key) < 64)) {
|
||||
// Create Key
|
||||
$encryption = new Encryption();
|
||||
$key = bin2hex($encryption->createKey());
|
||||
config('Encryption')->key = $key;
|
||||
|
||||
// Write to .env
|
||||
$config_path = ROOTPATH . '.env';
|
||||
$new_config_path = WRITEPATH . '/backup/.env';
|
||||
$backup_path = WRITEPATH . '/backup/.env.bak';
|
||||
|
||||
$backup_folder = WRITEPATH . '/backup';
|
||||
|
||||
if (!file_exists($backup_folder) && !mkdir($backup_folder)) {
|
||||
log_message('error', 'Could not create backup folder');
|
||||
return false;
|
||||
if (!file_exists($backup_folder)) {
|
||||
@mkdir($backup_folder, 0750, true);
|
||||
}
|
||||
|
||||
if (!copy($config_path, $backup_path)) {
|
||||
log_message('error', "Unable to copy $config_path to $backup_path");
|
||||
if (!file_exists($config_path)) {
|
||||
$example_path = ROOTPATH . '.env.example';
|
||||
if (file_exists($example_path)) {
|
||||
@copy($example_path, $config_path);
|
||||
} else {
|
||||
@file_put_contents($config_path, "# OSPOS Configuration\n\n");
|
||||
}
|
||||
@chmod($config_path, 0640);
|
||||
}
|
||||
|
||||
// Copy to backup
|
||||
@chmod($config_path, 0660);
|
||||
@chmod($backup_path, 0660);
|
||||
if (file_exists($config_path)) {
|
||||
@copy($config_path, $backup_path);
|
||||
@chmod($backup_path, 0640);
|
||||
@chmod($config_path, 0640);
|
||||
|
||||
$config_file = file_get_contents($config_path);
|
||||
$config_file = preg_replace("/(encryption\.key.*=.*)('.*')/", "$1'$key'", $config_file);
|
||||
$config_file = file_get_contents($config_path);
|
||||
|
||||
if (!empty($old_key)) {
|
||||
$old_line = "# encryption.key = '$old_key' REMOVE IF UNNEEDED\r\n";
|
||||
$insertion_point = stripos($config_file, 'encryption.key');
|
||||
$config_file = substr_replace($config_file, $old_line, $insertion_point, 0);
|
||||
if (strpos($config_file, 'encryption.key') !== false) {
|
||||
$config_file = preg_replace("/(encryption\.key.*=.*)('.*')/", "$1'$key'", $config_file);
|
||||
} else {
|
||||
$config_file .= "\nencryption.key = '$key'\n";
|
||||
}
|
||||
|
||||
if (!empty($old_key)) {
|
||||
$old_line = "# encryption.key = '$old_key' REMOVE IF UNNEEDED\r\n";
|
||||
$insertion_point = stripos($config_file, 'encryption.key');
|
||||
if ($insertion_point !== false) {
|
||||
$config_file = substr_replace($config_file, $old_line, $insertion_point, 0);
|
||||
}
|
||||
}
|
||||
|
||||
@file_put_contents($config_path, $config_file);
|
||||
@chmod($config_path, 0640);
|
||||
|
||||
log_message('info', "Updated encryption key in $config_path");
|
||||
}
|
||||
|
||||
$handle = @fopen($config_path, 'w+');
|
||||
|
||||
if (empty($handle)) {
|
||||
log_message('error', "Unable to open $config_path for updating");
|
||||
return false;
|
||||
}
|
||||
|
||||
@chmod($config_path, 0660);
|
||||
$write_failed = !fwrite($handle, $config_file);
|
||||
fclose($handle);
|
||||
|
||||
if ($write_failed) {
|
||||
log_message('error', "Unable to write to $config_path for updating.");
|
||||
return false;
|
||||
}
|
||||
log_message('info', "File $config_path has been updated.");
|
||||
}
|
||||
|
||||
return true;
|
||||
@@ -74,23 +72,14 @@ function abort_encryption_conversion(): void
|
||||
$config_path = ROOTPATH . '.env';
|
||||
$backup_path = WRITEPATH . '/backup/.env.bak';
|
||||
|
||||
$config_file = file_get_contents($backup_path);
|
||||
|
||||
$handle = @fopen($config_path, 'w+');
|
||||
|
||||
if (empty($handle)) {
|
||||
log_message('error', "Unable to open $config_path to undo encryption conversion");
|
||||
} else {
|
||||
@chmod($config_path, 0660);
|
||||
$write_failed = !fwrite($handle, $config_file);
|
||||
fclose($handle);
|
||||
|
||||
if ($write_failed) {
|
||||
log_message('error', "Unable to write to $config_path to undo encryption conversion.");
|
||||
return;
|
||||
}
|
||||
log_message('info', "File $config_path has been updated to undo encryption conversion");
|
||||
if (!file_exists($backup_path)) {
|
||||
return;
|
||||
}
|
||||
|
||||
@chmod($config_path, 0640);
|
||||
$config_file = file_get_contents($backup_path);
|
||||
@file_put_contents($config_path, $config_file);
|
||||
log_message('info', "Restored $config_path from backup");
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -99,13 +88,10 @@ function abort_encryption_conversion(): void
|
||||
function remove_backup(): void
|
||||
{
|
||||
$backup_path = WRITEPATH . '/backup/.env.bak';
|
||||
if (! file_exists($backup_path)) {
|
||||
if (!file_exists($backup_path)) {
|
||||
return;
|
||||
}
|
||||
if (!unlink($backup_path)) {
|
||||
log_message('error', "Unable to remove $backup_path.");
|
||||
return;
|
||||
}
|
||||
log_message('info', "File $backup_path has been removed");
|
||||
@unlink($backup_path);
|
||||
log_message('info', "Removed $backup_path");
|
||||
}
|
||||
|
||||
|
||||
@@ -143,8 +143,7 @@ function get_tax_rates_manage_table_headers(): string
|
||||
*/
|
||||
function get_tax_rates_data_row($tax_rates_row): array
|
||||
{
|
||||
$router = service('router');
|
||||
$controller_name = strtolower($router->controllerName());
|
||||
$controller_name = 'taxes';
|
||||
|
||||
return [
|
||||
'tax_rate_id' => $tax_rates_row->tax_rate_id,
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
<?php
|
||||
|
||||
return [
|
||||
"all" => "الجميع",
|
||||
"columns" => "أعمدة",
|
||||
"hide_show_pagination" => "عرض/إخفاء أرقام الصفحات",
|
||||
"loading" => "جارى التحميل، برجاء الإنتظار ...",
|
||||
"page_from_to" => "عرض {0} إلى {1} من {2} صفوف",
|
||||
"refresh" => "إعادة تحميل",
|
||||
"rows_per_page" => "{0} صف بالصفحة",
|
||||
"toggle" => "تغيير",
|
||||
'all' => "الجميع",
|
||||
'columns' => "أعمدة",
|
||||
'hide_show_pagination' => "عرض/إخفاء أرقام الصفحات",
|
||||
'loading' => "جارى التحميل، برجاء الإنتظار",
|
||||
'page_from_to' => "عرض {0} إلى {1} من {2} صفوف",
|
||||
'refresh' => "إعادة تحميل",
|
||||
'rows_per_page' => "{0} صف بالصفحة",
|
||||
'toggle' => "تغيير",
|
||||
];
|
||||
|
||||
@@ -282,6 +282,7 @@ return [
|
||||
"right" => "يمين",
|
||||
"sales_invoice_format" => "شكل فاتورة البيع",
|
||||
"sales_quote_format" => "شكل فاتورة عرض الاسعار",
|
||||
"mailpath_invalid" => "",
|
||||
"saved_successfully" => "تم حفظ التهيئة بنجاح.",
|
||||
"saved_unsuccessfully" => "لم يتم حفظ التهيئة بنجاح.",
|
||||
"security_issue" => "تحذير من ثغرة أمنية",
|
||||
|
||||
@@ -9,6 +9,15 @@ return [
|
||||
"login" => "دخول",
|
||||
"logout" => "تسجيل خروج",
|
||||
"migration_needed" => "سيبدأ ترحيل قاعدة البيانات إلى{0} بعد تسجيل الدخول.",
|
||||
"migration_required" => "",
|
||||
"migration_auth_message" => "",
|
||||
"migration_initializing" => "",
|
||||
"migration_running" => "",
|
||||
"migration_complete" => "",
|
||||
"migration_complete_login" => "",
|
||||
"migration_failed" => "",
|
||||
"migration_error_connection" => "",
|
||||
"migration_complete_redirect" => "",
|
||||
"password" => "كلمة السر",
|
||||
"required_username" => "",
|
||||
"username" => "اسم المستخدم",
|
||||
|
||||
@@ -73,6 +73,12 @@ return [
|
||||
"employee" => "الموظف",
|
||||
"entry" => "ادخال",
|
||||
"error_editing_item" => "خطاء فى تحرير الصنف",
|
||||
"negative_price_invalid" => "",
|
||||
"negative_quantity_invalid" => "",
|
||||
"negative_discount_invalid" => "",
|
||||
"discount_percent_exceeds_100" => "",
|
||||
"discount_exceeds_item_total" => "",
|
||||
"negative_total_invalid" => "",
|
||||
"find_or_scan_item" => "بحث/مسح باركود صنف",
|
||||
"find_or_scan_item_or_receipt" => "بحث/مسح باركود صنف أو ايصال",
|
||||
"giftcard" => "بطاقة هدية",
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
<?php
|
||||
|
||||
return [
|
||||
"all" => "الكل",
|
||||
"columns" => "أعمدة",
|
||||
"hide_show_pagination" => "عرض/إخفاء أرقام الصفحات",
|
||||
"loading" => "جارى التحميل، برجاء الإنتظار ...",
|
||||
"page_from_to" => "عرض {0} إلى {1} من {2} صفوف",
|
||||
"refresh" => "إعادة تحميل",
|
||||
"rows_per_page" => "{0} صف بالصفحة",
|
||||
"toggle" => "تغيير",
|
||||
'all' => "الكل",
|
||||
'columns' => "أعمدة",
|
||||
'hide_show_pagination' => "عرض/إخفاء أرقام الصفحات",
|
||||
'loading' => "جارى التحميل، برجاء الإنتظار",
|
||||
'page_from_to' => "عرض {0} إلى {1} من {2} صفوف",
|
||||
'refresh' => "إعادة تحميل",
|
||||
'rows_per_page' => "{0} صف بالصفحة",
|
||||
'toggle' => "تغيير",
|
||||
];
|
||||
|
||||
@@ -282,6 +282,7 @@ return [
|
||||
"right" => "يمين",
|
||||
"sales_invoice_format" => "شكل فاتورة البيع",
|
||||
"sales_quote_format" => "شكل فاتورة عرض الاسعار",
|
||||
"mailpath_invalid" => "",
|
||||
"saved_successfully" => "تم حفظ التهيئة بنجاح.",
|
||||
"saved_unsuccessfully" => "لم يتم حفظ التهيئة بنجاح.",
|
||||
"security_issue" => "تحذير من ثغرة أمنية",
|
||||
|
||||
@@ -9,6 +9,15 @@ return [
|
||||
"login" => "دخول",
|
||||
"logout" => "تسجيل خروج",
|
||||
"migration_needed" => "سيبدأ ترحيل قاعدة البيانات إلى{0} بعد تسجيل الدخول.",
|
||||
"migration_required" => "",
|
||||
"migration_auth_message" => "",
|
||||
"migration_initializing" => "",
|
||||
"migration_running" => "",
|
||||
"migration_complete" => "",
|
||||
"migration_complete_login" => "",
|
||||
"migration_failed" => "",
|
||||
"migration_error_connection" => "",
|
||||
"migration_complete_redirect" => "",
|
||||
"password" => "كلمة السر",
|
||||
"required_username" => "خانة أسم المستخدم مطلوبة.",
|
||||
"username" => "اسم المستخدم",
|
||||
|
||||
@@ -73,6 +73,12 @@ return [
|
||||
"employee" => "الموظف",
|
||||
"entry" => "ادخال",
|
||||
"error_editing_item" => "خطاء فى تعديل المادة",
|
||||
"negative_price_invalid" => "",
|
||||
"negative_quantity_invalid" => "",
|
||||
"negative_discount_invalid" => "",
|
||||
"discount_percent_exceeds_100" => "",
|
||||
"discount_exceeds_item_total" => "",
|
||||
"negative_total_invalid" => "",
|
||||
"find_or_scan_item" => "بحث/مسح باركود المادة",
|
||||
"find_or_scan_item_or_receipt" => "بحث/مسح باركود المادة أو الايصال",
|
||||
"giftcard" => "بطاقة هدية",
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
<?php
|
||||
|
||||
return [
|
||||
"all" => "hamısı",
|
||||
"columns" => "Sütunlar",
|
||||
"hide_show_pagination" => "Gizlət/Göstər səhifənin nömrələnməsin",
|
||||
"loading" => "Lütfən gözləyin, səhifə yüklənir...",
|
||||
"page_from_to" => "Göstər {0} bundan {1} buna {2} kimi",
|
||||
"refresh" => "Yenilə",
|
||||
"rows_per_page" => "{0} yazı səhifədə",
|
||||
"toggle" => "Keçid",
|
||||
'all' => "hamısı",
|
||||
'columns' => "Sütunlar",
|
||||
'hide_show_pagination' => "Gizlət/Göstər səhifənin nömrələnməsin",
|
||||
'loading' => "Lütfən gözləyin, səhifə yüklənir",
|
||||
'page_from_to' => "Göstər {0} bundan {1} buna {2} kimi",
|
||||
'refresh' => "Yenilə",
|
||||
'rows_per_page' => "{0} yazı səhifədə",
|
||||
'toggle' => "Keçid",
|
||||
];
|
||||
|
||||
@@ -282,6 +282,7 @@ return [
|
||||
"right" => "Konfiqurasiya ugursuz oldu saxlanilmadi",
|
||||
"sales_invoice_format" => "Satış Fatura Formatı",
|
||||
"sales_quote_format" => "Satış Sitat Formati",
|
||||
"mailpath_invalid" => "",
|
||||
"saved_successfully" => "Konfiqurasiya uğurla saxlanıldı.",
|
||||
"saved_unsuccessfully" => "Konfiqurasiyanı saxlamq mümkün olmadı.",
|
||||
"security_issue" => "Təhlükəsizlik açığı xəbərdarlığı",
|
||||
|
||||
@@ -9,6 +9,15 @@ return [
|
||||
"login" => "Giriş",
|
||||
"logout" => "Çıxış",
|
||||
"migration_needed" => "{0} -ə daxil olandan sonra verilənlər bazası miqrasiyası başlayacaq.",
|
||||
"migration_required" => "",
|
||||
"migration_auth_message" => "",
|
||||
"migration_initializing" => "",
|
||||
"migration_running" => "",
|
||||
"migration_complete" => "",
|
||||
"migration_complete_login" => "",
|
||||
"migration_failed" => "",
|
||||
"migration_error_connection" => "",
|
||||
"migration_complete_redirect" => "",
|
||||
"password" => "Şifrə",
|
||||
"required_username" => "",
|
||||
"username" => "İstifadəçi",
|
||||
|
||||
@@ -73,6 +73,12 @@ return [
|
||||
"employee" => "Əməkdaş",
|
||||
"entry" => "Daxil",
|
||||
"error_editing_item" => "XƏTA Malın redaktəsində",
|
||||
"negative_price_invalid" => "",
|
||||
"negative_quantity_invalid" => "",
|
||||
"negative_discount_invalid" => "",
|
||||
"discount_percent_exceeds_100" => "",
|
||||
"discount_exceeds_item_total" => "",
|
||||
"negative_total_invalid" => "",
|
||||
"find_or_scan_item" => "Malın axtarışı",
|
||||
"find_or_scan_item_or_receipt" => "Tapmaq skan etmək və ya kvitansiya",
|
||||
"giftcard" => "Hədiyyə Kartı",
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
<?php
|
||||
|
||||
return [
|
||||
"all" => "Всичко/и",
|
||||
"columns" => "Колони",
|
||||
"hide_show_pagination" => "Скриване / Показване на страници",
|
||||
"loading" => "Зареждане, моля изчакайте...",
|
||||
"page_from_to" => "Показани са {0} до {1} от {2} реда",
|
||||
"refresh" => "Опресняване",
|
||||
"rows_per_page" => "{0} редове на страница",
|
||||
"toggle" => "Щифт",
|
||||
'all' => "Всичко/и",
|
||||
'columns' => "Колони",
|
||||
'hide_show_pagination' => "Скриване / Показване на страници",
|
||||
'loading' => "Зареждане, моля изчакайте",
|
||||
'page_from_to' => "Показани са {0} до {1} от {2} реда",
|
||||
'refresh' => "Опресняване",
|
||||
'rows_per_page' => "{0} редове на страница",
|
||||
'toggle' => "Щифт",
|
||||
];
|
||||
|
||||
@@ -282,6 +282,7 @@ return [
|
||||
"right" => "Right",
|
||||
"sales_invoice_format" => "Sales Invoice Format",
|
||||
"sales_quote_format" => "Sales Quote Format",
|
||||
"mailpath_invalid" => "",
|
||||
"saved_successfully" => "Configuration save successful.",
|
||||
"saved_unsuccessfully" => "Configuration save failed.",
|
||||
"security_issue" => "Security Vulnerability Warning",
|
||||
|
||||
@@ -9,6 +9,15 @@ return [
|
||||
"login" => "Login",
|
||||
"logout" => "",
|
||||
"migration_needed" => "",
|
||||
"migration_required" => "",
|
||||
"migration_auth_message" => "",
|
||||
"migration_initializing" => "",
|
||||
"migration_running" => "",
|
||||
"migration_complete" => "",
|
||||
"migration_complete_login" => "",
|
||||
"migration_failed" => "",
|
||||
"migration_error_connection" => "",
|
||||
"migration_complete_redirect" => "",
|
||||
"password" => "Password",
|
||||
"required_username" => "",
|
||||
"username" => "Username",
|
||||
|
||||
@@ -73,6 +73,12 @@ return [
|
||||
"employee" => "Служител",
|
||||
"entry" => "Вход",
|
||||
"error_editing_item" => "Грешка при редактирането на елемента",
|
||||
"negative_price_invalid" => "",
|
||||
"negative_quantity_invalid" => "",
|
||||
"negative_discount_invalid" => "",
|
||||
"discount_percent_exceeds_100" => "",
|
||||
"discount_exceeds_item_total" => "",
|
||||
"negative_total_invalid" => "",
|
||||
"find_or_scan_item" => "Намерете или сканирайте елемента",
|
||||
"find_or_scan_item_or_receipt" => "Намерете или сканирайте елемент или разпис",
|
||||
"giftcard" => "Gift Карта",
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
<?php
|
||||
|
||||
return [
|
||||
"all" => "Sve",
|
||||
"columns" => "Kolone",
|
||||
"hide_show_pagination" => "Sakrij / prikaži paginaciju",
|
||||
"loading" => "Učitavanje sačekajte...",
|
||||
"page_from_to" => "Prikazivanje {0} do {1} od {2} redova",
|
||||
"refresh" => "Osvježi",
|
||||
"rows_per_page" => "{0} redova po stranici",
|
||||
"toggle" => "Promijeni prikaz",
|
||||
'all' => "Sve",
|
||||
'columns' => "Kolone",
|
||||
'hide_show_pagination' => "Sakrij / prikaži paginaciju",
|
||||
'loading' => "Učitavanje sačekajte",
|
||||
'page_from_to' => "Prikazivanje {0} do {1} od {2} redova",
|
||||
'refresh' => "Osvježi",
|
||||
'rows_per_page' => "{0} redova po stranici",
|
||||
'toggle' => "Promijeni prikaz",
|
||||
];
|
||||
|
||||
@@ -282,6 +282,7 @@ return [
|
||||
"right" => "Desno",
|
||||
"sales_invoice_format" => "Format fakture",
|
||||
"sales_quote_format" => "Format navedene prodaje",
|
||||
"mailpath_invalid" => "",
|
||||
"saved_successfully" => "Konfiguracija je uspješno snimljena.",
|
||||
"saved_unsuccessfully" => "Konfiguracija nije uspješno snimljena.",
|
||||
"security_issue" => "Upozorenje o sigurnosnoj ranjivosti",
|
||||
|
||||
@@ -9,6 +9,15 @@ return [
|
||||
"login" => "Prijava",
|
||||
"logout" => "Odjava",
|
||||
"migration_needed" => "Migracija baze podataka na {0} će početi nakon prijavljivanja.",
|
||||
"migration_required" => "",
|
||||
"migration_auth_message" => "",
|
||||
"migration_initializing" => "",
|
||||
"migration_running" => "",
|
||||
"migration_complete" => "",
|
||||
"migration_complete_login" => "",
|
||||
"migration_failed" => "",
|
||||
"migration_error_connection" => "",
|
||||
"migration_complete_redirect" => "",
|
||||
"password" => "Lozinka",
|
||||
"required_username" => "",
|
||||
"username" => "Korisničko ime",
|
||||
|
||||
@@ -73,6 +73,12 @@ return [
|
||||
"employee" => "Zaposlenik",
|
||||
"entry" => "Ulaz",
|
||||
"error_editing_item" => "Greška pri uređivanju artikla",
|
||||
"negative_price_invalid" => "",
|
||||
"negative_quantity_invalid" => "",
|
||||
"negative_discount_invalid" => "",
|
||||
"discount_percent_exceeds_100" => "",
|
||||
"discount_exceeds_item_total" => "",
|
||||
"negative_total_invalid" => "",
|
||||
"find_or_scan_item" => "Pronađi/Skeniraj artikal",
|
||||
"find_or_scan_item_or_receipt" => "Pronađi/Skeniraj artikal ili priznanicu",
|
||||
"giftcard" => "Poklon kartica",
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
<?php
|
||||
|
||||
return [
|
||||
"all" => "هەموو",
|
||||
"columns" => "ستنوونەکان",
|
||||
"hide_show_pagination" => "شاردنەوە/پێشاندانی لاپەڕەسازی",
|
||||
"loading" => "بارکردن، تکایە چاوەڕوان بن...",
|
||||
"page_from_to" => "پیشاندانی {0} بۆ {1} لە {2} ڕیزەکان",
|
||||
"refresh" => "ڕفرێش",
|
||||
"rows_per_page" => "{0} ڕیز بۆ هەر لاپەڕەیەک",
|
||||
"toggle" => "دوگمە",
|
||||
'all' => "هەموو",
|
||||
'columns' => "ستنوونەکان",
|
||||
'hide_show_pagination' => "شاردنەوە/پێشاندانی لاپەڕەسازی",
|
||||
'loading' => "بارکردن، تکایە چاوەڕوان بن",
|
||||
'page_from_to' => "پیشاندانی {0} بۆ {1} لە {2} ڕیزەکان",
|
||||
'refresh' => "ڕفرێش",
|
||||
'rows_per_page' => "{0} ڕیز بۆ هەر لاپەڕەیەک",
|
||||
'toggle' => "دوگمە",
|
||||
];
|
||||
|
||||
@@ -9,6 +9,15 @@ return [
|
||||
'login' => "چوونەژوورەوە",
|
||||
'logout' => "چوونەدەرەوە",
|
||||
'migration_needed' => "گواستنەوەی داتابەیس بۆ {0} دوای چوونەژوورەوە دەست پێدەکات.",
|
||||
'migration_required' => "",
|
||||
'migration_auth_message' => "",
|
||||
'migration_initializing' => "",
|
||||
'migration_running' => "",
|
||||
'migration_complete' => "",
|
||||
'migration_complete_login' => "",
|
||||
'migration_failed' => "",
|
||||
'migration_error_connection' => "",
|
||||
'migration_complete_redirect' => "",
|
||||
'password' => "وشەی نهێنی",
|
||||
'required_username' => "خانەی ناوی بەکارهێنەر پێویستە.",
|
||||
'username' => "ناوی بەکارهێنەر",
|
||||
|
||||
@@ -73,6 +73,12 @@ return [
|
||||
'employee' => "فەرمانبەر",
|
||||
'entry' => "تۆمار",
|
||||
'error_editing_item' => "هەڵە لە دەستکاریکردنی ئایتم",
|
||||
"negative_price_invalid" => "",
|
||||
"negative_quantity_invalid" => "",
|
||||
"negative_discount_invalid" => "",
|
||||
"discount_percent_exceeds_100" => "",
|
||||
"discount_exceeds_item_total" => "",
|
||||
"negative_total_invalid" => "",
|
||||
'find_or_scan_item' => "دۆزینەوە یان سکانکردنی ئایتم",
|
||||
'find_or_scan_item_or_receipt' => "دۆزینەوە یان سکانکردنی ئایتم یان پسوڵە",
|
||||
'giftcard' => "کارتی دیاری",
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
<?php
|
||||
|
||||
return [
|
||||
"all" => "Vše",
|
||||
"columns" => "Sloupce",
|
||||
"hide_show_pagination" => "Zobrazit/skrýt stránkování",
|
||||
"loading" => "Nahrávám, prosím počkejte...",
|
||||
"page_from_to" => "Zobrazeno {0} až {1} z {2} řádků",
|
||||
"refresh" => "Obnovit",
|
||||
"rows_per_page" => "{0} řádků na stránku",
|
||||
"toggle" => "Přepnout",
|
||||
'all' => "Vše",
|
||||
'columns' => "Sloupce",
|
||||
'hide_show_pagination' => "Zobrazit/skrýt stránkování",
|
||||
'loading' => "Nahrávám, prosím počkejte",
|
||||
'page_from_to' => "Zobrazeno {0} až {1} z {2} řádků",
|
||||
'refresh' => "Obnovit",
|
||||
'rows_per_page' => "{0} řádků na stránku",
|
||||
'toggle' => "Přepnout",
|
||||
];
|
||||
|
||||
@@ -282,6 +282,7 @@ return [
|
||||
"right" => "",
|
||||
"sales_invoice_format" => "",
|
||||
"sales_quote_format" => "",
|
||||
"mailpath_invalid" => "",
|
||||
"saved_successfully" => "",
|
||||
"saved_unsuccessfully" => "",
|
||||
"security_issue" => "Security Vulnerability Warning",
|
||||
|
||||
@@ -9,6 +9,15 @@ return [
|
||||
"login" => "Login",
|
||||
"logout" => "",
|
||||
"migration_needed" => "",
|
||||
"migration_required" => "",
|
||||
"migration_auth_message" => "",
|
||||
"migration_initializing" => "",
|
||||
"migration_running" => "",
|
||||
"migration_complete" => "",
|
||||
"migration_complete_login" => "",
|
||||
"migration_failed" => "",
|
||||
"migration_error_connection" => "",
|
||||
"migration_complete_redirect" => "",
|
||||
"password" => "Heslo",
|
||||
"required_username" => "",
|
||||
"username" => "Uživatelské jméno",
|
||||
|
||||
@@ -73,6 +73,12 @@ return [
|
||||
"employee" => "Prodávající",
|
||||
"entry" => "Záznam",
|
||||
"error_editing_item" => "Chyba při úpravě položky",
|
||||
"negative_price_invalid" => "",
|
||||
"negative_quantity_invalid" => "",
|
||||
"negative_discount_invalid" => "",
|
||||
"discount_percent_exceeds_100" => "",
|
||||
"discount_exceeds_item_total" => "",
|
||||
"negative_total_invalid" => "",
|
||||
"find_or_scan_item" => "Najít nebo skenovat položku",
|
||||
"find_or_scan_item_or_receipt" => "Najít nebo skenovat položku či účtenku",
|
||||
"giftcard" => "Dárkový poukaz",
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
<?php
|
||||
|
||||
return [
|
||||
"all" => "Alle",
|
||||
"columns" => "Kolonner",
|
||||
"hide_show_pagination" => "Gem/Vis sideinddeling",
|
||||
"loading" => "Indlæser, vent venligst...",
|
||||
"page_from_to" => "Viser {0} to {1} af {2} rækker",
|
||||
"refresh" => "Opdater",
|
||||
"rows_per_page" => "{0} rækker per side",
|
||||
"toggle" => "Skift",
|
||||
'all' => "Alle",
|
||||
'columns' => "Kolonner",
|
||||
'hide_show_pagination' => "Gem/Vis sideinddeling",
|
||||
'loading' => "Indlæser, vent venligst",
|
||||
'page_from_to' => "Viser {0} to {1} af {2} rækker",
|
||||
'refresh' => "Opdater",
|
||||
'rows_per_page' => "{0} rækker per side",
|
||||
'toggle' => "Skift",
|
||||
];
|
||||
|
||||
@@ -282,6 +282,7 @@ return [
|
||||
"right" => "Right",
|
||||
"sales_invoice_format" => "Sales Invoice Format",
|
||||
"sales_quote_format" => "Sales Quote Format",
|
||||
"mailpath_invalid" => "",
|
||||
"saved_successfully" => "Configuration save successful.",
|
||||
"saved_unsuccessfully" => "Configuration save failed.",
|
||||
"security_issue" => "Security Vulnerability Warning",
|
||||
|
||||
@@ -9,6 +9,15 @@ return [
|
||||
"login" => "",
|
||||
"logout" => "",
|
||||
"migration_needed" => "",
|
||||
"migration_required" => "",
|
||||
"migration_auth_message" => "",
|
||||
"migration_initializing" => "",
|
||||
"migration_running" => "",
|
||||
"migration_complete" => "",
|
||||
"migration_complete_login" => "",
|
||||
"migration_failed" => "",
|
||||
"migration_error_connection" => "",
|
||||
"migration_complete_redirect" => "",
|
||||
"password" => "",
|
||||
"required_username" => "",
|
||||
"username" => "",
|
||||
|
||||
@@ -73,6 +73,12 @@ return [
|
||||
"employee" => "",
|
||||
"entry" => "",
|
||||
"error_editing_item" => "",
|
||||
"negative_price_invalid" => "",
|
||||
"negative_quantity_invalid" => "",
|
||||
"negative_discount_invalid" => "",
|
||||
"discount_percent_exceeds_100" => "",
|
||||
"discount_exceeds_item_total" => "",
|
||||
"negative_total_invalid" => "",
|
||||
"find_or_scan_item" => "",
|
||||
"find_or_scan_item_or_receipt" => "",
|
||||
"giftcard" => "Gavekort",
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
<?php
|
||||
|
||||
return [
|
||||
"all" => "All",
|
||||
"columns" => "Spalten",
|
||||
"hide_show_pagination" => "Hide/Show pagination",
|
||||
"loading" => "Lade, bitte warten...",
|
||||
"page_from_to" => "Zeige {0} bis {1} von {2} Zeile(n)",
|
||||
"refresh" => "Refresh",
|
||||
"rows_per_page" => "{0} Einträge pro Seite",
|
||||
"toggle" => "Umschalten",
|
||||
'all' => "All",
|
||||
'columns' => "Spalten",
|
||||
'hide_show_pagination' => "Hide/Show pagination",
|
||||
'loading' => "Lade, bitte warten",
|
||||
'page_from_to' => "Zeige {0} bis {1} von {2} Zeile(n)",
|
||||
'refresh' => "Refresh",
|
||||
'rows_per_page' => "{0} Einträge pro Seite",
|
||||
'toggle' => "Umschalten",
|
||||
];
|
||||
|
||||
@@ -282,6 +282,7 @@ return [
|
||||
"right" => "Right",
|
||||
"sales_invoice_format" => "Format Verkaufsrechnung",
|
||||
"sales_quote_format" => "",
|
||||
"mailpath_invalid" => "Ungültiger Sendmail-Pfad. Nur Buchstaben, Zahlen, Bindestriche, Unterstriche, Schrägstriche und Punkte sind erlaubt.",
|
||||
"saved_successfully" => "Einstellungen erfolgreich gesichert",
|
||||
"saved_unsuccessfully" => "Einstellungen konnten nicht gesichert werden",
|
||||
"security_issue" => "Security Vulnerability Warning",
|
||||
|
||||
@@ -7,10 +7,19 @@ return [
|
||||
"invalid_installation" => "",
|
||||
"invalid_username_and_password" => "Ungültiger Benutzername/Passwort",
|
||||
"login" => "Login",
|
||||
"logout" => "",
|
||||
"migration_needed" => "",
|
||||
"logout" => "Abmelden",
|
||||
"migration_needed" => "Eine Datenbank-Migration auf {0} wird nach der Anmeldung gestartet.",
|
||||
"migration_required" => "Datenbank-Migration erforderlich",
|
||||
"migration_auth_message" => "Administrator-Anmeldedaten sind erforderlich, um die Datenbank-Migration auf Version {0} zu autorisieren. Bitte melden Sie sich an, um fortzufahren.",
|
||||
"migration_initializing" => "Datenbank wird initialisiert",
|
||||
"migration_running" => "Datenbank-Migrationen werden ausgeführt...",
|
||||
"migration_complete" => "Datenbank erfolgreich initialisiert!",
|
||||
"migration_complete_login" => "Sie können sich jetzt anmelden.",
|
||||
"migration_failed" => "Migration fehlgeschlagen",
|
||||
"migration_error_connection" => "Verbindungsfehler. Bitte versuchen Sie es erneut.",
|
||||
"migration_complete_redirect" => "Migration abgeschlossen. Weiterleitung zur Anmeldung...",
|
||||
"password" => "Passwort",
|
||||
"required_username" => "",
|
||||
"required_username" => "Das Feld Benutzername ist erforderlich.",
|
||||
"username" => "Benutzername",
|
||||
"welcome" => "",
|
||||
"welcome" => "Willkommen bei {0}!",
|
||||
];
|
||||
|
||||
@@ -73,6 +73,12 @@ return [
|
||||
"employee" => "Mitarbeiter",
|
||||
"entry" => "",
|
||||
"error_editing_item" => "Fehler beim Ändern des Artikels",
|
||||
"negative_price_invalid" => "",
|
||||
"negative_quantity_invalid" => "",
|
||||
"negative_discount_invalid" => "",
|
||||
"discount_percent_exceeds_100" => "",
|
||||
"discount_exceeds_item_total" => "",
|
||||
"negative_total_invalid" => "",
|
||||
"find_or_scan_item" => "Finde/Scanne Artikel",
|
||||
"find_or_scan_item_or_receipt" => "Finde/Scanne Artikel oder Quittung",
|
||||
"giftcard" => "Gutschein",
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
<?php
|
||||
|
||||
return [
|
||||
"all" => "Alle",
|
||||
"columns" => "Spalten",
|
||||
"hide_show_pagination" => "Seitenzahlen anzeigen/verbergen",
|
||||
"loading" => "Lade, bitte warten...",
|
||||
"page_from_to" => "Zeige {0} bis {1} von {2} Zeile(n)",
|
||||
"refresh" => "Aktualisieren",
|
||||
"rows_per_page" => "{0} Einträge pro Seite",
|
||||
"toggle" => "Umschalten",
|
||||
'all' => "Alle",
|
||||
'columns' => "Spalten",
|
||||
'hide_show_pagination' => "Seitenzahlen anzeigen/verbergen",
|
||||
'loading' => "Lade, bitte warten",
|
||||
'page_from_to' => "Zeige {0} bis {1} von {2} Zeile(n)",
|
||||
'refresh' => "Aktualisieren",
|
||||
'rows_per_page' => "{0} Einträge pro Seite",
|
||||
'toggle' => "Umschalten",
|
||||
];
|
||||
|
||||
@@ -282,6 +282,7 @@ return [
|
||||
"right" => "Rechts",
|
||||
"sales_invoice_format" => "Format Verkaufsrechnung",
|
||||
"sales_quote_format" => "Angebotsformat",
|
||||
"mailpath_invalid" => "Ungültiger Sendmail-Pfad. Nur Buchstaben, Zahlen, Bindestriche, Unterstriche, Schrägstriche und Punkte sind erlaubt.",
|
||||
"saved_successfully" => "Einstellungen erfolgreich gesichert.",
|
||||
"saved_unsuccessfully" => "Einstellungen konnten nicht gesichert werden.",
|
||||
"security_issue" => "Security Vulnerability Warning",
|
||||
|
||||
@@ -7,10 +7,19 @@ return [
|
||||
"invalid_installation" => "Die Installation ist nicht korrekt, überprüfen Sie Ihre php.ini-Datei.",
|
||||
"invalid_username_and_password" => "Ungültiger Benutzername oder Passwort.",
|
||||
"login" => "Login",
|
||||
"logout" => "",
|
||||
"migration_needed" => "",
|
||||
"logout" => "Abmelden",
|
||||
"migration_needed" => "Eine Datenbank-Migration auf {0} wird nach der Anmeldung gestartet.",
|
||||
"migration_required" => "Datenbank-Migration erforderlich",
|
||||
"migration_auth_message" => "Administrator-Anmeldedaten sind erforderlich, um die Datenbank-Migration auf Version {0} zu autorisieren. Bitte melden Sie sich an, um fortzufahren.",
|
||||
"migration_initializing" => "Datenbank wird initialisiert",
|
||||
"migration_running" => "Datenbank-Migrationen werden ausgeführt...",
|
||||
"migration_complete" => "Datenbank erfolgreich initialisiert!",
|
||||
"migration_complete_login" => "Sie können sich jetzt anmelden.",
|
||||
"migration_failed" => "Migration fehlgeschlagen",
|
||||
"migration_error_connection" => "Verbindungsfehler. Bitte versuchen Sie es erneut.",
|
||||
"migration_complete_redirect" => "Migration abgeschlossen. Weiterleitung zur Anmeldung...",
|
||||
"password" => "Passwort",
|
||||
"required_username" => "",
|
||||
"required_username" => "Das Feld Benutzername ist erforderlich.",
|
||||
"username" => "Benutzername",
|
||||
"welcome" => "",
|
||||
"welcome" => "Willkommen bei {0}!",
|
||||
];
|
||||
|
||||
@@ -73,6 +73,12 @@ return [
|
||||
"employee" => "Mitarbeiter",
|
||||
"entry" => "Eintrag",
|
||||
"error_editing_item" => "Fehler beim Ändern des Artikels",
|
||||
"negative_price_invalid" => "",
|
||||
"negative_quantity_invalid" => "",
|
||||
"negative_discount_invalid" => "",
|
||||
"discount_percent_exceeds_100" => "",
|
||||
"discount_exceeds_item_total" => "",
|
||||
"negative_total_invalid" => "",
|
||||
"find_or_scan_item" => "Finde/Scanne Artikel",
|
||||
"find_or_scan_item_or_receipt" => "Finde/Scanne Artikel oder Quittung",
|
||||
"giftcard" => "Gutschein",
|
||||
|
||||
@@ -282,6 +282,7 @@ return [
|
||||
"right" => "",
|
||||
"sales_invoice_format" => "",
|
||||
"sales_quote_format" => "",
|
||||
"mailpath_invalid" => "",
|
||||
"saved_successfully" => "",
|
||||
"saved_unsuccessfully" => "",
|
||||
"security_issue" => "Security Vulnerability Warning",
|
||||
|
||||
@@ -9,6 +9,15 @@ return [
|
||||
"login" => "",
|
||||
"logout" => "",
|
||||
"migration_needed" => "",
|
||||
"migration_required" => "",
|
||||
"migration_auth_message" => "",
|
||||
"migration_initializing" => "",
|
||||
"migration_running" => "",
|
||||
"migration_complete" => "",
|
||||
"migration_complete_login" => "",
|
||||
"migration_failed" => "",
|
||||
"migration_error_connection" => "",
|
||||
"migration_complete_redirect" => "",
|
||||
"password" => "",
|
||||
"required_username" => "",
|
||||
"username" => "",
|
||||
|
||||
@@ -73,6 +73,12 @@ return [
|
||||
"employee" => "Υπάλληλος",
|
||||
"entry" => "Εγγραφή",
|
||||
"error_editing_item" => "Σφάλμα επεξεργασίας είδους",
|
||||
"negative_price_invalid" => "",
|
||||
"negative_quantity_invalid" => "",
|
||||
"negative_discount_invalid" => "",
|
||||
"discount_percent_exceeds_100" => "",
|
||||
"discount_exceeds_item_total" => "",
|
||||
"negative_total_invalid" => "",
|
||||
"find_or_scan_item" => "Εύρεση ή Σκανάρισμα Είδους",
|
||||
"find_or_scan_item_or_receipt" => "Εύρεση ή Σκανάρισμα είδους ή Απόδειξης",
|
||||
"giftcard" => "Δωροκάρτα",
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
<?php
|
||||
|
||||
return [
|
||||
"all" => "All",
|
||||
"columns" => "Columns",
|
||||
"hide_show_pagination" => "Hide/Show pagination",
|
||||
"loading" => "Loading, please wait...",
|
||||
"page_from_to" => "Showing {0} to {1} of {2} rows",
|
||||
"refresh" => "Refresh",
|
||||
"rows_per_page" => "{0} rows per page",
|
||||
"toggle" => "Toggle",
|
||||
'all' => "All",
|
||||
'columns' => "Columns",
|
||||
'hide_show_pagination' => "Hide/Show pagination",
|
||||
'loading' => "Loading, please wait",
|
||||
'page_from_to' => "Showing {0} to {1} of {2} rows",
|
||||
'refresh' => "Refresh",
|
||||
'rows_per_page' => "{0} rows per page",
|
||||
'toggle' => "Toggle",
|
||||
];
|
||||
|
||||
@@ -282,6 +282,7 @@ return [
|
||||
"right" => "Right",
|
||||
"sales_invoice_format" => "Sales Invoice Format",
|
||||
"sales_quote_format" => "Sales Quote Format",
|
||||
"mailpath_invalid" => "Invalid sendmail path. Only letters, numbers, dashes, underscores, slashes and dots are allowed.",
|
||||
"saved_successfully" => "Configuration saved successfully.",
|
||||
"saved_unsuccessfully" => "Configuration save failed.",
|
||||
"security_issue" => "Security Vulnerability Warning",
|
||||
|
||||
@@ -9,6 +9,15 @@ return [
|
||||
"login" => "Login",
|
||||
"logout" => "Logout",
|
||||
"migration_needed" => "A database migration to {0} will start after login.",
|
||||
"migration_required" => "Database Migration Required",
|
||||
"migration_auth_message" => "Administrator credentials are required to authorize the database migration to version {0}. Please login to proceed.",
|
||||
"migration_initializing" => "Initializing Database",
|
||||
"migration_running" => "Running database migrations...",
|
||||
"migration_complete" => "Database initialized successfully!",
|
||||
"migration_complete_login" => "You can now log in.",
|
||||
"migration_failed" => "Migration failed",
|
||||
"migration_error_connection" => "Connection error. Please try again.",
|
||||
"migration_complete_redirect" => "Migration complete. Redirecting to login...",
|
||||
"password" => "Password",
|
||||
"required_username" => "The username field is required.",
|
||||
"username" => "Username",
|
||||
|
||||
@@ -73,6 +73,12 @@ return [
|
||||
"employee" => "Employee",
|
||||
"entry" => "Entry",
|
||||
"error_editing_item" => "Error editing item",
|
||||
"negative_price_invalid" => "Price cannot be negative.",
|
||||
"negative_quantity_invalid" => "Quantity cannot be negative.",
|
||||
"negative_discount_invalid" => "Discount cannot be negative.",
|
||||
"discount_percent_exceeds_100" => "Percentage discount cannot exceed 100%.",
|
||||
"discount_exceeds_item_total" => "Discount cannot exceed the item total.",
|
||||
"negative_total_invalid" => "Sale total cannot be negative. Check item discounts and quantities.",
|
||||
"find_or_scan_item" => "Find or Scan Item",
|
||||
"find_or_scan_item_or_receipt" => "Find or Scan Item or Receipt",
|
||||
"giftcard" => "Gift Card",
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
<?php
|
||||
|
||||
return [
|
||||
"all" => "all",
|
||||
"columns" => "Columns",
|
||||
"hide_show_pagination" => "Hide/Show pagination",
|
||||
"loading" => "Loading, please wait...",
|
||||
"page_from_to" => "Showing {0} to {1} of {2} rows",
|
||||
"refresh" => "Refresh",
|
||||
"rows_per_page" => "{0} rows per page",
|
||||
"toggle" => "Toggle",
|
||||
'all' => "all",
|
||||
'columns' => "Columns",
|
||||
'hide_show_pagination' => "Hide/Show pagination",
|
||||
'loading' => "Loading, please wait",
|
||||
'page_from_to' => "Showing {0} to {1} of {2} rows",
|
||||
'refresh' => "Refresh",
|
||||
'rows_per_page' => "{0} rows per page",
|
||||
'toggle' => "Toggle",
|
||||
];
|
||||
|
||||
@@ -282,6 +282,7 @@ return [
|
||||
"right" => "Right",
|
||||
"sales_invoice_format" => "Sales Invoice Format",
|
||||
"sales_quote_format" => "Sales Quote Format",
|
||||
"mailpath_invalid" => "Invalid sendmail path. Only letters, numbers, dashes, underscores, slashes and dots are allowed.",
|
||||
"saved_successfully" => "Configuration save successful.",
|
||||
"saved_unsuccessfully" => "Configuration save failed.",
|
||||
"security_issue" => "Security Vulnerability Warning",
|
||||
@@ -328,4 +329,6 @@ return [
|
||||
"wholesale_markup" => "",
|
||||
"work_order_enable" => "Work Order Support",
|
||||
"work_order_format" => "Work Order Format",
|
||||
"exif_fields_to_keep" => "EXIF Fields to Keep",
|
||||
"exif_fields_to_keep_tooltip" => "Select EXIF fields to preserve in uploaded images. Fields not selected will be removed. Leave empty to disable EXIF stripping. Keeps beneficial metadata while removing privacy-sensitive data like GPS location.",
|
||||
];
|
||||
|
||||
@@ -9,6 +9,15 @@ return [
|
||||
"login" => "Login",
|
||||
"logout" => "Logout",
|
||||
"migration_needed" => "A database migration to {0} will start after login.",
|
||||
"migration_required" => "Database Migration Required",
|
||||
"migration_auth_message" => "Administrator credentials are required to authorize the database migration to version {0}. Please login to proceed.",
|
||||
"migration_initializing" => "Initializing Database",
|
||||
"migration_running" => "Running database migrations...",
|
||||
"migration_complete" => "Database initialized successfully!",
|
||||
"migration_complete_login" => "You can now log in.",
|
||||
"migration_failed" => "Migration failed",
|
||||
"migration_error_connection" => "Connection error. Please try again.",
|
||||
"migration_complete_redirect" => "Migration complete. Redirecting to login...",
|
||||
"password" => "Password",
|
||||
"required_username" => "The username field is required.",
|
||||
"username" => "Username",
|
||||
|
||||
@@ -73,6 +73,12 @@ return [
|
||||
"employee" => "Employee",
|
||||
"entry" => "Entry",
|
||||
"error_editing_item" => "Error editing item",
|
||||
"negative_price_invalid" => "Price cannot be negative.",
|
||||
"negative_quantity_invalid" => "Quantity cannot be negative.",
|
||||
"negative_discount_invalid" => "Discount cannot be negative.",
|
||||
"discount_percent_exceeds_100" => "Percentage discount cannot exceed 100%.",
|
||||
"discount_exceeds_item_total" => "Discount cannot exceed the item total.",
|
||||
"negative_total_invalid" => "Sale total cannot be negative. Check item discounts and quantities.",
|
||||
"find_or_scan_item" => "Find or Scan Item",
|
||||
"find_or_scan_item_or_receipt" => "Find or Scan Item or Receipt",
|
||||
"giftcard" => "Gift Card",
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
<?php
|
||||
|
||||
return [
|
||||
"all" => "Todas",
|
||||
"columns" => "Columnas",
|
||||
"hide_show_pagination" => "Ocultar/Mostrar paginación",
|
||||
"loading" => "Por favor espere...",
|
||||
"page_from_to" => "Mostrando desde {0} hasta {1} - En total {2} resultados",
|
||||
"refresh" => "Refrescar",
|
||||
"rows_per_page" => "{0} resultados por página",
|
||||
"toggle" => "Ocultar/Mostrar",
|
||||
'all' => "Todas",
|
||||
'columns' => "Columnas",
|
||||
'hide_show_pagination' => "Ocultar/Mostrar paginación",
|
||||
'loading' => "Por favor espere",
|
||||
'page_from_to' => "Mostrando desde {0} hasta {1} - En total {2} resultados",
|
||||
'refresh' => "Refrescar",
|
||||
'rows_per_page' => "{0} resultados por página",
|
||||
'toggle' => "Ocultar/Mostrar",
|
||||
];
|
||||
|
||||
@@ -282,6 +282,7 @@ return [
|
||||
"right" => "Derecha",
|
||||
"sales_invoice_format" => "Formato de Facturas de Venta",
|
||||
"sales_quote_format" => "Formato de presupuesto de las ventas",
|
||||
"mailpath_invalid" => "Ruta de sendmail inválida. Solo se permiten letras, números, guiones, guiones bajos, barras y puntos.",
|
||||
"saved_successfully" => "Configuración guardada satisfactoriamente.",
|
||||
"saved_unsuccessfully" => "Configuración no guardada.",
|
||||
"security_issue" => "Advertencia de vulnerabilidad de seguridad",
|
||||
|
||||
@@ -9,6 +9,15 @@ return [
|
||||
"login" => "Iniciar Sesión",
|
||||
"logout" => "Cerrar sesión",
|
||||
"migration_needed" => "La migración de la base de datos a {0} se iniciará después del inicio de sesión.",
|
||||
"migration_required" => "Migración de base de datos requerida",
|
||||
"migration_auth_message" => "Se requieren credenciales de administrador para autorizar la migración de la base de datos a la versión {0}. Inicie sesión para continuar.",
|
||||
"migration_initializing" => "Inicializando base de datos",
|
||||
"migration_running" => "Ejecutando migraciones de base de datos...",
|
||||
"migration_complete" => "¡Base de datos inicializada correctamente!",
|
||||
"migration_complete_login" => "Ahora puede iniciar sesión.",
|
||||
"migration_failed" => "Migración fallida",
|
||||
"migration_error_connection" => "Error de conexión. Por favor, inténtelo de nuevo.",
|
||||
"migration_complete_redirect" => "Migración completada. Redirigiendo al inicio de sesión...",
|
||||
"password" => "Contraseña",
|
||||
"required_username" => "El campo de nombre de usuario es obligatorio.",
|
||||
"username" => "Usuario",
|
||||
|
||||
@@ -73,6 +73,12 @@ return [
|
||||
"employee" => "Empleado",
|
||||
"entry" => "Entrada",
|
||||
"error_editing_item" => "Error editando artículo",
|
||||
"negative_price_invalid" => "",
|
||||
"negative_quantity_invalid" => "",
|
||||
"negative_discount_invalid" => "",
|
||||
"discount_percent_exceeds_100" => "",
|
||||
"discount_exceeds_item_total" => "",
|
||||
"negative_total_invalid" => "",
|
||||
"find_or_scan_item" => "Encontrar/Escanear Artículo",
|
||||
"find_or_scan_item_or_receipt" => "Encontrar/Escanear Artículo o Entrada",
|
||||
"giftcard" => "Tarjeta de Regalo",
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
<?php
|
||||
|
||||
return [
|
||||
"all" => "Todos",
|
||||
"columns" => "Columnas",
|
||||
"hide_show_pagination" => "Ocultar/Mostrar paginación",
|
||||
"loading" => "Cargando, por favor espere...",
|
||||
"page_from_to" => "Mostrando de {0} a {1} de {2} registros",
|
||||
"refresh" => "Actualizar",
|
||||
"rows_per_page" => "{0} registros por página",
|
||||
"toggle" => "Establecer",
|
||||
'all' => "Todos",
|
||||
'columns' => "Columnas",
|
||||
'hide_show_pagination' => "Ocultar/Mostrar paginación",
|
||||
'loading' => "Cargando, por favor espere",
|
||||
'page_from_to' => "Mostrando de {0} a {1} de {2} registros",
|
||||
'refresh' => "Actualizar",
|
||||
'rows_per_page' => "{0} registros por página",
|
||||
'toggle' => "Establecer",
|
||||
];
|
||||
|
||||
@@ -282,6 +282,7 @@ return [
|
||||
"right" => "Right",
|
||||
"sales_invoice_format" => "Sales Invoice Format",
|
||||
"sales_quote_format" => "Sales Quote Format",
|
||||
"mailpath_invalid" => "Ruta de sendmail inválida. Solo se permiten letras, números, guiones, guiones bajos, barras y puntos.",
|
||||
"saved_successfully" => "Configuration save successful.",
|
||||
"saved_unsuccessfully" => "Configuration save failed.",
|
||||
"security_issue" => "Security Vulnerability Warning",
|
||||
|
||||
@@ -9,6 +9,15 @@ return [
|
||||
"login" => "Login",
|
||||
"logout" => "Salir",
|
||||
"migration_needed" => "Una migración de base de datos a {0} empezara después de entrar.",
|
||||
"migration_required" => "Migración de base de datos requerida",
|
||||
"migration_auth_message" => "Se requieren credenciales de administrador para autorizar la migración de la base de datos a la versión {0}. Inicie sesión para continuar.",
|
||||
"migration_initializing" => "Inicializando base de datos",
|
||||
"migration_running" => "Ejecutando migraciones de base de datos...",
|
||||
"migration_complete" => "¡Base de datos inicializada correctamente!",
|
||||
"migration_complete_login" => "Ahora puede iniciar sesión.",
|
||||
"migration_failed" => "Migración fallida",
|
||||
"migration_error_connection" => "Error de conexión. Por favor, inténtelo de nuevo.",
|
||||
"migration_complete_redirect" => "Migración completada. Redirigiendo al inicio de sesión...",
|
||||
"password" => "Contraseña",
|
||||
"required_username" => "El nombre de usuario es obligatorio.",
|
||||
"username" => "Usuario",
|
||||
|
||||
@@ -73,6 +73,12 @@ return [
|
||||
"employee" => "Empleado",
|
||||
"entry" => "Entrada",
|
||||
"error_editing_item" => "Error editando el artículo",
|
||||
"negative_price_invalid" => "",
|
||||
"negative_quantity_invalid" => "",
|
||||
"negative_discount_invalid" => "",
|
||||
"discount_percent_exceeds_100" => "",
|
||||
"discount_exceeds_item_total" => "",
|
||||
"negative_total_invalid" => "",
|
||||
"find_or_scan_item" => "Buscar o escanear artículo",
|
||||
"find_or_scan_item_or_receipt" => "Buscar o escanear artículo o recibo",
|
||||
"giftcard" => "Tarjeta de regalo",
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
<?php
|
||||
|
||||
return [
|
||||
"all" => "همه",
|
||||
"columns" => "ستون ها",
|
||||
"hide_show_pagination" => "پنهان کردن / نمایش صفحه بندی",
|
||||
"loading" => "...در حال بارگزاری، لطفا منتظر بمانید",
|
||||
"page_from_to" => "نمایش {0} تا {1} از {2} ردیف",
|
||||
"refresh" => "تازه کردن",
|
||||
"rows_per_page" => "صفر ردیف در هر صفحه",
|
||||
"toggle" => "تغییر وضعیت",
|
||||
'all' => "همه",
|
||||
'columns' => "ستون ها",
|
||||
'hide_show_pagination' => "پنهان کردن / نمایش صفحه بندی",
|
||||
'loading' => "در حال بارگزاری، لطفا منتظر بمانید",
|
||||
'page_from_to' => "نمایش {0} تا {1} از {2} ردیف",
|
||||
'refresh' => "تازه کردن",
|
||||
'rows_per_page' => "صفر ردیف در هر صفحه",
|
||||
'toggle' => "تغییر وضعیت",
|
||||
];
|
||||
|
||||
@@ -282,6 +282,7 @@ return [
|
||||
"right" => "درست",
|
||||
"sales_invoice_format" => "قالب فاکتور فروش",
|
||||
"sales_quote_format" => "قالب فروش قیمت",
|
||||
"mailpath_invalid" => "",
|
||||
"saved_successfully" => "پیکربندی ذخیره موفقیت آمیز است.",
|
||||
"saved_unsuccessfully" => "ذخیره پیکربندی انجام نشد.",
|
||||
"security_issue" => "هشدار آسیب پذیری امنیتی",
|
||||
|
||||
@@ -9,6 +9,15 @@ return [
|
||||
"login" => "وارد شدن",
|
||||
"logout" => "",
|
||||
"migration_needed" => "",
|
||||
"migration_required" => "",
|
||||
"migration_auth_message" => "",
|
||||
"migration_initializing" => "",
|
||||
"migration_running" => "",
|
||||
"migration_complete" => "",
|
||||
"migration_complete_login" => "",
|
||||
"migration_failed" => "",
|
||||
"migration_error_connection" => "",
|
||||
"migration_complete_redirect" => "",
|
||||
"password" => "کلمه عبور",
|
||||
"required_username" => "",
|
||||
"username" => "نام کاربری",
|
||||
|
||||
@@ -73,6 +73,12 @@ return [
|
||||
"employee" => "کارمند",
|
||||
"entry" => "ورود",
|
||||
"error_editing_item" => "خطا در ویرایش مورد",
|
||||
"negative_price_invalid" => "",
|
||||
"negative_quantity_invalid" => "",
|
||||
"negative_discount_invalid" => "",
|
||||
"discount_percent_exceeds_100" => "",
|
||||
"discount_exceeds_item_total" => "",
|
||||
"negative_total_invalid" => "",
|
||||
"find_or_scan_item" => "یافتن یا اسکن کردن مورد",
|
||||
"find_or_scan_item_or_receipt" => "یافتن یا اسکن کردن مورد یا رسید",
|
||||
"giftcard" => "کارت هدیه",
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
<?php
|
||||
|
||||
return [
|
||||
"all" => "tous",
|
||||
"columns" => "Colonnes",
|
||||
"hide_show_pagination" => "Masquer/Afficher la pagination",
|
||||
"loading" => "Chargement en cours, patientez, s'il vous plaît ...",
|
||||
"page_from_to" => "Affichage des lignes {0} à {1} sur {2} lignes au total",
|
||||
"refresh" => "Rafraîchir",
|
||||
"rows_per_page" => "{0} lignes par page",
|
||||
"toggle" => "Alterner",
|
||||
'all' => "tous",
|
||||
'columns' => "Colonnes",
|
||||
'hide_show_pagination' => "Masquer/Afficher la pagination",
|
||||
'loading' => "Chargement en cours, patientez, s'il vous plaît",
|
||||
'page_from_to' => "Affichage des lignes {0} à {1} sur {2} lignes au total",
|
||||
'refresh' => "Rafraîchir",
|
||||
'rows_per_page' => "{0} lignes par page",
|
||||
'toggle' => "Alterner",
|
||||
];
|
||||
|
||||
@@ -282,6 +282,7 @@ return [
|
||||
"right" => "Droite",
|
||||
"sales_invoice_format" => "Format de la facture de vente",
|
||||
"sales_quote_format" => "Format de devis de vente",
|
||||
"mailpath_invalid" => "Chemin sendmail invalide. Seuls les lettres, chiffres, tirets, underscores, barres obliques et points sont autorisés.",
|
||||
"saved_successfully" => "Configuration enregistrer avec succès.",
|
||||
"saved_unsuccessfully" => "L'enregistrement de configuration a échoué.",
|
||||
"security_issue" => "Avertissement de faille de sécurité",
|
||||
|
||||
@@ -9,6 +9,15 @@ return [
|
||||
"login" => "Login",
|
||||
"logout" => "Déconnexion",
|
||||
"migration_needed" => "Une migration de base de données vers {0} débutera après l'ouverture de session.",
|
||||
"migration_required" => "Migration de base de données requise",
|
||||
"migration_auth_message" => "Les identifiants administrateur sont requis pour autoriser la migration de la base de données vers la version {0}. Veuillez vous connecter pour continuer.",
|
||||
"migration_initializing" => "Initialisation de la base de données",
|
||||
"migration_running" => "Exécution des migrations de la base de données...",
|
||||
"migration_complete" => "Base de données initialisée avec succès !",
|
||||
"migration_complete_login" => "Vous pouvez maintenant vous connecter.",
|
||||
"migration_failed" => "Échec de la migration",
|
||||
"migration_error_connection" => "Erreur de connexion. Veuillez réessayer.",
|
||||
"migration_complete_redirect" => "Migration terminée. Redirection vers la connexion...",
|
||||
"password" => "Mot de passe",
|
||||
"required_username" => "Le champ nom utilisateur est obligatoire.",
|
||||
"username" => "Nom d'utilisateur",
|
||||
|
||||
@@ -73,6 +73,12 @@ return [
|
||||
"employee" => "Employé",
|
||||
"entry" => "Entrée",
|
||||
"error_editing_item" => "Érreur lors de l'édition",
|
||||
"negative_price_invalid" => "",
|
||||
"negative_quantity_invalid" => "",
|
||||
"negative_discount_invalid" => "",
|
||||
"discount_percent_exceeds_100" => "",
|
||||
"discount_exceeds_item_total" => "",
|
||||
"negative_total_invalid" => "",
|
||||
"find_or_scan_item" => "Trouver/Scanner Article",
|
||||
"find_or_scan_item_or_receipt" => "Trouver/Scanner Article OU Reçu",
|
||||
"giftcard" => "Carte Cadeau",
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
<?php
|
||||
|
||||
return [
|
||||
"all" => "הכול",
|
||||
"columns" => "עמודות",
|
||||
"hide_show_pagination" => "הסתר / הצג מספור דפים",
|
||||
"loading" => "טוען, אנא המתן ...",
|
||||
"page_from_to" => "מציג {0} ל {1} מתוך {2} שורות",
|
||||
"refresh" => "רענן",
|
||||
"rows_per_page" => "{0} שורות לדף",
|
||||
"toggle" => "החלף",
|
||||
'all' => "הכול",
|
||||
'columns' => "עמודות",
|
||||
'hide_show_pagination' => "הסתר / הצג מספור דפים",
|
||||
'loading' => "טוען, אנא המתן",
|
||||
'page_from_to' => "מציג {0} ל {1} מתוך {2} שורות",
|
||||
'refresh' => "רענן",
|
||||
'rows_per_page' => "{0} שורות לדף",
|
||||
'toggle' => "החלף",
|
||||
];
|
||||
|
||||
@@ -282,6 +282,7 @@ return [
|
||||
"right" => "ימין",
|
||||
"sales_invoice_format" => "תבנית חשבונית מכירות",
|
||||
"sales_quote_format" => "תבנית חשבונית הצעת מחיר",
|
||||
"mailpath_invalid" => "",
|
||||
"saved_successfully" => "ההגדרות נשמרו בהצלחה.",
|
||||
"saved_unsuccessfully" => "שמירת ההגדרות נכשלה.",
|
||||
"security_issue" => "Security Vulnerability Warning",
|
||||
|
||||
@@ -9,6 +9,15 @@ return [
|
||||
"login" => "כניסה",
|
||||
"logout" => "",
|
||||
"migration_needed" => "",
|
||||
"migration_required" => "",
|
||||
"migration_auth_message" => "",
|
||||
"migration_initializing" => "",
|
||||
"migration_running" => "",
|
||||
"migration_complete" => "",
|
||||
"migration_complete_login" => "",
|
||||
"migration_failed" => "",
|
||||
"migration_error_connection" => "",
|
||||
"migration_complete_redirect" => "",
|
||||
"password" => "סיסמה",
|
||||
"required_username" => "",
|
||||
"username" => "שם משתמש",
|
||||
|
||||
@@ -73,6 +73,12 @@ return [
|
||||
"employee" => "עובד",
|
||||
"entry" => "ערך",
|
||||
"error_editing_item" => "שגיאה בעריכת פריט",
|
||||
"negative_price_invalid" => "",
|
||||
"negative_quantity_invalid" => "",
|
||||
"negative_discount_invalid" => "",
|
||||
"discount_percent_exceeds_100" => "",
|
||||
"discount_exceeds_item_total" => "",
|
||||
"negative_total_invalid" => "",
|
||||
"find_or_scan_item" => "חיפוש או סריקה של פריט",
|
||||
"find_or_scan_item_or_receipt" => "חיפוש או סריקה של פריט או קבלה",
|
||||
"giftcard" => "כרטיס מתנה",
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
<?php
|
||||
|
||||
return [
|
||||
"all" => "Sve",
|
||||
"columns" => "Kolone",
|
||||
"hide_show_pagination" => "Prikaži/sakrij stranice",
|
||||
"loading" => "Molimo pričekajte ...",
|
||||
"page_from_to" => "Prikazujem {0}. - {1} od ukupnog broja zapisa {2}",
|
||||
"refresh" => "Osvježi",
|
||||
"rows_per_page" => "{0} broj zapisa po stranici",
|
||||
"toggle" => "Promijeni prikaz",
|
||||
'all' => "Sve",
|
||||
'columns' => "Kolone",
|
||||
'hide_show_pagination' => "Prikaži/sakrij stranice",
|
||||
'loading' => "Molimo pričekajte",
|
||||
'page_from_to' => "Prikazujem {0}. - {1} od ukupnog broja zapisa {2}",
|
||||
'refresh' => "Osvježi",
|
||||
'rows_per_page' => "{0} broj zapisa po stranici",
|
||||
'toggle' => "Promijeni prikaz",
|
||||
];
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user