This commit is contained in:
10
.editorconfig
Normal file
10
.editorconfig
Normal file
@ -0,0 +1,10 @@
|
||||
root = true
|
||||
|
||||
[*]
|
||||
end_of_line = lf
|
||||
insert_final_newline = true
|
||||
indent_style = space
|
||||
indent_size = 4
|
||||
|
||||
[{package.json,*.yml}]
|
||||
indent_size = 2
|
1
.gitattributes
vendored
Normal file
1
.gitattributes
vendored
Normal file
@ -0,0 +1 @@
|
||||
* text=auto
|
58
.github/CODE_OF_CONDUCT.md
vendored
Normal file
58
.github/CODE_OF_CONDUCT.md
vendored
Normal file
@ -0,0 +1,58 @@
|
||||
# Code of Conduct
|
||||
|
||||
### Our Pledge
|
||||
|
||||
In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to making
|
||||
participation in our project and our community a harassment-free experience for everyone, regardless of age, body size,
|
||||
disability, ethnicity, gender identity and expression, level of experience, nationality, personal appearance, race,
|
||||
religion, or sexual identity and orientation.
|
||||
|
||||
### Our Standards
|
||||
|
||||
Examples of behavior that contributes to creating a positive environment include:
|
||||
|
||||
* Using welcoming and inclusive language.
|
||||
* Being respectful of differing viewpoints and experiences.
|
||||
* Gracefully accepting constructive criticism.
|
||||
* Focusing on what is best for the community.
|
||||
* Showing empathy towards other community members.
|
||||
|
||||
Examples of unacceptable behavior by participants include:
|
||||
|
||||
* The use of sexualized language or imagery and unwelcome sexual attention or advances.
|
||||
* Trolling, insulting/derogatory comments, and personal or political attacks.
|
||||
* Public or private harassment.
|
||||
* Publishing others' private information, such as a physical or electronic address, without explicit permission.
|
||||
* Other conduct which could reasonably be considered inappropriate in a professional setting.
|
||||
|
||||
### Our Responsibilities
|
||||
|
||||
Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take
|
||||
appropriate and fair corrective action in response to any instances of unacceptable behavior.
|
||||
|
||||
Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits,
|
||||
issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any
|
||||
contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful.
|
||||
|
||||
### Scope
|
||||
|
||||
This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the
|
||||
project or its community. Examples of representing a project or community include using an official project e-mail
|
||||
address, posting via an official social media account, or acting as an appointed representative at an online or offline
|
||||
event. Representation of a project may be further defined and clarified by project maintainers.
|
||||
|
||||
### Enforcement
|
||||
|
||||
Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project owner at
|
||||
[alextselegidis@gmail.com](alextselegidis@gmail.com). All complaints will be reviewed and investigated and will result
|
||||
in a response that is deemed necessary and appropriate to the circumstances. The project owner is obligated to maintain
|
||||
confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be
|
||||
posted separately.
|
||||
|
||||
Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent
|
||||
repercussions as determined by other members of the project's leadership.
|
||||
|
||||
### Attribution
|
||||
|
||||
This Code of Conduct is adapted from the [Contributor Covenant](http://contributor-covenant.org), version 1.4,
|
||||
available at http://contributor-covenant.org/version/1/4.
|
17
.github/CONTRIBUTING.md
vendored
Normal file
17
.github/CONTRIBUTING.md
vendored
Normal file
@ -0,0 +1,17 @@
|
||||
# Contributing
|
||||
|
||||
When contributing to this repository, please first discuss the change you wish to make via issue, email, or any other
|
||||
method with the owner of this repository before making a change.
|
||||
|
||||
Please note we have a code of conduct, please follow it in all your interactions with the project.
|
||||
|
||||
## Pull Request Process
|
||||
|
||||
1. Ensure any install or build dependencies are removed before the end of the layer when doing a build.
|
||||
2. Ensure that your changes comply with the project's coding guidelines and that it's sufficiently documented.
|
||||
3. Update the README.md with details of changes to the interface, this includes new environment variables, exposed
|
||||
ports, useful file locations and container parameters.
|
||||
4. Target the develop branch for your Pull Requests as this is were new changes are introduced.
|
||||
4. After being successfully reviewed pull requests will be merged to develop branch and will finally be included in an
|
||||
upcoming release.
|
||||
|
4
.github/SECURITY.md
vendored
Normal file
4
.github/SECURITY.md
vendored
Normal file
@ -0,0 +1,4 @@
|
||||
# Security Vulnerabilities
|
||||
|
||||
If you discover a security vulnerability within Easy!Appointments, please send an email to info@easyappointments.org.
|
||||
All security vulnerabilities will be promptly addressed.
|
23
.github/workflows/ci.yml
vendored
Normal file
23
.github/workflows/ci.yml
vendored
Normal file
@ -0,0 +1,23 @@
|
||||
name: CI
|
||||
|
||||
on: [ push ]
|
||||
|
||||
jobs:
|
||||
build-test:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Git clone
|
||||
uses: actions/checkout@v2
|
||||
- name: Install dependencies
|
||||
uses: php-actions/composer@v6
|
||||
with:
|
||||
php_version: 8.2
|
||||
php_extensions: gd
|
||||
version: 2
|
||||
- name: PHPUnit Tests
|
||||
uses: php-actions/phpunit@v3
|
||||
env:
|
||||
TEST_NAME: Test
|
||||
with:
|
||||
configuration: phpunit.xml
|
33
.gitignore
vendored
Normal file
33
.gitignore
vendored
Normal file
@ -0,0 +1,33 @@
|
||||
/*.zip
|
||||
/.idea/
|
||||
/.tmp-package/
|
||||
/build/
|
||||
/docs/apigen/html
|
||||
/docs/jsdoc/html
|
||||
/docs/plato/html
|
||||
/docker/mysql/
|
||||
/docker/baikal/
|
||||
/docker/openldap/
|
||||
/node_modules/
|
||||
/npm-debug.log
|
||||
/assets/js/**/*.min.js
|
||||
/assets/css/**/*.css
|
||||
/assets/vendor
|
||||
/config.php
|
||||
/storage/backups/*
|
||||
!/storage/backups/.htaccess
|
||||
!/storage/backups/index.html
|
||||
/storage/cache/*
|
||||
!/storage/cache/.htaccess
|
||||
!/storage/cache/index.html
|
||||
/storage/logs/*
|
||||
!/storage/logs/.htaccess
|
||||
!/storage/logs/index.html
|
||||
/storage/sessions/*
|
||||
!/storage/sessions/.htaccess
|
||||
!/storage/sessions/index.html
|
||||
/storage/uploads/*
|
||||
!/storage/uploads/index.html
|
||||
/vendor/
|
||||
/metafile
|
||||
.DS_Store
|
11
.prettierignore
Normal file
11
.prettierignore
Normal file
@ -0,0 +1,11 @@
|
||||
.idea
|
||||
.github
|
||||
.run
|
||||
application/language/*
|
||||
build
|
||||
docker
|
||||
docs
|
||||
node_modules
|
||||
storage
|
||||
system
|
||||
vendor
|
12
.prettierrc.json
Normal file
12
.prettierrc.json
Normal file
@ -0,0 +1,12 @@
|
||||
{
|
||||
"printWidth": 120,
|
||||
"tabWidth": 4,
|
||||
"singleQuote": true,
|
||||
"bracketSpacing": false,
|
||||
"bracketSameLine": false,
|
||||
"quoteProps": "preserve",
|
||||
"trailingComma": "all",
|
||||
"trailingCommaPHP": true,
|
||||
"phpVersion": "8.0",
|
||||
"plugins": ["@prettier/plugin-php"]
|
||||
}
|
17
.run/Build.run.xml
Normal file
17
.run/Build.run.xml
Normal file
@ -0,0 +1,17 @@
|
||||
<component name="ProjectRunConfigurationManager">
|
||||
<configuration default="false" name="Build" type="ShConfigurationType">
|
||||
<option name="SCRIPT_TEXT" value="if [ -z "$(docker compose ls | grep $(basename "$PWD"))" ]; then docker stop $(docker ps -a -q) && docker network prune -f && docker compose up -d --force-recreate && docker exec -it $(basename "$PWD")-php-fpm-1 npm run build; else docker exec -it $(basename "$PWD")-php-fpm-1 npm run build; fi " />
|
||||
<option name="INDEPENDENT_SCRIPT_PATH" value="true" />
|
||||
<option name="SCRIPT_PATH" value="" />
|
||||
<option name="SCRIPT_OPTIONS" value="" />
|
||||
<option name="INDEPENDENT_SCRIPT_WORKING_DIRECTORY" value="true" />
|
||||
<option name="SCRIPT_WORKING_DIRECTORY" value="$PROJECT_DIR$" />
|
||||
<option name="INDEPENDENT_INTERPRETER_PATH" value="true" />
|
||||
<option name="INTERPRETER_PATH" value="/bin/zsh" />
|
||||
<option name="INTERPRETER_OPTIONS" value="" />
|
||||
<option name="EXECUTE_IN_TERMINAL" value="true" />
|
||||
<option name="EXECUTE_SCRIPT_FILE" value="false" />
|
||||
<envs />
|
||||
<method v="2" />
|
||||
</configuration>
|
||||
</component>
|
17
.run/SSH.run.xml
Normal file
17
.run/SSH.run.xml
Normal file
@ -0,0 +1,17 @@
|
||||
<component name="ProjectRunConfigurationManager">
|
||||
<configuration default="false" name="SSH" type="ShConfigurationType">
|
||||
<option name="SCRIPT_TEXT" value="if [ -z "$(docker compose ls | grep $(basename "$PWD"))" ]; then docker stop $(docker ps -a -q) && docker network prune -f && docker compose up -d --force-recreate && docker exec -it $(basename "$PWD")-php-fpm-1 bash; else docker exec -it $(basename "$PWD")-php-fpm-1 bash; fi" />
|
||||
<option name="INDEPENDENT_SCRIPT_PATH" value="true" />
|
||||
<option name="SCRIPT_PATH" value="" />
|
||||
<option name="SCRIPT_OPTIONS" value="" />
|
||||
<option name="INDEPENDENT_SCRIPT_WORKING_DIRECTORY" value="true" />
|
||||
<option name="SCRIPT_WORKING_DIRECTORY" value="$PROJECT_DIR$" />
|
||||
<option name="INDEPENDENT_INTERPRETER_PATH" value="true" />
|
||||
<option name="INTERPRETER_PATH" value="/bin/zsh" />
|
||||
<option name="INTERPRETER_OPTIONS" value="" />
|
||||
<option name="EXECUTE_IN_TERMINAL" value="true" />
|
||||
<option name="EXECUTE_SCRIPT_FILE" value="false" />
|
||||
<envs />
|
||||
<method v="2" />
|
||||
</configuration>
|
||||
</component>
|
17
.run/Start.run.xml
Normal file
17
.run/Start.run.xml
Normal file
@ -0,0 +1,17 @@
|
||||
<component name="ProjectRunConfigurationManager">
|
||||
<configuration default="false" name="Start" type="ShConfigurationType">
|
||||
<option name="SCRIPT_TEXT" value="if [ -z "$(docker compose ls | grep $(basename "$PWD"))" ]; then docker stop $(docker ps -a -q) && docker network prune -f && docker compose up -d --force-recreate && docker exec -it $(basename "$PWD")-php-fpm-1 npm start; else echo "Docker compose is already running\!"; fi " />
|
||||
<option name="INDEPENDENT_SCRIPT_PATH" value="true" />
|
||||
<option name="SCRIPT_PATH" value="" />
|
||||
<option name="SCRIPT_OPTIONS" value="" />
|
||||
<option name="INDEPENDENT_SCRIPT_WORKING_DIRECTORY" value="true" />
|
||||
<option name="SCRIPT_WORKING_DIRECTORY" value="$PROJECT_DIR$" />
|
||||
<option name="INDEPENDENT_INTERPRETER_PATH" value="true" />
|
||||
<option name="INTERPRETER_PATH" value="/bin/zsh" />
|
||||
<option name="INTERPRETER_OPTIONS" value="" />
|
||||
<option name="EXECUTE_IN_TERMINAL" value="true" />
|
||||
<option name="EXECUTE_SCRIPT_FILE" value="false" />
|
||||
<envs />
|
||||
<method v="2" />
|
||||
</configuration>
|
||||
</component>
|
17
.run/Stop.run.xml
Normal file
17
.run/Stop.run.xml
Normal file
@ -0,0 +1,17 @@
|
||||
<component name="ProjectRunConfigurationManager">
|
||||
<configuration default="false" name="Stop" type="ShConfigurationType">
|
||||
<option name="SCRIPT_TEXT" value="docker stop $(docker ps -a -q)" />
|
||||
<option name="INDEPENDENT_SCRIPT_PATH" value="true" />
|
||||
<option name="SCRIPT_PATH" value="" />
|
||||
<option name="SCRIPT_OPTIONS" value="" />
|
||||
<option name="INDEPENDENT_SCRIPT_WORKING_DIRECTORY" value="true" />
|
||||
<option name="SCRIPT_WORKING_DIRECTORY" value="$PROJECT_DIR$" />
|
||||
<option name="INDEPENDENT_INTERPRETER_PATH" value="true" />
|
||||
<option name="INTERPRETER_PATH" value="/bin/zsh" />
|
||||
<option name="INTERPRETER_OPTIONS" value="" />
|
||||
<option name="EXECUTE_IN_TERMINAL" value="true" />
|
||||
<option name="EXECUTE_SCRIPT_FILE" value="false" />
|
||||
<envs />
|
||||
<method v="2" />
|
||||
</configuration>
|
||||
</component>
|
392
CHANGELOG.md
Normal file
392
CHANGELOG.md
Normal file
@ -0,0 +1,392 @@
|
||||
# Easy!Appointments - Changelog
|
||||
|
||||
This file contains the code changes that were introduced into each release (starting from v1.1.0) so that is easy for
|
||||
developers to maintain and readjust their custom modifications on the main project codebase.
|
||||
|
||||
## [1.5.1] - 2025-01-20
|
||||
|
||||
### Added
|
||||
|
||||
- Add support for PHP 8.4 (#1640)
|
||||
- Add new secret token header field to webhooks and default to X-EA-Token (#1607)
|
||||
- Add company colors to notifications (#1569)
|
||||
- Add Albanian Language Translations (#1646)
|
||||
|
||||
### Fixed
|
||||
|
||||
- Fix the date parsing issue on Safari web browsers during the booking process (#1584)
|
||||
- Fix working plan configuration am/pm hour parsing so that it works in all languages (#1606)
|
||||
- Improve the CalDAV syncing mechanism so that it connects to more systems without problems (#1622)
|
||||
- Fix various 1.5.0 API issues (#1562)
|
||||
- Correct email issues by replacing the internal email library with phpmailer (#1587)
|
||||
- Fix ICS file mimetype (#1630)
|
||||
- Incorrect Timezone Handling in CalDAV Synchronization Causes Time Shifts (#1626)
|
||||
- No color change in the appointment modal window (in the backend calendar) (#1641)
|
||||
- The plain user password might appear in the log messages in case of an error (#1590)
|
||||
- Fix loop when going a month back in an edge case (#1577)
|
||||
- Dedicated provider links do not pre-select the relevant provider on the booking page (#1651)
|
||||
- Correct the service / provider booking header display (#1650)
|
||||
|
||||
|
||||
|
||||
## [1.5.0] - 2024-07-07
|
||||
|
||||
### Added
|
||||
|
||||
- Display month with the earliest available day (#1075)
|
||||
- Allow admins to define the business closed dates (blocked-periods) (#432)
|
||||
- Allow working plan exceptions to be defined as non-working days (#1383)
|
||||
- Create an official docker image of the project and host it on Docker Hub(#1116)
|
||||
- Automatically select the next available date in the booking page or display a message if this month is unavailable (#1204)
|
||||
- Add Open Graph information to the public booking page so that it renders nicely once shared on social media apps (#1382)
|
||||
- Preselect the date with a query parameter (#1376)
|
||||
- Add the location and notes fields to the appointment email notifications (if a value was provided) (#1341)
|
||||
- Add date, from and till query parameters to the filter the appointments index results by date (#1134)
|
||||
- Allow the users to define their own status and assign them to appointments (#244)
|
||||
- Add new setting for limiting new public bookings in the future (#1203)
|
||||
- Automatically enable the secure cookie config if the current installation uses HTTPS (#1126)
|
||||
- Add language and timezone properties to the customer API resource (#1157)
|
||||
- Add support for the definition of custom webhooks via the settings page (#581)
|
||||
- Allow the user to select their own preferred language (#1263)
|
||||
- Support multiple Bootswatch themes for the app (#1205)
|
||||
- Providers and secretaries must only be able to see and manage their own customers (#1199)
|
||||
- Use the default service duration if the user just clicks on a calendar slot for creating a new appointment (#1237)
|
||||
- Google Calendar synchronisation failure when symbols/emoji appear in events to be imported (#1182)
|
||||
- Add the customer timezone field in the appointment modal of the calendar page (#1094)
|
||||
- Add a new setting that toggles the login link of the booking page (#1148)
|
||||
- Add custom Matomo analytics integration (#974)
|
||||
- Prefill the form field though url parameters (#1021)
|
||||
- Color code events by provider or service (#422)
|
||||
- Service duration values shorter than 5 minutes should be acceptable via the services page (#1110)
|
||||
- Add a new "is_private" flag to services and providers so that they do not appear in the booking page (#378)
|
||||
- Skip the first booking step when only one service and one provider are available (#349)
|
||||
- Enable the change of the brand logo and colors from the backend (#789)
|
||||
- Add the ability to temporarily block new appointments / set away message (#940)
|
||||
- Add optional (configurable with setting) phone number validation (#820)
|
||||
- Add an option to deactivate the remove-all-data function for customers (#808)
|
||||
- Skip the first booking step if both provider and service are preselected (#1117)
|
||||
- Make delete appointment via API to send emails just like the calendar page does (#1101)
|
||||
- Create new layout structure for the markup, so that common HTML markup is being reused (#1152)
|
||||
- Have an option to hide customer data fields during booking (#1081)
|
||||
- Add a SECURITY.md file to the repository (#1122)
|
||||
- Add support for custom fields on customers (#1133)
|
||||
- Add from email/name and reply-to settings in the email.php configuration file (#1465)
|
||||
- Create a new setting that will define the default timezone of the application (#1390)
|
||||
- Integrate CalDAV Protocol for appointment syncing (#209)
|
||||
- Add LDAP / Active Directory integration (#128)
|
||||
|
||||
### Changed
|
||||
|
||||
- Do not allow a customer to book the same hours multiple times (#1420)
|
||||
- All the user roles with access to the backend calendar page can filter by services (#956)
|
||||
- Update Bootstrap to version 5 (#1150)
|
||||
- Update FullCalendar to version 5 (#1151)
|
||||
- The availability generation algorithm needs performance improvements when many appointments are stored in the system (#1171)
|
||||
- Support for relative paths when loading resources or working with the session (#1158)
|
||||
- Support line breaks when displaying the service description (#1149)
|
||||
- Remove the CodeIgniter fork from the composer.json file and re-import the system directory (#1109)
|
||||
|
||||
### Fixed
|
||||
|
||||
- Sync all the providers without errors, when the user clicks on the "sync" button and "all" is selected in the calendar page (#1365)
|
||||
- Non-working time not showing correctly in the week view of calendar (#1381)
|
||||
- Make sure the booking cancellation is a post request and has a reason value provided (#1178)
|
||||
|
||||
### Removed
|
||||
|
||||
- Remove the engine directory and files form the app (they're deprecated) (#971)
|
||||
- Remove the PHPMailer dependency from the app and use the built-in CodeIgniter mailer (#970)
|
||||
|
||||
|
||||
## [1.4.3] - 2022-03-08
|
||||
|
||||
### Added
|
||||
|
||||
- #1208: Security configuration enhancements in the application.
|
||||
- #1209: Add support for PHP 8.1.
|
||||
|
||||
### Changed
|
||||
|
||||
- #1207: Replace CodeIgniter with the system directory for smaller package size and more control over the framework.
|
||||
- #1210: Move the change language operation into a new public controller.
|
||||
- #1212: Remove the Google Calendar read-only scope use as it is not needed.
|
||||
- #1213: Switch to go-to-latest database migration configuration for simplicity.
|
||||
- #1216 Replace Google Client JS with the Google Calendar Template link in the book success page enhancement.
|
||||
|
||||
### Fixed
|
||||
|
||||
- #1211: The table calendar view breaks for secretaries and providers due to appointment and unavailability removals bug.
|
||||
- #1214: Provider and secretary users can only add unavailabilities for their authorized users bug.
|
||||
|
||||
## [1.4.2] - 2021-07-27
|
||||
|
||||
### Added
|
||||
|
||||
- #1004: Add support for line breaks when displaying the service description in the frontend.
|
||||
- #1040: Support all-day events while syncing with Google Calendar.
|
||||
|
||||
### Fixed
|
||||
|
||||
- #961: Timezone/UX issue: Wrong day is selected when timezone differs by -1 day.
|
||||
- #966: Secretaries are getting notification emails for providers that are not assigned to them.
|
||||
- #980: Missing Pacific (and potentially other) timezones.
|
||||
- #982: The Any-Provider option might lead to double bookings, if all the providers have the same number of appointments for the selected date.
|
||||
- #986: Managed to replicate appointment hash collisions.
|
||||
- #989: Fix Critical mistake resulting in wrong date
|
||||
- #990: The API availabilities controller throws an error when generating availability for services with multiple attendants.
|
||||
- #991: Available hours generated with the "Any Provider" option in the booking page, may use the information of a provider that is not assigned to the selected service.
|
||||
- #993: Add support for PHP8 (vendor packages need to be updated).
|
||||
- #1000: Small fix for the display of the delete button in table view.
|
||||
- #1011: Working plan exception - details pane shows incorrect details.
|
||||
- #1023: Backend calendar table events missing or duplicated.
|
||||
- #1026: The timepicker sliders do not work when using an iOS device.
|
||||
- #1029: Enhance SMTP functions of PHPMailer.
|
||||
- #1043: Unavailable events do not block time from services with multiple attendants.
|
||||
- #1046: Make sure that saving the modifications of a single break does not cancel any pending break edits.
|
||||
- #1068: Set minimum service duration field value to honor the value of EVENT_MINIMUM_DURATION.
|
||||
- #1073: Update PHPMailer dependencies.
|
||||
- #1074: In case of deletion of one appointment, system sends email to admins anyway even if they have email notifications disabled.
|
||||
- #1092: Javascript RangeError on appointment change causing disabled calendar dates.
|
||||
|
||||
## [1.4.1] - 2020-12-14
|
||||
|
||||
### Added
|
||||
|
||||
- #952: Add timezone support in the REST API, when managing users.
|
||||
- #955: Display confirmation modal when disabling a connected Google Calendar Sync.
|
||||
|
||||
### Fixed
|
||||
|
||||
- #945: Google Calendar sync throws an error with all day Google Calendar Events.
|
||||
- #946: Typo in JavaScript code leads to a broken calendar view, when loading unavailability events with note contents.
|
||||
- #948: Multiple attendant services may lead to double booking.
|
||||
- #950: Cannot create provider without services via the API, some values (other endpoints) are optional too.
|
||||
- #953: Current time indicator in fullcalendar is showing time in local timezone and not in the user selected timezone.
|
||||
- #954: The password must be provided via the API when creating new users.
|
||||
|
||||
## [1.4.0] - 2020-12-09
|
||||
|
||||
### Added
|
||||
|
||||
- #203: Appointment location / 12-hour format / sync notes and location in Google Calendar.
|
||||
- #221: Fixed/Improved sort breaks increasingly by hour within day.
|
||||
- #247: Add new system-wide setting for removing the "Any Provider" option of the booking page.
|
||||
- #251: Automatically populate the appointment end datetime in API.
|
||||
- #301: Automatically reload the backend calendar events.
|
||||
- #313: How to set the timezone from the user booking the appointment.
|
||||
- #365: Only allow appointments for a few weeks in advance.
|
||||
- #431: Add support for working plan exceptions.
|
||||
- #471: Add new system-wide setting that enable users to choose the first day of the week.
|
||||
- #496: Add pagination on every backend page in order to make filter requests faster.
|
||||
- #501: Integrate script for assets minification.
|
||||
- #502: Config::DEBUG value toggles the use of normal or minified asset files.
|
||||
- #546: Add appointment edit link in the backend customers page.
|
||||
- #550: Multi-Lang Front-End selection popup not working on mobile.
|
||||
- #551: Front-End booking calendar not syncing with business logic working plan.
|
||||
- #572: Ensure the database structure is compatible to at least MySQL 5.5.
|
||||
- #576: Appointment cancelled exception not showing properly.
|
||||
- #610: Token based authentication for the Rest API.
|
||||
- #648: Add a warning when customers delete their personal information.
|
||||
- #655: Creating an appointment requires user to enter their phone number enhancement.
|
||||
- #659: Automatically detect browser language enhancement.
|
||||
- #663: Language selector not working under legacy iOS (v.10.3.1).
|
||||
- #680: Generate new password in the generate_random_string function may create duplicate passwords, plus it is not secure enough.
|
||||
- #739: Enhance the table view mode by replacing the tables with fullcalendar instances.
|
||||
- #770: Store customer's language and use it with notifications or when the customer manages and existing appointment.
|
||||
- #889: Notify admins and secretaries on appointment changes.
|
||||
|
||||
### Changed
|
||||
|
||||
- #386: Service price should be optional.
|
||||
- #428: Enable book advance timeout values in days.
|
||||
- #568: Sort providers alphabetically in the booking page.
|
||||
- #745: Add appointment notes preview in the event popover.
|
||||
|
||||
### Fixed
|
||||
|
||||
- #171: Google calendar sync - wrong timezone for appointments.
|
||||
- #195: Fix Google calendar sync activation error (JavaScript).
|
||||
- #298: Provider availability issue when selecting the "Any Provider" option.
|
||||
- #396: Start and end time do not update correctly during calendar time selection on iPad (and other Safari based devices).
|
||||
- #447: Captcha error using docker (500 error).
|
||||
- #506: Working plan created in version v1.2.1 wrongly displayed in backend with version v1.3.1.
|
||||
- #507: Need to manually clean the cache when migrating from v1.2.1 to v1.3.1.
|
||||
- #541: Can't remove (empty) customer notes field.
|
||||
- #549: Querying appointments API endpoint with the q parmeter produces PHP warnings.
|
||||
- #557: App not connecting to MySQL with fresh docker run.
|
||||
- #562: Unavailability periods with length of more than 1 day are not handled correctly.
|
||||
- #563: Description field overflows with long text.
|
||||
- #600: Unable to select Language on mobile phones.
|
||||
- #611: Double replacement when using translation to other languages.
|
||||
- #664: Easy!Appointments v1.3.2 allows sensitive information disclosure (username and password hash).
|
||||
- #687: Errors when the provider modifies an appointment.
|
||||
- #705: The alert notification of the installation is not being displayed on error.
|
||||
- #757: Corrected display of datetimepickers when editing events.
|
||||
- #801: Invalid time duration during appointment registration could lead to DOS of the service.
|
||||
- #813: Hyperlinks are not being displayed correctly inside legal contents (they are escaped).
|
||||
- #839: Provider is missing on appointment modal opened after a click on the link sent with the provider email confirmation.
|
||||
- #840: Start/end datetime are not correctly initialized on Safari when the appointment modal is opened after a click in the backend calendar.
|
||||
- #883: Appointment date is wrongly changed to today in some case.
|
||||
- #903: Notification not working when creating/updating/deleting an appointment from the REST API.
|
||||
|
||||
## [1.3.2] - 2018-07-29
|
||||
|
||||
### Fixed
|
||||
|
||||
- #480: Make the app GDPR - new EU privacy regulations compliant.
|
||||
- #485: Make REST API search check with "q" parameter case insensitive.
|
||||
- #489: REST API response headers must use the Content-Type application/json value.
|
||||
- #500: Performance optimization in backend calendar page, after the user clicks the insert appointment button.
|
||||
- #510: Providers should not be able to create appointments for other providers in the backend calendar page.
|
||||
- #512: Only show appointments of the currently logged in provider.
|
||||
|
||||
## [1.3.1] - 2018-06-03
|
||||
|
||||
### Added
|
||||
|
||||
- #410: Time format from American style to European
|
||||
- #441: Added time format selection
|
||||
- #452: Provide more information when errors occur during the installation.
|
||||
|
||||
### Changed
|
||||
|
||||
- #494: French translation corrections/improvements.
|
||||
|
||||
### Fixed
|
||||
|
||||
- #433: Selected date when editing an appointment
|
||||
- #436: All days unavailable in agendaDay view
|
||||
- #438: Error on update process from 1.2.1 to 1.3.0
|
||||
- #440: Correct label text for customer phone number in back-end calendar event popup.
|
||||
- #453: Unavailable periods not taken into account when more than one customer
|
||||
- #455: French Spelling
|
||||
- #459: Aggregate Appointment API crashes when a break was added
|
||||
- #461: Invalid working plan parsing for foreign languages on day view of the default calendar view.
|
||||
- #475: Booking page date selection is broken with any_provider option selected.
|
||||
- #483: In backend, calendar for providers become unselectable if switched to calendar for service.
|
||||
- #491: Replace hardcoded string with translation in appointment details email template.
|
||||
- #495: Database migration fixes (from 1.2.1 to 1.3.x).
|
||||
- #497: Backend settings are not being displayed on page load when the user is not an admin.
|
||||
|
||||
## [1.3.0] - 2018-02-28
|
||||
|
||||
### Added
|
||||
|
||||
- #65: Insert new appointment by clicking directly on the calendar.
|
||||
- #122: Add customer email and phone number in the event popover of the backend/calendar page.
|
||||
- #152: Add support for American Time Format (AM/PM).
|
||||
- #176: Add Docker container for Easy!Appointments development.
|
||||
- #362: Add Arabic language translation.
|
||||
- #395: Add aggregates GET parameter in the appointments REST API resource.
|
||||
- #397: Allow PHP v7.1 compatibility.
|
||||
- #398: Send ICS files with customer/provider email confirmations.
|
||||
- #399: Integrate cache busting for assets into the app.
|
||||
- #402: Create simple "update" page.
|
||||
|
||||
### Changed
|
||||
|
||||
- #276: Update FullCalendar dependency.
|
||||
- #394: Corrections in the Bootstrap classes in view files.
|
||||
- #401: Replace loading spinner graphic with a newer one.
|
||||
- #403: All calendars will start with Sunday as the first day.
|
||||
|
||||
### Fixed
|
||||
|
||||
- #155: Appointment management modal is not updated after appointment duration resize.
|
||||
- #236: Duplicate availabilities with short service duration and unavailabilities ignorance.
|
||||
- #315: Calendar doesn't update when Attendants number changes.
|
||||
- #334: Use of session_start() function may cause issues as the default options are not being used.
|
||||
- #336: Deleting provider doesn't work in some languages.
|
||||
- #337: Full day appointment with multiple attendants are not being taken into concern during availabilities generation.
|
||||
- #342: Email notifications must honor the date format value.
|
||||
- #370: AJAX Error: SyntaxError: Unexpected token < in JSON at position 0
|
||||
|
||||
### Removed
|
||||
|
||||
- #400: Remove jscrollpane dependency.
|
||||
|
||||
### Deprecated
|
||||
|
||||
- The availabilities generation and AJAX endpoints will change with a future release.
|
||||
|
||||
## [1.2.1] - 2017-05-21
|
||||
|
||||
### Changed
|
||||
|
||||
- #241: Set default sessions save_path directory because many servers do not have this option set.
|
||||
|
||||
### Fixed
|
||||
|
||||
- #306: Back-end login does not work with PHP 7.1.
|
||||
|
||||
## [1.2.0] - 2016-11-09
|
||||
|
||||
### Added
|
||||
|
||||
- #24: Provide dedicated URL for separate provider/service bookings.
|
||||
- #45: Add secure flag to CI_SESSION when HTTPS is enabled.
|
||||
- #54: Default auto-complete for the appointment end time according to service duration.
|
||||
- #109: Accept multiple attendants in a single service session.
|
||||
- #180: Add login link to booking footer.
|
||||
- #182: Add new appointment dashboard view: Table Scheduler
|
||||
- #183: Display the appointments modal when the user clicks in an empty calendar slot.
|
||||
- #185: Enable fixed availabilities setting for services.
|
||||
- #174: Implementation of a REST API.
|
||||
- #175: The backend must be responsive as well.
|
||||
- #178: Load the application/config/email.php settings to PHPMailer instance.
|
||||
|
||||
### Changed
|
||||
|
||||
- #63: Update vendor files (CodeIgniter, FullCalendar, Bootstrap, jQuery ...)
|
||||
|
||||
### Fixed
|
||||
|
||||
- #173: JS Scrollbars do not work for backend/users entries.
|
||||
- #137: Invalid responsive behavior in frontend booking wizard (step #2 - appointment date & time).
|
||||
- #136: Loading spinner is not shown in during the installation.
|
||||
- #127: Links in header contain index.php
|
||||
- #22: Google Calendar Sync - Time Zone Issue
|
||||
|
||||
## [1.1.1] - 2016-02-14
|
||||
|
||||
### Fixed
|
||||
|
||||
- #116: Book advance timeout not taken into account for proposed appointments.
|
||||
- #118: Google Calendar and notification mail problem bug.
|
||||
- #120: Invalid appointment date set after editing an existing appointment.
|
||||
|
||||
## [1.1.0] 2016-01-24
|
||||
|
||||
### Added
|
||||
|
||||
- #14: Add Google Analytics tracking for the booking page.
|
||||
- #15: Add captcha to booking page.
|
||||
- #16: Responsive Frontend
|
||||
- #25: Add a disable customer mail notifications setting
|
||||
- #27: Support american time format within the app.
|
||||
- #31: Double booking when two users try to book the same appointment hour and at the same time.
|
||||
|
||||
### Changed
|
||||
|
||||
- #4: Raising more useful exceptions and enable error logging by default.
|
||||
- #13: Upgrade to Bootstrap 3.x.x.
|
||||
- #38: Renamed `configuration.php` file to `config.php` and changed the `SystemConfiguration` class to `Config`. This class will contain constants with the project configuration and will be statically used.
|
||||
- #39: Add latest translations to source code so that user can select them immediately.
|
||||
- #42: Place all external assets to "ext" directory.
|
||||
|
||||
### Removed
|
||||
|
||||
- #40: Removed `.htaccess` file and updated all the URLs with the `index.php` file so that mod_rewrite problems are eliminated.
|
||||
- #41: Removed `cancel.php` file. Frontend must use the `message.php` file for displaying simple messages to user.
|
||||
|
||||
### Fixed
|
||||
|
||||
- #6: Business Logic created is not getting assigned to service provider.
|
||||
- #10: Unable to use address tags in email address.
|
||||
- #18: Duration is not changing when adding a new appointment.
|
||||
- #21: Fix Easy!Appointments installation problems with AJAX requests.
|
||||
- #66: Trouble with breaks for providers.
|
||||
|
||||
## [1.0.0] - 2014-01-19
|
||||
|
||||
First Easy!Appointments release ever! ☺
|
40
Dockerfile
Normal file
40
Dockerfile
Normal file
@ -0,0 +1,40 @@
|
||||
# ======================
|
||||
# Etapa 1: Compilacion
|
||||
# ======================
|
||||
FROM php:8.1-cli AS builder
|
||||
|
||||
# Instalar herramientas
|
||||
RUN apt-get update && apt-get install -y \
|
||||
git curl unzip zip nodejs npm libzip-dev libpng-dev libjpeg-dev \
|
||||
libfreetype6-dev libonig-dev libxml2-dev
|
||||
|
||||
RUN docker-php-ext-install pdo pdo_mysql mbstring zip gd mysqli
|
||||
|
||||
# Instalar Composer
|
||||
RUN curl -sS https://getcomposer.org/installer | php -- --install-dir=/usr/local/bin --filename=composer
|
||||
|
||||
# Copiar el codigo fuente
|
||||
WORKDIR /app
|
||||
COPY . .
|
||||
|
||||
# Instalar dependencias PHP y JS + compilar
|
||||
RUN composer install --no-dev --optimize-autoloader
|
||||
RUN npm install && npm run build
|
||||
|
||||
# ======================
|
||||
# Etapa 2: Imagen final
|
||||
# ======================
|
||||
FROM php:8.1-apache
|
||||
|
||||
RUN apt-get update && apt-get install -y libzip-dev libpng-dev libjpeg-dev \
|
||||
libfreetype6-dev libonig-dev libxml2-dev && \
|
||||
docker-php-ext-install pdo pdo_mysql mbstring zip gd mysqli && \
|
||||
a2enmod rewrite
|
||||
|
||||
WORKDIR /var/www/html
|
||||
|
||||
# Copiar solo los archivos ya listos desde la etapa anterior
|
||||
COPY --from=builder /app /var/www/html
|
||||
|
||||
# Permisos adecuados
|
||||
RUN chown -R www-data:www-data /var/www/html && chmod -R 755 /var/www/html
|
674
LICENSE
Normal file
674
LICENSE
Normal file
@ -0,0 +1,674 @@
|
||||
GNU GENERAL PUBLIC LICENSE
|
||||
Version 3, 29 June 2007
|
||||
|
||||
Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>
|
||||
Everyone is permitted to copy and distribute verbatim copies
|
||||
of this license document, but changing it is not allowed.
|
||||
|
||||
Preamble
|
||||
|
||||
The GNU General Public License is a free, copyleft license for
|
||||
software and other kinds of works.
|
||||
|
||||
The licenses for most software and other practical works are designed
|
||||
to take away your freedom to share and change the works. By contrast,
|
||||
the GNU General Public License is intended to guarantee your freedom to
|
||||
share and change all versions of a program--to make sure it remains free
|
||||
software for all its users. We, the Free Software Foundation, use the
|
||||
GNU General Public License for most of our software; it applies also to
|
||||
any other work released this way by its authors. You can apply it to
|
||||
your programs, too.
|
||||
|
||||
When we speak of free software, we are referring to freedom, not
|
||||
price. Our General Public Licenses are designed to make sure that you
|
||||
have the freedom to distribute copies of free software (and charge for
|
||||
them if you wish), that you receive source code or can get it if you
|
||||
want it, that you can change the software or use pieces of it in new
|
||||
free programs, and that you know you can do these things.
|
||||
|
||||
To protect your rights, we need to prevent others from denying you
|
||||
these rights or asking you to surrender the rights. Therefore, you have
|
||||
certain responsibilities if you distribute copies of the software, or if
|
||||
you modify it: responsibilities to respect the freedom of others.
|
||||
|
||||
For example, if you distribute copies of such a program, whether
|
||||
gratis or for a fee, you must pass on to the recipients the same
|
||||
freedoms that you received. You must make sure that they, too, receive
|
||||
or can get the source code. And you must show them these terms so they
|
||||
know their rights.
|
||||
|
||||
Developers that use the GNU GPL protect your rights with two steps:
|
||||
(1) assert copyright on the software, and (2) offer you this License
|
||||
giving you legal permission to copy, distribute and/or modify it.
|
||||
|
||||
For the developers' and authors' protection, the GPL clearly explains
|
||||
that there is no warranty for this free software. For both users' and
|
||||
authors' sake, the GPL requires that modified versions be marked as
|
||||
changed, so that their problems will not be attributed erroneously to
|
||||
authors of previous versions.
|
||||
|
||||
Some devices are designed to deny users access to install or run
|
||||
modified versions of the software inside them, although the manufacturer
|
||||
can do so. This is fundamentally incompatible with the aim of
|
||||
protecting users' freedom to change the software. The systematic
|
||||
pattern of such abuse occurs in the area of products for individuals to
|
||||
use, which is precisely where it is most unacceptable. Therefore, we
|
||||
have designed this version of the GPL to prohibit the practice for those
|
||||
products. If such problems arise substantially in other domains, we
|
||||
stand ready to extend this provision to those domains in future versions
|
||||
of the GPL, as needed to protect the freedom of users.
|
||||
|
||||
Finally, every program is threatened constantly by software patents.
|
||||
States should not allow patents to restrict development and use of
|
||||
software on general-purpose computers, but in those that do, we wish to
|
||||
avoid the special danger that patents applied to a free program could
|
||||
make it effectively proprietary. To prevent this, the GPL assures that
|
||||
patents cannot be used to render the program non-free.
|
||||
|
||||
The precise terms and conditions for copying, distribution and
|
||||
modification follow.
|
||||
|
||||
TERMS AND CONDITIONS
|
||||
|
||||
0. Definitions.
|
||||
|
||||
"This License" refers to version 3 of the GNU General Public License.
|
||||
|
||||
"Copyright" also means copyright-like laws that apply to other kinds of
|
||||
works, such as semiconductor masks.
|
||||
|
||||
"The Program" refers to any copyrightable work licensed under this
|
||||
License. Each licensee is addressed as "you". "Licensees" and
|
||||
"recipients" may be individuals or organizations.
|
||||
|
||||
To "modify" a work means to copy from or adapt all or part of the work
|
||||
in a fashion requiring copyright permission, other than the making of an
|
||||
exact copy. The resulting work is called a "modified version" of the
|
||||
earlier work or a work "based on" the earlier work.
|
||||
|
||||
A "covered work" means either the unmodified Program or a work based
|
||||
on the Program.
|
||||
|
||||
To "propagate" a work means to do anything with it that, without
|
||||
permission, would make you directly or secondarily liable for
|
||||
infringement under applicable copyright law, except executing it on a
|
||||
computer or modifying a private copy. Propagation includes copying,
|
||||
distribution (with or without modification), making available to the
|
||||
public, and in some countries other activities as well.
|
||||
|
||||
To "convey" a work means any kind of propagation that enables other
|
||||
parties to make or receive copies. Mere interaction with a user through
|
||||
a computer network, with no transfer of a copy, is not conveying.
|
||||
|
||||
An interactive user interface displays "Appropriate Legal Notices"
|
||||
to the extent that it includes a convenient and prominently visible
|
||||
feature that (1) displays an appropriate copyright notice, and (2)
|
||||
tells the user that there is no warranty for the work (except to the
|
||||
extent that warranties are provided), that licensees may convey the
|
||||
work under this License, and how to view a copy of this License. If
|
||||
the interface presents a list of user commands or options, such as a
|
||||
menu, a prominent item in the list meets this criterion.
|
||||
|
||||
1. Source Code.
|
||||
|
||||
The "source code" for a work means the preferred form of the work
|
||||
for making modifications to it. "Object code" means any non-source
|
||||
form of a work.
|
||||
|
||||
A "Standard Interface" means an interface that either is an official
|
||||
standard defined by a recognized standards body, or, in the case of
|
||||
interfaces specified for a particular programming language, one that
|
||||
is widely used among developers working in that language.
|
||||
|
||||
The "System Libraries" of an executable work include anything, other
|
||||
than the work as a whole, that (a) is included in the normal form of
|
||||
packaging a Major Component, but which is not part of that Major
|
||||
Component, and (b) serves only to enable use of the work with that
|
||||
Major Component, or to implement a Standard Interface for which an
|
||||
implementation is available to the public in source code form. A
|
||||
"Major Component", in this context, means a major essential component
|
||||
(kernel, window system, and so on) of the specific operating system
|
||||
(if any) on which the executable work runs, or a compiler used to
|
||||
produce the work, or an object code interpreter used to run it.
|
||||
|
||||
The "Corresponding Source" for a work in object code form means all
|
||||
the source code needed to generate, install, and (for an executable
|
||||
work) run the object code and to modify the work, including scripts to
|
||||
control those activities. However, it does not include the work's
|
||||
System Libraries, or general-purpose tools or generally available free
|
||||
programs which are used unmodified in performing those activities but
|
||||
which are not part of the work. For example, Corresponding Source
|
||||
includes interface definition files associated with source files for
|
||||
the work, and the source code for shared libraries and dynamically
|
||||
linked subprograms that the work is specifically designed to require,
|
||||
such as by intimate data communication or control flow between those
|
||||
subprograms and other parts of the work.
|
||||
|
||||
The Corresponding Source need not include anything that users
|
||||
can regenerate automatically from other parts of the Corresponding
|
||||
Source.
|
||||
|
||||
The Corresponding Source for a work in source code form is that
|
||||
same work.
|
||||
|
||||
2. Basic Permissions.
|
||||
|
||||
All rights granted under this License are granted for the term of
|
||||
copyright on the Program, and are irrevocable provided the stated
|
||||
conditions are met. This License explicitly affirms your unlimited
|
||||
permission to run the unmodified Program. The output from running a
|
||||
covered work is covered by this License only if the output, given its
|
||||
content, constitutes a covered work. This License acknowledges your
|
||||
rights of fair use or other equivalent, as provided by copyright law.
|
||||
|
||||
You may make, run and propagate covered works that you do not
|
||||
convey, without conditions so long as your license otherwise remains
|
||||
in force. You may convey covered works to others for the sole purpose
|
||||
of having them make modifications exclusively for you, or provide you
|
||||
with facilities for running those works, provided that you comply with
|
||||
the terms of this License in conveying all material for which you do
|
||||
not control copyright. Those thus making or running the covered works
|
||||
for you must do so exclusively on your behalf, under your direction
|
||||
and control, on terms that prohibit them from making any copies of
|
||||
your copyrighted material outside their relationship with you.
|
||||
|
||||
Conveying under any other circumstances is permitted solely under
|
||||
the conditions stated below. Sublicensing is not allowed; section 10
|
||||
makes it unnecessary.
|
||||
|
||||
3. Protecting Users' Legal Rights From Anti-Circumvention Law.
|
||||
|
||||
No covered work shall be deemed part of an effective technological
|
||||
measure under any applicable law fulfilling obligations under article
|
||||
11 of the WIPO copyright treaty adopted on 20 December 1996, or
|
||||
similar laws prohibiting or restricting circumvention of such
|
||||
measures.
|
||||
|
||||
When you convey a covered work, you waive any legal power to forbid
|
||||
circumvention of technological measures to the extent such circumvention
|
||||
is effected by exercising rights under this License with respect to
|
||||
the covered work, and you disclaim any intention to limit operation or
|
||||
modification of the work as a means of enforcing, against the work's
|
||||
users, your or third parties' legal rights to forbid circumvention of
|
||||
technological measures.
|
||||
|
||||
4. Conveying Verbatim Copies.
|
||||
|
||||
You may convey verbatim copies of the Program's source code as you
|
||||
receive it, in any medium, provided that you conspicuously and
|
||||
appropriately publish on each copy an appropriate copyright notice;
|
||||
keep intact all notices stating that this License and any
|
||||
non-permissive terms added in accord with section 7 apply to the code;
|
||||
keep intact all notices of the absence of any warranty; and give all
|
||||
recipients a copy of this License along with the Program.
|
||||
|
||||
You may charge any price or no price for each copy that you convey,
|
||||
and you may offer support or warranty protection for a fee.
|
||||
|
||||
5. Conveying Modified Source Versions.
|
||||
|
||||
You may convey a work based on the Program, or the modifications to
|
||||
produce it from the Program, in the form of source code under the
|
||||
terms of section 4, provided that you also meet all of these conditions:
|
||||
|
||||
a) The work must carry prominent notices stating that you modified
|
||||
it, and giving a relevant date.
|
||||
|
||||
b) The work must carry prominent notices stating that it is
|
||||
released under this License and any conditions added under section
|
||||
7. This requirement modifies the requirement in section 4 to
|
||||
"keep intact all notices".
|
||||
|
||||
c) You must license the entire work, as a whole, under this
|
||||
License to anyone who comes into possession of a copy. This
|
||||
License will therefore apply, along with any applicable section 7
|
||||
additional terms, to the whole of the work, and all its parts,
|
||||
regardless of how they are packaged. This License gives no
|
||||
permission to license the work in any other way, but it does not
|
||||
invalidate such permission if you have separately received it.
|
||||
|
||||
d) If the work has interactive user interfaces, each must display
|
||||
Appropriate Legal Notices; however, if the Program has interactive
|
||||
interfaces that do not display Appropriate Legal Notices, your
|
||||
work need not make them do so.
|
||||
|
||||
A compilation of a covered work with other separate and independent
|
||||
works, which are not by their nature extensions of the covered work,
|
||||
and which are not combined with it such as to form a larger program,
|
||||
in or on a volume of a storage or distribution medium, is called an
|
||||
"aggregate" if the compilation and its resulting copyright are not
|
||||
used to limit the access or legal rights of the compilation's users
|
||||
beyond what the individual works permit. Inclusion of a covered work
|
||||
in an aggregate does not cause this License to apply to the other
|
||||
parts of the aggregate.
|
||||
|
||||
6. Conveying Non-Source Forms.
|
||||
|
||||
You may convey a covered work in object code form under the terms
|
||||
of sections 4 and 5, provided that you also convey the
|
||||
machine-readable Corresponding Source under the terms of this License,
|
||||
in one of these ways:
|
||||
|
||||
a) Convey the object code in, or embodied in, a physical product
|
||||
(including a physical distribution medium), accompanied by the
|
||||
Corresponding Source fixed on a durable physical medium
|
||||
customarily used for software interchange.
|
||||
|
||||
b) Convey the object code in, or embodied in, a physical product
|
||||
(including a physical distribution medium), accompanied by a
|
||||
written offer, valid for at least three years and valid for as
|
||||
long as you offer spare parts or customer support for that product
|
||||
model, to give anyone who possesses the object code either (1) a
|
||||
copy of the Corresponding Source for all the software in the
|
||||
product that is covered by this License, on a durable physical
|
||||
medium customarily used for software interchange, for a price no
|
||||
more than your reasonable cost of physically performing this
|
||||
conveying of source, or (2) access to copy the
|
||||
Corresponding Source from a network server at no charge.
|
||||
|
||||
c) Convey individual copies of the object code with a copy of the
|
||||
written offer to provide the Corresponding Source. This
|
||||
alternative is allowed only occasionally and noncommercially, and
|
||||
only if you received the object code with such an offer, in accord
|
||||
with subsection 6b.
|
||||
|
||||
d) Convey the object code by offering access from a designated
|
||||
place (gratis or for a charge), and offer equivalent access to the
|
||||
Corresponding Source in the same way through the same place at no
|
||||
further charge. You need not require recipients to copy the
|
||||
Corresponding Source along with the object code. If the place to
|
||||
copy the object code is a network server, the Corresponding Source
|
||||
may be on a different server (operated by you or a third party)
|
||||
that supports equivalent copying facilities, provided you maintain
|
||||
clear directions next to the object code saying where to find the
|
||||
Corresponding Source. Regardless of what server hosts the
|
||||
Corresponding Source, you remain obligated to ensure that it is
|
||||
available for as long as needed to satisfy these requirements.
|
||||
|
||||
e) Convey the object code using peer-to-peer transmission, provided
|
||||
you inform other peers where the object code and Corresponding
|
||||
Source of the work are being offered to the general public at no
|
||||
charge under subsection 6d.
|
||||
|
||||
A separable portion of the object code, whose source code is excluded
|
||||
from the Corresponding Source as a System Library, need not be
|
||||
included in conveying the object code work.
|
||||
|
||||
A "User Product" is either (1) a "consumer product", which means any
|
||||
tangible personal property which is normally used for personal, family,
|
||||
or household purposes, or (2) anything designed or sold for incorporation
|
||||
into a dwelling. In determining whether a product is a consumer product,
|
||||
doubtful cases shall be resolved in favor of coverage. For a particular
|
||||
product received by a particular user, "normally used" refers to a
|
||||
typical or common use of that class of product, regardless of the status
|
||||
of the particular user or of the way in which the particular user
|
||||
actually uses, or expects or is expected to use, the product. A product
|
||||
is a consumer product regardless of whether the product has substantial
|
||||
commercial, industrial or non-consumer uses, unless such uses represent
|
||||
the only significant mode of use of the product.
|
||||
|
||||
"Installation Information" for a User Product means any methods,
|
||||
procedures, authorization keys, or other information required to install
|
||||
and execute modified versions of a covered work in that User Product from
|
||||
a modified version of its Corresponding Source. The information must
|
||||
suffice to ensure that the continued functioning of the modified object
|
||||
code is in no case prevented or interfered with solely because
|
||||
modification has been made.
|
||||
|
||||
If you convey an object code work under this section in, or with, or
|
||||
specifically for use in, a User Product, and the conveying occurs as
|
||||
part of a transaction in which the right of possession and use of the
|
||||
User Product is transferred to the recipient in perpetuity or for a
|
||||
fixed term (regardless of how the transaction is characterized), the
|
||||
Corresponding Source conveyed under this section must be accompanied
|
||||
by the Installation Information. But this requirement does not apply
|
||||
if neither you nor any third party retains the ability to install
|
||||
modified object code on the User Product (for example, the work has
|
||||
been installed in ROM).
|
||||
|
||||
The requirement to provide Installation Information does not include a
|
||||
requirement to continue to provide support service, warranty, or updates
|
||||
for a work that has been modified or installed by the recipient, or for
|
||||
the User Product in which it has been modified or installed. Access to a
|
||||
network may be denied when the modification itself materially and
|
||||
adversely affects the operation of the network or violates the rules and
|
||||
protocols for communication across the network.
|
||||
|
||||
Corresponding Source conveyed, and Installation Information provided,
|
||||
in accord with this section must be in a format that is publicly
|
||||
documented (and with an implementation available to the public in
|
||||
source code form), and must require no special password or key for
|
||||
unpacking, reading or copying.
|
||||
|
||||
7. Additional Terms.
|
||||
|
||||
"Additional permissions" are terms that supplement the terms of this
|
||||
License by making exceptions from one or more of its conditions.
|
||||
Additional permissions that are applicable to the entire Program shall
|
||||
be treated as though they were included in this License, to the extent
|
||||
that they are valid under applicable law. If additional permissions
|
||||
apply only to part of the Program, that part may be used separately
|
||||
under those permissions, but the entire Program remains governed by
|
||||
this License without regard to the additional permissions.
|
||||
|
||||
When you convey a copy of a covered work, you may at your option
|
||||
remove any additional permissions from that copy, or from any part of
|
||||
it. (Additional permissions may be written to require their own
|
||||
removal in certain cases when you modify the work.) You may place
|
||||
additional permissions on material, added by you to a covered work,
|
||||
for which you have or can give appropriate copyright permission.
|
||||
|
||||
Notwithstanding any other provision of this License, for material you
|
||||
add to a covered work, you may (if authorized by the copyright holders of
|
||||
that material) supplement the terms of this License with terms:
|
||||
|
||||
a) Disclaiming warranty or limiting liability differently from the
|
||||
terms of sections 15 and 16 of this License; or
|
||||
|
||||
b) Requiring preservation of specified reasonable legal notices or
|
||||
author attributions in that material or in the Appropriate Legal
|
||||
Notices displayed by works containing it; or
|
||||
|
||||
c) Prohibiting misrepresentation of the origin of that material, or
|
||||
requiring that modified versions of such material be marked in
|
||||
reasonable ways as different from the original version; or
|
||||
|
||||
d) Limiting the use for publicity purposes of names of licensors or
|
||||
authors of the material; or
|
||||
|
||||
e) Declining to grant rights under trademark law for use of some
|
||||
trade names, trademarks, or service marks; or
|
||||
|
||||
f) Requiring indemnification of licensors and authors of that
|
||||
material by anyone who conveys the material (or modified versions of
|
||||
it) with contractual assumptions of liability to the recipient, for
|
||||
any liability that these contractual assumptions directly impose on
|
||||
those licensors and authors.
|
||||
|
||||
All other non-permissive additional terms are considered "further
|
||||
restrictions" within the meaning of section 10. If the Program as you
|
||||
received it, or any part of it, contains a notice stating that it is
|
||||
governed by this License along with a term that is a further
|
||||
restriction, you may remove that term. If a license document contains
|
||||
a further restriction but permits relicensing or conveying under this
|
||||
License, you may add to a covered work material governed by the terms
|
||||
of that license document, provided that the further restriction does
|
||||
not survive such relicensing or conveying.
|
||||
|
||||
If you add terms to a covered work in accord with this section, you
|
||||
must place, in the relevant source files, a statement of the
|
||||
additional terms that apply to those files, or a notice indicating
|
||||
where to find the applicable terms.
|
||||
|
||||
Additional terms, permissive or non-permissive, may be stated in the
|
||||
form of a separately written license, or stated as exceptions;
|
||||
the above requirements apply either way.
|
||||
|
||||
8. Termination.
|
||||
|
||||
You may not propagate or modify a covered work except as expressly
|
||||
provided under this License. Any attempt otherwise to propagate or
|
||||
modify it is void, and will automatically terminate your rights under
|
||||
this License (including any patent licenses granted under the third
|
||||
paragraph of section 11).
|
||||
|
||||
However, if you cease all violation of this License, then your
|
||||
license from a particular copyright holder is reinstated (a)
|
||||
provisionally, unless and until the copyright holder explicitly and
|
||||
finally terminates your license, and (b) permanently, if the copyright
|
||||
holder fails to notify you of the violation by some reasonable means
|
||||
prior to 60 days after the cessation.
|
||||
|
||||
Moreover, your license from a particular copyright holder is
|
||||
reinstated permanently if the copyright holder notifies you of the
|
||||
violation by some reasonable means, this is the first time you have
|
||||
received notice of violation of this License (for any work) from that
|
||||
copyright holder, and you cure the violation prior to 30 days after
|
||||
your receipt of the notice.
|
||||
|
||||
Termination of your rights under this section does not terminate the
|
||||
licenses of parties who have received copies or rights from you under
|
||||
this License. If your rights have been terminated and not permanently
|
||||
reinstated, you do not qualify to receive new licenses for the same
|
||||
material under section 10.
|
||||
|
||||
9. Acceptance Not Required for Having Copies.
|
||||
|
||||
You are not required to accept this License in order to receive or
|
||||
run a copy of the Program. Ancillary propagation of a covered work
|
||||
occurring solely as a consequence of using peer-to-peer transmission
|
||||
to receive a copy likewise does not require acceptance. However,
|
||||
nothing other than this License grants you permission to propagate or
|
||||
modify any covered work. These actions infringe copyright if you do
|
||||
not accept this License. Therefore, by modifying or propagating a
|
||||
covered work, you indicate your acceptance of this License to do so.
|
||||
|
||||
10. Automatic Licensing of Downstream Recipients.
|
||||
|
||||
Each time you convey a covered work, the recipient automatically
|
||||
receives a license from the original licensors, to run, modify and
|
||||
propagate that work, subject to this License. You are not responsible
|
||||
for enforcing compliance by third parties with this License.
|
||||
|
||||
An "entity transaction" is a transaction transferring control of an
|
||||
organization, or substantially all assets of one, or subdividing an
|
||||
organization, or merging organizations. If propagation of a covered
|
||||
work results from an entity transaction, each party to that
|
||||
transaction who receives a copy of the work also receives whatever
|
||||
licenses to the work the party's predecessor in interest had or could
|
||||
give under the previous paragraph, plus a right to possession of the
|
||||
Corresponding Source of the work from the predecessor in interest, if
|
||||
the predecessor has it or can get it with reasonable efforts.
|
||||
|
||||
You may not impose any further restrictions on the exercise of the
|
||||
rights granted or affirmed under this License. For example, you may
|
||||
not impose a license fee, royalty, or other charge for exercise of
|
||||
rights granted under this License, and you may not initiate litigation
|
||||
(including a cross-claim or counterclaim in a lawsuit) alleging that
|
||||
any patent claim is infringed by making, using, selling, offering for
|
||||
sale, or importing the Program or any portion of it.
|
||||
|
||||
11. Patents.
|
||||
|
||||
A "contributor" is a copyright holder who authorizes use under this
|
||||
License of the Program or a work on which the Program is based. The
|
||||
work thus licensed is called the contributor's "contributor version".
|
||||
|
||||
A contributor's "essential patent claims" are all patent claims
|
||||
owned or controlled by the contributor, whether already acquired or
|
||||
hereafter acquired, that would be infringed by some manner, permitted
|
||||
by this License, of making, using, or selling its contributor version,
|
||||
but do not include claims that would be infringed only as a
|
||||
consequence of further modification of the contributor version. For
|
||||
purposes of this definition, "control" includes the right to grant
|
||||
patent sublicenses in a manner consistent with the requirements of
|
||||
this License.
|
||||
|
||||
Each contributor grants you a non-exclusive, worldwide, royalty-free
|
||||
patent license under the contributor's essential patent claims, to
|
||||
make, use, sell, offer for sale, import and otherwise run, modify and
|
||||
propagate the contents of its contributor version.
|
||||
|
||||
In the following three paragraphs, a "patent license" is any express
|
||||
agreement or commitment, however denominated, not to enforce a patent
|
||||
(such as an express permission to practice a patent or covenant not to
|
||||
sue for patent infringement). To "grant" such a patent license to a
|
||||
party means to make such an agreement or commitment not to enforce a
|
||||
patent against the party.
|
||||
|
||||
If you convey a covered work, knowingly relying on a patent license,
|
||||
and the Corresponding Source of the work is not available for anyone
|
||||
to copy, free of charge and under the terms of this License, through a
|
||||
publicly available network server or other readily accessible means,
|
||||
then you must either (1) cause the Corresponding Source to be so
|
||||
available, or (2) arrange to deprive yourself of the benefit of the
|
||||
patent license for this particular work, or (3) arrange, in a manner
|
||||
consistent with the requirements of this License, to extend the patent
|
||||
license to downstream recipients. "Knowingly relying" means you have
|
||||
actual knowledge that, but for the patent license, your conveying the
|
||||
covered work in a country, or your recipient's use of the covered work
|
||||
in a country, would infringe one or more identifiable patents in that
|
||||
country that you have reason to believe are valid.
|
||||
|
||||
If, pursuant to or in connection with a single transaction or
|
||||
arrangement, you convey, or propagate by procuring conveyance of, a
|
||||
covered work, and grant a patent license to some of the parties
|
||||
receiving the covered work authorizing them to use, propagate, modify
|
||||
or convey a specific copy of the covered work, then the patent license
|
||||
you grant is automatically extended to all recipients of the covered
|
||||
work and works based on it.
|
||||
|
||||
A patent license is "discriminatory" if it does not include within
|
||||
the scope of its coverage, prohibits the exercise of, or is
|
||||
conditioned on the non-exercise of one or more of the rights that are
|
||||
specifically granted under this License. You may not convey a covered
|
||||
work if you are a party to an arrangement with a third party that is
|
||||
in the business of distributing software, under which you make payment
|
||||
to the third party based on the extent of your activity of conveying
|
||||
the work, and under which the third party grants, to any of the
|
||||
parties who would receive the covered work from you, a discriminatory
|
||||
patent license (a) in connection with copies of the covered work
|
||||
conveyed by you (or copies made from those copies), or (b) primarily
|
||||
for and in connection with specific products or compilations that
|
||||
contain the covered work, unless you entered into that arrangement,
|
||||
or that patent license was granted, prior to 28 March 2007.
|
||||
|
||||
Nothing in this License shall be construed as excluding or limiting
|
||||
any implied license or other defenses to infringement that may
|
||||
otherwise be available to you under applicable patent law.
|
||||
|
||||
12. No Surrender of Others' Freedom.
|
||||
|
||||
If conditions are imposed on you (whether by court order, agreement or
|
||||
otherwise) that contradict the conditions of this License, they do not
|
||||
excuse you from the conditions of this License. If you cannot convey a
|
||||
covered work so as to satisfy simultaneously your obligations under this
|
||||
License and any other pertinent obligations, then as a consequence you may
|
||||
not convey it at all. For example, if you agree to terms that obligate you
|
||||
to collect a royalty for further conveying from those to whom you convey
|
||||
the Program, the only way you could satisfy both those terms and this
|
||||
License would be to refrain entirely from conveying the Program.
|
||||
|
||||
13. Use with the GNU Affero General Public License.
|
||||
|
||||
Notwithstanding any other provision of this License, you have
|
||||
permission to link or combine any covered work with a work licensed
|
||||
under version 3 of the GNU Affero General Public License into a single
|
||||
combined work, and to convey the resulting work. The terms of this
|
||||
License will continue to apply to the part which is the covered work,
|
||||
but the special requirements of the GNU Affero General Public License,
|
||||
section 13, concerning interaction through a network will apply to the
|
||||
combination as such.
|
||||
|
||||
14. Revised Versions of this License.
|
||||
|
||||
The Free Software Foundation may publish revised and/or new versions of
|
||||
the GNU General Public License from time to time. Such new versions will
|
||||
be similar in spirit to the present version, but may differ in detail to
|
||||
address new problems or concerns.
|
||||
|
||||
Each version is given a distinguishing version number. If the
|
||||
Program specifies that a certain numbered version of the GNU General
|
||||
Public License "or any later version" applies to it, you have the
|
||||
option of following the terms and conditions either of that numbered
|
||||
version or of any later version published by the Free Software
|
||||
Foundation. If the Program does not specify a version number of the
|
||||
GNU General Public License, you may choose any version ever published
|
||||
by the Free Software Foundation.
|
||||
|
||||
If the Program specifies that a proxy can decide which future
|
||||
versions of the GNU General Public License can be used, that proxy's
|
||||
public statement of acceptance of a version permanently authorizes you
|
||||
to choose that version for the Program.
|
||||
|
||||
Later license versions may give you additional or different
|
||||
permissions. However, no additional obligations are imposed on any
|
||||
author or copyright holder as a result of your choosing to follow a
|
||||
later version.
|
||||
|
||||
15. Disclaimer of Warranty.
|
||||
|
||||
THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
|
||||
APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
|
||||
HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
|
||||
OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
|
||||
THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
|
||||
PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
|
||||
IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
|
||||
ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
|
||||
|
||||
16. Limitation of Liability.
|
||||
|
||||
IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
|
||||
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
|
||||
THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
|
||||
GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
|
||||
USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
|
||||
DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
|
||||
PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
|
||||
EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
|
||||
SUCH DAMAGES.
|
||||
|
||||
17. Interpretation of Sections 15 and 16.
|
||||
|
||||
If the disclaimer of warranty and limitation of liability provided
|
||||
above cannot be given local legal effect according to their terms,
|
||||
reviewing courts shall apply local law that most closely approximates
|
||||
an absolute waiver of all civil liability in connection with the
|
||||
Program, unless a warranty or assumption of liability accompanies a
|
||||
copy of the Program in return for a fee.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
How to Apply These Terms to Your New Programs
|
||||
|
||||
If you develop a new program, and you want it to be of the greatest
|
||||
possible use to the public, the best way to achieve this is to make it
|
||||
free software which everyone can redistribute and change under these terms.
|
||||
|
||||
To do so, attach the following notices to the program. It is safest
|
||||
to attach them to the start of each source file to most effectively
|
||||
state the exclusion of warranty; and each file should have at least
|
||||
the "copyright" line and a pointer to where the full notice is found.
|
||||
|
||||
{one line to give the program's name and a brief idea of what it does.}
|
||||
Copyright (C) {year} {name of author}
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
Also add information on how to contact you by electronic and paper mail.
|
||||
|
||||
If the program does terminal interaction, make it output a short
|
||||
notice like this when it starts in an interactive mode:
|
||||
|
||||
{project} Copyright (C) {year} {fullname}
|
||||
This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
|
||||
This is free software, and you are welcome to redistribute it
|
||||
under certain conditions; type `show c' for details.
|
||||
|
||||
The hypothetical commands `show w' and `show c' should show the appropriate
|
||||
parts of the General Public License. Of course, your program's commands
|
||||
might be different; for a GUI interface, you would use an "about box".
|
||||
|
||||
You should also get your employer (if you work as a programmer) or school,
|
||||
if any, to sign a "copyright disclaimer" for the program, if necessary.
|
||||
For more information on this, and how to apply and follow the GNU GPL, see
|
||||
<http://www.gnu.org/licenses/>.
|
||||
|
||||
The GNU General Public License does not permit incorporating your program
|
||||
into proprietary programs. If your program is a subroutine library, you
|
||||
may consider it more useful to permit linking proprietary applications with
|
||||
the library. If this is what you want to do, use the GNU Lesser General
|
||||
Public License instead of this License. But first, please read
|
||||
<http://www.gnu.org/philosophy/why-not-lgpl.html>.
|
111
README.md
Normal file
111
README.md
Normal file
@ -0,0 +1,111 @@
|
||||
<h1 align="center">
|
||||
<br>
|
||||
<a href="https://easyappointments.org">
|
||||
<img src="https://raw.githubusercontent.com/alextselegidis/easyappointments/develop/logo.png" alt="Easy!Appointments" width="150">
|
||||
</a>
|
||||
<br>
|
||||
Easy!Appointments
|
||||
<br>
|
||||
</h1>
|
||||
|
||||
<br>
|
||||
|
||||
<h4 align="center">
|
||||
A powerful Open Source Appointment Scheduler that can be installed on your server.
|
||||
</h4>
|
||||
|
||||
<p align="center">
|
||||
<img alt="GitHub" src="https://img.shields.io/github/license/alextselegidis/easyappointments?style=for-the-badge">
|
||||
<img alt="GitHub release (latest by date)" src="https://img.shields.io/github/v/release/alextselegidis/easyappointments?style=for-the-badge">
|
||||
<img alt="GitHub All Releases" src="https://img.shields.io/github/downloads/alextselegidis/easyappointments/total?style=for-the-badge">
|
||||
<a href="https://discord.com/invite/UeeSkaw">
|
||||
<img alt="Chat On Discord" src="https://img.shields.io/badge/chat-on%20discord-7289da?style=for-the-badge&logo=discord&logoColor=white">
|
||||
</a>
|
||||
</p>
|
||||
|
||||
<p align="center">
|
||||
<a href="#about">About</a> •
|
||||
<a href="#features">Features</a> •
|
||||
<a href="#setup">Setup</a> •
|
||||
<a href="#installation">Installation</a> •
|
||||
<a href="#license">License</a>
|
||||
</p>
|
||||
|
||||

|
||||
|
||||
## About
|
||||
|
||||
**Easy!Appointments** is a highly customizable web application that allows customers to book appointments with you
|
||||
via a sophisticated web interface. Moreover, it provides the ability to sync your data with Google Calendar so you can
|
||||
use them with other services. It is an open source project that you can download and install **even for commercial use**.
|
||||
Easy!Appointments will run smoothly with your existing website as it can be installed in a single folder of the
|
||||
server and of course share an existing database.
|
||||
|
||||
## Features
|
||||
|
||||
The application is designed to be flexible enough so that it can handle any enterprise work flow.
|
||||
|
||||
* Customers and appointments management.
|
||||
* Services and providers organization.
|
||||
* Working plan and booking rules.
|
||||
* Google Calendar synchronization.
|
||||
* Email notifications system.
|
||||
* Self hosted installation.
|
||||
* Translated user interface.
|
||||
* User community support.
|
||||
|
||||
## Setup
|
||||
|
||||
To clone and run this application, you'll need [Git](https://git-scm.com), [Node.js](https://nodejs.org/en/download/) (which comes with [npm](http://npmjs.com)) and [Composer](https://getcomposer.org) installed on your computer. From your command line:
|
||||
|
||||
```bash
|
||||
# Clone this repository
|
||||
$ git clone https://github.com/alextselegidis/easyappointments.git
|
||||
|
||||
# Go into the repository
|
||||
$ cd easyappointments
|
||||
|
||||
# Install dependencies
|
||||
$ npm install && composer install
|
||||
|
||||
# Start the file watcher
|
||||
$ npm start
|
||||
```
|
||||
|
||||
Note: If you're using Linux Bash for Windows, [see this guide](https://www.howtogeek.com/261575/how-to-run-graphical-linux-desktop-applications-from-windows-10s-bash-shell/) or use `node` from the command prompt.
|
||||
|
||||
You can build the files by running `npm run build`. This command will bundle everything to a `build` directory.
|
||||
|
||||
## Installation
|
||||
|
||||
You will need to perform the following steps to install the application on your server:
|
||||
|
||||
* Make sure that your server has Apache/Nginx, PHP (8.2+) and MySQL installed.
|
||||
* Create a new database (or use an existing one).
|
||||
* Copy the "easyappointments" source folder on your server.
|
||||
* Make sure that the "storage" directory is writable.
|
||||
* Rename the "config-sample.php" file to "config.php" and update its contents based on your environment.
|
||||
* Open the browser on the Easy!Appointments URL and follow the installation guide.
|
||||
|
||||
That's it! You can now use Easy!Appointments at your will.
|
||||
|
||||
You will find the latest release at [easyappointments.org](https://easyappointments.org).
|
||||
If you have problems installing or configuring the application visit the
|
||||
[official support group](https://groups.google.com/forum/#!forum/easy-appointments).
|
||||
You can also report problems on the [issues page](https://github.com/alextselegidis/easyappointments/issues)
|
||||
and help the development progress.
|
||||
|
||||
## License
|
||||
|
||||
Code Licensed Under [GPL v3.0](https://www.gnu.org/licenses/gpl-3.0.en.html) | Content Under [CC BY 3.0](https://creativecommons.org/licenses/by/3.0/)
|
||||
|
||||
---
|
||||
|
||||
Website [alextselegidis.com](https://alextselegidis.com) ·
|
||||
GitHub [alextselegidis](https://github.com/alextselegidis) ·
|
||||
Twitter [@alextselegidis](https://twitter.com/AlexTselegidis)
|
||||
|
||||
###### More Projects On Github
|
||||
###### ⇾ [Plainpad · Self Hosted Note Taking App](https://github.com/alextselegidis/plainpad)
|
||||
###### ⇾ [Questionful · Web Questionnaires Made Easy](https://github.com/alextselegidis/questionful)
|
||||
###### ⇾ [Integravy · Service Orchestration At Your Fingertips](https://github.com/alextselegidis/integravy)
|
1
application/.htaccess
Normal file
1
application/.htaccess
Normal file
@ -0,0 +1 @@
|
||||
Deny from all
|
18
application/config/app.php
Normal file
18
application/config/app.php
Normal file
@ -0,0 +1,18 @@
|
||||
<?php defined('BASEPATH') or exit('No direct script access allowed');
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| App Configuration
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| Declare some of the global config values of Easy!Appointments.
|
||||
|
|
||||
*/
|
||||
|
||||
$config['version'] = '1.5.1'; // This must be changed manually.
|
||||
|
||||
$config['url'] = Config::BASE_URL;
|
||||
|
||||
$config['debug'] = Config::DEBUG_MODE;
|
||||
|
||||
$config['cache_busting_token'] = 'TSJ77';
|
133
application/config/autoload.php
Normal file
133
application/config/autoload.php
Normal file
@ -0,0 +1,133 @@
|
||||
<?php defined('BASEPATH') or exit('No direct script access allowed');
|
||||
|
||||
/*
|
||||
| -------------------------------------------------------------------
|
||||
| AUTO-LOADER
|
||||
| -------------------------------------------------------------------
|
||||
| This file specifies which systems should be loaded by default.
|
||||
|
|
||||
| In order to keep the framework as light-weight as possible only the
|
||||
| absolute minimal resources are loaded by default. For example,
|
||||
| the database is not connected to automatically since no assumption
|
||||
| is made regarding whether you intend to use it. This file lets
|
||||
| you globally define which systems you would like loaded with every
|
||||
| request.
|
||||
|
|
||||
| -------------------------------------------------------------------
|
||||
| Instructions
|
||||
| -------------------------------------------------------------------
|
||||
|
|
||||
| These are the things you can load automatically:
|
||||
|
|
||||
| 1. Packages
|
||||
| 2. Libraries
|
||||
| 3. Helper files
|
||||
| 4. Custom config files
|
||||
| 5. Language files
|
||||
| 6. Models
|
||||
|
|
||||
*/
|
||||
|
||||
/*
|
||||
| -------------------------------------------------------------------
|
||||
| Auto-load Packages
|
||||
| -------------------------------------------------------------------
|
||||
| Prototype:
|
||||
|
|
||||
| $autoload['packages'] = array(APPPATH.'third_party', '/usr/local/shared');
|
||||
|
|
||||
*/
|
||||
|
||||
$autoload['packages'] = [];
|
||||
|
||||
/*
|
||||
| -------------------------------------------------------------------
|
||||
| Auto-load Libraries
|
||||
| -------------------------------------------------------------------
|
||||
| These are the classes located in the system/libraries folder
|
||||
| or in your application/libraries folder.
|
||||
|
|
||||
| Prototype:
|
||||
|
|
||||
| $autoload['libraries'] = array('database', 'session', 'xmlrpc');
|
||||
*/
|
||||
|
||||
$autoload['libraries'] = ['database', 'session'];
|
||||
|
||||
/*
|
||||
| -------------------------------------------------------------------
|
||||
| Auto-load Helper Files
|
||||
| -------------------------------------------------------------------
|
||||
| Prototype:
|
||||
|
|
||||
| $autoload['helper'] = array('url', 'file');
|
||||
*/
|
||||
|
||||
$autoload['helper'] = [
|
||||
'array',
|
||||
'asset',
|
||||
'config',
|
||||
'date',
|
||||
'debug',
|
||||
'env',
|
||||
'file',
|
||||
'html',
|
||||
'http',
|
||||
'installation',
|
||||
'language',
|
||||
'password',
|
||||
'path',
|
||||
'permission',
|
||||
'rate_limit',
|
||||
'routes',
|
||||
'session',
|
||||
'setting',
|
||||
'string',
|
||||
'url',
|
||||
'validation'
|
||||
];
|
||||
|
||||
/*
|
||||
| -------------------------------------------------------------------
|
||||
| Auto-load Config files
|
||||
| -------------------------------------------------------------------
|
||||
| Prototype:
|
||||
|
|
||||
| $autoload['config'] = array('config1', 'config2');
|
||||
|
|
||||
| NOTE: This item is intended for use ONLY if you have created custom
|
||||
| config files. Otherwise, leave it blank.
|
||||
|
|
||||
*/
|
||||
|
||||
$autoload['config'] = ['app', 'google', 'email'];
|
||||
|
||||
/*
|
||||
| -------------------------------------------------------------------
|
||||
| Auto-load Language files
|
||||
| -------------------------------------------------------------------
|
||||
| Prototype:
|
||||
|
|
||||
| $autoload['language'] = array('lang1', 'lang2');
|
||||
|
|
||||
| NOTE: Do not include the "_lang" part of your file. For example
|
||||
| "codeigniter_lang.php" would be referenced as array('codeigniter');
|
||||
|
|
||||
*/
|
||||
|
||||
$autoload['language'] = [];
|
||||
|
||||
/*
|
||||
| -------------------------------------------------------------------
|
||||
| Auto-load Models
|
||||
| -------------------------------------------------------------------
|
||||
| Prototype:
|
||||
|
|
||||
| $autoload['model'] = array('model1', 'model2');
|
||||
|
|
||||
*/
|
||||
|
||||
$autoload['model'] = [];
|
||||
|
||||
/* End of file autoload.php */
|
||||
/* Location: ./application/config/autoload.php */
|
476
application/config/config.php
Normal file
476
application/config/config.php
Normal file
@ -0,0 +1,476 @@
|
||||
<?php defined('BASEPATH') or exit('No direct script access allowed');
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Base Site URL
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| URL to your CodeIgniter root. Typically this will be your base URL,
|
||||
| WITH a trailing slash:
|
||||
|
|
||||
| http://example.com/
|
||||
|
|
||||
| If this is not set then CodeIgniter will guess the protocol, domain and
|
||||
| path to your installation.
|
||||
|
|
||||
*/
|
||||
|
||||
$protocol =
|
||||
(isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] === 'on') ||
|
||||
(isset($_SERVER['SERVER_PORT']) && (int) $_SERVER['SERVER_PORT'] === 443) ||
|
||||
(isset($_SERVER['HTTP_X_FORWARDED_PROTO']) && $_SERVER['HTTP_X_FORWARDED_PROTO'] === 'https')
|
||||
? 'https://'
|
||||
: 'http://';
|
||||
|
||||
$domain = $_SERVER['HTTP_HOST'] ?? 'localhost';
|
||||
|
||||
$request_uri = dirname($_SERVER['SCRIPT_NAME'] ?? 'index.php');
|
||||
|
||||
if ($request_uri === '.') {
|
||||
$request_uri = '';
|
||||
}
|
||||
|
||||
$config['base_url'] = !is_cli() ? trim($protocol . $domain . $request_uri, '/') : Config::BASE_URL;
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Index File
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| Typically this will be your index.php file, unless you've renamed it to
|
||||
| something else. If you are using mod_rewrite to remove the page set this
|
||||
| variable so that it is blank.
|
||||
|
|
||||
*/
|
||||
$config['index_page'] = 'index.php';
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| URI PROTOCOL
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| This item determines which server global should be used to retrieve the
|
||||
| URI string. The default setting of 'AUTO' works for most servers.
|
||||
| If your links do not seem to work, try one of the other delicious flavors:
|
||||
|
|
||||
| 'AUTO' Default - auto-detects
|
||||
| 'PATH_INFO' Uses the PATH_INFO
|
||||
| 'QUERY_STRING' Uses the QUERY_STRING
|
||||
| 'REQUEST_URI' Uses the REQUEST_URI
|
||||
| 'ORIG_PATH_INFO' Uses the ORIG_PATH_INFO
|
||||
|
|
||||
*/
|
||||
$config['uri_protocol'] = 'AUTO';
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| URL suffix
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| This option allows you to add a suffix to all URLs generated by CodeIgniter.
|
||||
| For more information please see the user guide:
|
||||
|
|
||||
| http://codeigniter.com/user_guide/general/urls.html
|
||||
*/
|
||||
|
||||
$config['url_suffix'] = '';
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Default Language
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| This determines which set of language files should be used. Make sure
|
||||
| there is an available translation if you intend to use something other
|
||||
| than english.
|
||||
|
|
||||
*/
|
||||
|
||||
$languages = [
|
||||
'sq' => 'albanian',
|
||||
'ar' => 'arabic',
|
||||
'bs' => 'bosnian',
|
||||
'bu' => 'bulgarian',
|
||||
'ca' => 'catalan',
|
||||
'cs' => 'czech',
|
||||
'da' => 'danish',
|
||||
'de' => 'german',
|
||||
'el' => 'greek',
|
||||
'en' => 'english',
|
||||
'es' => 'spanish',
|
||||
'et' => 'estonian',
|
||||
'fa' => 'persian',
|
||||
'fi' => 'finnish',
|
||||
'fr' => 'french',
|
||||
'he' => 'hebrew',
|
||||
'hi' => 'hindi',
|
||||
'hr' => 'croatian',
|
||||
'hu' => 'hungarian',
|
||||
'it' => 'italian',
|
||||
'ja' => 'japanese',
|
||||
'lb' => 'luxembourgish',
|
||||
'lt' => 'lithuanian',
|
||||
'lv' => 'latvian',
|
||||
'mr' => 'marathi',
|
||||
'nl' => 'dutch',
|
||||
'no' => 'norwegian',
|
||||
'pl' => 'polish',
|
||||
'pt' => 'portuguese',
|
||||
'ro' => 'romanian',
|
||||
'rs' => 'serbian',
|
||||
'ru' => 'russian',
|
||||
'sk' => 'slovak',
|
||||
'sl' => 'slovenian',
|
||||
'sv' => 'swedish',
|
||||
'th' => 'thai',
|
||||
'tr' => 'turkish',
|
||||
'zh' => 'chinese',
|
||||
];
|
||||
|
||||
$config['language_codes'] = $languages;
|
||||
|
||||
$language_code = isset($_SERVER['HTTP_ACCEPT_LANGUAGE']) ? substr($_SERVER['HTTP_ACCEPT_LANGUAGE'], 0, 2) : 'en';
|
||||
|
||||
$config['language'] =
|
||||
$_GET['language'] ??
|
||||
(isset($_SERVER['HTTP_ACCEPT_LANGUAGE'], $languages[$language_code])
|
||||
? $languages[$language_code]
|
||||
: Config::LANGUAGE);
|
||||
|
||||
$config['language_code'] = array_search($config['language'], $languages) ?: 'en';
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Available Languages
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| Each item of this array must be a directory with the translation files in
|
||||
| the /application/language directory. The users will be able to select one
|
||||
| of these languages.
|
||||
|
|
||||
*/
|
||||
$config['available_languages'] = [
|
||||
'albanian',
|
||||
'arabic',
|
||||
'bosnian',
|
||||
'bulgarian',
|
||||
'catalan',
|
||||
'chinese',
|
||||
'croatian',
|
||||
'czech',
|
||||
'danish',
|
||||
'dutch',
|
||||
'english',
|
||||
'estonian',
|
||||
'finnish',
|
||||
'french',
|
||||
'german',
|
||||
'greek',
|
||||
'hebrew',
|
||||
'hindi',
|
||||
'hungarian',
|
||||
'italian',
|
||||
'japanese',
|
||||
'latvian',
|
||||
'lithuanian',
|
||||
'luxembourgish',
|
||||
'marathi',
|
||||
'norwegian',
|
||||
'persian',
|
||||
'polish',
|
||||
'portuguese',
|
||||
'portuguese-br',
|
||||
'romanian',
|
||||
'russian',
|
||||
'serbian',
|
||||
'slovak',
|
||||
'slovenian',
|
||||
'spanish',
|
||||
'swedish',
|
||||
'thai',
|
||||
'traditional-chinese',
|
||||
'turkish',
|
||||
];
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Default Character Set
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| This determines which character set is used by default in various methods
|
||||
| that require a character set to be provided.
|
||||
|
|
||||
*/
|
||||
$config['charset'] = 'UTF-8';
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Enable/Disable System Hooks
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| If you would like to use the 'hooks' feature you must enable it by
|
||||
| setting this variable to TRUE (boolean). See the user guide for details.
|
||||
|
|
||||
*/
|
||||
$config['enable_hooks'] = true;
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Class Extension Prefix
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| This item allows you to set the filename/classname prefix when extending
|
||||
| native libraries. For more information please see the user guide:
|
||||
|
|
||||
| http://codeigniter.com/user_guide/general/core_classes.html
|
||||
| http://codeigniter.com/user_guide/general/creating_libraries.html
|
||||
|
|
||||
*/
|
||||
$config['subclass_prefix'] = 'EA_';
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Allowed URL Characters
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| This lets you specify with a regular expression which characters are permitted
|
||||
| within your URLs. When someone tries to submit a URL with disallowed
|
||||
| characters they will get a warning message.
|
||||
|
|
||||
| As a security measure you are STRONGLY encouraged to restrict URLs to
|
||||
| as few characters as possible. By default, only these are allowed: a-z 0-9~%.:_-
|
||||
|
|
||||
| Leave blank to allow all characters -- but only if you are insane.
|
||||
|
|
||||
| DO NOT CHANGE THIS UNLESS YOU FULLY UNDERSTAND THE REPERCUSSIONS!!
|
||||
|
|
||||
*/
|
||||
$config['permitted_uri_chars'] = 'a-z 0-9~%.:_\-';
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Enable Query Strings
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| By default CodeIgniter uses search-engine friendly segment based URLs:
|
||||
| example.com/who/what/where/
|
||||
|
|
||||
| By default CodeIgniter enables access to the $_GET array. If for some
|
||||
| reason you would like to disable it, set 'allow_get_array' to FALSE.
|
||||
|
|
||||
| You can optionally enable standard query string based URLs:
|
||||
| example.com?who=me&what=something&where=here
|
||||
|
|
||||
| Options are: TRUE or FALSE (boolean)
|
||||
|
|
||||
| The other items let you set the query string 'words' that will
|
||||
| invoke your controllers and its functions:
|
||||
| example.com/index.php?c=controller&m=function
|
||||
|
|
||||
| Please note that some of the helpers won't work as expected when
|
||||
| this feature is enabled, since CodeIgniter is designed primarily to
|
||||
| use segment based URLs.
|
||||
|
|
||||
*/
|
||||
$config['allow_get_array'] = true;
|
||||
$config['enable_query_strings'] = false;
|
||||
$config['controller_trigger'] = 'c';
|
||||
$config['function_trigger'] = 'm';
|
||||
$config['directory_trigger'] = 'd'; // experimental not currently in use
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Error Logging Threshold
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| If you have enabled error logging, you can set an error threshold to
|
||||
| determine what gets logged. Threshold options are:
|
||||
| You can enable error logging by setting a threshold over zero. The
|
||||
| threshold determines what gets logged. Threshold options are:
|
||||
|
|
||||
| 0 = Disables logging, Error logging TURNED OFF
|
||||
| 1 = Error Messages (including PHP errors)
|
||||
| 2 = Debug Messages
|
||||
| 3 = Informational Messages
|
||||
| 4 = All Messages
|
||||
|
|
||||
| For a live site you'll usually only enable Errors (1) to be logged otherwise
|
||||
| your log files will fill up very fast.
|
||||
|
|
||||
*/
|
||||
$config['log_threshold'] = 1;
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Error Logging Directory Path
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| Leave this BLANK unless you would like to set something other than the default
|
||||
| application/logs/ folder. Use a full server path with trailing slash.
|
||||
|
|
||||
*/
|
||||
$config['log_path'] = __DIR__ . '/../../storage/logs/';
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Date Format for Logs
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| Each item that is logged has an associated date. You can use PHP date
|
||||
| codes to set your own date formatting
|
||||
|
|
||||
*/
|
||||
$config['log_date_format'] = 'Y-m-d H:i:s';
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Cache Directory Path
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| Leave this BLANK unless you would like to set something other than the default
|
||||
| system/cache/ folder. Use a full server path with trailing slash.
|
||||
|
|
||||
*/
|
||||
$config['cache_path'] = __DIR__ . '/../../storage/cache/';
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Encryption Key
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| If you use the Encryption class or the Session class you
|
||||
| MUST set an encryption key. See the user guide for info.
|
||||
|
|
||||
*/
|
||||
$config['encryption_key'] = base64_encode(APPPATH);
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Session Variables
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| 'sess_cookie_name' = the name you want for the cookie
|
||||
| 'sess_expiration' = the number of SECONDS you want the session to last.
|
||||
| by default sessions last 7200 seconds (two hours). Set to zero for no expiration.
|
||||
| 'sess_expire_on_close' = Whether to cause the session to expire automatically
|
||||
| when the browser window is closed
|
||||
| 'sess_encrypt_cookie' = Whether to encrypt the cookie
|
||||
| 'sess_use_database' = Whether to save the session data to a database
|
||||
| 'sess_table_name' = The name of the session database table
|
||||
| 'sess_match_ip' = Whether to match the user's IP address when reading the session data
|
||||
| 'sess_match_useragent' = Whether to match the User Agent when reading the session data
|
||||
| 'sess_time_to_update' = how many seconds between CI refreshing Session Information
|
||||
|
|
||||
*/
|
||||
$config['sess_driver'] = 'files';
|
||||
$config['sess_cookie_name'] = 'ea_session';
|
||||
$config['sess_expiration'] = 7200;
|
||||
$config['sess_save_path'] = __DIR__ . '/../../storage/sessions';
|
||||
$config['sess_match_ip'] = false;
|
||||
$config['sess_time_to_update'] = 300;
|
||||
$config['sess_regenerate_destroy'] = true;
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Cookie Related Variables
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| 'cookie_prefix' = Set a prefix if you need to avoid collisions
|
||||
| 'cookie_domain' = Set to .your-domain.com for site-wide cookies
|
||||
| 'cookie_path' = Typically will be a forward slash
|
||||
| 'cookie_secure' = Cookies will only be set if a secure HTTPS connection exists.
|
||||
|
|
||||
*/
|
||||
$config['cookie_prefix'] = '';
|
||||
$config['cookie_domain'] = '';
|
||||
$config['cookie_path'] = '/';
|
||||
$config['cookie_secure'] = strpos($config['base_url'], 'https') !== false;
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Cross Site Request Forgery
|
||||
|--------------------------------------------------------------------------
|
||||
| Enables a CSRF cookie token to be set. When set to TRUE, token will be
|
||||
| checked on a submitted form. If you are accepting user data, it is strongly
|
||||
| recommended CSRF protection be enabled.
|
||||
|
|
||||
| 'csrf_token_name' = The token name
|
||||
| 'csrf_cookie_name' = The cookie name
|
||||
| 'csrf_expire' = The number in seconds the token should expire.
|
||||
*/
|
||||
$config['csrf_protection'] = true;
|
||||
$config['csrf_token_name'] = 'csrf_token';
|
||||
$config['csrf_cookie_name'] = 'csrf_cookie';
|
||||
$config['csrf_expire'] = 7200;
|
||||
$config['csrf_exclude_uris'] = ['api/v1/.*', 'booking/.*', 'booking_cancellation/.*', 'booking_confirmation/.*'];
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Output Compression
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| Enables Gzip output compression for faster page loads. When enabled,
|
||||
| the output class will test whether your server supports Gzip.
|
||||
| Even if it does, however, not all browsers support compression
|
||||
| so enable only if you are reasonably sure your visitors can handle it.
|
||||
|
|
||||
| VERY IMPORTANT: If you are getting a blank page when compression is enabled it
|
||||
| means you are prematurely outputting something to your browser. It could
|
||||
| even be a line of whitespace at the end of one of your scripts. For
|
||||
| compression to work, nothing can be sent before the output buffer is called
|
||||
| by the output class. Do not 'echo' any values with compression enabled.
|
||||
|
|
||||
*/
|
||||
$config['compress_output'] = false;
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Master Time Reference
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| Options are 'local' or 'gmt'. This pref tells the system whether to use
|
||||
| your server's local time as the master 'now' reference, or convert it to
|
||||
| GMT. See the 'date helper' page of the user guide for information
|
||||
| regarding date handling.
|
||||
|
|
||||
*/
|
||||
$config['time_reference'] = 'local';
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Rewrite PHP Short Tags
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| If your PHP installation does not have short tag support enabled CI
|
||||
| can rewrite the tags on-the-fly, enabling you to utilize that syntax
|
||||
| in your view files. Options are TRUE or FALSE (boolean)
|
||||
|
|
||||
*/
|
||||
$config['rewrite_short_tags'] = false;
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Reverse Proxy IPs
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| If your server is behind a reverse proxy, you must whitelist the proxy IP
|
||||
| addresses from which CodeIgniter should trust the HTTP_X_FORWARDED_FOR
|
||||
| header in order to properly identify the visitor's IP address.
|
||||
| Comma-delimited, e.g. '10.0.1.200,10.0.1.201'
|
||||
|
|
||||
*/
|
||||
$config['proxy_ips'] = '';
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Rate Limiting
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| Toggle the rate limiting feature in your application. Using rate limiting
|
||||
| will control the number of requests a client can send to the app.
|
||||
|
|
||||
*/
|
||||
$config['rate_limiting'] = true;
|
||||
|
||||
/* End of file config.php */
|
||||
/* Location: ./application/config/config.php */
|
157
application/config/constants.php
Normal file
157
application/config/constants.php
Normal file
@ -0,0 +1,157 @@
|
||||
<?php defined('BASEPATH') or exit('No direct script access allowed');
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| File and Directory Modes
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| These prefs are used when checking and setting modes when working
|
||||
| with the file system. The defaults are fine on servers with proper
|
||||
| security, but you may wish (or even need) to change the values in
|
||||
| certain environments (Apache running a separate process for each
|
||||
| user, PHP under CGI with Apache suEXEC, etc.). Octal values should
|
||||
| always be used to set the mode correctly.
|
||||
|
|
||||
*/
|
||||
const FILE_READ_MODE = 0644;
|
||||
const FILE_WRITE_MODE = 0666;
|
||||
const DIR_READ_MODE = 0755;
|
||||
const DIR_WRITE_MODE = 0777;
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| File Stream Modes
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| These modes are used when working with fopen()/popen()
|
||||
|
|
||||
*/
|
||||
|
||||
const FOPEN_READ = 'rb';
|
||||
const FOPEN_READ_WRITE = 'r+b';
|
||||
const FOPEN_WRITE_CREATE_DESTRUCTIVE = 'wb'; // truncates existing file data, use with care
|
||||
const FOPEN_READ_WRITE_CREATE_DESTRUCTIVE = 'w+b'; // truncates existing file data, use with care
|
||||
const FOPEN_WRITE_CREATE = 'ab';
|
||||
const FOPEN_READ_WRITE_CREATE = 'a+b';
|
||||
const FOPEN_WRITE_CREATE_STRICT = 'xb';
|
||||
const FOPEN_READ_WRITE_CREATE_STRICT = 'x+b';
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Application Data
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| These constants are used globally from the application when handling data.
|
||||
|
|
||||
*/
|
||||
const DB_SLUG_CUSTOMER = 'customer';
|
||||
const DB_SLUG_PROVIDER = 'provider';
|
||||
const DB_SLUG_ADMIN = 'admin';
|
||||
const DB_SLUG_SECRETARY = 'secretary';
|
||||
|
||||
const FILTER_TYPE_ALL = 'all';
|
||||
const FILTER_TYPE_PROVIDER = 'provider';
|
||||
const FILTER_TYPE_SERVICE = 'service';
|
||||
|
||||
const AJAX_SUCCESS = 'SUCCESS';
|
||||
const AJAX_FAILURE = 'FAILURE';
|
||||
|
||||
const SETTINGS_SYSTEM = 'SETTINGS_SYSTEM';
|
||||
const SETTINGS_USER = 'SETTINGS_USER';
|
||||
|
||||
const PRIV_VIEW = 1;
|
||||
const PRIV_ADD = 2;
|
||||
const PRIV_EDIT = 4;
|
||||
const PRIV_DELETE = 8;
|
||||
|
||||
const PRIV_APPOINTMENTS = 'appointments';
|
||||
const PRIV_CUSTOMERS = 'customers';
|
||||
const PRIV_SERVICES = 'services';
|
||||
const PRIV_USERS = 'users';
|
||||
const PRIV_SYSTEM_SETTINGS = 'system_settings';
|
||||
const PRIV_USER_SETTINGS = 'user_settings';
|
||||
const PRIV_WEBHOOKS = 'webhooks';
|
||||
const PRIV_BLOCKED_PERIODS = 'blocked_periods';
|
||||
|
||||
const DATE_FORMAT_DMY = 'DMY';
|
||||
const DATE_FORMAT_MDY = 'MDY';
|
||||
const DATE_FORMAT_YMD = 'YMD';
|
||||
|
||||
const TIME_FORMAT_REGULAR = 'regular';
|
||||
const TIME_FORMAT_MILITARY = 'military';
|
||||
|
||||
const MIN_PASSWORD_LENGTH = 7;
|
||||
const MAX_PASSWORD_LENGTH = 100;
|
||||
const ANY_PROVIDER = 'any-provider';
|
||||
|
||||
const CALENDAR_VIEW_DEFAULT = 'default';
|
||||
const CALENDAR_VIEW_TABLE = 'table';
|
||||
|
||||
const AVAILABILITIES_TYPE_FLEXIBLE = 'flexible';
|
||||
const AVAILABILITIES_TYPE_FIXED = 'fixed';
|
||||
|
||||
const EVENT_MINIMUM_DURATION = 5; // Minutes
|
||||
|
||||
const DEFAULT_COMPANY_COLOR = '#ffffff';
|
||||
|
||||
const LDAP_DEFAULT_FILTER = '(&(objectClass=*)(|(cn={{KEYWORD}})(sn={{KEYWORD}})(mail={{KEYWORD}})(givenName={{KEYWORD}})(uid={{KEYWORD}})))';
|
||||
|
||||
const LDAP_WHITELISTED_ATTRIBUTES = [
|
||||
'givenname',
|
||||
'cn',
|
||||
'dn',
|
||||
'sn',
|
||||
'mail',
|
||||
'telephonenumber',
|
||||
'description',
|
||||
'member',
|
||||
'objectclass',
|
||||
'objectcategory',
|
||||
'instancetype',
|
||||
'whencreated',
|
||||
'name',
|
||||
'samaccountname',
|
||||
'samaccounttype',
|
||||
'objectcategory',
|
||||
'memberof',
|
||||
'distinguishedname',
|
||||
];
|
||||
|
||||
const LDAP_DEFAULT_FIELD_MAPPING = [
|
||||
'first_name' => 'givenname',
|
||||
'last_name' => 'sn',
|
||||
'email' => 'mail',
|
||||
'phone_number' => 'telephonenumber',
|
||||
'username' => 'cn',
|
||||
];
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Webhook Actions
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| External application endpoints can subscribe to these webhook actions.
|
||||
|
|
||||
*/
|
||||
|
||||
const WEBHOOK_APPOINTMENT_SAVE = 'appointment_save';
|
||||
const WEBHOOK_APPOINTMENT_DELETE = 'appointment_delete';
|
||||
const WEBHOOK_UNAVAILABILITY_SAVE = 'unavailability_save';
|
||||
const WEBHOOK_UNAVAILABILITY_DELETE = 'unavailability_delete';
|
||||
const WEBHOOK_CUSTOMER_SAVE = 'customer_save';
|
||||
const WEBHOOK_CUSTOMER_DELETE = 'customer_delete';
|
||||
const WEBHOOK_SERVICE_SAVE = 'service_save';
|
||||
const WEBHOOK_SERVICE_DELETE = 'service_delete';
|
||||
const WEBHOOK_SERVICE_CATEGORY_SAVE = 'service_category_save';
|
||||
const WEBHOOK_SERVICE_CATEGORY_DELETE = 'service_category_delete';
|
||||
const WEBHOOK_PROVIDER_SAVE = 'provider_save';
|
||||
const WEBHOOK_PROVIDER_DELETE = 'provider_delete';
|
||||
const WEBHOOK_SECRETARY_SAVE = 'secretary_save';
|
||||
const WEBHOOK_SECRETARY_DELETE = 'secretary_delete';
|
||||
const WEBHOOK_ADMIN_SAVE = 'admin_save';
|
||||
const WEBHOOK_ADMIN_DELETE = 'admin_delete';
|
||||
const WEBHOOK_BLOCKED_PERIOD_SAVE = 'blocked_period_save';
|
||||
const WEBHOOK_BLOCKED_PERIOD_DELETE = 'blocked_period_delete';
|
||||
|
||||
/* End of file constants.php */
|
||||
/* Location: ./application/config/constants.php */
|
69
application/config/database.php
Normal file
69
application/config/database.php
Normal file
@ -0,0 +1,69 @@
|
||||
<?php defined('BASEPATH') or exit('No direct script access allowed');
|
||||
|
||||
/*
|
||||
| -------------------------------------------------------------------
|
||||
| DATABASE CONNECTIVITY SETTINGS
|
||||
| -------------------------------------------------------------------
|
||||
| This file will contain the settings needed to access your database.
|
||||
|
|
||||
| For complete instructions please consult the 'Database Connection'
|
||||
| page of the User Guide.
|
||||
|
|
||||
| -------------------------------------------------------------------
|
||||
| EXPLANATION OF VARIABLES
|
||||
| -------------------------------------------------------------------
|
||||
|
|
||||
| ['hostname'] The hostname of your database server.
|
||||
| ['username'] The username used to connect to the database
|
||||
| ['password'] The password used to connect to the database
|
||||
| ['database'] The name of the database you want to connect to
|
||||
| ['dbdriver'] The database type. ie: mysql. Currently supported:
|
||||
mysql, mysqli, postgre, odbc, mssql, sqlite, oci8
|
||||
| ['dbprefix'] You can add an optional prefix, which will be added
|
||||
| to the table name when using the Active Record class
|
||||
| ['pconnect'] TRUE/FALSE - Whether to use a persistent connection
|
||||
| ['db_debug'] TRUE/FALSE - Whether database errors should be displayed.
|
||||
| ['cache_on'] TRUE/FALSE - Enables/disables query caching
|
||||
| ['cachedir'] The path to the folder where cache files should be stored
|
||||
| ['char_set'] The character set used in communicating with the database
|
||||
| ['dbcollat'] The character collation used in communicating with the database
|
||||
| NOTE: For MySQL and MySQLi databases, this setting is only used
|
||||
| as a backup if your server is running PHP < 5.2.3 or MySQL < 5.0.7
|
||||
| (and in table creation queries made with DB Forge).
|
||||
| There is an incompatibility in PHP with mysql_real_escape_string() which
|
||||
| can make your site vulnerable to SQL injection if you are using a
|
||||
| multi-byte character set and are running versions lower than these.
|
||||
| Sites using Latin-1 or UTF-8 database character set and collation are unaffected.
|
||||
| ['swap_pre'] A default table prefix that should be swapped with the dbprefix
|
||||
| ['autoinit'] Whether to automatically initialize the database.
|
||||
| ['stricton'] TRUE/FALSE - forces 'Strict Mode' connections
|
||||
| - good for ensuring strict SQL while developing
|
||||
|
|
||||
| The $active_group variable lets you choose which connection group to
|
||||
| make active. By default there is only one group (the 'default' group).
|
||||
|
|
||||
| The $active_record variables lets you determine whether or not to load
|
||||
| the active record class
|
||||
*/
|
||||
|
||||
$active_group = 'default';
|
||||
$query_builder = true;
|
||||
|
||||
$db['default']['hostname'] = Config::DB_HOST;
|
||||
$db['default']['username'] = Config::DB_USERNAME;
|
||||
$db['default']['password'] = Config::DB_PASSWORD;
|
||||
$db['default']['database'] = Config::DB_NAME;
|
||||
$db['default']['dbdriver'] = 'mysqli';
|
||||
$db['default']['dbprefix'] = 'ea_';
|
||||
$db['default']['pconnect'] = true;
|
||||
$db['default']['db_debug'] = true;
|
||||
$db['default']['cache_on'] = false;
|
||||
$db['default']['cachedir'] = '';
|
||||
$db['default']['char_set'] = 'utf8mb4';
|
||||
$db['default']['dbcollat'] = 'utf8mb4_unicode_ci';
|
||||
$db['default']['swap_pre'] = '';
|
||||
$db['default']['autoinit'] = true;
|
||||
$db['default']['stricton'] = false;
|
||||
|
||||
/* End of file database.php */
|
||||
/* Location: ./application/config/database.php */
|
21
application/config/email.php
Normal file
21
application/config/email.php
Normal file
@ -0,0 +1,21 @@
|
||||
<?php defined('BASEPATH') or exit('No direct script access allowed');
|
||||
|
||||
// Add custom values by settings them to the $config array.
|
||||
// Example: $config['smtp_host'] = 'smtp.gmail.com';
|
||||
// @link https://codeigniter.com/user_guide/libraries/email.html
|
||||
|
||||
$config['useragent'] = 'Easy!Appointments';
|
||||
$config['protocol'] = 'mail'; // or 'smtp'
|
||||
$config['mailtype'] = 'html'; // or 'text'
|
||||
// $config['smtp_debug'] = '0'; // or '1'
|
||||
// $config['smtp_auth'] = TRUE; //or FALSE for anonymous relay.
|
||||
// $config['smtp_host'] = '';
|
||||
// $config['smtp_user'] = '';
|
||||
// $config['smtp_pass'] = '';
|
||||
// $config['smtp_crypto'] = 'ssl'; // or 'tls'
|
||||
// $config['smtp_port'] = 25;
|
||||
// $config['from_name'] = '';
|
||||
// $config['from_address'] = '';
|
||||
// $config['reply_to'] = '';
|
||||
$config['crlf'] = "\r\n";
|
||||
$config['newline'] = "\r\n";
|
17
application/config/google.php
Normal file
17
application/config/google.php
Normal file
@ -0,0 +1,17 @@
|
||||
<?php defined('BASEPATH') or exit('No direct script access allowed');
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Google Calendar - Internal Configuration
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| Declare some of the global config values of the Google Calendar
|
||||
| synchronization feature.
|
||||
|
|
||||
*/
|
||||
|
||||
$config['google_sync_feature'] = Config::GOOGLE_SYNC_FEATURE;
|
||||
|
||||
$config['google_client_id'] = Config::GOOGLE_CLIENT_ID;
|
||||
|
||||
$config['google_client_secret'] = Config::GOOGLE_CLIENT_SECRET;
|
15
application/config/hooks.php
Normal file
15
application/config/hooks.php
Normal file
@ -0,0 +1,15 @@
|
||||
<?php defined('BASEPATH') or exit('No direct script access allowed');
|
||||
|
||||
/*
|
||||
| -------------------------------------------------------------------------
|
||||
| Hooks
|
||||
| -------------------------------------------------------------------------
|
||||
| This file lets you define "hooks" to extend CI without hacking the core
|
||||
| files. Please see the user guide for info:
|
||||
|
|
||||
| http://codeigniter.com/user_guide/general/hooks.html
|
||||
|
|
||||
*/
|
||||
|
||||
/* End of file hooks.php */
|
||||
/* Location: ./application/config/hooks.php */
|
10
application/config/index.html
Normal file
10
application/config/index.html
Normal file
@ -0,0 +1,10 @@
|
||||
<html>
|
||||
<head>
|
||||
<title>403 Forbidden</title>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<p>Directory access is forbidden.</p>
|
||||
|
||||
</body>
|
||||
</html>
|
39
application/config/migration.php
Normal file
39
application/config/migration.php
Normal file
@ -0,0 +1,39 @@
|
||||
<?php defined('BASEPATH') or exit('No direct script access allowed');
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Enable/Disable Migrations
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| Migrations are disabled by default but should be enabled
|
||||
| whenever you intend to do a schema migration.
|
||||
|
|
||||
*/
|
||||
$config['migration_enabled'] = true;
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Migrations version
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| This is used to set migration version that the file system should be on.
|
||||
| If you run $this->migration->latest() this is the version that schema will
|
||||
| be upgraded / downgraded to.
|
||||
|
|
||||
*/
|
||||
$config['migration_version'] = 0;
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Migrations Path
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| Path to your "migrations" folder.
|
||||
| Typically, it will be within your application path.
|
||||
| Also, writing permission is required within the "migrations" path.
|
||||
|
|
||||
*/
|
||||
$config['migration_path'] = APPPATH . 'migrations/';
|
||||
|
||||
/* End of file migration.php */
|
||||
/* Location: ./application/config/migration.php */
|
294
application/config/mimes.php
Normal file
294
application/config/mimes.php
Normal file
@ -0,0 +1,294 @@
|
||||
<?php
|
||||
defined('BASEPATH') or exit('No direct script access allowed');
|
||||
|
||||
/*
|
||||
| -------------------------------------------------------------------
|
||||
| MIME TYPES
|
||||
| -------------------------------------------------------------------
|
||||
| This file contains an array of mime types. It is used by the
|
||||
| Upload class to help identify allowed file types.
|
||||
|
|
||||
*/
|
||||
return [
|
||||
'hqx' => [
|
||||
'application/mac-binhex40',
|
||||
'application/mac-binhex',
|
||||
'application/x-binhex40',
|
||||
'application/x-mac-binhex40',
|
||||
],
|
||||
'cpt' => 'application/mac-compactpro',
|
||||
'csv' => [
|
||||
'text/x-comma-separated-values',
|
||||
'text/comma-separated-values',
|
||||
'application/octet-stream',
|
||||
'application/vnd.ms-excel',
|
||||
'application/x-csv',
|
||||
'text/x-csv',
|
||||
'text/csv',
|
||||
'application/csv',
|
||||
'application/excel',
|
||||
'application/vnd.msexcel',
|
||||
'text/plain',
|
||||
],
|
||||
'bin' => [
|
||||
'application/macbinary',
|
||||
'application/mac-binary',
|
||||
'application/octet-stream',
|
||||
'application/x-binary',
|
||||
'application/x-macbinary',
|
||||
],
|
||||
'dms' => 'application/octet-stream',
|
||||
'lha' => 'application/octet-stream',
|
||||
'lzh' => 'application/octet-stream',
|
||||
'exe' => ['application/octet-stream', 'application/x-msdownload'],
|
||||
'class' => 'application/octet-stream',
|
||||
'psd' => ['application/x-photoshop', 'image/vnd.adobe.photoshop'],
|
||||
'so' => 'application/octet-stream',
|
||||
'sea' => 'application/octet-stream',
|
||||
'dll' => 'application/octet-stream',
|
||||
'oda' => 'application/oda',
|
||||
'pdf' => ['application/pdf', 'application/force-download', 'application/x-download', 'binary/octet-stream'],
|
||||
'ai' => ['application/pdf', 'application/postscript'],
|
||||
'eps' => 'application/postscript',
|
||||
'ps' => 'application/postscript',
|
||||
'smi' => 'application/smil',
|
||||
'smil' => 'application/smil',
|
||||
'mif' => 'application/vnd.mif',
|
||||
'xls' => [
|
||||
'application/vnd.ms-excel',
|
||||
'application/msexcel',
|
||||
'application/x-msexcel',
|
||||
'application/x-ms-excel',
|
||||
'application/x-excel',
|
||||
'application/x-dos_ms_excel',
|
||||
'application/xls',
|
||||
'application/x-xls',
|
||||
'application/excel',
|
||||
'application/download',
|
||||
'application/vnd.ms-office',
|
||||
'application/msword',
|
||||
],
|
||||
'ppt' => [
|
||||
'application/powerpoint',
|
||||
'application/vnd.ms-powerpoint',
|
||||
'application/vnd.ms-office',
|
||||
'application/msword',
|
||||
],
|
||||
'pptx' => [
|
||||
'application/vnd.openxmlformats-officedocument.presentationml.presentation',
|
||||
'application/x-zip',
|
||||
'application/zip',
|
||||
],
|
||||
'wbxml' => 'application/wbxml',
|
||||
'wmlc' => 'application/wmlc',
|
||||
'dcr' => 'application/x-director',
|
||||
'dir' => 'application/x-director',
|
||||
'dxr' => 'application/x-director',
|
||||
'dvi' => 'application/x-dvi',
|
||||
'gtar' => 'application/x-gtar',
|
||||
'gz' => 'application/x-gzip',
|
||||
'gzip' => 'application/x-gzip',
|
||||
'php' => [
|
||||
'application/x-httpd-php',
|
||||
'application/php',
|
||||
'application/x-php',
|
||||
'text/php',
|
||||
'text/x-php',
|
||||
'application/x-httpd-php-source',
|
||||
],
|
||||
'php4' => 'application/x-httpd-php',
|
||||
'php3' => 'application/x-httpd-php',
|
||||
'phtml' => 'application/x-httpd-php',
|
||||
'phps' => 'application/x-httpd-php-source',
|
||||
'js' => ['application/x-javascript', 'text/plain'],
|
||||
'swf' => 'application/x-shockwave-flash',
|
||||
'sit' => 'application/x-stuffit',
|
||||
'tar' => 'application/x-tar',
|
||||
'tgz' => ['application/x-tar', 'application/x-gzip-compressed'],
|
||||
'z' => 'application/x-compress',
|
||||
'xhtml' => 'application/xhtml+xml',
|
||||
'xht' => 'application/xhtml+xml',
|
||||
'zip' => [
|
||||
'application/x-zip',
|
||||
'application/zip',
|
||||
'application/x-zip-compressed',
|
||||
'application/s-compressed',
|
||||
'multipart/x-zip',
|
||||
],
|
||||
'rar' => ['application/x-rar', 'application/rar', 'application/x-rar-compressed'],
|
||||
'mid' => 'audio/midi',
|
||||
'midi' => 'audio/midi',
|
||||
'mpga' => 'audio/mpeg',
|
||||
'mp2' => 'audio/mpeg',
|
||||
'mp3' => ['audio/mpeg', 'audio/mpg', 'audio/mpeg3', 'audio/mp3'],
|
||||
'aif' => ['audio/x-aiff', 'audio/aiff'],
|
||||
'aiff' => ['audio/x-aiff', 'audio/aiff'],
|
||||
'aifc' => 'audio/x-aiff',
|
||||
'ram' => 'audio/x-pn-realaudio',
|
||||
'rm' => 'audio/x-pn-realaudio',
|
||||
'rpm' => 'audio/x-pn-realaudio-plugin',
|
||||
'ra' => 'audio/x-realaudio',
|
||||
'rv' => 'video/vnd.rn-realvideo',
|
||||
'wav' => ['audio/x-wav', 'audio/wave', 'audio/wav'],
|
||||
'bmp' => [
|
||||
'image/bmp',
|
||||
'image/x-bmp',
|
||||
'image/x-bitmap',
|
||||
'image/x-xbitmap',
|
||||
'image/x-win-bitmap',
|
||||
'image/x-windows-bmp',
|
||||
'image/ms-bmp',
|
||||
'image/x-ms-bmp',
|
||||
'application/bmp',
|
||||
'application/x-bmp',
|
||||
'application/x-win-bitmap',
|
||||
],
|
||||
'gif' => 'image/gif',
|
||||
'jpeg' => ['image/jpeg', 'image/pjpeg'],
|
||||
'jpg' => ['image/jpeg', 'image/pjpeg'],
|
||||
'jpe' => ['image/jpeg', 'image/pjpeg'],
|
||||
'jp2' => ['image/jp2', 'video/mj2', 'image/jpx', 'image/jpm'],
|
||||
'j2k' => ['image/jp2', 'video/mj2', 'image/jpx', 'image/jpm'],
|
||||
'jpf' => ['image/jp2', 'video/mj2', 'image/jpx', 'image/jpm'],
|
||||
'jpg2' => ['image/jp2', 'video/mj2', 'image/jpx', 'image/jpm'],
|
||||
'jpx' => ['image/jp2', 'video/mj2', 'image/jpx', 'image/jpm'],
|
||||
'jpm' => ['image/jp2', 'video/mj2', 'image/jpx', 'image/jpm'],
|
||||
'mj2' => ['image/jp2', 'video/mj2', 'image/jpx', 'image/jpm'],
|
||||
'mjp2' => ['image/jp2', 'video/mj2', 'image/jpx', 'image/jpm'],
|
||||
'png' => ['image/png', 'image/x-png'],
|
||||
'tiff' => 'image/tiff',
|
||||
'tif' => 'image/tiff',
|
||||
'css' => ['text/css', 'text/plain'],
|
||||
'html' => ['text/html', 'text/plain'],
|
||||
'htm' => ['text/html', 'text/plain'],
|
||||
'shtml' => ['text/html', 'text/plain'],
|
||||
'txt' => 'text/plain',
|
||||
'text' => 'text/plain',
|
||||
'log' => ['text/plain', 'text/x-log'],
|
||||
'rtx' => 'text/richtext',
|
||||
'rtf' => 'text/rtf',
|
||||
'xml' => ['application/xml', 'text/xml', 'text/plain'],
|
||||
'xsl' => ['application/xml', 'text/xsl', 'text/xml'],
|
||||
'mpeg' => 'video/mpeg',
|
||||
'mpg' => 'video/mpeg',
|
||||
'mpe' => 'video/mpeg',
|
||||
'qt' => 'video/quicktime',
|
||||
'mov' => 'video/quicktime',
|
||||
'avi' => ['video/x-msvideo', 'video/msvideo', 'video/avi', 'application/x-troff-msvideo'],
|
||||
'movie' => 'video/x-sgi-movie',
|
||||
'doc' => ['application/msword', 'application/vnd.ms-office'],
|
||||
'docx' => [
|
||||
'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
|
||||
'application/zip',
|
||||
'application/msword',
|
||||
'application/x-zip',
|
||||
],
|
||||
'dot' => ['application/msword', 'application/vnd.ms-office'],
|
||||
'dotx' => [
|
||||
'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
|
||||
'application/zip',
|
||||
'application/msword',
|
||||
],
|
||||
'xlsx' => [
|
||||
'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
|
||||
'application/zip',
|
||||
'application/vnd.ms-excel',
|
||||
'application/msword',
|
||||
'application/x-zip',
|
||||
],
|
||||
'word' => ['application/msword', 'application/octet-stream'],
|
||||
'xl' => 'application/excel',
|
||||
'eml' => 'message/rfc822',
|
||||
'json' => ['application/json', 'text/json'],
|
||||
'pem' => ['application/x-x509-user-cert', 'application/x-pem-file', 'application/octet-stream'],
|
||||
'p10' => ['application/x-pkcs10', 'application/pkcs10'],
|
||||
'p12' => 'application/x-pkcs12',
|
||||
'p7a' => 'application/x-pkcs7-signature',
|
||||
'p7c' => ['application/pkcs7-mime', 'application/x-pkcs7-mime'],
|
||||
'p7m' => ['application/pkcs7-mime', 'application/x-pkcs7-mime'],
|
||||
'p7r' => 'application/x-pkcs7-certreqresp',
|
||||
'p7s' => 'application/pkcs7-signature',
|
||||
'crt' => ['application/x-x509-ca-cert', 'application/x-x509-user-cert', 'application/pkix-cert'],
|
||||
'crl' => ['application/pkix-crl', 'application/pkcs-crl'],
|
||||
'der' => 'application/x-x509-ca-cert',
|
||||
'kdb' => 'application/octet-stream',
|
||||
'pgp' => 'application/pgp',
|
||||
'gpg' => 'application/gpg-keys',
|
||||
'sst' => 'application/octet-stream',
|
||||
'csr' => 'application/octet-stream',
|
||||
'rsa' => 'application/x-pkcs7',
|
||||
'cer' => ['application/pkix-cert', 'application/x-x509-ca-cert'],
|
||||
'3g2' => 'video/3gpp2',
|
||||
'3gp' => ['video/3gp', 'video/3gpp'],
|
||||
'mp4' => 'video/mp4',
|
||||
'm4a' => 'audio/x-m4a',
|
||||
'f4v' => ['video/mp4', 'video/x-f4v'],
|
||||
'flv' => 'video/x-flv',
|
||||
'webm' => 'video/webm',
|
||||
'aac' => 'audio/x-acc',
|
||||
'm4u' => 'application/vnd.mpegurl',
|
||||
'm3u' => 'text/plain',
|
||||
'xspf' => 'application/xspf+xml',
|
||||
'vlc' => 'application/videolan',
|
||||
'wmv' => ['video/x-ms-wmv', 'video/x-ms-asf'],
|
||||
'au' => 'audio/x-au',
|
||||
'ac3' => 'audio/ac3',
|
||||
'flac' => 'audio/x-flac',
|
||||
'ogg' => ['audio/ogg', 'video/ogg', 'application/ogg'],
|
||||
'kmz' => ['application/vnd.google-earth.kmz', 'application/zip', 'application/x-zip'],
|
||||
'kml' => ['application/vnd.google-earth.kml+xml', 'application/xml', 'text/xml'],
|
||||
'ics' => 'text/calendar',
|
||||
'ical' => 'text/calendar',
|
||||
'zsh' => 'text/x-scriptzsh',
|
||||
'7z' => [
|
||||
'application/x-7z-compressed',
|
||||
'application/x-compressed',
|
||||
'application/x-zip-compressed',
|
||||
'application/zip',
|
||||
'multipart/x-zip',
|
||||
],
|
||||
'7zip' => [
|
||||
'application/x-7z-compressed',
|
||||
'application/x-compressed',
|
||||
'application/x-zip-compressed',
|
||||
'application/zip',
|
||||
'multipart/x-zip',
|
||||
],
|
||||
'cdr' => [
|
||||
'application/cdr',
|
||||
'application/coreldraw',
|
||||
'application/x-cdr',
|
||||
'application/x-coreldraw',
|
||||
'image/cdr',
|
||||
'image/x-cdr',
|
||||
'zz-application/zz-winassoc-cdr',
|
||||
],
|
||||
'wma' => ['audio/x-ms-wma', 'video/x-ms-asf'],
|
||||
'jar' => [
|
||||
'application/java-archive',
|
||||
'application/x-java-application',
|
||||
'application/x-jar',
|
||||
'application/x-compressed',
|
||||
],
|
||||
'svg' => ['image/svg+xml', 'application/xml', 'text/xml'],
|
||||
'vcf' => 'text/x-vcard',
|
||||
'srt' => ['text/srt', 'text/plain'],
|
||||
'vtt' => ['text/vtt', 'text/plain'],
|
||||
'ico' => ['image/x-icon', 'image/x-ico', 'image/vnd.microsoft.icon'],
|
||||
'odc' => 'application/vnd.oasis.opendocument.chart',
|
||||
'otc' => 'application/vnd.oasis.opendocument.chart-template',
|
||||
'odf' => 'application/vnd.oasis.opendocument.formula',
|
||||
'otf' => 'application/vnd.oasis.opendocument.formula-template',
|
||||
'odg' => 'application/vnd.oasis.opendocument.graphics',
|
||||
'otg' => 'application/vnd.oasis.opendocument.graphics-template',
|
||||
'odi' => 'application/vnd.oasis.opendocument.image',
|
||||
'oti' => 'application/vnd.oasis.opendocument.image-template',
|
||||
'odp' => 'application/vnd.oasis.opendocument.presentation',
|
||||
'otp' => 'application/vnd.oasis.opendocument.presentation-template',
|
||||
'ods' => 'application/vnd.oasis.opendocument.spreadsheet',
|
||||
'ots' => 'application/vnd.oasis.opendocument.spreadsheet-template',
|
||||
'odt' => 'application/vnd.oasis.opendocument.text',
|
||||
'odm' => 'application/vnd.oasis.opendocument.text-master',
|
||||
'ott' => 'application/vnd.oasis.opendocument.text-template',
|
||||
'oth' => 'application/vnd.oasis.opendocument.text-web',
|
||||
];
|
16
application/config/profiler.php
Normal file
16
application/config/profiler.php
Normal file
@ -0,0 +1,16 @@
|
||||
<?php defined('BASEPATH') or exit('No direct script access allowed');
|
||||
|
||||
/*
|
||||
| -------------------------------------------------------------------------
|
||||
| Profiler Sections
|
||||
| -------------------------------------------------------------------------
|
||||
| This file lets you determine whether or not various sections of Profiler
|
||||
| data are displayed when the Profiler is enabled.
|
||||
| Please see the user guide for info:
|
||||
|
|
||||
| http://codeigniter.com/user_guide/general/profiling.html
|
||||
|
|
||||
*/
|
||||
|
||||
/* End of file profiler.php */
|
||||
/* Location: ./application/config/profiler.php */
|
163
application/config/routes.php
Normal file
163
application/config/routes.php
Normal file
@ -0,0 +1,163 @@
|
||||
<?php defined('BASEPATH') or exit('No direct script access allowed');
|
||||
|
||||
/*
|
||||
| -------------------------------------------------------------------------
|
||||
| URI ROUTING
|
||||
| -------------------------------------------------------------------------
|
||||
| This file lets you re-map URI requests to specific controller functions.
|
||||
|
|
||||
| Typically there is a one-to-one relationship between a URL string
|
||||
| and its corresponding controller class/method. The segments in a
|
||||
| URL normally follow this pattern:
|
||||
|
|
||||
| example.com/class/method/id/
|
||||
|
|
||||
| In some instances, however, you may want to remap this relationship
|
||||
| so that a different class/function is called than the one
|
||||
| corresponding to the URL.
|
||||
|
|
||||
| Please see the user guide for complete details:
|
||||
|
|
||||
| https://codeigniter.com/userguide3/general/routing.html
|
||||
|
|
||||
| -------------------------------------------------------------------------
|
||||
| RESERVED ROUTES
|
||||
| -------------------------------------------------------------------------
|
||||
|
|
||||
| There are three reserved routes:
|
||||
|
|
||||
| $route['default_controller'] = 'welcome';
|
||||
|
|
||||
| This route indicates which controller class should be loaded if the
|
||||
| URI contains no data. In the above example, the "welcome" class
|
||||
| would be loaded.
|
||||
|
|
||||
| $route['404_override'] = 'errors/page_missing';
|
||||
|
|
||||
| This route will tell the Router which controller/method to use if those
|
||||
| provided in the URL cannot be matched to a valid route.
|
||||
|
|
||||
| $route['translate_uri_dashes'] = FALSE;
|
||||
|
|
||||
| This is not exactly a route, but allows you to automatically route
|
||||
| controller and method names that contain dashes. '-' isn't a valid
|
||||
| class or method name character, so it requires translation.
|
||||
| When you set this option to TRUE, it will replace ALL dashes with
|
||||
| underscores in the controller and method URI segments.
|
||||
|
|
||||
| Examples: my-controller/index -> my_controller/index
|
||||
| my-controller/my-method -> my_controller/my_method
|
||||
*/
|
||||
|
||||
require_once __DIR__ . '/../helpers/routes_helper.php';
|
||||
|
||||
$route['default_controller'] = 'booking';
|
||||
|
||||
$route['404_override'] = '';
|
||||
|
||||
$route['translate_uri_dashes'] = false;
|
||||
|
||||
/*
|
||||
| -------------------------------------------------------------------------
|
||||
| FRAME OPTIONS HEADERS
|
||||
| -------------------------------------------------------------------------
|
||||
| Set the appropriate headers so that iframe control and permissions are
|
||||
| properly configured.
|
||||
|
|
||||
| Enable this if you want to disable use of Easy!Appointments within an
|
||||
| iframe.
|
||||
|
|
||||
| Options:
|
||||
|
|
||||
| - DENY
|
||||
| - SAMEORIGIN
|
||||
|
|
||||
*/
|
||||
|
||||
// header('X-Frame-Options: SAMEORIGIN');
|
||||
|
||||
/*
|
||||
| -------------------------------------------------------------------------
|
||||
| CORS HEADERS
|
||||
| -------------------------------------------------------------------------
|
||||
| Set the appropriate headers so that CORS requirements are met and any
|
||||
| incoming preflight options request succeeds.
|
||||
|
|
||||
*/
|
||||
|
||||
header('Access-Control-Allow-Origin: ' . ($_SERVER['HTTP_ORIGIN'] ?? '*')); // NOTICE: Change this header to restrict CORS access.
|
||||
|
||||
header('Access-Control-Allow-Credentials: "true"');
|
||||
|
||||
if (isset($_SERVER['HTTP_ACCESS_CONTROL_REQUEST_METHOD'])) {
|
||||
// May also be using PUT, PATCH, HEAD etc
|
||||
header('Access-Control-Allow-Methods: GET, POST, PUT, PATCH, DELETE, OPTIONS, HEAD');
|
||||
}
|
||||
|
||||
if (isset($_SERVER['HTTP_ACCESS_CONTROL_REQUEST_HEADERS'])) {
|
||||
header('Access-Control-Allow-Headers: ' . $_SERVER['HTTP_ACCESS_CONTROL_REQUEST_HEADERS']);
|
||||
}
|
||||
|
||||
if (isset($_SERVER['REQUEST_METHOD']) && $_SERVER['REQUEST_METHOD'] === 'OPTIONS') {
|
||||
exit(0);
|
||||
}
|
||||
|
||||
/*
|
||||
| -------------------------------------------------------------------------
|
||||
| REST API ROUTING
|
||||
| -------------------------------------------------------------------------
|
||||
| Define the API resource routes using the routing helper function. By
|
||||
| default, each resource will have by default the following actions:
|
||||
|
|
||||
| - index [GET]
|
||||
|
|
||||
| - show/:id [GET]
|
||||
|
|
||||
| - store [POST]
|
||||
|
|
||||
| - update [PUT]
|
||||
|
|
||||
| - destroy [DELETE]
|
||||
|
|
||||
| Some resources like the availabilities and the settings do not follow this
|
||||
| pattern and are explicitly defined.
|
||||
|
|
||||
*/
|
||||
|
||||
route_api_resource($route, 'appointments', 'api/v1/');
|
||||
|
||||
route_api_resource($route, 'admins', 'api/v1/');
|
||||
|
||||
route_api_resource($route, 'service_categories', 'api/v1/');
|
||||
|
||||
route_api_resource($route, 'customers', 'api/v1/');
|
||||
|
||||
route_api_resource($route, 'providers', 'api/v1/');
|
||||
|
||||
route_api_resource($route, 'secretaries', 'api/v1/');
|
||||
|
||||
route_api_resource($route, 'services', 'api/v1/');
|
||||
|
||||
route_api_resource($route, 'unavailabilities', 'api/v1/');
|
||||
|
||||
route_api_resource($route, 'webhooks', 'api/v1/');
|
||||
|
||||
$route['api/v1/settings']['get'] = 'api/v1/settings_api_v1/index';
|
||||
|
||||
$route['api/v1/settings/(:any)']['get'] = 'api/v1/settings_api_v1/show/$1';
|
||||
|
||||
$route['api/v1/settings/(:any)']['put'] = 'api/v1/settings_api_v1/update/$1';
|
||||
|
||||
$route['api/v1/availabilities']['get'] = 'api/v1/availabilities_api_v1/get';
|
||||
|
||||
/*
|
||||
| -------------------------------------------------------------------------
|
||||
| CUSTOM ROUTING
|
||||
| -------------------------------------------------------------------------
|
||||
| You can add custom routes to the following section to define URL patterns
|
||||
| that are later mapped to the available controllers in the filesystem.
|
||||
|
|
||||
*/
|
||||
|
||||
/* End of file routes.php */
|
||||
/* Location: ./application/config/routes.php */
|
19
application/config/testing/routes.php
Normal file
19
application/config/testing/routes.php
Normal file
@ -0,0 +1,19 @@
|
||||
<?php defined('BASEPATH') or exit('No direct script access allowed');
|
||||
|
||||
/*
|
||||
| -------------------------------------------------------------------------
|
||||
| TESTING ROUTES
|
||||
| -------------------------------------------------------------------------
|
||||
| The following routes are defined in order for CI to be able to process
|
||||
| test execution requests via the CLI.
|
||||
|
|
||||
| The Test controller class is used as a placeholder for this purpose.
|
||||
|
|
||||
*/
|
||||
|
||||
$route['default_controller'] = 'test/index';
|
||||
$route['404_override'] = 'test/index'; // when in doubt, use the hammer
|
||||
$route['translate_uri_dashes'] = FALSE;
|
||||
|
||||
/* End of file routes.php */
|
||||
/* Location: ./application/config/testing/routes.php */
|
79
application/controllers/About.php
Normal file
79
application/controllers/About.php
Normal file
@ -0,0 +1,79 @@
|
||||
<?php defined('BASEPATH') or exit('No direct script access allowed');
|
||||
|
||||
/* ----------------------------------------------------------------------------
|
||||
* Easy!Appointments - Online Appointment Scheduler
|
||||
*
|
||||
* @package EasyAppointments
|
||||
* @author A.Tselegidis <alextselegidis@gmail.com>
|
||||
* @copyright Copyright (c) Alex Tselegidis
|
||||
* @license https://opensource.org/licenses/GPL-3.0 - GPLv3
|
||||
* @link https://easyappointments.org
|
||||
* @since v1.5.0
|
||||
* ---------------------------------------------------------------------------- */
|
||||
|
||||
/**
|
||||
* About controller.
|
||||
*
|
||||
* Handles about settings related operations.
|
||||
*
|
||||
* @package Controllers
|
||||
*/
|
||||
class About extends EA_Controller
|
||||
{
|
||||
/**
|
||||
* About constructor.
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
parent::__construct();
|
||||
|
||||
$this->load->model('appointments_model');
|
||||
$this->load->model('customers_model');
|
||||
$this->load->model('services_model');
|
||||
$this->load->model('providers_model');
|
||||
$this->load->model('roles_model');
|
||||
$this->load->model('settings_model');
|
||||
|
||||
$this->load->library('accounts');
|
||||
$this->load->library('google_sync');
|
||||
$this->load->library('notifications');
|
||||
$this->load->library('synchronization');
|
||||
$this->load->library('timezones');
|
||||
}
|
||||
|
||||
/**
|
||||
* Render the settings page.
|
||||
*/
|
||||
public function index(): void
|
||||
{
|
||||
session(['dest_url' => site_url('about')]);
|
||||
|
||||
$user_id = session('user_id');
|
||||
|
||||
if (cannot('view', PRIV_USER_SETTINGS)) {
|
||||
if ($user_id) {
|
||||
abort(403, 'Forbidden');
|
||||
}
|
||||
|
||||
redirect('login');
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$role_slug = session('role_slug');
|
||||
|
||||
script_vars([
|
||||
'user_id' => $user_id,
|
||||
'role_slug' => $role_slug,
|
||||
]);
|
||||
|
||||
html_vars([
|
||||
'page_title' => lang('settings'),
|
||||
'active_menu' => PRIV_SYSTEM_SETTINGS,
|
||||
'user_display_name' => $this->accounts->get_user_display_name($user_id),
|
||||
'privileges' => $this->roles_model->get_permissions_by_slug($role_slug),
|
||||
]);
|
||||
|
||||
$this->load->view('pages/about');
|
||||
}
|
||||
}
|
166
application/controllers/Account.php
Normal file
166
application/controllers/Account.php
Normal file
@ -0,0 +1,166 @@
|
||||
<?php defined('BASEPATH') or exit('No direct script access allowed');
|
||||
|
||||
/* ----------------------------------------------------------------------------
|
||||
* Easy!Appointments - Online Appointment Scheduler
|
||||
*
|
||||
* @package EasyAppointments
|
||||
* @author A.Tselegidis <alextselegidis@gmail.com>
|
||||
* @copyright Copyright (c) Alex Tselegidis
|
||||
* @license https://opensource.org/licenses/GPL-3.0 - GPLv3
|
||||
* @link https://easyappointments.org
|
||||
* @since v1.5.0
|
||||
* ---------------------------------------------------------------------------- */
|
||||
|
||||
/**
|
||||
* Account controller.
|
||||
*
|
||||
* Handles current account related operations.
|
||||
*
|
||||
* @package Controllers
|
||||
*/
|
||||
class Account extends EA_Controller
|
||||
{
|
||||
public array $allowed_user_fields = [
|
||||
'id',
|
||||
'first_name',
|
||||
'last_name',
|
||||
'email',
|
||||
'mobile_number',
|
||||
'phone_number',
|
||||
'address',
|
||||
'city',
|
||||
'state',
|
||||
'zip_code',
|
||||
'notes',
|
||||
'timezone',
|
||||
'language',
|
||||
'settings',
|
||||
];
|
||||
|
||||
public array $optional_user_fields = [
|
||||
//
|
||||
];
|
||||
|
||||
public array $allowed_user_setting_fields = ['username', 'password', 'notifications', 'calendar_view'];
|
||||
|
||||
public array $optional_user_setting_fields = [
|
||||
//
|
||||
];
|
||||
|
||||
/**
|
||||
* Account constructor.
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
parent::__construct();
|
||||
|
||||
$this->load->model('appointments_model');
|
||||
$this->load->model('customers_model');
|
||||
$this->load->model('services_model');
|
||||
$this->load->model('providers_model');
|
||||
$this->load->model('roles_model');
|
||||
$this->load->model('settings_model');
|
||||
|
||||
$this->load->library('accounts');
|
||||
$this->load->library('google_sync');
|
||||
$this->load->library('notifications');
|
||||
$this->load->library('synchronization');
|
||||
$this->load->library('timezones');
|
||||
}
|
||||
|
||||
/**
|
||||
* Render the settings page.
|
||||
*/
|
||||
public function index(): void
|
||||
{
|
||||
session(['dest_url' => site_url('account')]);
|
||||
|
||||
$user_id = session('user_id');
|
||||
|
||||
if (cannot('view', PRIV_USER_SETTINGS)) {
|
||||
if ($user_id) {
|
||||
abort(403, 'Forbidden');
|
||||
}
|
||||
|
||||
redirect('login');
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$account = $this->users_model->find($user_id);
|
||||
|
||||
script_vars([
|
||||
'account' => $account,
|
||||
]);
|
||||
|
||||
html_vars([
|
||||
'page_title' => lang('settings'),
|
||||
'active_menu' => PRIV_SYSTEM_SETTINGS,
|
||||
'user_display_name' => $this->accounts->get_user_display_name($user_id),
|
||||
'grouped_timezones' => $this->timezones->to_grouped_array(),
|
||||
]);
|
||||
|
||||
$this->load->view('pages/account');
|
||||
}
|
||||
|
||||
/**
|
||||
* Save general settings.
|
||||
*/
|
||||
public function save(): void
|
||||
{
|
||||
try {
|
||||
if (cannot('edit', PRIV_USER_SETTINGS)) {
|
||||
throw new RuntimeException('You do not have the required permissions for this task.');
|
||||
}
|
||||
|
||||
$account = request('account');
|
||||
|
||||
$account['id'] = session('user_id');
|
||||
|
||||
$this->users_model->only($account, $this->allowed_user_fields);
|
||||
|
||||
$this->users_model->optional($account, $this->optional_user_fields);
|
||||
|
||||
$this->users_model->only($account['settings'], $this->allowed_user_setting_fields);
|
||||
|
||||
$this->users_model->optional($account['settings'], $this->optional_user_setting_fields);
|
||||
|
||||
if (empty($account['password'])) {
|
||||
unset($account['password']);
|
||||
}
|
||||
|
||||
$this->users_model->save($account);
|
||||
|
||||
session([
|
||||
'user_email' => $account['email'],
|
||||
'username' => $account['settings']['username'],
|
||||
'timezone' => $account['timezone'],
|
||||
'language' => $account['language'],
|
||||
]);
|
||||
|
||||
response();
|
||||
} catch (Throwable $e) {
|
||||
json_exception($e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Make sure the username is valid and unique in the database.
|
||||
*/
|
||||
public function validate_username(): void
|
||||
{
|
||||
try {
|
||||
$username = request('username');
|
||||
|
||||
$user_id = request('user_id');
|
||||
|
||||
$is_valid = $this->users_model->validate_username($username, $user_id);
|
||||
|
||||
json_response([
|
||||
'is_valid' => $is_valid,
|
||||
]);
|
||||
} catch (Throwable $e) {
|
||||
json_exception($e);
|
||||
}
|
||||
}
|
||||
}
|
247
application/controllers/Admins.php
Normal file
247
application/controllers/Admins.php
Normal file
@ -0,0 +1,247 @@
|
||||
<?php defined('BASEPATH') or exit('No direct script access allowed');
|
||||
|
||||
/* ----------------------------------------------------------------------------
|
||||
* Easy!Appointments - Online Appointment Scheduler
|
||||
*
|
||||
* @package EasyAppointments
|
||||
* @author A.Tselegidis <alextselegidis@gmail.com>
|
||||
* @copyright Copyright (c) Alex Tselegidis
|
||||
* @license https://opensource.org/licenses/GPL-3.0 - GPLv3
|
||||
* @link https://easyappointments.org
|
||||
* @since v1.0.0
|
||||
* ---------------------------------------------------------------------------- */
|
||||
|
||||
/**
|
||||
* Admins controller.
|
||||
*
|
||||
* Handles the admins related operations.
|
||||
*
|
||||
* @package Controllers
|
||||
*/
|
||||
class Admins extends EA_Controller
|
||||
{
|
||||
public array $allowed_admin_fields = [
|
||||
'id',
|
||||
'first_name',
|
||||
'last_name',
|
||||
'email',
|
||||
'mobile_number',
|
||||
'phone_number',
|
||||
'address',
|
||||
'city',
|
||||
'state',
|
||||
'zip_code',
|
||||
'notes',
|
||||
'timezone',
|
||||
'language',
|
||||
'ldap_dn',
|
||||
'settings',
|
||||
];
|
||||
|
||||
public array $optional_admin_fields = [
|
||||
//
|
||||
];
|
||||
|
||||
public array $allowed_admin_setting_fields = ['username', 'password', 'notifications', 'calendar_view'];
|
||||
|
||||
public array $optional_admin_setting_fields = [
|
||||
//
|
||||
];
|
||||
|
||||
/**
|
||||
* Admins constructor.
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
parent::__construct();
|
||||
|
||||
$this->load->model('admins_model');
|
||||
$this->load->model('roles_model');
|
||||
|
||||
$this->load->library('accounts');
|
||||
$this->load->library('timezones');
|
||||
$this->load->library('webhooks_client');
|
||||
}
|
||||
|
||||
/**
|
||||
* Render the backend admins page.
|
||||
*
|
||||
* On this page admin users will be able to manage admins, which are eventually selected by customers during the
|
||||
* booking process.
|
||||
*/
|
||||
public function index(): void
|
||||
{
|
||||
session(['dest_url' => site_url('admins')]);
|
||||
|
||||
$user_id = session('user_id');
|
||||
|
||||
if (cannot('view', PRIV_USERS)) {
|
||||
if ($user_id) {
|
||||
abort(403, 'Forbidden');
|
||||
}
|
||||
|
||||
redirect('login');
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$role_slug = session('role_slug');
|
||||
|
||||
script_vars([
|
||||
'user_id' => $user_id,
|
||||
'role_slug' => $role_slug,
|
||||
'timezones' => $this->timezones->to_array(),
|
||||
'min_password_length' => MIN_PASSWORD_LENGTH,
|
||||
'default_language' => setting('default_language'),
|
||||
'default_timezone' => setting('default_timezone'),
|
||||
]);
|
||||
|
||||
html_vars([
|
||||
'page_title' => lang('admins'),
|
||||
'active_menu' => PRIV_USERS,
|
||||
'user_display_name' => $this->accounts->get_user_display_name($user_id),
|
||||
'grouped_timezones' => $this->timezones->to_grouped_array(),
|
||||
'privileges' => $this->roles_model->get_permissions_by_slug($role_slug),
|
||||
]);
|
||||
|
||||
$this->load->view('pages/admins');
|
||||
}
|
||||
|
||||
/**
|
||||
* Filter admins by the provided keyword.
|
||||
*/
|
||||
public function search(): void
|
||||
{
|
||||
try {
|
||||
if (cannot('view', PRIV_USERS)) {
|
||||
abort(403, 'Forbidden');
|
||||
}
|
||||
|
||||
$keyword = request('keyword', '');
|
||||
|
||||
$order_by = request('order_by', 'update_datetime DESC');
|
||||
|
||||
$limit = request('limit', 1000);
|
||||
|
||||
$offset = (int) request('offset', '0');
|
||||
|
||||
$admins = $this->admins_model->search($keyword, $limit, $offset, $order_by);
|
||||
|
||||
json_response($admins);
|
||||
} catch (Throwable $e) {
|
||||
json_exception($e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Store a new admin.
|
||||
*/
|
||||
public function store(): void
|
||||
{
|
||||
try {
|
||||
if (cannot('add', PRIV_USERS)) {
|
||||
abort(403, 'Forbidden');
|
||||
}
|
||||
|
||||
$admin = request('admin');
|
||||
|
||||
$this->admins_model->only($admin, $this->allowed_admin_fields);
|
||||
|
||||
$this->admins_model->optional($admin, $this->optional_admin_fields);
|
||||
|
||||
$this->admins_model->only($admin['settings'], $this->allowed_admin_setting_fields);
|
||||
|
||||
$this->admins_model->optional($admin['settings'], $this->optional_admin_setting_fields);
|
||||
|
||||
$admin_id = $this->admins_model->save($admin);
|
||||
|
||||
$admin = $this->admins_model->find($admin_id);
|
||||
|
||||
$this->webhooks_client->trigger(WEBHOOK_ADMIN_SAVE, $admin);
|
||||
|
||||
json_response([
|
||||
'success' => true,
|
||||
'id' => $admin_id,
|
||||
]);
|
||||
} catch (Throwable $e) {
|
||||
json_exception($e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Find an admin.
|
||||
*/
|
||||
public function find(): void
|
||||
{
|
||||
try {
|
||||
if (cannot('view', PRIV_USERS)) {
|
||||
abort(403, 'Forbidden');
|
||||
}
|
||||
|
||||
$admin_id = request('admin_id');
|
||||
|
||||
$admin = $this->admins_model->find($admin_id);
|
||||
|
||||
json_response($admin);
|
||||
} catch (Throwable $e) {
|
||||
json_exception($e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Update an admin.
|
||||
*/
|
||||
public function update(): void
|
||||
{
|
||||
try {
|
||||
if (cannot('edit', PRIV_USERS)) {
|
||||
abort(403, 'Forbidden');
|
||||
}
|
||||
|
||||
$admin = request('admin');
|
||||
|
||||
$this->admins_model->only($admin, $this->allowed_admin_fields);
|
||||
|
||||
$this->admins_model->only($admin['settings'], $this->allowed_admin_setting_fields);
|
||||
|
||||
$admin_id = $this->admins_model->save($admin);
|
||||
|
||||
$admin = $this->admins_model->find($admin_id);
|
||||
|
||||
$this->webhooks_client->trigger(WEBHOOK_ADMIN_SAVE, $admin);
|
||||
|
||||
json_response([
|
||||
'success' => true,
|
||||
'id' => $admin_id,
|
||||
]);
|
||||
} catch (Throwable $e) {
|
||||
json_exception($e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove an admin.
|
||||
*/
|
||||
public function destroy(): void
|
||||
{
|
||||
try {
|
||||
if (cannot('delete', PRIV_USERS)) {
|
||||
abort(403, 'Forbidden');
|
||||
}
|
||||
|
||||
$admin_id = request('admin_id');
|
||||
|
||||
$admin = $this->admins_model->find($admin_id);
|
||||
|
||||
$this->admins_model->delete($admin_id);
|
||||
|
||||
$this->webhooks_client->trigger(WEBHOOK_ADMIN_DELETE, $admin);
|
||||
|
||||
json_response([
|
||||
'success' => true,
|
||||
]);
|
||||
} catch (Throwable $e) {
|
||||
json_exception($e);
|
||||
}
|
||||
}
|
||||
}
|
102
application/controllers/Api_settings.php
Normal file
102
application/controllers/Api_settings.php
Normal file
@ -0,0 +1,102 @@
|
||||
<?php defined('BASEPATH') or exit('No direct script access allowed');
|
||||
|
||||
/* ----------------------------------------------------------------------------
|
||||
* Easy!Appointments - Online Appointment Scheduler
|
||||
*
|
||||
* @package EasyAppointments
|
||||
* @author A.Tselegidis <alextselegidis@gmail.com>
|
||||
* @copyright Copyright (c) Alex Tselegidis
|
||||
* @license https://opensource.org/licenses/GPL-3.0 - GPLv3
|
||||
* @link https://easyappointments.org
|
||||
* @since v1.5.0
|
||||
* ---------------------------------------------------------------------------- */
|
||||
|
||||
/**
|
||||
* API settings controller.
|
||||
*
|
||||
* Handles API settings related operations.
|
||||
*
|
||||
* @package Controllers
|
||||
*/
|
||||
class Api_settings extends EA_Controller
|
||||
{
|
||||
/**
|
||||
* Api_settings constructor.
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
parent::__construct();
|
||||
|
||||
$this->load->model('settings_model');
|
||||
|
||||
$this->load->library('accounts');
|
||||
}
|
||||
|
||||
/**
|
||||
* Render the settings page.
|
||||
*/
|
||||
public function index(): void
|
||||
{
|
||||
session(['dest_url' => site_url('api_settings')]);
|
||||
|
||||
$user_id = session('user_id');
|
||||
|
||||
if (cannot('view', PRIV_SYSTEM_SETTINGS)) {
|
||||
if ($user_id) {
|
||||
abort(403, 'Forbidden');
|
||||
}
|
||||
|
||||
redirect('login');
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$role_slug = session('role_slug');
|
||||
|
||||
script_vars([
|
||||
'user_id' => $user_id,
|
||||
'role_slug' => $role_slug,
|
||||
'api_settings' => $this->settings_model->get('name like "api_%"'),
|
||||
]);
|
||||
|
||||
html_vars([
|
||||
'page_title' => lang('api'),
|
||||
'active_menu' => PRIV_SYSTEM_SETTINGS,
|
||||
'user_display_name' => $this->accounts->get_user_display_name($user_id),
|
||||
]);
|
||||
|
||||
$this->load->view('pages/api_settings');
|
||||
}
|
||||
|
||||
/**
|
||||
* Save general settings.
|
||||
*/
|
||||
public function save(): void
|
||||
{
|
||||
try {
|
||||
if (cannot('edit', PRIV_SYSTEM_SETTINGS)) {
|
||||
throw new RuntimeException('You do not have the required permissions for this task.');
|
||||
}
|
||||
|
||||
$settings = request('api_settings', []);
|
||||
|
||||
foreach ($settings as $setting) {
|
||||
$existing_setting = $this->settings_model
|
||||
->query()
|
||||
->where('name', $setting['name'])
|
||||
->get()
|
||||
->row_array();
|
||||
|
||||
if (!empty($existing_setting)) {
|
||||
$setting['id'] = $existing_setting['id'];
|
||||
}
|
||||
|
||||
$this->settings_model->save($setting);
|
||||
}
|
||||
|
||||
response();
|
||||
} catch (Throwable $e) {
|
||||
json_exception($e);
|
||||
}
|
||||
}
|
||||
}
|
200
application/controllers/Appointments.php
Normal file
200
application/controllers/Appointments.php
Normal file
@ -0,0 +1,200 @@
|
||||
<?php defined('BASEPATH') or exit('No direct script access allowed');
|
||||
|
||||
/* ----------------------------------------------------------------------------
|
||||
* Easy!Appointments - Online Appointment Scheduler
|
||||
*
|
||||
* @package EasyAppointments
|
||||
* @author A.Tselegidis <alextselegidis@gmail.com>
|
||||
* @copyright Copyright (c) Alex Tselegidis
|
||||
* @license https://opensource.org/licenses/GPL-3.0 - GPLv3
|
||||
* @link https://easyappointments.org
|
||||
* @since v1.0.0
|
||||
* ---------------------------------------------------------------------------- */
|
||||
|
||||
/**
|
||||
* Appointments controller.
|
||||
*
|
||||
* Handles the appointments related operations.
|
||||
*
|
||||
* Notice: This file used to have the booking page related code which since v1.5 has now moved to the Booking.php
|
||||
* controller for improved consistency.
|
||||
*
|
||||
* @package Controllers
|
||||
*/
|
||||
class Appointments extends EA_Controller
|
||||
{
|
||||
public array $allowed_appointment_fields = [
|
||||
'id',
|
||||
'start_datetime',
|
||||
'end_datetime',
|
||||
'location',
|
||||
'notes',
|
||||
'color',
|
||||
'status',
|
||||
'is_unavailability',
|
||||
'id_users_provider',
|
||||
'id_users_customer',
|
||||
'id_services',
|
||||
];
|
||||
|
||||
public array $optional_appointment_fields = [
|
||||
//
|
||||
];
|
||||
|
||||
/**
|
||||
* Appointments constructor.
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
parent::__construct();
|
||||
|
||||
$this->load->model('appointments_model');
|
||||
$this->load->model('roles_model');
|
||||
|
||||
$this->load->library('accounts');
|
||||
$this->load->library('timezones');
|
||||
$this->load->library('webhooks_client');
|
||||
}
|
||||
|
||||
/**
|
||||
* Support backwards compatibility for appointment links that still point to this URL.
|
||||
*
|
||||
* @param string $appointment_hash
|
||||
*
|
||||
* @deprecated Since 1.5
|
||||
*/
|
||||
public function index(string $appointment_hash = ''): void
|
||||
{
|
||||
redirect('booking/' . $appointment_hash);
|
||||
}
|
||||
|
||||
/**
|
||||
* Filter appointments by the provided keyword.
|
||||
*/
|
||||
public function search(): void
|
||||
{
|
||||
try {
|
||||
if (cannot('view', PRIV_APPOINTMENTS)) {
|
||||
abort(403, 'Forbidden');
|
||||
}
|
||||
|
||||
$keyword = request('keyword', '');
|
||||
|
||||
$order_by = request('order_by', 'update_datetime DESC');
|
||||
|
||||
$limit = request('limit', 1000);
|
||||
|
||||
$offset = (int) request('offset', '0');
|
||||
|
||||
$appointments = $this->appointments_model->search($keyword, $limit, $offset, $order_by);
|
||||
|
||||
json_response($appointments);
|
||||
} catch (Throwable $e) {
|
||||
json_exception($e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Store a new appointment.
|
||||
*/
|
||||
public function store(): void
|
||||
{
|
||||
try {
|
||||
if (cannot('add', PRIV_APPOINTMENTS)) {
|
||||
abort(403, 'Forbidden');
|
||||
}
|
||||
|
||||
$appointment = json_decode(request('appointment'), true);
|
||||
|
||||
$this->appointments_model->only($appointment, $this->allowed_appointment_fields);
|
||||
|
||||
$this->appointments_model->optional($appointment, $this->optional_appointment_fields);
|
||||
|
||||
$appointment_id = $this->appointments_model->save($appointment);
|
||||
|
||||
$appointment = $this->appointments_model->find($appointment);
|
||||
|
||||
$this->webhooks_client->trigger(WEBHOOK_APPOINTMENT_SAVE, $appointment);
|
||||
|
||||
json_response([
|
||||
'success' => true,
|
||||
'id' => $appointment_id,
|
||||
]);
|
||||
} catch (Throwable $e) {
|
||||
json_exception($e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Find an appointment.
|
||||
*/
|
||||
public function find(): void
|
||||
{
|
||||
try {
|
||||
if (cannot('view', PRIV_APPOINTMENTS)) {
|
||||
abort(403, 'Forbidden');
|
||||
}
|
||||
|
||||
$appointment_id = request('appointment_id');
|
||||
|
||||
$appointment = $this->appointments_model->find($appointment_id);
|
||||
|
||||
json_response($appointment);
|
||||
} catch (Throwable $e) {
|
||||
json_exception($e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Update a appointment.
|
||||
*/
|
||||
public function update(): void
|
||||
{
|
||||
try {
|
||||
if (cannot('edit', PRIV_APPOINTMENTS)) {
|
||||
abort(403, 'Forbidden');
|
||||
}
|
||||
|
||||
$appointment = json_decode(request('appointment'), true);
|
||||
|
||||
$this->appointments_model->only($appointment, $this->allowed_appointment_fields);
|
||||
|
||||
$this->appointments_model->optional($appointment, $this->optional_appointment_fields);
|
||||
|
||||
$appointment_id = $this->appointments_model->save($appointment);
|
||||
|
||||
json_response([
|
||||
'success' => true,
|
||||
'id' => $appointment_id,
|
||||
]);
|
||||
} catch (Throwable $e) {
|
||||
json_exception($e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove a appointment.
|
||||
*/
|
||||
public function destroy(): void
|
||||
{
|
||||
try {
|
||||
if (cannot('delete', PRIV_APPOINTMENTS)) {
|
||||
abort(403, 'Forbidden');
|
||||
}
|
||||
|
||||
$appointment_id = request('appointment_id');
|
||||
|
||||
$appointment = $this->appointments_model->find($appointment_id);
|
||||
|
||||
$this->appointments_model->delete($appointment_id);
|
||||
|
||||
$this->webhooks_client->trigger(WEBHOOK_APPOINTMENT_DELETE, $appointment);
|
||||
|
||||
json_response([
|
||||
'success' => true,
|
||||
]);
|
||||
} catch (Throwable $e) {
|
||||
json_exception($e);
|
||||
}
|
||||
}
|
||||
}
|
103
application/controllers/Backend.php
Normal file
103
application/controllers/Backend.php
Normal file
@ -0,0 +1,103 @@
|
||||
<?php defined('BASEPATH') or exit('No direct script access allowed');
|
||||
|
||||
/* ----------------------------------------------------------------------------
|
||||
* Easy!Appointments - Online Appointment Scheduler
|
||||
*
|
||||
* @package EasyAppointments
|
||||
* @author A.Tselegidis <alextselegidis@gmail.com>
|
||||
* @copyright Copyright (c) Alex Tselegidis
|
||||
* @license https://opensource.org/licenses/GPL-3.0 - GPLv3
|
||||
* @link https://easyappointments.org
|
||||
* @since v1.0.0
|
||||
* ---------------------------------------------------------------------------- */
|
||||
|
||||
/*
|
||||
|------------------------------------------------------------------------------
|
||||
| Deprecation Notice
|
||||
|------------------------------------------------------------------------------
|
||||
|
|
||||
| This file is still in the project for backwards compatibility reasons and for
|
||||
| providing additional information on how to migrate your code to the latest
|
||||
| codebase state.
|
||||
|
|
||||
| Visit the Easy!Appointments Developers website for more information:
|
||||
|
|
||||
| https://developers.easyappointments.org
|
||||
|
|
||||
| Since v1.5, the methods of this controller were ported to standalone controller
|
||||
| classes, that can both handle the page rendering and all asynchronous HTTP
|
||||
| requests.
|
||||
|
|
||||
*/
|
||||
|
||||
/**
|
||||
* Backend controller.
|
||||
*
|
||||
* Handles the backend related operations.
|
||||
*
|
||||
* @package Controllers
|
||||
*
|
||||
* @deprecated Since 1.5
|
||||
*/
|
||||
class Backend extends EA_Controller
|
||||
{
|
||||
/**
|
||||
* Display the calendar page.
|
||||
*
|
||||
* @param string $appointment_hash Appointment edit dialog will appear when the page loads (default '').
|
||||
*/
|
||||
public function index(string $appointment_hash = ''): void
|
||||
{
|
||||
if (empty($appointment_hash)) {
|
||||
redirect('calendar');
|
||||
} else {
|
||||
redirect('calendar/reschedule/' . $appointment_hash);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Display the customers page.
|
||||
*/
|
||||
public function customers(): void
|
||||
{
|
||||
redirect('customers');
|
||||
}
|
||||
|
||||
/**
|
||||
* Display the services page.
|
||||
*/
|
||||
public function services(): void
|
||||
{
|
||||
redirect('services');
|
||||
}
|
||||
|
||||
/**
|
||||
* Display the users page.
|
||||
*
|
||||
* Notice: Since the "users" page is split into multiple pages (providers, secretaries, admins), this method will
|
||||
* redirect to "providers" page by default
|
||||
*/
|
||||
public function users(): void
|
||||
{
|
||||
redirect('providers');
|
||||
}
|
||||
|
||||
/**
|
||||
* Display settings page.
|
||||
*
|
||||
* Notice: Since the "settings" page is split into multiple pages (general, business, booking etc.), this method will
|
||||
* redirect to "general" page by default.
|
||||
*/
|
||||
public function settings(): void
|
||||
{
|
||||
redirect('general_settings');
|
||||
}
|
||||
|
||||
/**
|
||||
* Display the update page.
|
||||
*/
|
||||
public function update(): void
|
||||
{
|
||||
redirect('update');
|
||||
}
|
||||
}
|
307
application/controllers/Backend_api.php
Normal file
307
application/controllers/Backend_api.php
Normal file
@ -0,0 +1,307 @@
|
||||
<?php defined('BASEPATH') or exit('No direct script access allowed');
|
||||
|
||||
/* ----------------------------------------------------------------------------
|
||||
* Easy!Appointments - Online Appointment Scheduler
|
||||
*
|
||||
* @package EasyAppointments
|
||||
* @author A.Tselegidis <alextselegidis@gmail.com>
|
||||
* @copyright Copyright (c) Alex Tselegidis
|
||||
* @license https://opensource.org/licenses/GPL-3.0 - GPLv3
|
||||
* @link https://easyappointments.org
|
||||
* @since v1.0.0
|
||||
* ---------------------------------------------------------------------------- */
|
||||
|
||||
/*
|
||||
|------------------------------------------------------------------------------
|
||||
| Deprecation Notice
|
||||
|------------------------------------------------------------------------------
|
||||
|
|
||||
| This file is still in the project for backwards compatibility reasons and for
|
||||
| providing additional information on how to migrate your code to the latest
|
||||
| codebase state.
|
||||
|
|
||||
| Visit the Easy!Appointments Developers website for more information:
|
||||
|
|
||||
| https://developers.easyappointments.org
|
||||
|
|
||||
| Since v1.5, the methods of this controller were ported to standalone controller
|
||||
| classes, that can both handle the page rendering and all asynchronous HTTP
|
||||
| requests.
|
||||
|
|
||||
*/
|
||||
|
||||
/**
|
||||
* Backend API controller.
|
||||
*
|
||||
* Handles the backend API related operations.
|
||||
*
|
||||
* @package Controllers
|
||||
*
|
||||
* @deprecated Since 1.5
|
||||
*/
|
||||
class Backend_api extends EA_Controller
|
||||
{
|
||||
/**
|
||||
* Get Calendar Events
|
||||
*/
|
||||
public function ajax_get_calendar_events(): void
|
||||
{
|
||||
redirect('calendar/get_calendar_appointments_for_table_view');
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the registered appointments for the given date period and record.
|
||||
*/
|
||||
public function ajax_get_calendar_appointments(): void
|
||||
{
|
||||
redirect('calendar/get_calendar_appointments');
|
||||
}
|
||||
|
||||
/**
|
||||
* Save appointment changes that are made from the backend calendar page.
|
||||
*/
|
||||
public function ajax_save_appointment(): void
|
||||
{
|
||||
redirect('calendar/save_appointment');
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete appointment from the database.
|
||||
*/
|
||||
public function ajax_delete_appointment(): void
|
||||
{
|
||||
redirect('calendar/delete_appointment');
|
||||
}
|
||||
|
||||
/**
|
||||
* Disable a providers sync setting.
|
||||
*/
|
||||
public function ajax_disable_provider_sync(): void
|
||||
{
|
||||
redirect('google/disable_provider_sync');
|
||||
}
|
||||
|
||||
/**
|
||||
* Filter the customer records with the given key string.
|
||||
*/
|
||||
public function ajax_filter_customers(): void
|
||||
{
|
||||
redirect('customers/search');
|
||||
}
|
||||
|
||||
/**
|
||||
* Insert or update an unavailability.
|
||||
*/
|
||||
public function ajax_save_unavailability(): void
|
||||
{
|
||||
redirect('calendar/save_unavailability');
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete an unavailability time period from database.
|
||||
*/
|
||||
public function ajax_delete_unavailability(): void
|
||||
{
|
||||
redirect('calendar/delete_unavailability');
|
||||
}
|
||||
|
||||
/**
|
||||
* Insert of update working plan exceptions to database.
|
||||
*/
|
||||
public function ajax_save_working_plan_exception(): void
|
||||
{
|
||||
redirect('calendar/save_working_plan_exception');
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete a working plan exceptions time period to database.
|
||||
*/
|
||||
public function ajax_delete_working_plan_exception(): void
|
||||
{
|
||||
redirect('calendar/delete_working_plan_exception');
|
||||
}
|
||||
|
||||
/**
|
||||
* Save (insert or update) a customer record.
|
||||
*/
|
||||
public function ajax_save_customer(): void
|
||||
{
|
||||
redirect('customers/create'); // or "customers/update"
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete customer from database.
|
||||
*/
|
||||
public function ajax_delete_customer(): void
|
||||
{
|
||||
redirect('customers/destroy');
|
||||
}
|
||||
|
||||
/**
|
||||
* Save (insert or update) service record.
|
||||
*/
|
||||
public function ajax_save_service(): void
|
||||
{
|
||||
redirect('services/create'); // or "services/update"
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete service record from database.
|
||||
*/
|
||||
public function ajax_delete_service(): void
|
||||
{
|
||||
redirect('services/destroy');
|
||||
}
|
||||
|
||||
/**
|
||||
* Filter service records by given key string.
|
||||
*/
|
||||
public function ajax_filter_services(): void
|
||||
{
|
||||
redirect('services/search');
|
||||
}
|
||||
|
||||
/**
|
||||
* Save (insert or update) category record.
|
||||
*/
|
||||
public function ajax_save_service_category(): void
|
||||
{
|
||||
redirect('categories/create'); // or "categories/update"
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete category record from database.
|
||||
*/
|
||||
public function ajax_delete_service_category(): void
|
||||
{
|
||||
redirect('categories/destroy');
|
||||
}
|
||||
|
||||
/**
|
||||
* Filter services categories with key string.
|
||||
*/
|
||||
public function ajax_filter_service_categories(): void
|
||||
{
|
||||
redirect('categories/search');
|
||||
}
|
||||
|
||||
/**
|
||||
* Filter admin records with string key.
|
||||
*/
|
||||
public function ajax_filter_admins(): void
|
||||
{
|
||||
redirect('admins/search');
|
||||
}
|
||||
|
||||
/**
|
||||
* Save (insert or update) admin record into database.
|
||||
*/
|
||||
public function ajax_save_admin(): void
|
||||
{
|
||||
redirect('admins/create'); // or "admins/update"
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete an admin record from the database.
|
||||
*/
|
||||
public function ajax_delete_admin(): void
|
||||
{
|
||||
redirect('admins/destroy');
|
||||
}
|
||||
|
||||
/**
|
||||
* Filter provider records with string key.
|
||||
*/
|
||||
public function ajax_filter_providers(): void
|
||||
{
|
||||
redirect('providers/search');
|
||||
}
|
||||
|
||||
/**
|
||||
* Save (insert or update) a provider record into database.
|
||||
*/
|
||||
public function ajax_save_provider(): void
|
||||
{
|
||||
redirect('providers/create'); // or "providers/update"
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete a provider record from the database.
|
||||
*/
|
||||
public function ajax_delete_provider(): void
|
||||
{
|
||||
redirect('providers/destroy');
|
||||
}
|
||||
|
||||
/**
|
||||
* Filter secretary records with string key.
|
||||
*/
|
||||
public function ajax_filter_secretaries(): void
|
||||
{
|
||||
redirect('secretaries/search');
|
||||
}
|
||||
|
||||
/**
|
||||
* Save (insert or update) a secretary record into database.
|
||||
*/
|
||||
public function ajax_save_secretary(): void
|
||||
{
|
||||
redirect('secretaries/create'); // or "secretaries/update"
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete a secretary record from the database.
|
||||
*/
|
||||
public function ajax_delete_secretary(): void
|
||||
{
|
||||
redirect('secretaries/destroy');
|
||||
}
|
||||
|
||||
/**
|
||||
* Save a setting or multiple settings in the database.
|
||||
*/
|
||||
public function ajax_save_settings(): void
|
||||
{
|
||||
redirect('general_settings/save'); // or "business_settings/save", "booking_settings/save", "legal_settings/save"
|
||||
}
|
||||
|
||||
/**
|
||||
* This method checks whether the username already exists in the database.
|
||||
*/
|
||||
public function ajax_validate_username(): void
|
||||
{
|
||||
redirect('account/validate_username');
|
||||
}
|
||||
|
||||
/**
|
||||
* Change system language for current user.
|
||||
*/
|
||||
public function ajax_change_language(): void
|
||||
{
|
||||
redirect('account/change_language');
|
||||
}
|
||||
|
||||
/**
|
||||
* This method will return a list of the available Google Calendars.
|
||||
*/
|
||||
public function ajax_get_google_calendars(): void
|
||||
{
|
||||
redirect('google/get_google_calendars');
|
||||
}
|
||||
|
||||
/**
|
||||
* Select a specific google calendar for a provider.
|
||||
*/
|
||||
public function ajax_select_google_calendar(): void
|
||||
{
|
||||
redirect('google/select_google_calendar');
|
||||
}
|
||||
|
||||
/**
|
||||
* Apply global working plan to all providers.
|
||||
*/
|
||||
public function ajax_apply_global_working_plan(): void
|
||||
{
|
||||
redirect('business_settings/apply_global_working_plan');
|
||||
}
|
||||
}
|
220
application/controllers/Blocked_periods.php
Normal file
220
application/controllers/Blocked_periods.php
Normal file
@ -0,0 +1,220 @@
|
||||
<?php defined('BASEPATH') or exit('No direct script access allowed');
|
||||
|
||||
/* ----------------------------------------------------------------------------
|
||||
* Easy!Appointments - Online Appointment Scheduler
|
||||
*
|
||||
* @package EasyAppointments
|
||||
* @author A.Tselegidis <alextselegidis@gmail.com>
|
||||
* @copyright Copyright (c) Alex Tselegidis
|
||||
* @license https://opensource.org/licenses/GPL-3.0 - GPLv3
|
||||
* @link https://easyappointments.org
|
||||
* @since v1.0.0
|
||||
* ---------------------------------------------------------------------------- */
|
||||
|
||||
/**
|
||||
* Blocked_periods controller.
|
||||
*
|
||||
* Handles the blocked-periods related operations.
|
||||
*
|
||||
* @package Controllers
|
||||
*/
|
||||
class Blocked_periods extends EA_Controller
|
||||
{
|
||||
public array $allowed_blocked_period_fields = ['id', 'name', 'start_datetime', 'end_datetime', 'notes'];
|
||||
|
||||
public array $optional_blocked_period_fields = [
|
||||
//
|
||||
];
|
||||
|
||||
/**
|
||||
* Blocked_periods constructor.
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
parent::__construct();
|
||||
|
||||
$this->load->model('blocked_periods_model');
|
||||
$this->load->model('roles_model');
|
||||
|
||||
$this->load->library('accounts');
|
||||
$this->load->library('timezones');
|
||||
$this->load->library('webhooks_client');
|
||||
}
|
||||
|
||||
/**
|
||||
* Render the backend blocked-periods page.
|
||||
*
|
||||
* On this page admin users will be able to manage blocked-periods, which are eventually selected by customers during the
|
||||
* booking process.
|
||||
*/
|
||||
public function index(): void
|
||||
{
|
||||
session(['dest_url' => site_url('blocked_periods')]);
|
||||
|
||||
$user_id = session('user_id');
|
||||
|
||||
if (cannot('view', PRIV_BLOCKED_PERIODS)) {
|
||||
if ($user_id) {
|
||||
abort(403, 'Forbidden');
|
||||
}
|
||||
|
||||
redirect('login');
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$role_slug = session('role_slug');
|
||||
|
||||
script_vars([
|
||||
'user_id' => $user_id,
|
||||
'role_slug' => $role_slug,
|
||||
'date_format' => setting('date_format'),
|
||||
'time_format' => setting('time_format'),
|
||||
'first_weekday' => setting('first_weekday'),
|
||||
]);
|
||||
|
||||
html_vars([
|
||||
'page_title' => lang('blocked_periods'),
|
||||
'active_menu' => PRIV_BLOCKED_PERIODS,
|
||||
'user_display_name' => $this->accounts->get_user_display_name($user_id),
|
||||
'timezones' => $this->timezones->to_array(),
|
||||
'privileges' => $this->roles_model->get_permissions_by_slug($role_slug),
|
||||
]);
|
||||
|
||||
$this->load->view('pages/blocked_periods');
|
||||
}
|
||||
|
||||
/**
|
||||
* Filter blocked-periods by the provided keyword.
|
||||
*/
|
||||
public function search(): void
|
||||
{
|
||||
try {
|
||||
if (cannot('view', PRIV_BLOCKED_PERIODS)) {
|
||||
abort(403, 'Forbidden');
|
||||
}
|
||||
|
||||
$keyword = request('keyword', '');
|
||||
|
||||
$order_by = request('order_by', 'update_datetime DESC');
|
||||
|
||||
$limit = request('limit', 1000);
|
||||
|
||||
$offset = (int) request('offset', '0');
|
||||
|
||||
$blocked_periods = $this->blocked_periods_model->search($keyword, $limit, $offset, $order_by);
|
||||
|
||||
json_response($blocked_periods);
|
||||
} catch (Throwable $e) {
|
||||
json_exception($e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Store a new service-category.
|
||||
*/
|
||||
public function store(): void
|
||||
{
|
||||
try {
|
||||
if (cannot('add', PRIV_BLOCKED_PERIODS)) {
|
||||
abort(403, 'Forbidden');
|
||||
}
|
||||
|
||||
$blocked_period = request('blocked_period');
|
||||
|
||||
$this->blocked_periods_model->only($blocked_period, $this->allowed_blocked_period_fields);
|
||||
|
||||
$this->blocked_periods_model->optional($blocked_period, $this->optional_blocked_period_fields);
|
||||
|
||||
$blocked_period_id = $this->blocked_periods_model->save($blocked_period);
|
||||
|
||||
$blocked_period = $this->blocked_periods_model->find($blocked_period_id);
|
||||
|
||||
$this->webhooks_client->trigger(WEBHOOK_BLOCKED_PERIOD_SAVE, $blocked_period);
|
||||
|
||||
json_response([
|
||||
'success' => true,
|
||||
'id' => $blocked_period_id,
|
||||
]);
|
||||
} catch (Throwable $e) {
|
||||
json_exception($e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Find a service-category.
|
||||
*/
|
||||
public function find(): void
|
||||
{
|
||||
try {
|
||||
if (cannot('view', PRIV_BLOCKED_PERIODS)) {
|
||||
abort(403, 'Forbidden');
|
||||
}
|
||||
|
||||
$blocked_period_id = request('blocked_period_id');
|
||||
|
||||
$blocked_period = $this->blocked_periods_model->find($blocked_period_id);
|
||||
|
||||
json_response($blocked_period);
|
||||
} catch (Throwable $e) {
|
||||
json_exception($e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Update a service-category.
|
||||
*/
|
||||
public function update(): void
|
||||
{
|
||||
try {
|
||||
if (cannot('edit', PRIV_BLOCKED_PERIODS)) {
|
||||
abort(403, 'Forbidden');
|
||||
}
|
||||
|
||||
$blocked_period = request('blocked_period');
|
||||
|
||||
$this->blocked_periods_model->only($blocked_period, $this->allowed_blocked_period_fields);
|
||||
|
||||
$this->blocked_periods_model->optional($blocked_period, $this->optional_blocked_period_fields);
|
||||
|
||||
$blocked_period_id = $this->blocked_periods_model->save($blocked_period);
|
||||
|
||||
$blocked_period = $this->blocked_periods_model->find($blocked_period_id);
|
||||
|
||||
$this->webhooks_client->trigger(WEBHOOK_BLOCKED_PERIOD_SAVE, $blocked_period);
|
||||
|
||||
json_response([
|
||||
'success' => true,
|
||||
'id' => $blocked_period_id,
|
||||
]);
|
||||
} catch (Throwable $e) {
|
||||
json_exception($e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove a service-category.
|
||||
*/
|
||||
public function destroy(): void
|
||||
{
|
||||
try {
|
||||
if (cannot('delete', PRIV_BLOCKED_PERIODS)) {
|
||||
abort(403, 'Forbidden');
|
||||
}
|
||||
|
||||
$blocked_period_id = request('blocked_period_id');
|
||||
|
||||
$blocked_period = $this->blocked_periods_model->find($blocked_period_id);
|
||||
|
||||
$this->blocked_periods_model->delete($blocked_period_id);
|
||||
|
||||
$this->webhooks_client->trigger(WEBHOOK_BLOCKED_PERIOD_DELETE, $blocked_period);
|
||||
|
||||
json_response([
|
||||
'success' => true,
|
||||
]);
|
||||
} catch (Throwable $e) {
|
||||
json_exception($e);
|
||||
}
|
||||
}
|
||||
}
|
772
application/controllers/Booking.php
Normal file
772
application/controllers/Booking.php
Normal file
@ -0,0 +1,772 @@
|
||||
<?php defined('BASEPATH') or exit('No direct script access allowed');
|
||||
|
||||
/* ----------------------------------------------------------------------------
|
||||
* Easy!Appointments - Online Appointment Scheduler
|
||||
*
|
||||
* @package EasyAppointments
|
||||
* @author A.Tselegidis <alextselegidis@gmail.com>
|
||||
* @copyright Copyright (c) Alex Tselegidis
|
||||
* @license https://opensource.org/licenses/GPL-3.0 - GPLv3
|
||||
* @link https://easyappointments.org
|
||||
* @since v1.0.0
|
||||
* ---------------------------------------------------------------------------- */
|
||||
|
||||
/**
|
||||
* Booking controller.
|
||||
*
|
||||
* Handles the booking related operations.
|
||||
*
|
||||
* Notice: This file used to have the booking page related code which since v1.5 has now moved to the Booking.php
|
||||
* controller for improved consistency.
|
||||
*
|
||||
* @package Controllers
|
||||
*/
|
||||
class Booking extends EA_Controller
|
||||
{
|
||||
public array $allowed_customer_fields = [
|
||||
'id',
|
||||
'first_name',
|
||||
'last_name',
|
||||
'email',
|
||||
'phone_number',
|
||||
'address',
|
||||
'city',
|
||||
'state',
|
||||
'zip_code',
|
||||
'timezone',
|
||||
'language',
|
||||
'custom_field_1',
|
||||
'custom_field_2',
|
||||
'custom_field_3',
|
||||
'custom_field_4',
|
||||
'custom_field_5',
|
||||
];
|
||||
public mixed $allowed_provider_fields = ['id', 'first_name', 'last_name', 'services', 'timezone'];
|
||||
public array $allowed_appointment_fields = [
|
||||
'id',
|
||||
'start_datetime',
|
||||
'end_datetime',
|
||||
'location',
|
||||
'notes',
|
||||
'color',
|
||||
'status',
|
||||
'is_unavailability',
|
||||
'id_users_provider',
|
||||
'id_users_customer',
|
||||
'id_services',
|
||||
];
|
||||
|
||||
/**
|
||||
* Booking constructor.
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
parent::__construct();
|
||||
|
||||
$this->load->model('appointments_model');
|
||||
$this->load->model('providers_model');
|
||||
$this->load->model('admins_model');
|
||||
$this->load->model('secretaries_model');
|
||||
$this->load->model('service_categories_model');
|
||||
$this->load->model('services_model');
|
||||
$this->load->model('customers_model');
|
||||
$this->load->model('settings_model');
|
||||
$this->load->model('consents_model');
|
||||
|
||||
$this->load->library('timezones');
|
||||
$this->load->library('synchronization');
|
||||
$this->load->library('notifications');
|
||||
$this->load->library('availability');
|
||||
$this->load->library('webhooks_client');
|
||||
}
|
||||
|
||||
/**
|
||||
* Render the booking page and display the selected appointment.
|
||||
*
|
||||
* This method will call the "index" callback to handle the page rendering.
|
||||
*
|
||||
* @param string $appointment_hash
|
||||
*/
|
||||
public function reschedule(string $appointment_hash): void
|
||||
{
|
||||
html_vars(['appointment_hash' => $appointment_hash]);
|
||||
|
||||
$this->index();
|
||||
}
|
||||
|
||||
/**
|
||||
* Render the booking page.
|
||||
*
|
||||
* This method creates the appointment book wizard.
|
||||
*/
|
||||
public function index(): void
|
||||
{
|
||||
if (!is_app_installed()) {
|
||||
redirect('installation');
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$company_name = setting('company_name');
|
||||
$company_logo = setting('company_logo');
|
||||
$company_color = setting('company_color');
|
||||
$disable_booking = setting('disable_booking');
|
||||
$google_analytics_code = setting('google_analytics_code');
|
||||
$matomo_analytics_url = setting('matomo_analytics_url');
|
||||
$matomo_analytics_site_id = setting('matomo_analytics_site_id');
|
||||
|
||||
if ($disable_booking) {
|
||||
$disable_booking_message = setting('disable_booking_message');
|
||||
|
||||
html_vars([
|
||||
'show_message' => true,
|
||||
'page_title' => lang('page_title') . ' ' . $company_name,
|
||||
'message_title' => lang('booking_is_disabled'),
|
||||
'message_text' => $disable_booking_message,
|
||||
'message_icon' => base_url('assets/img/error.png'),
|
||||
'google_analytics_code' => $google_analytics_code,
|
||||
'matomo_analytics_url' => $matomo_analytics_url,
|
||||
'matomo_analytics_site_id' => $matomo_analytics_site_id,
|
||||
]);
|
||||
|
||||
$this->load->view('pages/booking_message');
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$available_services = $this->services_model->get_available_services(true);
|
||||
$available_providers = $this->providers_model->get_available_providers(true);
|
||||
|
||||
foreach ($available_providers as &$available_provider) {
|
||||
// Only expose the required provider data.
|
||||
|
||||
$this->providers_model->only($available_provider, $this->allowed_provider_fields);
|
||||
}
|
||||
|
||||
$date_format = setting('date_format');
|
||||
$time_format = setting('time_format');
|
||||
$first_weekday = setting('first_weekday');
|
||||
$display_first_name = setting('display_first_name');
|
||||
$require_first_name = setting('require_first_name');
|
||||
$display_last_name = setting('display_last_name');
|
||||
$require_last_name = setting('require_last_name');
|
||||
$display_email = setting('display_email');
|
||||
$require_email = setting('require_email');
|
||||
$display_phone_number = setting('display_phone_number');
|
||||
$require_phone_number = setting('require_phone_number');
|
||||
$display_address = setting('display_address');
|
||||
$require_address = setting('require_address');
|
||||
$display_city = setting('display_city');
|
||||
$require_city = setting('require_city');
|
||||
$display_zip_code = setting('display_zip_code');
|
||||
$require_zip_code = setting('require_zip_code');
|
||||
$display_notes = setting('display_notes');
|
||||
$require_notes = setting('require_notes');
|
||||
$display_cookie_notice = setting('display_cookie_notice');
|
||||
$cookie_notice_content = setting('cookie_notice_content');
|
||||
$display_terms_and_conditions = setting('display_terms_and_conditions');
|
||||
$terms_and_conditions_content = setting('terms_and_conditions_content');
|
||||
$display_privacy_policy = setting('display_privacy_policy');
|
||||
$privacy_policy_content = setting('privacy_policy_content');
|
||||
$display_any_provider = setting('display_any_provider');
|
||||
$display_login_button = setting('display_login_button');
|
||||
$display_delete_personal_information = setting('display_delete_personal_information');
|
||||
$book_advance_timeout = setting('book_advance_timeout');
|
||||
$theme = request('theme', setting('theme', 'default'));
|
||||
|
||||
if (empty($theme) || !file_exists(__DIR__ . '/../../assets/css/themes/' . $theme . '.min.css')) {
|
||||
$theme = 'default';
|
||||
}
|
||||
|
||||
$timezones = $this->timezones->to_array();
|
||||
$grouped_timezones = $this->timezones->to_grouped_array();
|
||||
|
||||
$appointment_hash = html_vars('appointment_hash');
|
||||
|
||||
if (!empty($appointment_hash)) {
|
||||
// Load the appointments data and enable the manage mode of the booking page.
|
||||
|
||||
$manage_mode = true;
|
||||
|
||||
$results = $this->appointments_model->get(['hash' => $appointment_hash]);
|
||||
|
||||
if (empty($results)) {
|
||||
html_vars([
|
||||
'show_message' => true,
|
||||
'page_title' => lang('page_title') . ' ' . $company_name,
|
||||
'message_title' => lang('appointment_not_found'),
|
||||
'message_text' => lang('appointment_does_not_exist_in_db'),
|
||||
'message_icon' => base_url('assets/img/error.png'),
|
||||
'google_analytics_code' => $google_analytics_code,
|
||||
'matomo_analytics_url' => $matomo_analytics_url,
|
||||
'matomo_analytics_site_id' => $matomo_analytics_site_id,
|
||||
]);
|
||||
|
||||
$this->load->view('pages/booking_message');
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// Make sure the appointment can still be rescheduled.
|
||||
|
||||
$start_datetime = strtotime($results[0]['start_datetime']);
|
||||
|
||||
$limit = strtotime('+' . $book_advance_timeout . ' minutes', strtotime('now'));
|
||||
|
||||
if ($start_datetime < $limit) {
|
||||
$hours = floor($book_advance_timeout / 60);
|
||||
|
||||
$minutes = $book_advance_timeout % 60;
|
||||
|
||||
html_vars([
|
||||
'show_message' => true,
|
||||
'page_title' => lang('page_title') . ' ' . $company_name,
|
||||
'message_title' => lang('appointment_locked'),
|
||||
'message_text' => strtr(lang('appointment_locked_message'), [
|
||||
'{$limit}' => sprintf('%02d:%02d', $hours, $minutes),
|
||||
]),
|
||||
'message_icon' => base_url('assets/img/error.png'),
|
||||
'google_analytics_code' => $google_analytics_code,
|
||||
'matomo_analytics_url' => $matomo_analytics_url,
|
||||
'matomo_analytics_site_id' => $matomo_analytics_site_id,
|
||||
]);
|
||||
|
||||
$this->load->view('pages/booking_message');
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$appointment = $results[0];
|
||||
$provider = $this->providers_model->find($appointment['id_users_provider']);
|
||||
$customer = $this->customers_model->find($appointment['id_users_customer']);
|
||||
$customer_token = md5(uniqid(mt_rand(), true));
|
||||
|
||||
// Cache the token for 10 minutes.
|
||||
$this->cache->save('customer-token-' . $customer_token, $customer['id'], 600);
|
||||
} else {
|
||||
$manage_mode = false;
|
||||
$customer_token = false;
|
||||
$appointment = null;
|
||||
$provider = null;
|
||||
$customer = null;
|
||||
}
|
||||
|
||||
script_vars([
|
||||
'manage_mode' => $manage_mode,
|
||||
'available_services' => $available_services,
|
||||
'available_providers' => $available_providers,
|
||||
'date_format' => $date_format,
|
||||
'time_format' => $time_format,
|
||||
'first_weekday' => $first_weekday,
|
||||
'display_cookie_notice' => $display_cookie_notice,
|
||||
'display_any_provider' => setting('display_any_provider'),
|
||||
'future_booking_limit' => setting('future_booking_limit'),
|
||||
'appointment_data' => $appointment,
|
||||
'provider_data' => $provider,
|
||||
'customer_data' => $customer,
|
||||
'default_language' => setting('default_language'),
|
||||
'default_timezone' => setting('default_timezone'),
|
||||
]);
|
||||
|
||||
html_vars([
|
||||
'available_services' => $available_services,
|
||||
'available_providers' => $available_providers,
|
||||
'theme' => $theme,
|
||||
'company_name' => $company_name,
|
||||
'company_logo' => $company_logo,
|
||||
'company_color' => $company_color === '#ffffff' ? '' : $company_color,
|
||||
'date_format' => $date_format,
|
||||
'time_format' => $time_format,
|
||||
'first_weekday' => $first_weekday,
|
||||
'display_first_name' => $display_first_name,
|
||||
'require_first_name' => $require_first_name,
|
||||
'display_last_name' => $display_last_name,
|
||||
'require_last_name' => $require_last_name,
|
||||
'display_email' => $display_email,
|
||||
'require_email' => $require_email,
|
||||
'display_phone_number' => $display_phone_number,
|
||||
'require_phone_number' => $require_phone_number,
|
||||
'display_address' => $display_address,
|
||||
'require_address' => $require_address,
|
||||
'display_city' => $display_city,
|
||||
'require_city' => $require_city,
|
||||
'display_zip_code' => $display_zip_code,
|
||||
'require_zip_code' => $require_zip_code,
|
||||
'display_notes' => $display_notes,
|
||||
'require_notes' => $require_notes,
|
||||
'display_cookie_notice' => $display_cookie_notice,
|
||||
'cookie_notice_content' => $cookie_notice_content,
|
||||
'display_terms_and_conditions' => $display_terms_and_conditions,
|
||||
'terms_and_conditions_content' => $terms_and_conditions_content,
|
||||
'display_privacy_policy' => $display_privacy_policy,
|
||||
'privacy_policy_content' => $privacy_policy_content,
|
||||
'display_any_provider' => $display_any_provider,
|
||||
'display_login_button' => $display_login_button,
|
||||
'display_delete_personal_information' => $display_delete_personal_information,
|
||||
'google_analytics_code' => $google_analytics_code,
|
||||
'matomo_analytics_url' => $matomo_analytics_url,
|
||||
'matomo_analytics_site_id' => $matomo_analytics_site_id,
|
||||
'timezones' => $timezones,
|
||||
'grouped_timezones' => $grouped_timezones,
|
||||
'manage_mode' => $manage_mode,
|
||||
'customer_token' => $customer_token,
|
||||
'appointment_data' => $appointment,
|
||||
'provider_data' => $provider,
|
||||
'customer_data' => $customer,
|
||||
]);
|
||||
|
||||
$this->load->view('pages/booking');
|
||||
}
|
||||
|
||||
/**
|
||||
* Register the appointment to the database.
|
||||
*/
|
||||
public function register(): void
|
||||
{
|
||||
try {
|
||||
$disable_booking = setting('disable_booking');
|
||||
|
||||
if ($disable_booking) {
|
||||
abort(403);
|
||||
}
|
||||
|
||||
$post_data = request('post_data');
|
||||
$captcha = request('captcha');
|
||||
$appointment = $post_data['appointment'];
|
||||
$customer = $post_data['customer'];
|
||||
$manage_mode = filter_var($post_data['manage_mode'], FILTER_VALIDATE_BOOLEAN);
|
||||
|
||||
if (!array_key_exists('address', $customer)) {
|
||||
$customer['address'] = '';
|
||||
}
|
||||
|
||||
if (!array_key_exists('city', $customer)) {
|
||||
$customer['city'] = '';
|
||||
}
|
||||
|
||||
if (!array_key_exists('zip_code', $customer)) {
|
||||
$customer['zip_code'] = '';
|
||||
}
|
||||
|
||||
if (!array_key_exists('notes', $customer)) {
|
||||
$customer['notes'] = '';
|
||||
}
|
||||
|
||||
if (!array_key_exists('phone_number', $customer)) {
|
||||
$customer['phone_number'] = '';
|
||||
}
|
||||
|
||||
// Check appointment availability before registering it to the database.
|
||||
$appointment['id_users_provider'] = $this->check_datetime_availability();
|
||||
|
||||
if (!$appointment['id_users_provider']) {
|
||||
throw new RuntimeException(lang('requested_hour_is_unavailable'));
|
||||
}
|
||||
|
||||
$provider = $this->providers_model->find($appointment['id_users_provider']);
|
||||
|
||||
$service = $this->services_model->find($appointment['id_services']);
|
||||
|
||||
$require_captcha = (bool) setting('require_captcha');
|
||||
|
||||
$captcha_phrase = session('captcha_phrase');
|
||||
|
||||
// Validate the CAPTCHA string.
|
||||
|
||||
if ($require_captcha && strtoupper($captcha_phrase) !== strtoupper($captcha)) {
|
||||
json_response([
|
||||
'captcha_verification' => false,
|
||||
]);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if ($this->customers_model->exists($customer)) {
|
||||
$customer['id'] = $this->customers_model->find_record_id($customer);
|
||||
|
||||
$existing_appointments = $this->appointments_model->get([
|
||||
'id !=' => $manage_mode ? $appointment['id'] : null,
|
||||
'id_users_customer' => $customer['id'],
|
||||
'start_datetime <=' => $appointment['start_datetime'],
|
||||
'end_datetime >=' => $appointment['end_datetime'],
|
||||
]);
|
||||
|
||||
if (count($existing_appointments)) {
|
||||
throw new RuntimeException(lang('customer_is_already_booked'));
|
||||
}
|
||||
}
|
||||
|
||||
if (empty($appointment['location']) && !empty($service['location'])) {
|
||||
$appointment['location'] = $service['location'];
|
||||
}
|
||||
|
||||
if (empty($appointment['color']) && !empty($service['color'])) {
|
||||
$appointment['color'] = $service['color'];
|
||||
}
|
||||
|
||||
$customer_ip = $this->input->ip_address();
|
||||
|
||||
// Create the consents (if needed).
|
||||
$consent = [
|
||||
'first_name' => $customer['first_name'] ?? '-',
|
||||
'last_name' => $customer['last_name'] ?? '-',
|
||||
'email' => $customer['email'] ?? '-',
|
||||
'ip' => $customer_ip,
|
||||
];
|
||||
|
||||
if (setting('display_terms_and_conditions')) {
|
||||
$consent['type'] = 'terms-and-conditions';
|
||||
|
||||
$this->consents_model->save($consent);
|
||||
}
|
||||
|
||||
if (setting('display_privacy_policy')) {
|
||||
$consent['type'] = 'privacy-policy';
|
||||
|
||||
$this->consents_model->save($consent);
|
||||
}
|
||||
|
||||
// Save customer language (the language which is used to render the booking page).
|
||||
$customer['language'] = session('language') ?? config('language');
|
||||
|
||||
$this->customers_model->only($customer, $this->allowed_customer_fields);
|
||||
|
||||
$customer_id = $this->customers_model->save($customer);
|
||||
$customer = $this->customers_model->find($customer_id);
|
||||
|
||||
$appointment['id_users_customer'] = $customer_id;
|
||||
$appointment['is_unavailability'] = false;
|
||||
$appointment['color'] = $service['color'];
|
||||
|
||||
$appointment_status_options_json = setting('appointment_status_options', '[]');
|
||||
$appointment_status_options = json_decode($appointment_status_options_json, true) ?? [];
|
||||
$appointment['status'] = $appointment_status_options[0] ?? null;
|
||||
|
||||
$this->appointments_model->only($appointment, $this->allowed_appointment_fields);
|
||||
|
||||
$appointment_id = $this->appointments_model->save($appointment);
|
||||
$appointment = $this->appointments_model->find($appointment_id);
|
||||
|
||||
$company_color = setting('company_color');
|
||||
|
||||
$settings = [
|
||||
'company_name' => setting('company_name'),
|
||||
'company_link' => setting('company_link'),
|
||||
'company_email' => setting('company_email'),
|
||||
'company_color' => !empty($company_color) && $company_color != DEFAULT_COMPANY_COLOR ? $company_color : null,
|
||||
'date_format' => setting('date_format'),
|
||||
'time_format' => setting('time_format'),
|
||||
];
|
||||
|
||||
$this->synchronization->sync_appointment_saved($appointment, $service, $provider, $customer, $settings);
|
||||
|
||||
$this->notifications->notify_appointment_saved(
|
||||
$appointment,
|
||||
$service,
|
||||
$provider,
|
||||
$customer,
|
||||
$settings,
|
||||
$manage_mode,
|
||||
);
|
||||
|
||||
$this->webhooks_client->trigger(WEBHOOK_APPOINTMENT_SAVE, $appointment);
|
||||
|
||||
$response = [
|
||||
'appointment_id' => $appointment['id'],
|
||||
'appointment_hash' => $appointment['hash'],
|
||||
];
|
||||
|
||||
json_response($response);
|
||||
} catch (Throwable $e) {
|
||||
json_exception($e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check whether the provider is still available in the selected appointment date.
|
||||
*
|
||||
* It is possible that two or more customers select the same appointment date and time concurrently. The app won't
|
||||
* allow this to happen, so one of the two will eventually get the selected date and the other one will have
|
||||
* to choose for another one.
|
||||
*
|
||||
* Use this method just before the customer confirms the appointment registration. If the selected date was reserved
|
||||
* in the meanwhile, the customer must be prompted to select another time.
|
||||
*
|
||||
* @return int|null Returns the ID of the provider that is available for the appointment.
|
||||
*
|
||||
* @throws Exception
|
||||
*/
|
||||
protected function check_datetime_availability(): ?int
|
||||
{
|
||||
$post_data = request('post_data');
|
||||
|
||||
$appointment = $post_data['appointment'];
|
||||
|
||||
$appointment_start = new DateTime($appointment['start_datetime']);
|
||||
|
||||
$date = $appointment_start->format('Y-m-d');
|
||||
|
||||
$hour = $appointment_start->format('H:i');
|
||||
|
||||
if ($appointment['id_users_provider'] === ANY_PROVIDER) {
|
||||
$appointment['id_users_provider'] = $this->search_any_provider($appointment['id_services'], $date, $hour);
|
||||
|
||||
return $appointment['id_users_provider'];
|
||||
}
|
||||
|
||||
$service = $this->services_model->find($appointment['id_services']);
|
||||
|
||||
$exclude_appointment_id = $appointment['id'] ?? null;
|
||||
|
||||
$provider = $this->providers_model->find($appointment['id_users_provider']);
|
||||
|
||||
$available_hours = $this->availability->get_available_hours(
|
||||
$date,
|
||||
$service,
|
||||
$provider,
|
||||
$exclude_appointment_id,
|
||||
);
|
||||
|
||||
$is_still_available = false;
|
||||
|
||||
$appointment_hour = date('H:i', strtotime($appointment['start_datetime']));
|
||||
|
||||
foreach ($available_hours as $available_hour) {
|
||||
if ($appointment_hour === $available_hour) {
|
||||
$is_still_available = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return $is_still_available ? $appointment['id_users_provider'] : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Search for any provider that can handle the requested service.
|
||||
*
|
||||
* This method will return the database ID of the provider with the most available periods.
|
||||
*
|
||||
* @param int $service_id Service ID
|
||||
* @param string $date Selected date (Y-m-d).
|
||||
* @param string|null $hour Selected hour (H:i).
|
||||
*
|
||||
* @return int|null Returns the ID of the provider that can provide the service at the selected date.
|
||||
*
|
||||
* @throws Exception
|
||||
*/
|
||||
protected function search_any_provider(int $service_id, string $date, ?string $hour = null): ?int
|
||||
{
|
||||
$available_providers = $this->providers_model->get_available_providers(true);
|
||||
|
||||
$service = $this->services_model->find($service_id);
|
||||
|
||||
$provider_id = null;
|
||||
|
||||
$max_hours_count = 0;
|
||||
|
||||
foreach ($available_providers as $provider) {
|
||||
foreach ($provider['services'] as $provider_service_id) {
|
||||
if ($provider_service_id == $service_id) {
|
||||
// Check if the provider is available for the requested date.
|
||||
$available_hours = $this->availability->get_available_hours($date, $service, $provider);
|
||||
|
||||
if (
|
||||
count($available_hours) > $max_hours_count &&
|
||||
(empty($hour) || in_array($hour, $available_hours))
|
||||
) {
|
||||
$provider_id = $provider['id'];
|
||||
|
||||
$max_hours_count = count($available_hours);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $provider_id;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the available appointment hours for the selected date.
|
||||
*
|
||||
* This method answers to an AJAX request. It calculates the available hours for the given service, provider and
|
||||
* date.
|
||||
*/
|
||||
public function get_available_hours(): void
|
||||
{
|
||||
try {
|
||||
$disable_booking = setting('disable_booking');
|
||||
|
||||
if ($disable_booking) {
|
||||
abort(403);
|
||||
}
|
||||
|
||||
$provider_id = request('provider_id');
|
||||
$service_id = request('service_id');
|
||||
$selected_date = request('selected_date');
|
||||
|
||||
// Do not continue if there was no provider selected (more likely there is no provider in the system).
|
||||
|
||||
if (empty($provider_id)) {
|
||||
json_response();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// If manage mode is TRUE then the following we should not consider the selected appointment when
|
||||
// calculating the available time periods of the provider.
|
||||
|
||||
$exclude_appointment_id = request('manage_mode') ? request('appointment_id') : null;
|
||||
|
||||
// If the user has selected the "any-provider" option then we will need to search for an available provider
|
||||
// that will provide the requested service.
|
||||
|
||||
$service = $this->services_model->find($service_id);
|
||||
|
||||
if ($provider_id === ANY_PROVIDER) {
|
||||
$providers = $this->providers_model->get();
|
||||
|
||||
$available_hours = [];
|
||||
|
||||
foreach ($providers as $provider) {
|
||||
if (!in_array($service_id, $provider['services'])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$provider_available_hours = $this->availability->get_available_hours(
|
||||
$selected_date,
|
||||
$service,
|
||||
$provider,
|
||||
$exclude_appointment_id,
|
||||
);
|
||||
|
||||
$available_hours = array_merge($available_hours, $provider_available_hours);
|
||||
}
|
||||
|
||||
$available_hours = array_unique(array_values($available_hours));
|
||||
|
||||
sort($available_hours);
|
||||
|
||||
$response = $available_hours;
|
||||
} else {
|
||||
$provider = $this->providers_model->find($provider_id);
|
||||
|
||||
$response = $this->availability->get_available_hours(
|
||||
$selected_date,
|
||||
$service,
|
||||
$provider,
|
||||
$exclude_appointment_id,
|
||||
);
|
||||
}
|
||||
|
||||
json_response($response);
|
||||
} catch (Throwable $e) {
|
||||
json_exception($e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get Unavailable Dates
|
||||
*
|
||||
* Get an array with the available dates of a specific provider, service and month of the year. Provide the
|
||||
* "provider_id", "service_id" and "selected_date" as GET parameters to the request. The "selected_date" parameter
|
||||
* must have the "Y-m-d" format.
|
||||
*
|
||||
* Outputs a JSON string with the unavailability dates. that are unavailability.
|
||||
*/
|
||||
public function get_unavailable_dates(): void
|
||||
{
|
||||
try {
|
||||
$disable_booking = setting('disable_booking');
|
||||
|
||||
if ($disable_booking) {
|
||||
abort(403);
|
||||
}
|
||||
|
||||
$provider_id = request('provider_id');
|
||||
$service_id = request('service_id');
|
||||
$appointment_id = request('appointment_id');
|
||||
$manage_mode = filter_var(request('manage_mode'), FILTER_VALIDATE_BOOLEAN);
|
||||
$selected_date_string = request('selected_date');
|
||||
$selected_date = new DateTime($selected_date_string);
|
||||
$number_of_days_in_month = (int) $selected_date->format('t');
|
||||
$unavailable_dates = [];
|
||||
|
||||
$provider_ids =
|
||||
$provider_id === ANY_PROVIDER ? $this->search_providers_by_service($service_id) : [$provider_id];
|
||||
|
||||
$exclude_appointment_id = $manage_mode ? $appointment_id : null;
|
||||
|
||||
// Get the service record.
|
||||
$service = $this->services_model->find($service_id);
|
||||
|
||||
for ($i = 1; $i <= $number_of_days_in_month; $i++) {
|
||||
$current_date = new DateTime($selected_date->format('Y-m') . '-' . $i);
|
||||
|
||||
if ($current_date < new DateTime(date('Y-m-d 00:00:00'))) {
|
||||
// Past dates become immediately unavailability.
|
||||
$unavailable_dates[] = $current_date->format('Y-m-d');
|
||||
continue;
|
||||
}
|
||||
|
||||
// Finding at least one slot of availability.
|
||||
foreach ($provider_ids as $current_provider_id) {
|
||||
$provider = $this->providers_model->find($current_provider_id);
|
||||
|
||||
$available_hours = $this->availability->get_available_hours(
|
||||
$current_date->format('Y-m-d'),
|
||||
$service,
|
||||
$provider,
|
||||
$exclude_appointment_id,
|
||||
);
|
||||
|
||||
if (!empty($available_hours)) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// No availability amongst all the provider.
|
||||
if (empty($available_hours)) {
|
||||
$unavailable_dates[] = $current_date->format('Y-m-d');
|
||||
}
|
||||
}
|
||||
|
||||
if (count($unavailable_dates) === $number_of_days_in_month) {
|
||||
json_response([
|
||||
'is_month_unavailable' => true,
|
||||
]);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
json_response($unavailable_dates);
|
||||
} catch (Throwable $e) {
|
||||
json_exception($e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Search for any provider that can handle the requested service.
|
||||
*
|
||||
* This method will return the database ID of the providers affected to the requested service.
|
||||
*
|
||||
* @param int $service_id The requested service ID.
|
||||
*
|
||||
* @return array Returns the ID of the provider that can provide the requested service.
|
||||
*/
|
||||
protected function search_providers_by_service(int $service_id): array
|
||||
{
|
||||
$available_providers = $this->providers_model->get_available_providers(true);
|
||||
$provider_list = [];
|
||||
|
||||
foreach ($available_providers as $provider) {
|
||||
foreach ($provider['services'] as $provider_service_id) {
|
||||
if ($provider_service_id === $service_id) {
|
||||
// Check if the provider is affected to the selected service.
|
||||
$provider_list[] = $provider['id'];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $provider_list;
|
||||
}
|
||||
}
|
130
application/controllers/Booking_cancellation.php
Normal file
130
application/controllers/Booking_cancellation.php
Normal file
@ -0,0 +1,130 @@
|
||||
<?php defined('BASEPATH') or exit('No direct script access allowed');
|
||||
|
||||
/* ----------------------------------------------------------------------------
|
||||
* Easy!Appointments - Online Appointment Scheduler
|
||||
*
|
||||
* @package EasyAppointments
|
||||
* @author A.Tselegidis <alextselegidis@gmail.com>
|
||||
* @copyright Copyright (c) Alex Tselegidis
|
||||
* @license https://opensource.org/licenses/GPL-3.0 - GPLv3
|
||||
* @link https://easyappointments.org
|
||||
* @since v1.5.0
|
||||
* ---------------------------------------------------------------------------- */
|
||||
|
||||
/**
|
||||
* Booking cancellation controller.
|
||||
*
|
||||
* Handles the booking cancellation related operations.
|
||||
*
|
||||
* @package Controllers
|
||||
*/
|
||||
class Booking_cancellation extends EA_Controller
|
||||
{
|
||||
/**
|
||||
* Booking_cancellation constructor.
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
parent::__construct();
|
||||
|
||||
$this->load->model('appointments_model');
|
||||
$this->load->model('providers_model');
|
||||
$this->load->model('services_model');
|
||||
$this->load->model('customers_model');
|
||||
|
||||
$this->load->library('synchronization');
|
||||
$this->load->library('notifications');
|
||||
$this->load->library('webhooks_client');
|
||||
}
|
||||
|
||||
/**
|
||||
* Cancel an existing appointment.
|
||||
*
|
||||
* This method removes an appointment from the company's schedule. In order for the appointment to be deleted, the
|
||||
* hash string must be provided. The customer can only cancel the appointment if the edit time period is not over
|
||||
* yet.
|
||||
*
|
||||
* @param string $appointment_hash This appointment hash identifier.
|
||||
*/
|
||||
public function of(string $appointment_hash): void
|
||||
{
|
||||
try {
|
||||
$disable_booking = setting('disable_booking');
|
||||
|
||||
if ($disable_booking) {
|
||||
abort(403);
|
||||
}
|
||||
|
||||
$cancellation_reason = request('cancellation_reason');
|
||||
|
||||
if ($this->input->method() !== 'post' || empty($cancellation_reason)) {
|
||||
abort(403, 'Forbidden');
|
||||
}
|
||||
|
||||
$occurrences = $this->appointments_model->get(['hash' => $appointment_hash]);
|
||||
|
||||
if (empty($occurrences)) {
|
||||
html_vars([
|
||||
'page_title' => lang('appointment_not_found'),
|
||||
'company_color' => setting('company_color'),
|
||||
'message_title' => lang('appointment_not_found'),
|
||||
'message_text' => lang('appointment_does_not_exist_in_db'),
|
||||
'message_icon' => base_url('assets/img/error.png'),
|
||||
'google_analytics_code' => setting('google_analytics_code'),
|
||||
'matomo_analytics_url' => setting('matomo_analytics_url'),
|
||||
'matomo_analytics_site_id' => setting('matomo_analytics_site_id'),
|
||||
]);
|
||||
|
||||
$this->load->view('pages/booking_message');
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$appointment = $occurrences[0];
|
||||
|
||||
$provider = $this->providers_model->find($appointment['id_users_provider']);
|
||||
|
||||
$customer = $this->customers_model->find($appointment['id_users_customer']);
|
||||
|
||||
$service = $this->services_model->find($appointment['id_services']);
|
||||
|
||||
$company_color = setting('company_color');
|
||||
|
||||
$settings = [
|
||||
'company_name' => setting('company_name'),
|
||||
'company_email' => setting('company_email'),
|
||||
'company_link' => setting('company_link'),
|
||||
'company_color' => !empty($company_color) && $company_color != DEFAULT_COMPANY_COLOR ? $company_color : null,
|
||||
'date_format' => setting('date_format'),
|
||||
'time_format' => setting('time_format'),
|
||||
];
|
||||
|
||||
$this->appointments_model->delete($appointment['id']);
|
||||
|
||||
$this->synchronization->sync_appointment_deleted($appointment, $provider);
|
||||
|
||||
$this->notifications->notify_appointment_deleted(
|
||||
$appointment,
|
||||
$service,
|
||||
$provider,
|
||||
$customer,
|
||||
$settings,
|
||||
$cancellation_reason,
|
||||
);
|
||||
|
||||
$this->webhooks_client->trigger(WEBHOOK_APPOINTMENT_DELETE, $appointment);
|
||||
} catch (Throwable $e) {
|
||||
log_message('error', 'Booking Cancellation Exception: ' . $e->getMessage());
|
||||
}
|
||||
|
||||
html_vars([
|
||||
'page_title' => lang('appointment_cancelled_title'),
|
||||
'company_color' => setting('company_color'),
|
||||
'google_analytics_code' => setting('google_analytics_code'),
|
||||
'matomo_analytics_url' => setting('matomo_analytics_url'),
|
||||
'matomo_analytics_site_id' => setting('matomo_analytics_site_id'),
|
||||
]);
|
||||
|
||||
$this->load->view('pages/booking_cancellation');
|
||||
}
|
||||
}
|
70
application/controllers/Booking_confirmation.php
Normal file
70
application/controllers/Booking_confirmation.php
Normal file
@ -0,0 +1,70 @@
|
||||
<?php defined('BASEPATH') or exit('No direct script access allowed');
|
||||
|
||||
/* ----------------------------------------------------------------------------
|
||||
* Easy!Appointments - Online Appointment Scheduler
|
||||
*
|
||||
* @package EasyAppointments
|
||||
* @author A.Tselegidis <alextselegidis@gmail.com>
|
||||
* @copyright Copyright (c) Alex Tselegidis
|
||||
* @license https://opensource.org/licenses/GPL-3.0 - GPLv3
|
||||
* @link https://easyappointments.org
|
||||
* @since v1.5.0
|
||||
* ---------------------------------------------------------------------------- */
|
||||
|
||||
/**
|
||||
* Booking confirmation controller.
|
||||
*
|
||||
* Handles the booking confirmation related operations.
|
||||
*
|
||||
* @package Controllers
|
||||
*/
|
||||
class Booking_confirmation extends EA_Controller
|
||||
{
|
||||
/**
|
||||
* Booking_confirmation constructor.
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
parent::__construct();
|
||||
|
||||
$this->load->model('appointments_model');
|
||||
$this->load->model('providers_model');
|
||||
$this->load->model('services_model');
|
||||
$this->load->model('customers_model');
|
||||
|
||||
$this->load->library('google_sync');
|
||||
}
|
||||
|
||||
/**
|
||||
* Display the appointment registration success page.
|
||||
*
|
||||
* @throws Exception
|
||||
*/
|
||||
public function of(): void
|
||||
{
|
||||
$appointment_hash = $this->uri->segment(3);
|
||||
|
||||
$occurrences = $this->appointments_model->get(['hash' => $appointment_hash]);
|
||||
|
||||
if (empty($occurrences)) {
|
||||
redirect('appointments'); // The appointment does not exist.
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$appointment = $occurrences[0];
|
||||
|
||||
$add_to_google_url = $this->google_sync->get_add_to_google_url($appointment['id']);
|
||||
|
||||
html_vars([
|
||||
'page_title' => lang('success'),
|
||||
'company_color' => setting('company_color'),
|
||||
'google_analytics_code' => setting('google_analytics_code'),
|
||||
'matomo_analytics_url' => setting('matomo_analytics_url'),
|
||||
'matomo_analytics_site_id' => setting('matomo_analytics_site_id'),
|
||||
'add_to_google_url' => $add_to_google_url,
|
||||
]);
|
||||
|
||||
$this->load->view('pages/booking_confirmation');
|
||||
}
|
||||
}
|
121
application/controllers/Booking_settings.php
Normal file
121
application/controllers/Booking_settings.php
Normal file
@ -0,0 +1,121 @@
|
||||
<?php defined('BASEPATH') or exit('No direct script access allowed');
|
||||
|
||||
/* ----------------------------------------------------------------------------
|
||||
* Easy!Appointments - Online Appointment Scheduler
|
||||
*
|
||||
* @package EasyAppointments
|
||||
* @author A.Tselegidis <alextselegidis@gmail.com>
|
||||
* @copyright Copyright (c) Alex Tselegidis
|
||||
* @license https://opensource.org/licenses/GPL-3.0 - GPLv3
|
||||
* @link https://easyappointments.org
|
||||
* @since v1.5.0
|
||||
* ---------------------------------------------------------------------------- */
|
||||
|
||||
/**
|
||||
* Booking settings controller.
|
||||
*
|
||||
* Handles booking settings related operations.
|
||||
*
|
||||
* @package Controllers
|
||||
*/
|
||||
class Booking_settings extends EA_Controller
|
||||
{
|
||||
public array $allowed_setting_fields = ['id', 'name', 'value'];
|
||||
|
||||
public array $optional_setting_fields = [
|
||||
//
|
||||
];
|
||||
|
||||
/**
|
||||
* Booking_settings constructor.
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
parent::__construct();
|
||||
|
||||
$this->load->model('appointments_model');
|
||||
$this->load->model('customers_model');
|
||||
$this->load->model('services_model');
|
||||
$this->load->model('providers_model');
|
||||
$this->load->model('roles_model');
|
||||
$this->load->model('settings_model');
|
||||
|
||||
$this->load->library('accounts');
|
||||
$this->load->library('google_sync');
|
||||
$this->load->library('notifications');
|
||||
$this->load->library('synchronization');
|
||||
$this->load->library('timezones');
|
||||
}
|
||||
|
||||
/**
|
||||
* Render the settings page.
|
||||
*/
|
||||
public function index(): void
|
||||
{
|
||||
session(['dest_url' => site_url('booking_settings')]);
|
||||
|
||||
$user_id = session('user_id');
|
||||
|
||||
if (cannot('view', PRIV_SYSTEM_SETTINGS)) {
|
||||
if ($user_id) {
|
||||
abort(403, 'Forbidden');
|
||||
}
|
||||
|
||||
redirect('login');
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$role_slug = session('role_slug');
|
||||
|
||||
script_vars([
|
||||
'user_id' => $user_id,
|
||||
'role_slug' => $role_slug,
|
||||
'booking_settings' => $this->settings_model->get_batch(),
|
||||
]);
|
||||
|
||||
html_vars([
|
||||
'page_title' => lang('settings'),
|
||||
'active_menu' => PRIV_SYSTEM_SETTINGS,
|
||||
'user_display_name' => $this->accounts->get_user_display_name($user_id),
|
||||
]);
|
||||
|
||||
$this->load->view('pages/booking_settings');
|
||||
}
|
||||
|
||||
/**
|
||||
* Save booking settings.
|
||||
*/
|
||||
public function save(): void
|
||||
{
|
||||
try {
|
||||
if (cannot('edit', PRIV_SYSTEM_SETTINGS)) {
|
||||
throw new RuntimeException('You do not have the required permissions for this task.');
|
||||
}
|
||||
|
||||
$settings = request('booking_settings', []);
|
||||
|
||||
foreach ($settings as $setting) {
|
||||
$existing_setting = $this->settings_model
|
||||
->query()
|
||||
->where('name', $setting['name'])
|
||||
->get()
|
||||
->row_array();
|
||||
|
||||
if (!empty($existing_setting)) {
|
||||
$setting['id'] = $existing_setting['id'];
|
||||
}
|
||||
|
||||
$this->settings_model->only($setting, $this->allowed_setting_fields);
|
||||
|
||||
$this->settings_model->optional($setting, $this->optional_setting_fields);
|
||||
|
||||
$this->settings_model->save($setting);
|
||||
}
|
||||
|
||||
response();
|
||||
} catch (Throwable $e) {
|
||||
json_exception($e);
|
||||
}
|
||||
}
|
||||
}
|
147
application/controllers/Business_settings.php
Normal file
147
application/controllers/Business_settings.php
Normal file
@ -0,0 +1,147 @@
|
||||
<?php defined('BASEPATH') or exit('No direct script access allowed');
|
||||
|
||||
/* ----------------------------------------------------------------------------
|
||||
* Easy!Appointments - Online Appointment Scheduler
|
||||
*
|
||||
* @package EasyAppointments
|
||||
* @author A.Tselegidis <alextselegidis@gmail.com>
|
||||
* @copyright Copyright (c) Alex Tselegidis
|
||||
* @license https://opensource.org/licenses/GPL-3.0 - GPLv3
|
||||
* @link https://easyappointments.org
|
||||
* @since v1.5.0
|
||||
* ---------------------------------------------------------------------------- */
|
||||
|
||||
/**
|
||||
* Business logic controller.
|
||||
*
|
||||
* Handles general settings related operations.
|
||||
*
|
||||
* @package Controllers
|
||||
*/
|
||||
class Business_settings extends EA_Controller
|
||||
{
|
||||
public array $allowed_setting_fields = ['id', 'name', 'value'];
|
||||
|
||||
public array $optional_setting_fields = [
|
||||
//
|
||||
];
|
||||
|
||||
/**
|
||||
* Business_logic constructor.
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
parent::__construct();
|
||||
|
||||
$this->load->model('appointments_model');
|
||||
$this->load->model('customers_model');
|
||||
$this->load->model('services_model');
|
||||
$this->load->model('providers_model');
|
||||
$this->load->model('roles_model');
|
||||
$this->load->model('settings_model');
|
||||
|
||||
$this->load->library('accounts');
|
||||
$this->load->library('google_sync');
|
||||
$this->load->library('notifications');
|
||||
$this->load->library('synchronization');
|
||||
$this->load->library('timezones');
|
||||
}
|
||||
|
||||
/**
|
||||
* Render the settings page.
|
||||
*/
|
||||
public function index(): void
|
||||
{
|
||||
session(['dest_url' => site_url('business_settings')]);
|
||||
|
||||
$user_id = session('user_id');
|
||||
|
||||
if (cannot('view', PRIV_SYSTEM_SETTINGS)) {
|
||||
if ($user_id) {
|
||||
abort(403, 'Forbidden');
|
||||
}
|
||||
|
||||
redirect('login');
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$role_slug = session('role_slug');
|
||||
|
||||
script_vars([
|
||||
'user_id' => $user_id,
|
||||
'role_slug' => $role_slug,
|
||||
'business_settings' => $this->settings_model->get(),
|
||||
'first_weekday' => setting('first_weekday'),
|
||||
'time_format' => setting('time_format'),
|
||||
]);
|
||||
|
||||
html_vars([
|
||||
'page_title' => lang('settings'),
|
||||
'active_menu' => PRIV_SYSTEM_SETTINGS,
|
||||
'user_display_name' => $this->accounts->get_user_display_name($user_id),
|
||||
]);
|
||||
|
||||
$this->load->view('pages/business_settings');
|
||||
}
|
||||
|
||||
/**
|
||||
* Save general settings.
|
||||
*/
|
||||
public function save(): void
|
||||
{
|
||||
try {
|
||||
if (cannot('edit', PRIV_SYSTEM_SETTINGS)) {
|
||||
throw new RuntimeException('You do not have the required permissions for this task.');
|
||||
}
|
||||
|
||||
$settings = request('business_settings', []);
|
||||
|
||||
foreach ($settings as $setting) {
|
||||
$existing_setting = $this->settings_model
|
||||
->query()
|
||||
->where('name', $setting['name'])
|
||||
->get()
|
||||
->row_array();
|
||||
|
||||
if (!empty($existing_setting)) {
|
||||
$setting['id'] = $existing_setting['id'];
|
||||
}
|
||||
|
||||
$this->settings_model->only($setting, $this->allowed_setting_fields);
|
||||
|
||||
$this->settings_model->optional($setting, $this->optional_setting_fields);
|
||||
|
||||
$this->settings_model->save($setting);
|
||||
}
|
||||
|
||||
response();
|
||||
} catch (Throwable $e) {
|
||||
json_exception($e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Apply global working plan to all providers.
|
||||
*/
|
||||
public function apply_global_working_plan(): void
|
||||
{
|
||||
try {
|
||||
if (cannot('edit', PRIV_SYSTEM_SETTINGS)) {
|
||||
throw new RuntimeException('You do not have the required permissions for this task.');
|
||||
}
|
||||
|
||||
$working_plan = request('working_plan');
|
||||
|
||||
$providers = $this->providers_model->get();
|
||||
|
||||
foreach ($providers as $provider) {
|
||||
$this->providers_model->set_setting($provider['id'], 'working_plan', $working_plan);
|
||||
}
|
||||
|
||||
response();
|
||||
} catch (Throwable $e) {
|
||||
json_exception($e);
|
||||
}
|
||||
}
|
||||
}
|
315
application/controllers/Caldav.php
Normal file
315
application/controllers/Caldav.php
Normal file
@ -0,0 +1,315 @@
|
||||
<?php defined('BASEPATH') or exit('No direct script access allowed');
|
||||
|
||||
/* ----------------------------------------------------------------------------
|
||||
* Easy!Appointments - Online Appointment Scheduler
|
||||
*
|
||||
* @package EasyAppointments
|
||||
* @author A.Tselegidis <alextselegidis@gmail.com>
|
||||
* @copyright Copyright (c) Alex Tselegidis
|
||||
* @license https://opensource.org/licenses/GPL-3.0 - GPLv3
|
||||
* @link https://easyappointments.org
|
||||
* @since v1.5.0
|
||||
* ---------------------------------------------------------------------------- */
|
||||
|
||||
use GuzzleHttp\Exception\GuzzleException;
|
||||
use Jsvrcek\ICS\Exception\CalendarEventException;
|
||||
|
||||
/**
|
||||
* Caldav controller.
|
||||
*
|
||||
* Handles the Caldav Calendar synchronization related operations.
|
||||
*
|
||||
* @package Controllers
|
||||
*/
|
||||
class Caldav extends EA_Controller
|
||||
{
|
||||
/**
|
||||
* Caldav constructor.
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
parent::__construct();
|
||||
|
||||
$this->load->library('caldav_sync');
|
||||
|
||||
$this->load->model('appointments_model');
|
||||
$this->load->model('unavailabilities_model');
|
||||
$this->load->model('providers_model');
|
||||
}
|
||||
|
||||
/**
|
||||
* Connect to the target CalDAV server
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function connect_to_server(): void
|
||||
{
|
||||
try {
|
||||
$provider_id = request('provider_id');
|
||||
|
||||
$user_id = session('user_id');
|
||||
|
||||
if (cannot('edit', PRIV_USERS) && (int) $user_id !== (int) $provider_id) {
|
||||
throw new RuntimeException('You do not have the required permissions for this task.');
|
||||
}
|
||||
|
||||
$caldav_url = request('caldav_url');
|
||||
$caldav_username = request('caldav_username');
|
||||
$caldav_password = request('caldav_password');
|
||||
|
||||
$this->caldav_sync->test_connection($caldav_url, $caldav_username, $caldav_password);
|
||||
|
||||
$provider = $this->providers_model->find($provider_id);
|
||||
|
||||
$provider['settings']['caldav_sync'] = true;
|
||||
$provider['settings']['caldav_url'] = $caldav_url;
|
||||
$provider['settings']['caldav_username'] = $caldav_username;
|
||||
$provider['settings']['caldav_password'] = $caldav_password;
|
||||
|
||||
$this->providers_model->save($provider);
|
||||
|
||||
json_response([
|
||||
'success' => true,
|
||||
]);
|
||||
} catch (GuzzleException | InvalidArgumentException $e) {
|
||||
json_response([
|
||||
'success' => false,
|
||||
'message' => $e->getMessage(),
|
||||
]);
|
||||
} catch (Throwable $e) {
|
||||
json_exception($e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sync the provider events with the remote CalDAV calendar.
|
||||
*
|
||||
* @param string $provider_id Provider ID (String because this is used with HTTP and CLI)
|
||||
*
|
||||
* @return void
|
||||
*
|
||||
* @throws CalendarEventException
|
||||
* @throws Exception
|
||||
* @throws Throwable
|
||||
*/
|
||||
public static function sync(string $provider_id): void
|
||||
{
|
||||
/** @var EA_Controller $CI */
|
||||
$CI = get_instance();
|
||||
|
||||
$CI->load->library('caldav_sync');
|
||||
|
||||
// Load the libraries as this method is called statically from the CLI command
|
||||
|
||||
$CI->load->model('appointments_model');
|
||||
$CI->load->model('unavailabilities_model');
|
||||
$CI->load->model('providers_model');
|
||||
$CI->load->model('services_model');
|
||||
$CI->load->model('customers_model');
|
||||
$CI->load->model('settings_model');
|
||||
|
||||
$user_id = session('user_id');
|
||||
|
||||
if (!$user_id && !is_cli()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (empty($provider_id)) {
|
||||
throw new InvalidArgumentException('No provider ID provided.');
|
||||
}
|
||||
|
||||
$provider = $CI->providers_model->find($provider_id);
|
||||
|
||||
// Check whether the selected provider has the CalDAV Sync enabled.
|
||||
|
||||
if (!$provider['settings']['caldav_sync']) {
|
||||
return; // The selected provider does not have the CalDAV Sync enabled.
|
||||
}
|
||||
|
||||
// Fetch provider's appointments that belong to the sync time period.
|
||||
|
||||
$sync_past_days = $provider['settings']['sync_past_days'];
|
||||
|
||||
$sync_future_days = $provider['settings']['sync_future_days'];
|
||||
|
||||
$start_date_time = date('Y-m-d H:i:s', strtotime('-' . $sync_past_days . ' days'));
|
||||
|
||||
$end_date_time = date('Y-m-d H:i:s', strtotime('+' . $sync_future_days . ' days'));
|
||||
|
||||
$where = [
|
||||
'start_datetime >=' => $start_date_time,
|
||||
'end_datetime <=' => $end_date_time,
|
||||
'id_users_provider' => $provider['id'],
|
||||
];
|
||||
|
||||
$appointments = $CI->appointments_model->get($where);
|
||||
|
||||
$unavailabilities = $CI->unavailabilities_model->get($where);
|
||||
|
||||
$local_events = [...$appointments, ...$unavailabilities];
|
||||
|
||||
// Sync each appointment with CalDAV Calendar by following the project's sync protocol (see documentation).
|
||||
|
||||
foreach ($local_events as $local_event) {
|
||||
if (str_contains((string) $local_event['id_caldav_calendar'], 'RECURRENCE')) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!$local_event['is_unavailability']) {
|
||||
$service = $CI->services_model->find($local_event['id_services']);
|
||||
$customer = $CI->customers_model->find($local_event['id_users_customer']);
|
||||
$events_model = $CI->appointments_model;
|
||||
} else {
|
||||
$service = null;
|
||||
$customer = null;
|
||||
$events_model = $CI->unavailabilities_model;
|
||||
}
|
||||
|
||||
if (!$local_event['id_caldav_calendar']) {
|
||||
if (!$local_event['is_unavailability']) {
|
||||
$caldav_event_id = $CI->caldav_sync->save_appointment($local_event, $service, $provider, $customer);
|
||||
} else {
|
||||
$caldav_event_id = $CI->caldav_sync->save_unavailability($local_event, $provider);
|
||||
}
|
||||
|
||||
$local_event['id_caldav_calendar'] = $caldav_event_id;
|
||||
|
||||
$events_model->save($local_event); // Save the CalDAV Calendar ID.
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
// Appointment is synced with CalDAV Calendar.
|
||||
|
||||
try {
|
||||
$caldav_event = $CI->caldav_sync->get_event($provider, $local_event['id_caldav_calendar']);
|
||||
|
||||
if (!$caldav_event || $caldav_event['status'] === 'CANCELLED') {
|
||||
throw new Exception('Event is cancelled, remove the record from Easy!Appointments.');
|
||||
}
|
||||
|
||||
// If CalDAV Calendar event is different from Easy!Appointments appointment then update Easy!Appointments record.
|
||||
$local_event_start = strtotime($local_event['start_datetime']);
|
||||
$local_event_end = strtotime($local_event['end_datetime']);
|
||||
|
||||
$caldav_event_start = new DateTime($caldav_event['start_datetime']);
|
||||
$caldav_event_end = new DateTime($caldav_event['end_datetime']);
|
||||
|
||||
$is_different =
|
||||
$local_event_start !== $caldav_event_start->getTimestamp() ||
|
||||
$local_event_end !== $caldav_event_end->getTimestamp() ||
|
||||
$local_event['notes'] !== $caldav_event['description'];
|
||||
|
||||
if ($is_different) {
|
||||
$local_event['start_datetime'] = $caldav_event_start->format('Y-m-d H:i:s');
|
||||
$local_event['end_datetime'] = $caldav_event_end->format('Y-m-d H:i:s');
|
||||
$local_event['notes'] = $caldav_event['description'];
|
||||
$events_model->save($local_event);
|
||||
}
|
||||
} catch (Throwable) {
|
||||
// Appointment not found on CalDAV Calendar, delete from Easy!Appointments.
|
||||
$events_model->delete($local_event['id']);
|
||||
|
||||
$local_event['id_caldav_calendar'] = null;
|
||||
}
|
||||
}
|
||||
|
||||
// Add CalDAV Calendar events that do not exist in Easy!Appointments.
|
||||
|
||||
try {
|
||||
$caldav_events = $CI->caldav_sync->get_sync_events($provider, $start_date_time, $end_date_time);
|
||||
} catch (Throwable $e) {
|
||||
if ($e->getCode() === 404) {
|
||||
log_message('error', 'CalDAV - Remote Calendar not found for provider ID: ' . $provider_id);
|
||||
|
||||
return; // The remote calendar was not found.
|
||||
} else {
|
||||
throw $e;
|
||||
}
|
||||
}
|
||||
|
||||
$CI->appointments_model->delete_caldav_recurring_events($start_date_time, $end_date_time);
|
||||
|
||||
foreach ($caldav_events as $caldav_event) {
|
||||
if ($caldav_event['status'] === 'CANCELLED') {
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($caldav_event['start_datetime'] === $caldav_event['end_datetime']) {
|
||||
continue; // Cannot sync events with the same start and end date time value
|
||||
}
|
||||
|
||||
$appointment_results = $CI->appointments_model->get(['id_caldav_calendar' => $caldav_event['id']]);
|
||||
|
||||
if (!empty($appointment_results)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$unavailability_results = $CI->unavailabilities_model->get([
|
||||
'id_caldav_calendar' => $caldav_event['id'],
|
||||
]);
|
||||
|
||||
if (!empty($unavailability_results)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Record doesn't exist in the Easy!Appointments, so add the event now.
|
||||
|
||||
$local_event = [
|
||||
'start_datetime' => $caldav_event['start_datetime'],
|
||||
'end_datetime' => $caldav_event['end_datetime'],
|
||||
'location' => $caldav_event['location'],
|
||||
'notes' => $caldav_event['summary'] . ' ' . $caldav_event['description'],
|
||||
'id_users_provider' => $provider_id,
|
||||
'id_caldav_calendar' => $caldav_event['id'],
|
||||
];
|
||||
|
||||
$CI->unavailabilities_model->save($local_event);
|
||||
}
|
||||
|
||||
json_response([
|
||||
'success' => true,
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Disable a providers sync setting.
|
||||
*
|
||||
* This method resets the CalDAV related settings from the user_settings DB table.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function disable_provider_sync(): void
|
||||
{
|
||||
try {
|
||||
$provider_id = request('provider_id');
|
||||
|
||||
if (!$provider_id) {
|
||||
throw new Exception('Provider id not specified.');
|
||||
}
|
||||
|
||||
$user_id = session('user_id');
|
||||
|
||||
if (cannot('edit', PRIV_USERS) && (int) $user_id !== (int) $provider_id) {
|
||||
throw new RuntimeException('You do not have the required permissions for this task.');
|
||||
}
|
||||
|
||||
$provider = $this->providers_model->find($provider_id);
|
||||
|
||||
$provider['settings']['caldav_sync'] = false;
|
||||
$provider['settings']['caldav_url'] = null;
|
||||
$provider['settings']['caldav_username'] = null;
|
||||
$provider['settings']['caldav_password'] = null;
|
||||
|
||||
$this->providers_model->save($provider);
|
||||
|
||||
$this->appointments_model->clear_caldav_sync_ids($provider_id);
|
||||
|
||||
json_response([
|
||||
'success' => true,
|
||||
]);
|
||||
} catch (Throwable $e) {
|
||||
json_exception($e);
|
||||
}
|
||||
}
|
||||
}
|
807
application/controllers/Calendar.php
Normal file
807
application/controllers/Calendar.php
Normal file
@ -0,0 +1,807 @@
|
||||
<?php defined('BASEPATH') or exit('No direct script access allowed');
|
||||
|
||||
/* ----------------------------------------------------------------------------
|
||||
* Easy!Appointments - Online Appointment Scheduler
|
||||
*
|
||||
* @package EasyAppointments
|
||||
* @author A.Tselegidis <alextselegidis@gmail.com>
|
||||
* @copyright Copyright (c) Alex Tselegidis
|
||||
* @license https://opensource.org/licenses/GPL-3.0 - GPLv3
|
||||
* @link https://easyappointments.org
|
||||
* @since v1.5.0
|
||||
* ---------------------------------------------------------------------------- */
|
||||
|
||||
/**
|
||||
* Calendar controller.
|
||||
*
|
||||
* Handles calendar related operations.
|
||||
*
|
||||
* @package Controllers
|
||||
*/
|
||||
class Calendar extends EA_Controller
|
||||
{
|
||||
public array $allowed_customer_fields = [
|
||||
'id',
|
||||
'first_name',
|
||||
'last_name',
|
||||
'email',
|
||||
'phone_number',
|
||||
'address',
|
||||
'city',
|
||||
'state',
|
||||
'zip_code',
|
||||
'timezone',
|
||||
'language',
|
||||
'notes',
|
||||
'custom_field_1',
|
||||
'custom_field_2',
|
||||
'custom_field_3',
|
||||
'custom_field_4',
|
||||
'custom_field_5',
|
||||
];
|
||||
|
||||
public array $optional_customer_fields = [
|
||||
//
|
||||
];
|
||||
|
||||
public array $allowed_appointment_fields = [
|
||||
'id',
|
||||
'start_datetime',
|
||||
'end_datetime',
|
||||
'location',
|
||||
'notes',
|
||||
'color',
|
||||
'status',
|
||||
'is_unavailability',
|
||||
'id_users_provider',
|
||||
'id_users_customer',
|
||||
'id_services',
|
||||
];
|
||||
|
||||
public array $optional_appointment_fields = [
|
||||
//
|
||||
];
|
||||
|
||||
/**
|
||||
* Calendar constructor.
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
parent::__construct();
|
||||
|
||||
$this->load->model('appointments_model');
|
||||
$this->load->model('unavailabilities_model');
|
||||
$this->load->model('blocked_periods_model');
|
||||
$this->load->model('customers_model');
|
||||
$this->load->model('services_model');
|
||||
$this->load->model('providers_model');
|
||||
$this->load->model('roles_model');
|
||||
|
||||
$this->load->library('accounts');
|
||||
$this->load->library('google_sync');
|
||||
$this->load->library('notifications');
|
||||
$this->load->library('synchronization');
|
||||
$this->load->library('timezones');
|
||||
$this->load->library('webhooks_client');
|
||||
}
|
||||
|
||||
/**
|
||||
* Render the calendar page and display the selected appointment.
|
||||
*
|
||||
* This method will call the "index" callback to handle the page rendering.
|
||||
*
|
||||
* @param string $appointment_hash Appointment hash.
|
||||
*/
|
||||
public function reschedule(string $appointment_hash): void
|
||||
{
|
||||
$this->index($appointment_hash);
|
||||
}
|
||||
|
||||
/**
|
||||
* Display the main backend page.
|
||||
*
|
||||
* This method displays the main backend page. All login permission can view this page which displays a calendar
|
||||
* with the events of the selected provider or service. If a user has more privileges he will see more menus at the
|
||||
* top of the page.
|
||||
*
|
||||
* @param string $appointment_hash Appointment hash.
|
||||
*/
|
||||
public function index(string $appointment_hash = ''): void
|
||||
{
|
||||
session([
|
||||
'dest_url' => site_url('calendar/index' . (!empty($appointment_hash) ? '/' . $appointment_hash : '')),
|
||||
]);
|
||||
|
||||
$user_id = session('user_id');
|
||||
|
||||
if (cannot('view', PRIV_APPOINTMENTS)) {
|
||||
if ($user_id) {
|
||||
abort(403, 'Forbidden');
|
||||
}
|
||||
|
||||
redirect('login');
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$role_slug = session('role_slug');
|
||||
|
||||
$user = $this->users_model->find($user_id);
|
||||
|
||||
$secretary_providers = [];
|
||||
|
||||
if ($role_slug === DB_SLUG_SECRETARY) {
|
||||
$secretary = $this->secretaries_model->find(session('user_id'));
|
||||
|
||||
$secretary_providers = $secretary['providers'];
|
||||
}
|
||||
|
||||
$edit_appointment = null;
|
||||
|
||||
if (!empty($appointment_hash)) {
|
||||
$occurrences = $this->appointments_model->get(['hash' => $appointment_hash]);
|
||||
|
||||
if ($appointment_hash !== '' && !empty($occurrences)) {
|
||||
$edit_appointment = $occurrences[0];
|
||||
|
||||
$this->appointments_model->load($edit_appointment, ['customer']);
|
||||
}
|
||||
}
|
||||
|
||||
$privileges = $this->roles_model->get_permissions_by_slug($role_slug);
|
||||
|
||||
$available_providers = $this->providers_model->get_available_providers();
|
||||
|
||||
if ($role_slug === DB_SLUG_PROVIDER) {
|
||||
$available_providers = array_values(
|
||||
array_filter($available_providers, function ($available_provider) use ($user_id) {
|
||||
return (int) $available_provider['id'] === (int) $user_id;
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
if ($role_slug === DB_SLUG_SECRETARY) {
|
||||
$available_providers = array_values(
|
||||
array_filter($available_providers, function ($available_provider) use ($secretary_providers) {
|
||||
return in_array($available_provider['id'], $secretary_providers);
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
$available_services = $this->services_model->get_available_services();
|
||||
|
||||
$calendar_view = request('view', $user['settings']['calendar_view']);
|
||||
|
||||
$appointment_status_options = setting('appointment_status_options');
|
||||
|
||||
script_vars([
|
||||
'user_id' => $user_id,
|
||||
'role_slug' => $role_slug,
|
||||
'date_format' => setting('date_format'),
|
||||
'time_format' => setting('time_format'),
|
||||
'first_weekday' => setting('first_weekday'),
|
||||
'company_working_plan' => setting('company_working_plan'),
|
||||
'timezones' => $this->timezones->to_array(),
|
||||
'privileges' => $privileges,
|
||||
'calendar_view' => $calendar_view,
|
||||
'available_providers' => $available_providers,
|
||||
'available_services' => $available_services,
|
||||
'secretary_providers' => $secretary_providers,
|
||||
'edit_appointment' => $edit_appointment,
|
||||
'google_sync_feature' => config('google_sync_feature'),
|
||||
'customers' => $this->customers_model->get(null, 50, null, 'update_datetime DESC'),
|
||||
'default_language' => setting('default_language'),
|
||||
'default_timezone' => setting('default_timezone'),
|
||||
]);
|
||||
|
||||
html_vars([
|
||||
'page_title' => lang('calendar'),
|
||||
'active_menu' => PRIV_APPOINTMENTS,
|
||||
'user_display_name' => $this->accounts->get_user_display_name($user_id),
|
||||
'timezone' => session('timezone'),
|
||||
'timezones' => $this->timezones->to_array(),
|
||||
'grouped_timezones' => $this->timezones->to_grouped_array(),
|
||||
'privileges' => $privileges,
|
||||
'calendar_view' => $calendar_view,
|
||||
'available_providers' => $available_providers,
|
||||
'available_services' => $available_services,
|
||||
'secretary_providers' => $secretary_providers,
|
||||
'appointment_status_options' => json_decode($appointment_status_options, true) ?? [],
|
||||
'require_first_name' => setting('require_first_name'),
|
||||
'require_last_name' => setting('require_last_name'),
|
||||
'require_email' => setting('require_email'),
|
||||
'require_phone_number' => setting('require_phone_number'),
|
||||
'require_address' => setting('require_address'),
|
||||
'require_city' => setting('require_city'),
|
||||
'require_zip_code' => setting('require_zip_code'),
|
||||
'require_notes' => setting('require_notes'),
|
||||
]);
|
||||
|
||||
$this->load->view('pages/calendar');
|
||||
}
|
||||
|
||||
/**
|
||||
* Save appointment changes that are made from the backend calendar page.
|
||||
*/
|
||||
public function save_appointment(): void
|
||||
{
|
||||
try {
|
||||
$customer_data = request('customer_data');
|
||||
|
||||
$appointment_data = request('appointment_data');
|
||||
|
||||
$this->check_event_permissions((int) $appointment_data['id_users_provider']);
|
||||
|
||||
// Save customer changes to the database.
|
||||
if ($customer_data) {
|
||||
$customer = $customer_data;
|
||||
|
||||
$required_permissions = !empty($customer['id'])
|
||||
? can('add', PRIV_CUSTOMERS)
|
||||
: can('edit', PRIV_CUSTOMERS);
|
||||
|
||||
if (!$required_permissions) {
|
||||
throw new RuntimeException('You do not have the required permissions for this task.');
|
||||
}
|
||||
|
||||
$this->customers_model->only($customer, $this->allowed_customer_fields);
|
||||
|
||||
$this->customers_model->optional($customer, $this->optional_customer_fields);
|
||||
|
||||
$customer['id'] = $this->customers_model->save($customer);
|
||||
}
|
||||
|
||||
// Save appointment changes to the database.
|
||||
$manage_mode = !empty($appointment_data['id']);
|
||||
|
||||
if ($appointment_data) {
|
||||
$appointment = $appointment_data;
|
||||
|
||||
$required_permissions = !empty($appointment['id'])
|
||||
? can('add', PRIV_APPOINTMENTS)
|
||||
: can('edit', PRIV_APPOINTMENTS);
|
||||
|
||||
if (!$required_permissions) {
|
||||
throw new RuntimeException('You do not have the required permissions for this task.');
|
||||
}
|
||||
|
||||
// If the appointment does not contain the customer record id, then it means that is going to be inserted.
|
||||
|
||||
if (!isset($appointment['id_users_customer'])) {
|
||||
$appointment['id_users_customer'] = $customer['id'] ?? $customer_data['id'];
|
||||
}
|
||||
|
||||
if ($manage_mode && !empty($appointment['id'])) {
|
||||
$this->synchronization->remove_appointment_on_provider_change($appointment['id']);
|
||||
}
|
||||
|
||||
$this->appointments_model->only($appointment, $this->allowed_appointment_fields);
|
||||
|
||||
$this->appointments_model->optional($appointment, $this->optional_appointment_fields);
|
||||
|
||||
$appointment['id'] = $this->appointments_model->save($appointment);
|
||||
}
|
||||
|
||||
if (empty($appointment['id'])) {
|
||||
throw new RuntimeException('The appointment ID is not available.');
|
||||
}
|
||||
|
||||
$appointment = $this->appointments_model->find($appointment['id']);
|
||||
$provider = $this->providers_model->find($appointment['id_users_provider']);
|
||||
$customer = $this->customers_model->find($appointment['id_users_customer']);
|
||||
$service = $this->services_model->find($appointment['id_services']);
|
||||
|
||||
$company_color = setting('company_color');
|
||||
|
||||
$settings = [
|
||||
'company_name' => setting('company_name'),
|
||||
'company_link' => setting('company_link'),
|
||||
'company_email' => setting('company_email'),
|
||||
'company_color' => !empty($company_color) && $company_color != DEFAULT_COMPANY_COLOR ? $company_color : null,
|
||||
'date_format' => setting('date_format'),
|
||||
'time_format' => setting('time_format'),
|
||||
];
|
||||
|
||||
$this->synchronization->sync_appointment_saved($appointment, $service, $provider, $customer, $settings);
|
||||
|
||||
$this->notifications->notify_appointment_saved(
|
||||
$appointment,
|
||||
$service,
|
||||
$provider,
|
||||
$customer,
|
||||
$settings,
|
||||
$manage_mode,
|
||||
);
|
||||
|
||||
$this->webhooks_client->trigger(WEBHOOK_APPOINTMENT_SAVE, $appointment);
|
||||
|
||||
json_response([
|
||||
'success' => true,
|
||||
]);
|
||||
} catch (Throwable $e) {
|
||||
json_exception($e);
|
||||
}
|
||||
}
|
||||
|
||||
private function check_event_permissions(int $provider_id): void
|
||||
{
|
||||
$user_id = (int) session('user_id');
|
||||
$role_slug = session('role_slug');
|
||||
|
||||
if (
|
||||
$role_slug === DB_SLUG_SECRETARY &&
|
||||
!$this->secretaries_model->is_provider_supported($user_id, $provider_id)
|
||||
) {
|
||||
abort(403);
|
||||
}
|
||||
|
||||
if ($role_slug === DB_SLUG_PROVIDER && $user_id !== $provider_id) {
|
||||
abort(403);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete appointment from the database.
|
||||
*
|
||||
* This method deletes an existing appointment from the database. Once this action is finished it cannot be undone.
|
||||
* Notification emails are send to both provider and customer and the delete action is executed to the Google
|
||||
* Calendar account of the provider, if the "google_sync" setting is enabled.
|
||||
*/
|
||||
public function delete_appointment(): void
|
||||
{
|
||||
try {
|
||||
if (cannot('delete', 'appointments')) {
|
||||
throw new RuntimeException('You do not have the required permissions for this task.');
|
||||
}
|
||||
|
||||
$appointment_id = request('appointment_id');
|
||||
$cancellation_reason = (string) request('cancellation_reason');
|
||||
|
||||
if (empty($appointment_id)) {
|
||||
throw new InvalidArgumentException('No appointment id provided.');
|
||||
}
|
||||
|
||||
// Store appointment data for later use in this method.
|
||||
$appointment = $this->appointments_model->find($appointment_id);
|
||||
|
||||
$this->check_event_permissions((int) $appointment['id_users_provider']);
|
||||
|
||||
$provider = $this->providers_model->find($appointment['id_users_provider']);
|
||||
$customer = $this->customers_model->find($appointment['id_users_customer']);
|
||||
$service = $this->services_model->find($appointment['id_services']);
|
||||
|
||||
$company_color = setting('company_color');
|
||||
|
||||
$settings = [
|
||||
'company_name' => setting('company_name'),
|
||||
'company_email' => setting('company_email'),
|
||||
'company_link' => setting('company_link'),
|
||||
'company_color' => !empty($company_color) && $company_color != DEFAULT_COMPANY_COLOR ? $company_color : null,
|
||||
'date_format' => setting('date_format'),
|
||||
'time_format' => setting('time_format'),
|
||||
];
|
||||
|
||||
// Delete appointment record from the database.
|
||||
$this->appointments_model->delete($appointment_id);
|
||||
|
||||
$this->notifications->notify_appointment_deleted(
|
||||
$appointment,
|
||||
$service,
|
||||
$provider,
|
||||
$customer,
|
||||
$settings,
|
||||
$cancellation_reason,
|
||||
);
|
||||
|
||||
$this->synchronization->sync_appointment_deleted($appointment, $provider);
|
||||
|
||||
$this->webhooks_client->trigger(WEBHOOK_APPOINTMENT_DELETE, $appointment);
|
||||
|
||||
json_response([
|
||||
'success' => true,
|
||||
]);
|
||||
} catch (Throwable $e) {
|
||||
json_exception($e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Insert of update unavailability to database.
|
||||
*/
|
||||
public function save_unavailability(): void
|
||||
{
|
||||
try {
|
||||
// Check privileges
|
||||
$unavailability = request('unavailability');
|
||||
|
||||
$required_permissions = !isset($unavailability['id'])
|
||||
? can('add', PRIV_APPOINTMENTS)
|
||||
: can('edit', PRIV_APPOINTMENTS);
|
||||
|
||||
if (!$required_permissions) {
|
||||
throw new RuntimeException('You do not have the required permissions for this task.');
|
||||
}
|
||||
|
||||
$provider_id = (int) $unavailability['id_users_provider'];
|
||||
|
||||
$this->check_event_permissions($provider_id);
|
||||
|
||||
$provider = $this->providers_model->find($provider_id);
|
||||
|
||||
$unavailability_id = $this->unavailabilities_model->save($unavailability);
|
||||
|
||||
$unavailability = $this->unavailabilities_model->find($unavailability_id);
|
||||
|
||||
$this->synchronization->sync_unavailability_saved($unavailability, $provider);
|
||||
|
||||
$this->webhooks_client->trigger(WEBHOOK_UNAVAILABILITY_SAVE, $unavailability);
|
||||
|
||||
json_response([
|
||||
'success' => true,
|
||||
'warnings' => $warnings ?? [],
|
||||
]);
|
||||
} catch (Throwable $e) {
|
||||
json_exception($e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete an unavailability from database.
|
||||
*/
|
||||
public function delete_unavailability(): void
|
||||
{
|
||||
try {
|
||||
if (cannot('delete', PRIV_APPOINTMENTS)) {
|
||||
throw new RuntimeException('You do not have the required permissions for this task.');
|
||||
}
|
||||
|
||||
$unavailability_id = request('unavailability_id');
|
||||
|
||||
$unavailability = $this->unavailabilities_model->find($unavailability_id);
|
||||
|
||||
$this->check_event_permissions((int) $unavailability['id_users_provider']);
|
||||
|
||||
$provider = $this->providers_model->find($unavailability['id_users_provider']);
|
||||
|
||||
$this->unavailabilities_model->delete($unavailability_id);
|
||||
|
||||
$this->synchronization->sync_unavailability_deleted($unavailability, $provider);
|
||||
|
||||
$this->webhooks_client->trigger(WEBHOOK_UNAVAILABILITY_DELETE, $unavailability);
|
||||
|
||||
json_response([
|
||||
'success' => true,
|
||||
]);
|
||||
} catch (Throwable $e) {
|
||||
json_exception($e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Insert of update working plan exceptions to database.
|
||||
*/
|
||||
public function save_working_plan_exception(): void
|
||||
{
|
||||
try {
|
||||
if (cannot('edit', PRIV_USERS)) {
|
||||
throw new RuntimeException('You do not have the required permissions for this task.');
|
||||
}
|
||||
|
||||
$date = request('date');
|
||||
|
||||
$original_date = request('original_date');
|
||||
|
||||
$working_plan_exception = request('working_plan_exception');
|
||||
|
||||
if (!$working_plan_exception) {
|
||||
$working_plan_exception = null;
|
||||
}
|
||||
|
||||
$provider_id = request('provider_id');
|
||||
|
||||
$this->providers_model->save_working_plan_exception($provider_id, $date, $working_plan_exception);
|
||||
|
||||
if ($original_date && $date !== $original_date) {
|
||||
$this->providers_model->delete_working_plan_exception($provider_id, $original_date);
|
||||
}
|
||||
|
||||
json_response([
|
||||
'success' => true,
|
||||
]);
|
||||
} catch (Throwable $e) {
|
||||
json_exception($e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete a working plan exceptions time period to database.
|
||||
*/
|
||||
public function delete_working_plan_exception(): void
|
||||
{
|
||||
try {
|
||||
$required_permissions = can('edit', PRIV_CUSTOMERS);
|
||||
|
||||
if (!$required_permissions) {
|
||||
throw new RuntimeException('You do not have the required permissions for this task.');
|
||||
}
|
||||
|
||||
$date = request('date');
|
||||
|
||||
$provider_id = request('provider_id');
|
||||
|
||||
$this->providers_model->delete_working_plan_exception($provider_id, $date);
|
||||
|
||||
json_response([
|
||||
'success' => true,
|
||||
]);
|
||||
} catch (Throwable $e) {
|
||||
json_exception($e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get Calendar Events
|
||||
*
|
||||
* This method will return all the calendar events within a specified period.
|
||||
*/
|
||||
public function get_calendar_appointments_for_table_view(): void
|
||||
{
|
||||
try {
|
||||
$required_permissions = can('view', PRIV_APPOINTMENTS);
|
||||
|
||||
if (!$required_permissions) {
|
||||
throw new RuntimeException('You do not have the required permissions for this task.');
|
||||
}
|
||||
|
||||
$start_date = request('start_date') . ' 00:00:00';
|
||||
|
||||
$end_date = request('end_date') . ' 23:59:59';
|
||||
|
||||
$response = [
|
||||
'appointments' => $this->appointments_model->get([
|
||||
'start_datetime >=' => $start_date,
|
||||
'end_datetime <=' => $end_date,
|
||||
]),
|
||||
'unavailabilities' => $this->unavailabilities_model->get([
|
||||
'start_datetime >=' => $start_date,
|
||||
'end_datetime <=' => $end_date,
|
||||
]),
|
||||
];
|
||||
|
||||
foreach ($response['appointments'] as &$appointment) {
|
||||
$appointment['provider'] = $this->providers_model->find($appointment['id_users_provider']);
|
||||
$appointment['service'] = $this->services_model->find($appointment['id_services']);
|
||||
$appointment['customer'] = $this->customers_model->find($appointment['id_users_customer']);
|
||||
}
|
||||
|
||||
unset($appointment);
|
||||
|
||||
$user_id = session('user_id');
|
||||
|
||||
$role_slug = session('role_slug');
|
||||
|
||||
// If the current user is a provider he must only see his own appointments.
|
||||
if ($role_slug === DB_SLUG_PROVIDER) {
|
||||
foreach ($response['appointments'] as $index => $appointment) {
|
||||
if ((int) $appointment['id_users_provider'] !== (int) $user_id) {
|
||||
unset($response['appointments'][$index]);
|
||||
}
|
||||
}
|
||||
|
||||
$response['appointments'] = array_values($response['appointments']);
|
||||
|
||||
foreach ($response['unavailabilities'] as $index => $unavailability) {
|
||||
if ((int) $unavailability['id_users_provider'] !== (int) $user_id) {
|
||||
unset($response['unavailabilities'][$index]);
|
||||
}
|
||||
}
|
||||
|
||||
$response['unavailabilities'] = array_values($response['unavailabilities']);
|
||||
}
|
||||
|
||||
// If the current user is a secretary he must only see the appointments of his providers.
|
||||
if ($role_slug === DB_SLUG_SECRETARY) {
|
||||
$providers = $this->secretaries_model->find($user_id)['providers'];
|
||||
|
||||
foreach ($response['appointments'] as $index => $appointment) {
|
||||
if (!in_array((int) $appointment['id_users_provider'], $providers)) {
|
||||
unset($response['appointments'][$index]);
|
||||
}
|
||||
}
|
||||
|
||||
$response['appointments'] = array_values($response['appointments']);
|
||||
|
||||
foreach ($response['unavailabilities'] as $index => $unavailability) {
|
||||
if (!in_array((int) $unavailability['id_users_provider'], $providers)) {
|
||||
unset($response['unavailabilities'][$index]);
|
||||
}
|
||||
}
|
||||
|
||||
$response['unavailabilities'] = array_values($response['unavailabilities']);
|
||||
}
|
||||
|
||||
foreach ($response['unavailabilities'] as &$unavailability) {
|
||||
$unavailability['provider'] = $this->providers_model->find($unavailability['id_users_provider']);
|
||||
}
|
||||
|
||||
unset($unavailability);
|
||||
|
||||
// Add blocked periods to the response.
|
||||
$start_date = request('start_date');
|
||||
$end_date = request('end_date');
|
||||
$response['blocked_periods'] = $this->blocked_periods_model->get_for_period($start_date, $end_date);
|
||||
|
||||
json_response($response);
|
||||
} catch (Throwable $e) {
|
||||
json_exception($e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the registered appointments for the given date period and record.
|
||||
*
|
||||
* This method returns the database appointments and unavailability periods for the user selected date period and
|
||||
* record type (provider or service).
|
||||
*/
|
||||
public function get_calendar_appointments(): void
|
||||
{
|
||||
try {
|
||||
if (cannot('view', PRIV_APPOINTMENTS)) {
|
||||
throw new RuntimeException('You do not have the required permissions for this task.');
|
||||
}
|
||||
|
||||
$record_id = request('record_id');
|
||||
|
||||
$is_all = request('record_id') === FILTER_TYPE_ALL;
|
||||
|
||||
$filter_type = request('filter_type');
|
||||
|
||||
if (!$filter_type && !$is_all) {
|
||||
json_response([
|
||||
'appointments' => [],
|
||||
'unavailabilities' => [],
|
||||
]);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$record_id = $this->db->escape($record_id);
|
||||
|
||||
if ($filter_type == FILTER_TYPE_PROVIDER) {
|
||||
$where_id = 'id_users_provider';
|
||||
} elseif ($filter_type === FILTER_TYPE_SERVICE) {
|
||||
$where_id = 'id_services';
|
||||
} else {
|
||||
$where_id = $record_id;
|
||||
}
|
||||
|
||||
// Get appointments
|
||||
$start_date = $this->db->escape(request('start_date'));
|
||||
$end_date = $this->db->escape(date('Y-m-d', strtotime(request('end_date') . ' +1 day')));
|
||||
|
||||
$where_clause =
|
||||
$where_id .
|
||||
' = ' .
|
||||
$record_id .
|
||||
'
|
||||
AND ((start_datetime > ' .
|
||||
$start_date .
|
||||
' AND start_datetime < ' .
|
||||
$end_date .
|
||||
')
|
||||
or (end_datetime > ' .
|
||||
$start_date .
|
||||
' AND end_datetime < ' .
|
||||
$end_date .
|
||||
')
|
||||
or (start_datetime <= ' .
|
||||
$start_date .
|
||||
' AND end_datetime >= ' .
|
||||
$end_date .
|
||||
'))
|
||||
AND is_unavailability = 0
|
||||
';
|
||||
|
||||
$response['appointments'] = $this->appointments_model->get($where_clause);
|
||||
|
||||
foreach ($response['appointments'] as &$appointment) {
|
||||
$appointment['provider'] = $this->providers_model->find($appointment['id_users_provider']);
|
||||
$appointment['service'] = $this->services_model->find($appointment['id_services']);
|
||||
$appointment['customer'] = $this->customers_model->find($appointment['id_users_customer']);
|
||||
}
|
||||
|
||||
unset($appointment);
|
||||
|
||||
// Get unavailability periods (only for provider).
|
||||
$response['unavailabilities'] = [];
|
||||
|
||||
if ($filter_type == FILTER_TYPE_PROVIDER || $is_all) {
|
||||
$where_clause =
|
||||
$where_id .
|
||||
' = ' .
|
||||
$record_id .
|
||||
'
|
||||
AND ((start_datetime > ' .
|
||||
$start_date .
|
||||
' AND start_datetime < ' .
|
||||
$end_date .
|
||||
')
|
||||
or (end_datetime > ' .
|
||||
$start_date .
|
||||
' AND end_datetime < ' .
|
||||
$end_date .
|
||||
')
|
||||
or (start_datetime <= ' .
|
||||
$start_date .
|
||||
' AND end_datetime >= ' .
|
||||
$end_date .
|
||||
'))
|
||||
AND is_unavailability = 1
|
||||
';
|
||||
|
||||
$response['unavailabilities'] = $this->unavailabilities_model->get($where_clause);
|
||||
}
|
||||
|
||||
$user_id = session('user_id');
|
||||
|
||||
$role_slug = session('role_slug');
|
||||
|
||||
// If the current user is a provider he must only see his own appointments.
|
||||
if ($role_slug === DB_SLUG_PROVIDER) {
|
||||
foreach ($response['appointments'] as $index => $appointment) {
|
||||
if ((int) $appointment['id_users_provider'] !== (int) $user_id) {
|
||||
unset($response['appointments'][$index]);
|
||||
}
|
||||
}
|
||||
|
||||
$response['appointments'] = array_values($response['appointments']);
|
||||
|
||||
foreach ($response['unavailabilities'] as $index => $unavailability) {
|
||||
if ((int) $unavailability['id_users_provider'] !== (int) $user_id) {
|
||||
unset($response['unavailabilities'][$index]);
|
||||
}
|
||||
}
|
||||
|
||||
unset($unavailability);
|
||||
|
||||
$response['unavailabilities'] = array_values($response['unavailabilities']);
|
||||
}
|
||||
|
||||
// If the current user is a secretary he must only see the appointments of his providers.
|
||||
if ($role_slug === DB_SLUG_SECRETARY) {
|
||||
$providers = $this->secretaries_model->find($user_id)['providers'];
|
||||
|
||||
foreach ($response['appointments'] as $index => $appointment) {
|
||||
if (!in_array((int) $appointment['id_users_provider'], $providers)) {
|
||||
unset($response['appointments'][$index]);
|
||||
}
|
||||
}
|
||||
|
||||
$response['appointments'] = array_values($response['appointments']);
|
||||
|
||||
foreach ($response['unavailabilities'] as $index => $unavailability) {
|
||||
if (!in_array((int) $unavailability['id_users_provider'], $providers)) {
|
||||
unset($response['unavailabilities'][$index]);
|
||||
}
|
||||
}
|
||||
|
||||
$response['unavailabilities'] = array_values($response['unavailabilities']);
|
||||
}
|
||||
|
||||
foreach ($response['unavailabilities'] as &$unavailability) {
|
||||
$unavailability['provider'] = $this->providers_model->find($unavailability['id_users_provider']);
|
||||
}
|
||||
|
||||
unset($unavailability);
|
||||
|
||||
// Add blocked periods to the response.
|
||||
$start_date = request('start_date');
|
||||
$end_date = request('end_date');
|
||||
$response['blocked_periods'] = $this->blocked_periods_model->get_for_period($start_date, $end_date);
|
||||
|
||||
json_response($response);
|
||||
} catch (Throwable $e) {
|
||||
json_exception($e);
|
||||
}
|
||||
}
|
||||
}
|
47
application/controllers/Captcha.php
Normal file
47
application/controllers/Captcha.php
Normal file
@ -0,0 +1,47 @@
|
||||
<?php defined('BASEPATH') or exit('No direct script access allowed');
|
||||
|
||||
/* ----------------------------------------------------------------------------
|
||||
* Easy!Appointments - Online Appointment Scheduler
|
||||
*
|
||||
* @package EasyAppointments
|
||||
* @author A.Tselegidis <alextselegidis@gmail.com>
|
||||
* @copyright Copyright (c) Alex Tselegidis
|
||||
* @license https://opensource.org/licenses/GPL-3.0 - GPLv3
|
||||
* @link https://easyappointments.org
|
||||
* @since v1.0.0
|
||||
* ---------------------------------------------------------------------------- */
|
||||
|
||||
/**
|
||||
* Captcha controller.
|
||||
*
|
||||
* Handles the captcha operations.
|
||||
*
|
||||
* @package Controllers
|
||||
*/
|
||||
class Captcha extends EA_Controller
|
||||
{
|
||||
/**
|
||||
* Class Constructor
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
parent::__construct();
|
||||
|
||||
$this->load->library('captcha_builder');
|
||||
}
|
||||
|
||||
/**
|
||||
* Make a request to this method to get a captcha image.
|
||||
*/
|
||||
public function index(): void
|
||||
{
|
||||
$this->captcha_builder->setDistortion(true);
|
||||
$this->captcha_builder->setMaxBehindLines(1);
|
||||
$this->captcha_builder->setMaxFrontLines(1);
|
||||
$this->captcha_builder->setBackgroundColor(255, 255, 255);
|
||||
$this->captcha_builder->build();
|
||||
session(['captcha_phrase' => $this->captcha_builder->getPhrase()]);
|
||||
header('Content-type: image/jpeg');
|
||||
$this->captcha_builder->output();
|
||||
}
|
||||
}
|
73
application/controllers/Consents.php
Normal file
73
application/controllers/Consents.php
Normal file
@ -0,0 +1,73 @@
|
||||
<?php defined('BASEPATH') or exit('No direct script access allowed');
|
||||
|
||||
/* ----------------------------------------------------------------------------
|
||||
* Easy!Appointments - Online Appointment Scheduler
|
||||
*
|
||||
* @package EasyAppointments
|
||||
* @author A.Tselegidis <alextselegidis@gmail.com>
|
||||
* @copyright Copyright (c) Alex Tselegidis
|
||||
* @license https://opensource.org/licenses/GPL-3.0 - GPLv3
|
||||
* @link https://easyappointments.org
|
||||
* @since v1.3.2
|
||||
* ---------------------------------------------------------------------------- */
|
||||
|
||||
/**
|
||||
* Consents controller.
|
||||
*
|
||||
* Handles user consent related operations.
|
||||
*
|
||||
* @package Controllers
|
||||
*/
|
||||
class Consents extends EA_Controller
|
||||
{
|
||||
/**
|
||||
* Consents constructor.
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
parent::__construct();
|
||||
|
||||
$this->load->model('consents_model');
|
||||
}
|
||||
|
||||
/**
|
||||
* Save (insert or update) the consent
|
||||
*/
|
||||
public function save(): void
|
||||
{
|
||||
try {
|
||||
$consent = request('consent');
|
||||
|
||||
$consent['ip'] = $this->input->ip_address();
|
||||
|
||||
$occurrences = $this->consents_model->get(['ip' => $consent['ip']], 1, 0, 'create_datetime DESC');
|
||||
|
||||
if (!empty($occurrences)) {
|
||||
$last_consent = $occurrences[0];
|
||||
|
||||
$last_consent_create_datetime_instance = new DateTime($last_consent['create_datetime']);
|
||||
|
||||
$threshold_datetime_instance = new DateTime('-24 hours');
|
||||
|
||||
if ($last_consent_create_datetime_instance > $threshold_datetime_instance) {
|
||||
// Do not create a new consent.
|
||||
|
||||
json_response([
|
||||
'success' => true,
|
||||
]);
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
$consent['id'] = $this->consents_model->save($consent);
|
||||
|
||||
json_response([
|
||||
'success' => true,
|
||||
'id' => $consent['id'],
|
||||
]);
|
||||
} catch (Throwable $e) {
|
||||
json_exception($e);
|
||||
}
|
||||
}
|
||||
}
|
193
application/controllers/Console.php
Normal file
193
application/controllers/Console.php
Normal file
@ -0,0 +1,193 @@
|
||||
<?php defined('BASEPATH') or exit('No direct script access allowed');
|
||||
|
||||
/* ----------------------------------------------------------------------------
|
||||
* Easy!Appointments - Online Appointment Scheduler
|
||||
*
|
||||
* @package EasyAppointments
|
||||
* @author A.Tselegidis <alextselegidis@gmail.com>
|
||||
* @copyright Copyright (c) Alex Tselegidis
|
||||
* @license https://opensource.org/licenses/GPL-3.0 - GPLv3
|
||||
* @link https://easyappointments.org
|
||||
* @since v1.3.2
|
||||
* ---------------------------------------------------------------------------- */
|
||||
|
||||
use Jsvrcek\ICS\Exception\CalendarEventException;
|
||||
|
||||
require_once __DIR__ . '/Google.php';
|
||||
require_once __DIR__ . '/Caldav.php';
|
||||
|
||||
/**
|
||||
* Console controller.
|
||||
*
|
||||
* Handles all the Console related operations.
|
||||
*/
|
||||
class Console extends EA_Controller
|
||||
{
|
||||
/**
|
||||
* Console constructor.
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
if (!is_cli()) {
|
||||
exit('No direct script access allowed');
|
||||
}
|
||||
|
||||
parent::__construct();
|
||||
|
||||
$this->load->dbutil();
|
||||
|
||||
$this->load->library('instance');
|
||||
|
||||
$this->load->model('admins_model');
|
||||
$this->load->model('customers_model');
|
||||
$this->load->model('providers_model');
|
||||
$this->load->model('services_model');
|
||||
$this->load->model('settings_model');
|
||||
}
|
||||
|
||||
/**
|
||||
* Perform a console installation.
|
||||
*
|
||||
* Use this method to install Easy!Appointments directly from the terminal.
|
||||
*
|
||||
* Usage:
|
||||
*
|
||||
* php index.php console install
|
||||
*
|
||||
* @throws Exception
|
||||
*/
|
||||
public function install(): void
|
||||
{
|
||||
$this->instance->migrate('fresh');
|
||||
|
||||
$password = $this->instance->seed();
|
||||
|
||||
response(
|
||||
PHP_EOL . '⇾ Installation completed, login with "administrator" / "' . $password . '".' . PHP_EOL . PHP_EOL,
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Migrate the database to the latest state.
|
||||
*
|
||||
* Use this method to upgrade an Easy!Appointments instance to the latest database state.
|
||||
*
|
||||
* Notice:
|
||||
*
|
||||
* Do not use this method to install the app as it will not seed the database with the initial entries (admin,
|
||||
* provider, service, settings etc.).
|
||||
*
|
||||
* Usage:
|
||||
*
|
||||
* php index.php console migrate
|
||||
*
|
||||
* php index.php console migrate fresh
|
||||
*
|
||||
* @param string $type
|
||||
*/
|
||||
public function migrate(string $type = ''): void
|
||||
{
|
||||
$this->instance->migrate($type);
|
||||
}
|
||||
|
||||
/**
|
||||
* Seed the database with test data.
|
||||
*
|
||||
* Use this method to add test data to your database
|
||||
*
|
||||
* Usage:
|
||||
*
|
||||
* php index.php console seed
|
||||
* @throws Exception
|
||||
*/
|
||||
public function seed(): void
|
||||
{
|
||||
$this->instance->seed();
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a database backup file.
|
||||
*
|
||||
* Use this method to back up your Easy!Appointments data.
|
||||
*
|
||||
* Usage:
|
||||
*
|
||||
* php index.php console backup
|
||||
*
|
||||
* php index.php console backup /path/to/backup/folder
|
||||
*
|
||||
* @throws Exception
|
||||
*/
|
||||
public function backup(): void
|
||||
{
|
||||
$this->instance->backup($GLOBALS['argv'][3] ?? null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Trigger the synchronization of all provider calendars with Google Calendar.
|
||||
*
|
||||
* Use this method in a cronjob to automatically sync events between Easy!Appointments and Google Calendar.
|
||||
*
|
||||
* Notice:
|
||||
*
|
||||
* Google syncing must first be enabled for each individual provider from inside the backend calendar page.
|
||||
*
|
||||
* Usage:
|
||||
*
|
||||
* php index.php console sync
|
||||
*
|
||||
* @throws CalendarEventException
|
||||
* @throws Exception
|
||||
* @throws Throwable
|
||||
*/
|
||||
public function sync(): void
|
||||
{
|
||||
$providers = $this->providers_model->get();
|
||||
|
||||
foreach ($providers as $provider) {
|
||||
if (filter_var($provider['settings']['google_sync'], FILTER_VALIDATE_BOOLEAN)) {
|
||||
Google::sync((string) $provider['id']);
|
||||
}
|
||||
|
||||
if (filter_var($provider['settings']['caldav_sync'], FILTER_VALIDATE_BOOLEAN)) {
|
||||
Caldav::sync((string) $provider['id']);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Show help information about the console capabilities.
|
||||
*
|
||||
* Use this method to see the available commands.
|
||||
*
|
||||
* Usage:
|
||||
*
|
||||
* php index.php console help
|
||||
*/
|
||||
public function help(): void
|
||||
{
|
||||
$help = [
|
||||
'',
|
||||
'Easy!Appointments ' . config('version'),
|
||||
'',
|
||||
'Usage:',
|
||||
'',
|
||||
'⇾ php index.php console [command] [arguments]',
|
||||
'',
|
||||
'Commands:',
|
||||
'',
|
||||
'⇾ php index.php console migrate',
|
||||
'⇾ php index.php console migrate fresh',
|
||||
'⇾ php index.php console migrate up',
|
||||
'⇾ php index.php console migrate down',
|
||||
'⇾ php index.php console seed',
|
||||
'⇾ php index.php console install',
|
||||
'⇾ php index.php console backup',
|
||||
'⇾ php index.php console sync',
|
||||
'',
|
||||
'',
|
||||
];
|
||||
|
||||
response(implode(PHP_EOL, $help));
|
||||
}
|
||||
}
|
312
application/controllers/Customers.php
Normal file
312
application/controllers/Customers.php
Normal file
@ -0,0 +1,312 @@
|
||||
<?php defined('BASEPATH') or exit('No direct script access allowed');
|
||||
|
||||
/* ----------------------------------------------------------------------------
|
||||
* Easy!Appointments - Online Appointment Scheduler
|
||||
*
|
||||
* @package EasyAppointments
|
||||
* @author A.Tselegidis <alextselegidis@gmail.com>
|
||||
* @copyright Copyright (c) Alex Tselegidis
|
||||
* @license https://opensource.org/licenses/GPL-3.0 - GPLv3
|
||||
* @link https://easyappointments.org
|
||||
* @since v1.0.0
|
||||
* ---------------------------------------------------------------------------- */
|
||||
|
||||
/**
|
||||
* Customers controller.
|
||||
*
|
||||
* Handles the customers related operations.
|
||||
*
|
||||
* @package Controllers
|
||||
*/
|
||||
class Customers extends EA_Controller
|
||||
{
|
||||
public array $allowed_customer_fields = [
|
||||
'id',
|
||||
'first_name',
|
||||
'last_name',
|
||||
'email',
|
||||
'phone_number',
|
||||
'address',
|
||||
'city',
|
||||
'state',
|
||||
'zip_code',
|
||||
'notes',
|
||||
'timezone',
|
||||
'language',
|
||||
'custom_field_1',
|
||||
'custom_field_2',
|
||||
'custom_field_3',
|
||||
'custom_field_4',
|
||||
'custom_field_5',
|
||||
'ldap_dn',
|
||||
];
|
||||
|
||||
public array $optional_customer_fields = [
|
||||
//
|
||||
];
|
||||
|
||||
/**
|
||||
* Customers constructor.
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
parent::__construct();
|
||||
|
||||
$this->load->model('appointments_model');
|
||||
$this->load->model('customers_model');
|
||||
$this->load->model('secretaries_model');
|
||||
$this->load->model('roles_model');
|
||||
|
||||
$this->load->library('accounts');
|
||||
$this->load->library('permissions');
|
||||
$this->load->library('timezones');
|
||||
$this->load->library('webhooks_client');
|
||||
}
|
||||
|
||||
/**
|
||||
* Render the backend customers page.
|
||||
*
|
||||
* On this page admin users will be able to manage customers, which are eventually selected by customers during the
|
||||
* booking process.
|
||||
*/
|
||||
public function index(): void
|
||||
{
|
||||
session(['dest_url' => site_url('customers')]);
|
||||
|
||||
$user_id = session('user_id');
|
||||
|
||||
if (cannot('view', PRIV_CUSTOMERS)) {
|
||||
if ($user_id) {
|
||||
abort(403, 'Forbidden');
|
||||
}
|
||||
|
||||
redirect('login');
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$role_slug = session('role_slug');
|
||||
|
||||
$date_format = setting('date_format');
|
||||
$time_format = setting('time_format');
|
||||
$require_first_name = setting('require_first_name');
|
||||
$require_last_name = setting('require_last_name');
|
||||
$require_email = setting('require_email');
|
||||
$require_phone_number = setting('require_phone_number');
|
||||
$require_address = setting('require_address');
|
||||
$require_city = setting('require_city');
|
||||
$require_zip_code = setting('require_zip_code');
|
||||
|
||||
$secretary_providers = [];
|
||||
|
||||
if ($role_slug === DB_SLUG_SECRETARY) {
|
||||
$secretary = $this->secretaries_model->find($user_id);
|
||||
|
||||
$secretary_providers = $secretary['providers'];
|
||||
}
|
||||
|
||||
script_vars([
|
||||
'user_id' => $user_id,
|
||||
'role_slug' => $role_slug,
|
||||
'date_format' => $date_format,
|
||||
'time_format' => $time_format,
|
||||
'timezones' => $this->timezones->to_array(),
|
||||
'secretary_providers' => $secretary_providers,
|
||||
'default_language' => setting('default_language'),
|
||||
'default_timezone' => setting('default_timezone'),
|
||||
]);
|
||||
|
||||
html_vars([
|
||||
'page_title' => lang('customers'),
|
||||
'active_menu' => PRIV_CUSTOMERS,
|
||||
'user_display_name' => $this->accounts->get_user_display_name($user_id),
|
||||
'timezones' => $this->timezones->to_array(),
|
||||
'grouped_timezones' => $this->timezones->to_grouped_array(),
|
||||
'privileges' => $this->roles_model->get_permissions_by_slug($role_slug),
|
||||
'require_first_name' => $require_first_name,
|
||||
'require_last_name' => $require_last_name,
|
||||
'require_email' => $require_email,
|
||||
'require_phone_number' => $require_phone_number,
|
||||
'require_address' => $require_address,
|
||||
'require_city' => $require_city,
|
||||
'require_zip_code' => $require_zip_code,
|
||||
'available_languages' => config('available_languages'),
|
||||
]);
|
||||
|
||||
$this->load->view('pages/customers');
|
||||
}
|
||||
|
||||
/**
|
||||
* Find a customer.
|
||||
*/
|
||||
public function find(): void
|
||||
{
|
||||
try {
|
||||
if (cannot('view', PRIV_CUSTOMERS)) {
|
||||
abort(403, 'Forbidden');
|
||||
}
|
||||
|
||||
$user_id = session('user_id');
|
||||
|
||||
$customer_id = request('customer_id');
|
||||
|
||||
if (!$this->permissions->has_customer_access($user_id, $customer_id)) {
|
||||
abort(403, 'Forbidden');
|
||||
}
|
||||
|
||||
$customer = $this->customers_model->find($customer_id);
|
||||
|
||||
json_response($customer);
|
||||
} catch (Throwable $e) {
|
||||
json_exception($e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Filter customers by the provided keyword.
|
||||
*/
|
||||
public function search(): void
|
||||
{
|
||||
try {
|
||||
if (cannot('view', PRIV_CUSTOMERS)) {
|
||||
abort(403, 'Forbidden');
|
||||
}
|
||||
|
||||
$keyword = request('keyword', '');
|
||||
|
||||
$order_by = request('order_by', 'update_datetime DESC');
|
||||
|
||||
$limit = request('limit', 1000);
|
||||
|
||||
$offset = (int) request('offset', '0');
|
||||
|
||||
$customers = $this->customers_model->search($keyword, $limit, $offset, $order_by);
|
||||
|
||||
$user_id = session('user_id');
|
||||
|
||||
foreach ($customers as $index => &$customer) {
|
||||
if (!$this->permissions->has_customer_access($user_id, $customer['id'])) {
|
||||
unset($customers[$index]);
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
$appointments = $this->appointments_model->get(['id_users_customer' => $customer['id']]);
|
||||
|
||||
foreach ($appointments as &$appointment) {
|
||||
$this->appointments_model->load($appointment, ['service', 'provider']);
|
||||
}
|
||||
|
||||
$customer['appointments'] = $appointments;
|
||||
}
|
||||
|
||||
json_response(array_values($customers));
|
||||
} catch (Throwable $e) {
|
||||
json_exception($e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Store a new customer.
|
||||
*/
|
||||
public function store(): void
|
||||
{
|
||||
try {
|
||||
if (cannot('add', PRIV_CUSTOMERS)) {
|
||||
abort(403, 'Forbidden');
|
||||
}
|
||||
|
||||
if (session('role_slug') !== DB_SLUG_ADMIN && setting('limit_customer_visibility')) {
|
||||
abort(403);
|
||||
}
|
||||
|
||||
$customer = request('customer');
|
||||
|
||||
$this->customers_model->only($customer, $this->allowed_customer_fields);
|
||||
|
||||
$this->customers_model->optional($customer, $this->optional_customer_fields);
|
||||
|
||||
$customer_id = $this->customers_model->save($customer);
|
||||
|
||||
$customer = $this->customers_model->find($customer_id);
|
||||
|
||||
$this->webhooks_client->trigger(WEBHOOK_CUSTOMER_SAVE, $customer);
|
||||
|
||||
json_response([
|
||||
'success' => true,
|
||||
'id' => $customer_id,
|
||||
]);
|
||||
} catch (Throwable $e) {
|
||||
json_exception($e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Update a customer.
|
||||
*/
|
||||
public function update(): void
|
||||
{
|
||||
try {
|
||||
if (cannot('edit', PRIV_CUSTOMERS)) {
|
||||
abort(403, 'Forbidden');
|
||||
}
|
||||
|
||||
$user_id = session('user_id');
|
||||
|
||||
$customer = request('customer');
|
||||
|
||||
if (!$this->permissions->has_customer_access($user_id, $customer['id'])) {
|
||||
abort(403, 'Forbidden');
|
||||
}
|
||||
|
||||
$this->customers_model->only($customer, $this->allowed_customer_fields);
|
||||
|
||||
$this->customers_model->optional($customer, $this->optional_customer_fields);
|
||||
|
||||
$customer_id = $this->customers_model->save($customer);
|
||||
|
||||
$customer = $this->customers_model->find($customer_id);
|
||||
|
||||
$this->webhooks_client->trigger(WEBHOOK_CUSTOMER_SAVE, $customer);
|
||||
|
||||
json_response([
|
||||
'success' => true,
|
||||
'id' => $customer_id,
|
||||
]);
|
||||
} catch (Throwable $e) {
|
||||
json_exception($e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove a customer.
|
||||
*/
|
||||
public function destroy(): void
|
||||
{
|
||||
try {
|
||||
if (cannot('delete', PRIV_CUSTOMERS)) {
|
||||
abort(403, 'Forbidden');
|
||||
}
|
||||
|
||||
$user_id = session('user_id');
|
||||
|
||||
$customer_id = request('customer_id');
|
||||
|
||||
if (!$this->permissions->has_customer_access($user_id, $customer_id)) {
|
||||
abort(403, 'Forbidden');
|
||||
}
|
||||
|
||||
$customer = $this->customers_model->find($customer_id);
|
||||
|
||||
$this->customers_model->delete($customer_id);
|
||||
|
||||
$this->webhooks_client->trigger(WEBHOOK_CUSTOMER_DELETE, $customer);
|
||||
|
||||
json_response([
|
||||
'success' => true,
|
||||
]);
|
||||
} catch (Throwable $e) {
|
||||
json_exception($e);
|
||||
}
|
||||
}
|
||||
}
|
112
application/controllers/General_settings.php
Normal file
112
application/controllers/General_settings.php
Normal file
@ -0,0 +1,112 @@
|
||||
<?php defined('BASEPATH') or exit('No direct script access allowed');
|
||||
|
||||
/* ----------------------------------------------------------------------------
|
||||
* Easy!Appointments - Online Appointment Scheduler
|
||||
*
|
||||
* @package EasyAppointments
|
||||
* @author A.Tselegidis <alextselegidis@gmail.com>
|
||||
* @copyright Copyright (c) Alex Tselegidis
|
||||
* @license https://opensource.org/licenses/GPL-3.0 - GPLv3
|
||||
* @link https://easyappointments.org
|
||||
* @since v1.5.0
|
||||
* ---------------------------------------------------------------------------- */
|
||||
|
||||
/**
|
||||
* General settings controller.
|
||||
*
|
||||
* Handles general settings related operations.
|
||||
*
|
||||
* @package Controllers
|
||||
*/
|
||||
class General_settings extends EA_Controller
|
||||
{
|
||||
/**
|
||||
* Calendar constructor.
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
parent::__construct();
|
||||
|
||||
$this->load->model('settings_model');
|
||||
|
||||
$this->load->library('accounts');
|
||||
$this->load->library('timezones');
|
||||
}
|
||||
|
||||
/**
|
||||
* Render the settings page.
|
||||
*/
|
||||
public function index(): void
|
||||
{
|
||||
session(['dest_url' => site_url('general_settings')]);
|
||||
|
||||
$user_id = session('user_id');
|
||||
|
||||
if (cannot('view', PRIV_SYSTEM_SETTINGS)) {
|
||||
if ($user_id) {
|
||||
abort(403, 'Forbidden');
|
||||
}
|
||||
|
||||
redirect('login');
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$role_slug = session('role_slug');
|
||||
|
||||
$available_theme_files = glob(__DIR__ . '/../../assets/css/themes/*.min.css');
|
||||
|
||||
$available_themes = array_map(function ($available_theme_file) {
|
||||
return str_replace('.min.css', '', basename($available_theme_file));
|
||||
}, $available_theme_files);
|
||||
|
||||
script_vars([
|
||||
'user_id' => $user_id,
|
||||
'role_slug' => $role_slug,
|
||||
'timezones' => $this->timezones->to_array(),
|
||||
'general_settings' => $this->settings_model->get(),
|
||||
]);
|
||||
|
||||
html_vars([
|
||||
'page_title' => lang('settings'),
|
||||
'active_menu' => PRIV_SYSTEM_SETTINGS,
|
||||
'user_display_name' => $this->accounts->get_user_display_name($user_id),
|
||||
'grouped_timezones' => $this->timezones->to_grouped_array(),
|
||||
'available_themes' => $available_themes,
|
||||
]);
|
||||
|
||||
$this->load->view('pages/general_settings');
|
||||
}
|
||||
|
||||
/**
|
||||
* Save general settings.
|
||||
*/
|
||||
public function save(): void
|
||||
{
|
||||
try {
|
||||
if (cannot('edit', PRIV_SYSTEM_SETTINGS)) {
|
||||
throw new RuntimeException('You do not have the required permissions for this task.');
|
||||
}
|
||||
|
||||
$settings = request('general_settings', []);
|
||||
|
||||
foreach ($settings as $setting) {
|
||||
$existing_setting = $this->settings_model
|
||||
->query()
|
||||
->where('name', $setting['name'])
|
||||
->get()
|
||||
->row_array();
|
||||
|
||||
if (!empty($existing_setting)) {
|
||||
$setting['id'] = $existing_setting['id'];
|
||||
}
|
||||
|
||||
$this->settings_model->save($setting);
|
||||
}
|
||||
|
||||
response();
|
||||
} catch (Throwable $e) {
|
||||
json_exception($e);
|
||||
}
|
||||
}
|
||||
}
|
439
application/controllers/Google.php
Normal file
439
application/controllers/Google.php
Normal file
@ -0,0 +1,439 @@
|
||||
<?php defined('BASEPATH') or exit('No direct script access allowed');
|
||||
|
||||
/* ----------------------------------------------------------------------------
|
||||
* Easy!Appointments - Online Appointment Scheduler
|
||||
*
|
||||
* @package EasyAppointments
|
||||
* @author A.Tselegidis <alextselegidis@gmail.com>
|
||||
* @copyright Copyright (c) Alex Tselegidis
|
||||
* @license https://opensource.org/licenses/GPL-3.0 - GPLv3
|
||||
* @link https://easyappointments.org
|
||||
* @since v1.0.0
|
||||
* ---------------------------------------------------------------------------- */
|
||||
|
||||
/**
|
||||
* Google controller.
|
||||
*
|
||||
* Handles the Google Calendar synchronization related operations.
|
||||
*
|
||||
* @package Controllers
|
||||
*/
|
||||
class Google extends EA_Controller
|
||||
{
|
||||
/**
|
||||
* Google constructor.
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
parent::__construct();
|
||||
|
||||
$this->load->library('google_sync');
|
||||
|
||||
$this->load->model('appointments_model');
|
||||
$this->load->model('providers_model');
|
||||
$this->load->model('roles_model');
|
||||
}
|
||||
|
||||
/**
|
||||
* Complete synchronization of appointments between Google Calendar and Easy!Appointments.
|
||||
*
|
||||
* This method will completely sync the appointments of a provider with his Google Calendar account. The sync period
|
||||
* needs to be relatively small, because a lot of API calls might be necessary and this will lead to consuming the
|
||||
* Google limit for the Calendar API usage.
|
||||
*/
|
||||
public static function sync(?string $provider_id = null): void
|
||||
{
|
||||
try {
|
||||
/** @var EA_Controller $CI */
|
||||
$CI = get_instance();
|
||||
|
||||
$CI->load->library('google_sync');
|
||||
|
||||
// Load the libraries as this method is called statically from the CLI command
|
||||
|
||||
$CI->load->model('appointments_model');
|
||||
$CI->load->model('unavailabilities_model');
|
||||
$CI->load->model('providers_model');
|
||||
$CI->load->model('services_model');
|
||||
$CI->load->model('customers_model');
|
||||
$CI->load->model('settings_model');
|
||||
|
||||
$user_id = session('user_id');
|
||||
|
||||
if (!$user_id && !is_cli()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!$provider_id) {
|
||||
throw new InvalidArgumentException('No provider ID provided.');
|
||||
}
|
||||
|
||||
$provider = $CI->providers_model->find($provider_id);
|
||||
|
||||
// Check whether the selected provider has the Google Sync enabled.
|
||||
$google_sync = $CI->providers_model->get_setting($provider['id'], 'google_sync');
|
||||
|
||||
if (!$google_sync) {
|
||||
return; // The selected provider does not have the Google Sync enabled.
|
||||
}
|
||||
|
||||
$google_token = json_decode($provider['settings']['google_token'], true);
|
||||
|
||||
$CI->google_sync->refresh_token($google_token['refresh_token']);
|
||||
|
||||
// Fetch provider's appointments that belong to the sync time period.
|
||||
$sync_past_days = $provider['settings']['sync_past_days'];
|
||||
|
||||
$sync_future_days = $provider['settings']['sync_future_days'];
|
||||
|
||||
$start = strtotime('-' . $sync_past_days . ' days', strtotime(date('Y-m-d')));
|
||||
|
||||
$end = strtotime('+' . $sync_future_days . ' days', strtotime(date('Y-m-d')));
|
||||
|
||||
$where = [
|
||||
'start_datetime >=' => date('Y-m-d H:i:s', $start),
|
||||
'end_datetime <=' => date('Y-m-d H:i:s', $end),
|
||||
'id_users_provider' => $provider['id'],
|
||||
];
|
||||
|
||||
$appointments = $CI->appointments_model->get($where);
|
||||
|
||||
$unavailabilities = $CI->unavailabilities_model->get($where);
|
||||
|
||||
$local_events = [...$appointments, ...$unavailabilities];
|
||||
|
||||
$company_color = setting('company_color');
|
||||
|
||||
$settings = [
|
||||
'company_name' => setting('company_name'),
|
||||
'company_link' => setting('company_link'),
|
||||
'company_email' => setting('company_email'),
|
||||
'company_color' => !empty($company_color) && $company_color != DEFAULT_COMPANY_COLOR ? $company_color : null,
|
||||
];
|
||||
|
||||
$provider_timezone = new DateTimeZone($provider['timezone']);
|
||||
|
||||
// Sync each appointment with Google Calendar by following the project's sync protocol (see documentation).
|
||||
foreach ($local_events as $local_event) {
|
||||
if (!$local_event['is_unavailability']) {
|
||||
$service = $CI->services_model->find($local_event['id_services']);
|
||||
$customer = $CI->customers_model->find($local_event['id_users_customer']);
|
||||
$events_model = $CI->appointments_model;
|
||||
} else {
|
||||
$service = null;
|
||||
$customer = null;
|
||||
$events_model = $CI->unavailabilities_model;
|
||||
}
|
||||
|
||||
// If current appointment not synced yet, add to Google Calendar.
|
||||
if (!$local_event['id_google_calendar']) {
|
||||
if (!$local_event['is_unavailability']) {
|
||||
$google_event = $CI->google_sync->add_appointment(
|
||||
$local_event,
|
||||
$provider,
|
||||
$service,
|
||||
$customer,
|
||||
$settings,
|
||||
);
|
||||
} else {
|
||||
$google_event = $CI->google_sync->add_unavailability($provider, $local_event);
|
||||
}
|
||||
|
||||
$local_event['id_google_calendar'] = $google_event->getId();
|
||||
|
||||
$events_model->save($local_event); // Save the Google Calendar ID.
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
// Appointment is synced with Google Calendar.
|
||||
|
||||
try {
|
||||
$google_event = $CI->google_sync->get_event($provider, $local_event['id_google_calendar']);
|
||||
|
||||
if ($google_event->getStatus() == 'cancelled') {
|
||||
throw new Exception('Event is cancelled, remove the record from Easy!Appointments.');
|
||||
}
|
||||
|
||||
// If Google Calendar event is different from Easy!Appointments appointment then update Easy!Appointments record.
|
||||
$local_event_start = strtotime($local_event['start_datetime']);
|
||||
$local_event_end = strtotime($local_event['end_datetime']);
|
||||
$google_event_start = new DateTime(
|
||||
$google_event->getStart()->getDateTime() ?? $google_event->getEnd()->getDate(),
|
||||
);
|
||||
$google_event_start->setTimezone($provider_timezone);
|
||||
$google_event_end = new DateTime(
|
||||
$google_event->getEnd()->getDateTime() ?? $google_event->getEnd()->getDate(),
|
||||
);
|
||||
$google_event_end->setTimezone($provider_timezone);
|
||||
|
||||
$google_event_notes = $local_event['is_unavailability']
|
||||
? $google_event->getSummary() . ' ' . $google_event->getDescription()
|
||||
: $google_event->getDescription();
|
||||
|
||||
$is_different =
|
||||
$local_event_start !== $google_event_start->getTimestamp() ||
|
||||
$local_event_end !== $google_event_end->getTimestamp() ||
|
||||
$local_event['notes'] !== $google_event_notes;
|
||||
|
||||
if ($is_different) {
|
||||
$local_event['start_datetime'] = $google_event_start->format('Y-m-d H:i:s');
|
||||
$local_event['end_datetime'] = $google_event_end->format('Y-m-d H:i:s');
|
||||
$local_event['notes'] = $google_event_notes;
|
||||
$events_model->save($local_event);
|
||||
}
|
||||
} catch (Throwable) {
|
||||
// Appointment not found on Google Calendar, delete from Easy!Appointments.
|
||||
$events_model->delete($local_event['id']);
|
||||
|
||||
$local_event['id_google_calendar'] = null;
|
||||
}
|
||||
}
|
||||
|
||||
// Add Google Calendar events that do not exist in Easy!Appointments.
|
||||
$google_calendar = $provider['settings']['google_calendar'];
|
||||
|
||||
try {
|
||||
$google_events = $CI->google_sync->get_sync_events($google_calendar, $start, $end);
|
||||
} catch (Throwable $e) {
|
||||
if ($e->getCode() === 404) {
|
||||
log_message('error', 'Google - Remote Calendar not found for provider ID: ' . $provider_id);
|
||||
|
||||
return; // The remote calendar was not found.
|
||||
} else {
|
||||
throw $e;
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($google_events->getItems() as $google_event) {
|
||||
if ($google_event->getStatus() === 'cancelled') {
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($google_event->getStart() === null || $google_event->getEnd() === null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($google_event->getStart()->getDateTime() === $google_event->getEnd()->getDateTime()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$google_event_start = new DateTime($google_event->getStart()->getDateTime());
|
||||
$google_event_start->setTimezone($provider_timezone);
|
||||
$google_event_end = new DateTime($google_event->getEnd()->getDateTime());
|
||||
$google_event_end->setTimezone($provider_timezone);
|
||||
|
||||
$appointment_results = $CI->appointments_model->get(['id_google_calendar' => $google_event->getId()]);
|
||||
|
||||
if (!empty($appointment_results)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$unavailability_results = $CI->unavailabilities_model->get([
|
||||
'id_google_calendar' => $google_event->getId(),
|
||||
]);
|
||||
|
||||
if (!empty($unavailability_results)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Record doesn't exist in the Easy!Appointments, so add the event now.
|
||||
$local_event = [
|
||||
'start_datetime' => $google_event_start->format('Y-m-d H:i:s'),
|
||||
'end_datetime' => $google_event_end->format('Y-m-d H:i:s'),
|
||||
'is_unavailability' => true,
|
||||
'location' => $google_event->getLocation(),
|
||||
'notes' => $google_event->getSummary() . ' ' . $google_event->getDescription(),
|
||||
'id_users_provider' => $provider_id,
|
||||
'id_google_calendar' => $google_event->getId(),
|
||||
'id_users_customer' => null,
|
||||
'id_services' => null,
|
||||
];
|
||||
|
||||
$CI->unavailabilities_model->save($local_event);
|
||||
}
|
||||
|
||||
json_response([
|
||||
'success' => true,
|
||||
]);
|
||||
} catch (Throwable $e) {
|
||||
log_message(
|
||||
'error',
|
||||
'Google - Sync completed with an error (provider ID "' . $provider_id . '"): ' . $e->getMessage(),
|
||||
);
|
||||
|
||||
json_exception($e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Authorize Google Calendar API usage for a specific provider.
|
||||
*
|
||||
* Since it is required to follow the web application flow, in order to retrieve a refresh token from the Google API
|
||||
* service, this method is going to authorize the given provider.
|
||||
*
|
||||
* @param string $provider_id The provider id, for whom the sync authorization is made.
|
||||
*/
|
||||
public function oauth(string $provider_id): void
|
||||
{
|
||||
if (!$this->session->userdata('user_id')) {
|
||||
show_error('Forbidden', 403);
|
||||
}
|
||||
|
||||
// Store the provider id for use on the callback function.
|
||||
session(['oauth_provider_id' => $provider_id]);
|
||||
|
||||
// Redirect browser to google user content page.
|
||||
header('Location: ' . $this->google_sync->get_auth_url());
|
||||
}
|
||||
|
||||
/**
|
||||
* Callback method for the Google Calendar API authorization process.
|
||||
*
|
||||
* Once the user grants consent with his Google Calendar data usage, the Google OAuth service will redirect him back
|
||||
* in this page. Here we are going to store the refresh token, because this is what will be used to generate access
|
||||
* tokens in the future.
|
||||
*
|
||||
* IMPORTANT: Because it is necessary to authorize the application using the web server flow (see official
|
||||
* documentation of OAuth), every Easy!Appointments installation should use its own calendar api key. So in every
|
||||
* api console account, the "http://path-to-Easy!Appointments/google/oauth_callback" should be included in an
|
||||
* allowed redirect URL.
|
||||
*
|
||||
* @throws Exception
|
||||
*/
|
||||
public function oauth_callback(): void
|
||||
{
|
||||
if (!session('user_id')) {
|
||||
abort(403, 'Forbidden');
|
||||
}
|
||||
|
||||
$code = request('code');
|
||||
|
||||
if (empty($code)) {
|
||||
response('Code authorization failed.');
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$token = $this->google_sync->authenticate($code);
|
||||
|
||||
if (empty($token)) {
|
||||
response('Token authorization failed.');
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// Store the token into the database for future reference.
|
||||
$oauth_provider_id = session('oauth_provider_id');
|
||||
|
||||
if ($oauth_provider_id) {
|
||||
$this->providers_model->set_setting($oauth_provider_id, 'google_sync', true);
|
||||
$this->providers_model->set_setting($oauth_provider_id, 'google_token', json_encode($token));
|
||||
$this->providers_model->set_setting($oauth_provider_id, 'google_calendar', 'primary');
|
||||
} else {
|
||||
response('Sync provider id not specified.');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This method will return a list of the available Google Calendars.
|
||||
*
|
||||
* The user will need to select a specific calendar from this list to sync his appointments with. Google access must
|
||||
* be already granted for the specific provider.
|
||||
*/
|
||||
public function get_google_calendars(): void
|
||||
{
|
||||
try {
|
||||
$provider_id = (int) request('provider_id');
|
||||
|
||||
if (empty($provider_id)) {
|
||||
throw new Exception('Provider id is required in order to fetch the google calendars.');
|
||||
}
|
||||
|
||||
// Check if selected provider has sync enabled.
|
||||
$google_sync = $this->providers_model->get_setting($provider_id, 'google_sync');
|
||||
|
||||
if (!$google_sync) {
|
||||
json_response([
|
||||
'success' => false,
|
||||
]);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$google_token = json_decode($this->providers_model->get_setting($provider_id, 'google_token'), true);
|
||||
|
||||
$this->google_sync->refresh_token($google_token['refresh_token']);
|
||||
|
||||
$calendars = $this->google_sync->get_google_calendars();
|
||||
|
||||
json_response($calendars);
|
||||
} catch (Throwable $e) {
|
||||
json_exception($e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Select a specific google calendar for a provider.
|
||||
*
|
||||
* All the appointments will be synced with this particular calendar.
|
||||
*/
|
||||
public function select_google_calendar(): void
|
||||
{
|
||||
try {
|
||||
$provider_id = request('provider_id');
|
||||
|
||||
$user_id = session('user_id');
|
||||
|
||||
if (cannot('edit', PRIV_USERS) && (int) $user_id !== (int) $provider_id) {
|
||||
throw new RuntimeException('You do not have the required permissions for this task.');
|
||||
}
|
||||
|
||||
$calendar_id = request('calendar_id');
|
||||
|
||||
$this->providers_model->set_setting($provider_id, 'google_calendar', $calendar_id);
|
||||
|
||||
json_response([
|
||||
'success' => true,
|
||||
]);
|
||||
} catch (Throwable $e) {
|
||||
json_exception($e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Disable a providers sync setting.
|
||||
*
|
||||
* This method deletes the "google_sync" and "google_token" settings from the database.
|
||||
*
|
||||
* After that the provider's appointments will be no longer synced with Google Calendar.
|
||||
*/
|
||||
public function disable_provider_sync(): void
|
||||
{
|
||||
try {
|
||||
$provider_id = request('provider_id');
|
||||
|
||||
if (!$provider_id) {
|
||||
throw new Exception('Provider id not specified.');
|
||||
}
|
||||
|
||||
$user_id = session('user_id');
|
||||
|
||||
if (cannot('edit', PRIV_USERS) && (int) $user_id !== (int) $provider_id) {
|
||||
throw new RuntimeException('You do not have the required permissions for this task.');
|
||||
}
|
||||
|
||||
$this->providers_model->set_setting($provider_id, 'google_sync', false);
|
||||
|
||||
$this->providers_model->set_setting($provider_id, 'google_token');
|
||||
|
||||
$this->appointments_model->clear_google_sync_ids($provider_id);
|
||||
|
||||
json_response([
|
||||
'success' => true,
|
||||
]);
|
||||
} catch (Throwable $e) {
|
||||
json_exception($e);
|
||||
}
|
||||
}
|
||||
}
|
102
application/controllers/Google_analytics_settings.php
Normal file
102
application/controllers/Google_analytics_settings.php
Normal file
@ -0,0 +1,102 @@
|
||||
<?php defined('BASEPATH') or exit('No direct script access allowed');
|
||||
|
||||
/* ----------------------------------------------------------------------------
|
||||
* Easy!Appointments - Online Appointment Scheduler
|
||||
*
|
||||
* @package EasyAppointments
|
||||
* @author A.Tselegidis <alextselegidis@gmail.com>
|
||||
* @copyright Copyright (c) Alex Tselegidis
|
||||
* @license https://opensource.org/licenses/GPL-3.0 - GPLv3
|
||||
* @link https://easyappointments.org
|
||||
* @since v1.5.0
|
||||
* ---------------------------------------------------------------------------- */
|
||||
|
||||
/**
|
||||
* Google Analytics settings controller.
|
||||
*
|
||||
* Handles Google Analytics settings related operations.
|
||||
*
|
||||
* @package Controllers
|
||||
*/
|
||||
class Google_analytics_settings extends EA_Controller
|
||||
{
|
||||
/**
|
||||
* Google_analytics_settings constructor.
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
parent::__construct();
|
||||
|
||||
$this->load->model('settings_model');
|
||||
|
||||
$this->load->library('accounts');
|
||||
}
|
||||
|
||||
/**
|
||||
* Render the settings page.
|
||||
*/
|
||||
public function index(): void
|
||||
{
|
||||
session(['dest_url' => site_url('google_analytics_settings')]);
|
||||
|
||||
$user_id = session('user_id');
|
||||
|
||||
if (cannot('view', PRIV_SYSTEM_SETTINGS)) {
|
||||
if ($user_id) {
|
||||
abort(403, 'Forbidden');
|
||||
}
|
||||
|
||||
redirect('login');
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$role_slug = session('role_slug');
|
||||
|
||||
script_vars([
|
||||
'user_id' => $user_id,
|
||||
'role_slug' => $role_slug,
|
||||
'google_analytics_settings' => $this->settings_model->get('name like "google_analytics_%"'),
|
||||
]);
|
||||
|
||||
html_vars([
|
||||
'page_title' => lang('google_analytics'),
|
||||
'active_menu' => PRIV_SYSTEM_SETTINGS,
|
||||
'user_display_name' => $this->accounts->get_user_display_name($user_id),
|
||||
]);
|
||||
|
||||
$this->load->view('pages/google_analytics_settings');
|
||||
}
|
||||
|
||||
/**
|
||||
* Save general settings.
|
||||
*/
|
||||
public function save(): void
|
||||
{
|
||||
try {
|
||||
if (cannot('edit', PRIV_SYSTEM_SETTINGS)) {
|
||||
throw new RuntimeException('You do not have the required permissions for this task.');
|
||||
}
|
||||
|
||||
$settings = request('google_analytics_settings', []);
|
||||
|
||||
foreach ($settings as $setting) {
|
||||
$existing_setting = $this->settings_model
|
||||
->query()
|
||||
->where('name', $setting['name'])
|
||||
->get()
|
||||
->row_array();
|
||||
|
||||
if (!empty($existing_setting)) {
|
||||
$setting['id'] = $existing_setting['id'];
|
||||
}
|
||||
|
||||
$this->settings_model->save($setting);
|
||||
}
|
||||
|
||||
response();
|
||||
} catch (Throwable $e) {
|
||||
json_exception($e);
|
||||
}
|
||||
}
|
||||
}
|
140
application/controllers/Installation.php
Normal file
140
application/controllers/Installation.php
Normal file
@ -0,0 +1,140 @@
|
||||
<?php defined('BASEPATH') or exit('No direct script access allowed');
|
||||
|
||||
/* ----------------------------------------------------------------------------
|
||||
* Easy!Appointments - Online Appointment Scheduler
|
||||
*
|
||||
* @package EasyAppointments
|
||||
* @author A.Tselegidis <alextselegidis@gmail.com>
|
||||
* @copyright Copyright (c) Alex Tselegidis
|
||||
* @license https://opensource.org/licenses/GPL-3.0 - GPLv3
|
||||
* @link https://easyappointments.org
|
||||
* @since v1.1.0
|
||||
* ---------------------------------------------------------------------------- */
|
||||
|
||||
/**
|
||||
* Installation controller.
|
||||
*
|
||||
* Handles the installation related operations.
|
||||
*
|
||||
* @package Controllers
|
||||
*/
|
||||
class Installation extends EA_Controller
|
||||
{
|
||||
/**
|
||||
* Installation constructor.
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
parent::__construct();
|
||||
|
||||
$this->load->model('admins_model');
|
||||
$this->load->model('settings_model');
|
||||
$this->load->model('services_model');
|
||||
$this->load->model('providers_model');
|
||||
$this->load->model('customers_model');
|
||||
|
||||
$this->load->library('instance');
|
||||
}
|
||||
|
||||
/**
|
||||
* Display the installation page.
|
||||
*/
|
||||
public function index(): void
|
||||
{
|
||||
if (is_app_installed()) {
|
||||
redirect();
|
||||
return;
|
||||
}
|
||||
|
||||
$this->load->view('pages/installation', [
|
||||
'base_url' => config('base_url'),
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Installs Easy!Appointments on the server.
|
||||
*/
|
||||
public function perform(): void
|
||||
{
|
||||
try {
|
||||
if (is_app_installed()) {
|
||||
return;
|
||||
}
|
||||
|
||||
$admin = request('admin');
|
||||
$company = request('company');
|
||||
|
||||
$this->instance->migrate();
|
||||
|
||||
// Insert admin
|
||||
$admin['timezone'] = date_default_timezone_get();
|
||||
$admin['settings']['username'] = $admin['username'];
|
||||
$admin['settings']['password'] = $admin['password'];
|
||||
$admin['settings']['notifications'] = true;
|
||||
$admin['settings']['calendar_view'] = CALENDAR_VIEW_DEFAULT;
|
||||
unset($admin['username'], $admin['password']);
|
||||
$admin['id'] = $this->admins_model->save($admin);
|
||||
|
||||
session([
|
||||
'user_id' => $admin['id'],
|
||||
'user_email' => $admin['email'],
|
||||
'role_slug' => DB_SLUG_ADMIN,
|
||||
'language' => $admin['language'],
|
||||
'timezone' => $admin['timezone'],
|
||||
'username' => $admin['settings']['username'],
|
||||
]);
|
||||
|
||||
// Save company settings
|
||||
setting([
|
||||
'company_name' => $company['company_name'],
|
||||
'company_email' => $company['company_email'],
|
||||
'company_link' => $company['company_link'],
|
||||
]);
|
||||
|
||||
// Service
|
||||
$service_id = $this->services_model->save([
|
||||
'name' => 'Service',
|
||||
'duration' => '30',
|
||||
'price' => '0',
|
||||
'currency' => '',
|
||||
'availabilities_type' => 'flexible',
|
||||
'attendants_number' => '1',
|
||||
]);
|
||||
|
||||
// Provider
|
||||
$this->providers_model->save([
|
||||
'first_name' => 'Jane',
|
||||
'last_name' => 'Doe',
|
||||
'email' => 'jane@example.org',
|
||||
'phone_number' => '+1 (000) 000-0000',
|
||||
'services' => [$service_id],
|
||||
'language' => $admin['language'],
|
||||
'timezone' => $admin['timezone'],
|
||||
'settings' => [
|
||||
'username' => 'janedoe',
|
||||
'password' => random_string(),
|
||||
'working_plan' => setting('company_working_plan'),
|
||||
'notifications' => true,
|
||||
'google_sync' => false,
|
||||
'sync_past_days' => 30,
|
||||
'sync_future_days' => 90,
|
||||
'calendar_view' => CALENDAR_VIEW_DEFAULT,
|
||||
],
|
||||
]);
|
||||
|
||||
// Customer
|
||||
$this->customers_model->save([
|
||||
'first_name' => 'James',
|
||||
'last_name' => 'Doe',
|
||||
'email' => 'james@example.org',
|
||||
'phone_number' => '+1 (000) 000-0000',
|
||||
]);
|
||||
|
||||
json_response([
|
||||
'success' => true,
|
||||
]);
|
||||
} catch (Throwable $e) {
|
||||
json_exception($e);
|
||||
}
|
||||
}
|
||||
}
|
74
application/controllers/Integrations.php
Normal file
74
application/controllers/Integrations.php
Normal file
@ -0,0 +1,74 @@
|
||||
<?php defined('BASEPATH') or exit('No direct script access allowed');
|
||||
|
||||
/* ----------------------------------------------------------------------------
|
||||
* Easy!Appointments - Online Appointment Scheduler
|
||||
*
|
||||
* @package EasyAppointments
|
||||
* @author A.Tselegidis <alextselegidis@gmail.com>
|
||||
* @copyright Copyright (c) Alex Tselegidis
|
||||
* @license https://opensource.org/licenses/GPL-3.0 - GPLv3
|
||||
* @link https://easyappointments.org
|
||||
* @since v1.5.0
|
||||
* ---------------------------------------------------------------------------- */
|
||||
|
||||
/**
|
||||
* Integrations controller.
|
||||
*
|
||||
* Displays the integrations page.
|
||||
*
|
||||
* @package Controllers
|
||||
*/
|
||||
class Integrations extends EA_Controller
|
||||
{
|
||||
/**
|
||||
* Integrations constructor.
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
parent::__construct();
|
||||
|
||||
$this->load->model('appointments_model');
|
||||
$this->load->model('customers_model');
|
||||
$this->load->model('services_model');
|
||||
$this->load->model('providers_model');
|
||||
$this->load->model('roles_model');
|
||||
$this->load->model('settings_model');
|
||||
|
||||
$this->load->library('accounts');
|
||||
$this->load->library('google_sync');
|
||||
$this->load->library('notifications');
|
||||
$this->load->library('synchronization');
|
||||
$this->load->library('timezones');
|
||||
}
|
||||
|
||||
/**
|
||||
* Render the settings page.
|
||||
*/
|
||||
public function index(): void
|
||||
{
|
||||
session(['dest_url' => site_url('about')]);
|
||||
|
||||
$user_id = session('user_id');
|
||||
|
||||
if (cannot('view', PRIV_SYSTEM_SETTINGS)) {
|
||||
if ($user_id) {
|
||||
abort(403, 'Forbidden');
|
||||
}
|
||||
|
||||
redirect('login');
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$role_slug = session('role_slug');
|
||||
|
||||
html_vars([
|
||||
'page_title' => lang('integrations'),
|
||||
'active_menu' => PRIV_SYSTEM_SETTINGS,
|
||||
'user_display_name' => $this->accounts->get_user_display_name($user_id),
|
||||
'privileges' => $this->roles_model->get_permissions_by_slug($role_slug),
|
||||
]);
|
||||
|
||||
$this->load->view('pages/integrations');
|
||||
}
|
||||
}
|
132
application/controllers/Ldap_settings.php
Normal file
132
application/controllers/Ldap_settings.php
Normal file
@ -0,0 +1,132 @@
|
||||
<?php defined('BASEPATH') or exit('No direct script access allowed');
|
||||
|
||||
/* ----------------------------------------------------------------------------
|
||||
* Easy!Appointments - Online Appointment Scheduler
|
||||
*
|
||||
* @package EasyAppointments
|
||||
* @author A.Tselegidis <alextselegidis@gmail.com>
|
||||
* @copyright Copyright (c) Alex Tselegidis
|
||||
* @license https://opensource.org/licenses/GPL-3.0 - GPLv3
|
||||
* @link https://easyappointments.org
|
||||
* @since v1.5.0
|
||||
* ---------------------------------------------------------------------------- */
|
||||
|
||||
/**
|
||||
* LDAP settings controller.
|
||||
*
|
||||
* Handles LDAP settings related operations.
|
||||
*
|
||||
* @package Controllers
|
||||
*/
|
||||
class Ldap_settings extends EA_Controller
|
||||
{
|
||||
/**
|
||||
* Ldap_settings constructor.
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
parent::__construct();
|
||||
|
||||
$this->load->model('settings_model');
|
||||
|
||||
$this->load->library('accounts');
|
||||
$this->load->library('ldap_client');
|
||||
}
|
||||
|
||||
/**
|
||||
* Render the settings page.
|
||||
*/
|
||||
public function index(): void
|
||||
{
|
||||
session(['dest_url' => site_url('ldap_settings')]);
|
||||
|
||||
$user_id = session('user_id');
|
||||
|
||||
if (cannot('view', PRIV_SYSTEM_SETTINGS)) {
|
||||
if ($user_id) {
|
||||
abort(403, 'Forbidden');
|
||||
}
|
||||
|
||||
redirect('login');
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$role_slug = session('role_slug');
|
||||
|
||||
script_vars([
|
||||
'user_id' => $user_id,
|
||||
'role_slug' => $role_slug,
|
||||
'ldap_settings' => $this->settings_model->get('name like "ldap_%"'),
|
||||
'ldap_default_filter' => LDAP_DEFAULT_FILTER,
|
||||
'ldap_default_field_mapping' => LDAP_DEFAULT_FIELD_MAPPING,
|
||||
]);
|
||||
|
||||
html_vars([
|
||||
'page_title' => lang('ldap'),
|
||||
'active_menu' => PRIV_SYSTEM_SETTINGS,
|
||||
'user_display_name' => $this->accounts->get_user_display_name($user_id),
|
||||
'roles' => $this->roles_model->get(),
|
||||
]);
|
||||
|
||||
$this->load->view('pages/ldap_settings');
|
||||
}
|
||||
|
||||
/**
|
||||
* Save general settings.
|
||||
*/
|
||||
public function save(): void
|
||||
{
|
||||
try {
|
||||
if (cannot('edit', PRIV_SYSTEM_SETTINGS)) {
|
||||
throw new RuntimeException('You do not have the required permissions for this task.');
|
||||
}
|
||||
|
||||
$settings = request('ldap_settings', []);
|
||||
|
||||
foreach ($settings as $setting) {
|
||||
$existing_setting = $this->settings_model
|
||||
->query()
|
||||
->where('name', $setting['name'])
|
||||
->get()
|
||||
->row_array();
|
||||
|
||||
if (!empty($existing_setting)) {
|
||||
$setting['id'] = $existing_setting['id'];
|
||||
}
|
||||
|
||||
$this->settings_model->save($setting);
|
||||
}
|
||||
|
||||
response();
|
||||
} catch (Throwable $e) {
|
||||
json_exception($e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Search the LDAP directory.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function search(): void
|
||||
{
|
||||
try {
|
||||
if (cannot('edit', PRIV_SYSTEM_SETTINGS)) {
|
||||
throw new RuntimeException('You do not have the required permissions for this task.');
|
||||
}
|
||||
|
||||
if (!extension_loaded('ldap')) {
|
||||
throw new RuntimeException('The LDAP extension is not loaded.');
|
||||
}
|
||||
|
||||
$keyword = request('keyword');
|
||||
|
||||
$entries = $this->ldap_client->search($keyword);
|
||||
|
||||
json_response($entries);
|
||||
} catch (Throwable $e) {
|
||||
json_exception($e);
|
||||
}
|
||||
}
|
||||
}
|
102
application/controllers/Legal_settings.php
Normal file
102
application/controllers/Legal_settings.php
Normal file
@ -0,0 +1,102 @@
|
||||
<?php defined('BASEPATH') or exit('No direct script access allowed');
|
||||
|
||||
/* ----------------------------------------------------------------------------
|
||||
* Easy!Appointments - Online Appointment Scheduler
|
||||
*
|
||||
* @package EasyAppointments
|
||||
* @author A.Tselegidis <alextselegidis@gmail.com>
|
||||
* @copyright Copyright (c) Alex Tselegidis
|
||||
* @license https://opensource.org/licenses/GPL-3.0 - GPLv3
|
||||
* @link https://easyappointments.org
|
||||
* @since v1.5.0
|
||||
* ---------------------------------------------------------------------------- */
|
||||
|
||||
/**
|
||||
* Client form controller.
|
||||
*
|
||||
* Handles legal contents settings related operations.
|
||||
*
|
||||
* @package Controllers
|
||||
*/
|
||||
class Legal_settings extends EA_Controller
|
||||
{
|
||||
/**
|
||||
* Legal_contents constructor.
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
parent::__construct();
|
||||
|
||||
$this->load->model('settings_model');
|
||||
|
||||
$this->load->library('accounts');
|
||||
}
|
||||
|
||||
/**
|
||||
* Render the settings page.
|
||||
*/
|
||||
public function index(): void
|
||||
{
|
||||
session(['dest_url' => site_url('legal_settings')]);
|
||||
|
||||
$user_id = session('user_id');
|
||||
|
||||
if (cannot('view', PRIV_SYSTEM_SETTINGS)) {
|
||||
if ($user_id) {
|
||||
abort(403, 'Forbidden');
|
||||
}
|
||||
|
||||
redirect('login');
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$role_slug = session('role_slug');
|
||||
|
||||
script_vars([
|
||||
'user_id' => $user_id,
|
||||
'role_slug' => $role_slug,
|
||||
'legal_settings' => $this->settings_model->get(),
|
||||
]);
|
||||
|
||||
html_vars([
|
||||
'page_title' => lang('settings'),
|
||||
'active_menu' => PRIV_SYSTEM_SETTINGS,
|
||||
'user_display_name' => $this->accounts->get_user_display_name($user_id),
|
||||
]);
|
||||
|
||||
$this->load->view('pages/legal_settings');
|
||||
}
|
||||
|
||||
/**
|
||||
* Save legal settings.
|
||||
*/
|
||||
public function save(): void
|
||||
{
|
||||
try {
|
||||
if (cannot('edit', PRIV_SYSTEM_SETTINGS)) {
|
||||
throw new RuntimeException('You do not have the required permissions for this task.');
|
||||
}
|
||||
|
||||
$settings = request('legal_settings', []);
|
||||
|
||||
foreach ($settings as $setting) {
|
||||
$existing_setting = $this->settings_model
|
||||
->query()
|
||||
->where('name', $setting['name'])
|
||||
->get()
|
||||
->row_array();
|
||||
|
||||
if (!empty($existing_setting)) {
|
||||
$setting['id'] = $existing_setting['id'];
|
||||
}
|
||||
|
||||
$this->settings_model->save($setting);
|
||||
}
|
||||
|
||||
response();
|
||||
} catch (Throwable $e) {
|
||||
json_exception($e);
|
||||
}
|
||||
}
|
||||
}
|
55
application/controllers/Localization.php
Normal file
55
application/controllers/Localization.php
Normal file
@ -0,0 +1,55 @@
|
||||
<?php defined('BASEPATH') or exit('No direct script access allowed');
|
||||
|
||||
/* ----------------------------------------------------------------------------
|
||||
* Easy!Appointments - Open Source Web Scheduler
|
||||
*
|
||||
* @package EasyAppointments
|
||||
* @author A.Tselegidis <alextselegidis@gmail.com>
|
||||
* @copyright Copyright (c) 2013 - 2020, Alex Tselegidis
|
||||
* @license https://opensource.org/licenses/GPL-3.0 - GPLv3
|
||||
* @link https://easyappointments.org
|
||||
* @since v1.4.3
|
||||
* ---------------------------------------------------------------------------- */
|
||||
|
||||
/**
|
||||
* Localization Controller
|
||||
*
|
||||
* Contains all the localization related methods.
|
||||
*
|
||||
* @package Controllers
|
||||
*/
|
||||
class Localization extends EA_Controller
|
||||
{
|
||||
/**
|
||||
* Change system language for current user.
|
||||
*
|
||||
* The language setting is stored in session data and retrieved every time the user visits any of the system pages.
|
||||
*
|
||||
* Notice: This method used to be in the Backend_api.php.
|
||||
*/
|
||||
public function change_language(): void
|
||||
{
|
||||
try {
|
||||
// Check if language exists in the available languages.
|
||||
$language = request('language');
|
||||
|
||||
if (!in_array($language, config('available_languages'))) {
|
||||
throw new RuntimeException(
|
||||
'Translations for the given language does not exist (' . request('language') . ').',
|
||||
);
|
||||
}
|
||||
|
||||
$language = request('language');
|
||||
|
||||
session(['language' => $language]);
|
||||
|
||||
config(['language' => $language]);
|
||||
|
||||
json_response([
|
||||
'success' => true,
|
||||
]);
|
||||
} catch (Throwable $e) {
|
||||
json_exception($e);
|
||||
}
|
||||
}
|
||||
}
|
98
application/controllers/Login.php
Normal file
98
application/controllers/Login.php
Normal file
@ -0,0 +1,98 @@
|
||||
<?php defined('BASEPATH') or exit('No direct script access allowed');
|
||||
|
||||
/* ----------------------------------------------------------------------------
|
||||
* Easy!Appointments - Online Appointment Scheduler
|
||||
*
|
||||
* @package EasyAppointments
|
||||
* @author A.Tselegidis <alextselegidis@gmail.com>
|
||||
* @copyright Copyright (c) Alex Tselegidis
|
||||
* @license https://opensource.org/licenses/GPL-3.0 - GPLv3
|
||||
* @link https://easyappointments.org
|
||||
* @since v1.5.0
|
||||
* ---------------------------------------------------------------------------- */
|
||||
|
||||
/**
|
||||
* Login controller.
|
||||
*
|
||||
* Handles the login page functionality.
|
||||
*
|
||||
* @package Controllers
|
||||
*/
|
||||
class Login extends EA_Controller
|
||||
{
|
||||
/**
|
||||
* Login constructor.
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
parent::__construct();
|
||||
|
||||
$this->load->library('accounts');
|
||||
$this->load->library('ldap_client');
|
||||
$this->load->library('email_messages');
|
||||
|
||||
script_vars([
|
||||
'dest_url' => session('dest_url', site_url('calendar')),
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Render the login page.
|
||||
*/
|
||||
public function index(): void
|
||||
{
|
||||
if (session('user_id')) {
|
||||
redirect('calendar');
|
||||
return;
|
||||
}
|
||||
|
||||
html_vars([
|
||||
'page_title' => lang('login'),
|
||||
'base_url' => config('base_url'),
|
||||
'dest_url' => session('dest_url', site_url('calendar')),
|
||||
'company_name' => setting('company_name'),
|
||||
]);
|
||||
|
||||
$this->load->view('pages/login');
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate the provided credentials and start a new session if the validation was successful.
|
||||
*/
|
||||
public function validate(): void
|
||||
{
|
||||
try {
|
||||
$username = request('username');
|
||||
|
||||
if (empty($username)) {
|
||||
throw new InvalidArgumentException('No username value provided.');
|
||||
}
|
||||
|
||||
$password = request('password');
|
||||
|
||||
if (empty($password)) {
|
||||
throw new InvalidArgumentException('No password value provided.');
|
||||
}
|
||||
|
||||
$user_data = $this->accounts->check_login($username, $password);
|
||||
|
||||
if (empty($user_data)) {
|
||||
$user_data = $this->ldap_client->check_login($username, $password);
|
||||
}
|
||||
|
||||
if (empty($user_data)) {
|
||||
throw new InvalidArgumentException(lang('invalid_credentials_provided'));
|
||||
}
|
||||
|
||||
$this->session->sess_regenerate();
|
||||
|
||||
session($user_data); // Save data in the session.
|
||||
|
||||
json_response([
|
||||
'success' => true,
|
||||
]);
|
||||
} catch (Throwable $e) {
|
||||
json_exception($e);
|
||||
}
|
||||
}
|
||||
}
|
39
application/controllers/Logout.php
Normal file
39
application/controllers/Logout.php
Normal file
@ -0,0 +1,39 @@
|
||||
<?php defined('BASEPATH') or exit('No direct script access allowed');
|
||||
|
||||
/* ----------------------------------------------------------------------------
|
||||
* Easy!Appointments - Online Appointment Scheduler
|
||||
*
|
||||
* @package EasyAppointments
|
||||
* @author A.Tselegidis <alextselegidis@gmail.com>
|
||||
* @copyright Copyright (c) Alex Tselegidis
|
||||
* @license https://opensource.org/licenses/GPL-3.0 - GPLv3
|
||||
* @link https://easyappointments.org
|
||||
* @since v1.0.0
|
||||
* ---------------------------------------------------------------------------- */
|
||||
|
||||
/**
|
||||
* Logout controller.
|
||||
*
|
||||
* Handles the logout page functionality.
|
||||
*
|
||||
* @package Controllers
|
||||
*/
|
||||
class Logout extends EA_Controller
|
||||
{
|
||||
/**
|
||||
* Render the logout page.
|
||||
*/
|
||||
public function index(): void
|
||||
{
|
||||
$this->session->sess_destroy();
|
||||
|
||||
$company_name = setting('company_name');
|
||||
|
||||
html_vars([
|
||||
'page_title' => lang('log_out'),
|
||||
'company_name' => $company_name,
|
||||
]);
|
||||
|
||||
$this->load->view('pages/logout');
|
||||
}
|
||||
}
|
102
application/controllers/Matomo_analytics_settings.php
Normal file
102
application/controllers/Matomo_analytics_settings.php
Normal file
@ -0,0 +1,102 @@
|
||||
<?php defined('BASEPATH') or exit('No direct script access allowed');
|
||||
|
||||
/* ----------------------------------------------------------------------------
|
||||
* Easy!Appointments - Online Appointment Scheduler
|
||||
*
|
||||
* @package EasyAppointments
|
||||
* @author A.Tselegidis <alextselegidis@gmail.com>
|
||||
* @copyright Copyright (c) Alex Tselegidis
|
||||
* @license https://opensource.org/licenses/GPL-3.0 - GPLv3
|
||||
* @link https://easyappointments.org
|
||||
* @since v1.5.0
|
||||
* ---------------------------------------------------------------------------- */
|
||||
|
||||
/**
|
||||
* Matomo Analytics settings controller.
|
||||
*
|
||||
* Handles Matomo Analytics settings related operations.
|
||||
*
|
||||
* @package Controllers
|
||||
*/
|
||||
class Matomo_analytics_settings extends EA_Controller
|
||||
{
|
||||
/**
|
||||
* Matomo_analytics_settings constructor.
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
parent::__construct();
|
||||
|
||||
$this->load->model('settings_model');
|
||||
|
||||
$this->load->library('accounts');
|
||||
}
|
||||
|
||||
/**
|
||||
* Render the settings page.
|
||||
*/
|
||||
public function index(): void
|
||||
{
|
||||
session(['dest_url' => site_url('matomo_analytics_settings')]);
|
||||
|
||||
$user_id = session('user_id');
|
||||
|
||||
if (cannot('view', PRIV_SYSTEM_SETTINGS)) {
|
||||
if ($user_id) {
|
||||
abort(403, 'Forbidden');
|
||||
}
|
||||
|
||||
redirect('login');
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$role_slug = session('role_slug');
|
||||
|
||||
script_vars([
|
||||
'user_id' => $user_id,
|
||||
'role_slug' => $role_slug,
|
||||
'matomo_analytics_settings' => $this->settings_model->get('name like "matomo_analytics_%"'),
|
||||
]);
|
||||
|
||||
html_vars([
|
||||
'page_title' => lang('matomo_analytics'),
|
||||
'active_menu' => PRIV_SYSTEM_SETTINGS,
|
||||
'user_display_name' => $this->accounts->get_user_display_name($user_id),
|
||||
]);
|
||||
|
||||
$this->load->view('pages/matomo_analytics_settings');
|
||||
}
|
||||
|
||||
/**
|
||||
* Save general settings.
|
||||
*/
|
||||
public function save(): void
|
||||
{
|
||||
try {
|
||||
if (cannot('edit', PRIV_SYSTEM_SETTINGS)) {
|
||||
throw new RuntimeException('You do not have the required permissions for this task.');
|
||||
}
|
||||
|
||||
$settings = request('matomo_analytics_settings', []);
|
||||
|
||||
foreach ($settings as $setting) {
|
||||
$existing_setting = $this->settings_model
|
||||
->query()
|
||||
->where('name', $setting['name'])
|
||||
->get()
|
||||
->row_array();
|
||||
|
||||
if (!empty($existing_setting)) {
|
||||
$setting['id'] = $existing_setting['id'];
|
||||
}
|
||||
|
||||
$this->settings_model->save($setting);
|
||||
}
|
||||
|
||||
response();
|
||||
} catch (Throwable $e) {
|
||||
json_exception($e);
|
||||
}
|
||||
}
|
||||
}
|
70
application/controllers/Privacy.php
Normal file
70
application/controllers/Privacy.php
Normal file
@ -0,0 +1,70 @@
|
||||
<?php defined('BASEPATH') or exit('No direct script access allowed');
|
||||
|
||||
/* ----------------------------------------------------------------------------
|
||||
* Easy!Appointments - Online Appointment Scheduler
|
||||
*
|
||||
* @package EasyAppointments
|
||||
* @author A.Tselegidis <alextselegidis@gmail.com>
|
||||
* @copyright Copyright (c) Alex Tselegidis
|
||||
* @license https://opensource.org/licenses/GPL-3.0 - GPLv3
|
||||
* @link https://easyappointments.org
|
||||
* @since v1.3.2
|
||||
* ---------------------------------------------------------------------------- */
|
||||
|
||||
/**
|
||||
* Privacy controller.
|
||||
*
|
||||
* Handles the privacy related operations.
|
||||
*
|
||||
* @package Controllers
|
||||
*/
|
||||
class Privacy extends EA_Controller
|
||||
{
|
||||
/**
|
||||
* Privacy constructor.
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
parent::__construct();
|
||||
|
||||
$this->load->driver('cache', ['adapter' => 'file']);
|
||||
|
||||
$this->load->model('customers_model');
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove all customer data (including appointments) from the system.
|
||||
*/
|
||||
public function delete_personal_information(): void
|
||||
{
|
||||
try {
|
||||
$display_delete_personal_information = setting('display_delete_personal_information');
|
||||
|
||||
if (!$display_delete_personal_information) {
|
||||
abort(403, 'Forbidden');
|
||||
}
|
||||
|
||||
$customer_token = request('customer_token');
|
||||
|
||||
if (empty($customer_token)) {
|
||||
throw new InvalidArgumentException('Invalid customer token value provided.');
|
||||
}
|
||||
|
||||
$customer_id = $this->cache->get('customer-token-' . $customer_token);
|
||||
|
||||
if (empty($customer_id)) {
|
||||
throw new InvalidArgumentException(
|
||||
'Customer ID does not exist, please reload the page ' . 'and try again.',
|
||||
);
|
||||
}
|
||||
|
||||
$this->customers_model->delete($customer_id);
|
||||
|
||||
json_response([
|
||||
'success' => true,
|
||||
]);
|
||||
} catch (Throwable $e) {
|
||||
json_exception($e);
|
||||
}
|
||||
}
|
||||
}
|
279
application/controllers/Providers.php
Normal file
279
application/controllers/Providers.php
Normal file
@ -0,0 +1,279 @@
|
||||
<?php defined('BASEPATH') or exit('No direct script access allowed');
|
||||
|
||||
/* ----------------------------------------------------------------------------
|
||||
* Easy!Appointments - Online Appointment Scheduler
|
||||
*
|
||||
* @package EasyAppointments
|
||||
* @author A.Tselegidis <alextselegidis@gmail.com>
|
||||
* @copyright Copyright (c) Alex Tselegidis
|
||||
* @license https://opensource.org/licenses/GPL-3.0 - GPLv3
|
||||
* @link https://easyappointments.org
|
||||
* @since v1.0.0
|
||||
* ---------------------------------------------------------------------------- */
|
||||
|
||||
/**
|
||||
* Providers controller.
|
||||
*
|
||||
* Handles the providers related operations.
|
||||
*
|
||||
* @package Controllers
|
||||
*/
|
||||
class Providers extends EA_Controller
|
||||
{
|
||||
public array $allowed_provider_fields = [
|
||||
'id',
|
||||
'first_name',
|
||||
'last_name',
|
||||
'email',
|
||||
'alt_number',
|
||||
'phone_number',
|
||||
'address',
|
||||
'city',
|
||||
'state',
|
||||
'zip_code',
|
||||
'notes',
|
||||
'timezone',
|
||||
'language',
|
||||
'is_private',
|
||||
'ldap_dn',
|
||||
'id_roles',
|
||||
'settings',
|
||||
'services',
|
||||
];
|
||||
|
||||
public array $optional_provider_fields = [
|
||||
'services' => [],
|
||||
];
|
||||
|
||||
public array $allowed_provider_setting_fields = [
|
||||
'username',
|
||||
'password',
|
||||
'working_plan',
|
||||
'working_plan_exceptions',
|
||||
'notifications',
|
||||
'calendar_view',
|
||||
];
|
||||
|
||||
public array $optional_provider_setting_fields = [
|
||||
'working_plan' => null,
|
||||
'working_plan_exceptions' => '{}',
|
||||
];
|
||||
|
||||
public array $allowed_service_fields = ['id', 'name'];
|
||||
|
||||
/**
|
||||
* Providers constructor.
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
parent::__construct();
|
||||
|
||||
$this->load->model('providers_model');
|
||||
$this->load->model('services_model');
|
||||
$this->load->model('roles_model');
|
||||
|
||||
$this->load->library('accounts');
|
||||
$this->load->library('timezones');
|
||||
$this->load->library('webhooks_client');
|
||||
|
||||
$this->optional_provider_setting_fields['working_plan'] = setting('company_working_plan');
|
||||
}
|
||||
|
||||
/**
|
||||
* Render the backend providers page.
|
||||
*
|
||||
* On this page admin users will be able to manage providers, which are eventually selected by customers during the
|
||||
* booking process.
|
||||
*/
|
||||
public function index(): void
|
||||
{
|
||||
session(['dest_url' => site_url('providers')]);
|
||||
|
||||
$user_id = session('user_id');
|
||||
|
||||
if (cannot('view', PRIV_USERS)) {
|
||||
if ($user_id) {
|
||||
abort(403, 'Forbidden');
|
||||
}
|
||||
|
||||
redirect('login');
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$role_slug = session('role_slug');
|
||||
|
||||
$services = $this->services_model->get();
|
||||
|
||||
foreach ($services as &$service) {
|
||||
$this->services_model->only($service, $this->allowed_service_fields);
|
||||
}
|
||||
|
||||
script_vars([
|
||||
'user_id' => $user_id,
|
||||
'role_slug' => $role_slug,
|
||||
'company_working_plan' => setting('company_working_plan'),
|
||||
'date_format' => setting('date_format'),
|
||||
'time_format' => setting('time_format'),
|
||||
'first_weekday' => setting('first_weekday'),
|
||||
'min_password_length' => MIN_PASSWORD_LENGTH,
|
||||
'timezones' => $this->timezones->to_array(),
|
||||
'services' => $services,
|
||||
'default_language' => setting('default_language'),
|
||||
'default_timezone' => setting('default_timezone'),
|
||||
]);
|
||||
|
||||
html_vars([
|
||||
'page_title' => lang('providers'),
|
||||
'active_menu' => PRIV_USERS,
|
||||
'user_display_name' => $this->accounts->get_user_display_name($user_id),
|
||||
'grouped_timezones' => $this->timezones->to_grouped_array(),
|
||||
'privileges' => $this->roles_model->get_permissions_by_slug($role_slug),
|
||||
'services' => $this->services_model->get(),
|
||||
]);
|
||||
|
||||
$this->load->view('pages/providers');
|
||||
}
|
||||
|
||||
/**
|
||||
* Filter providers by the provided keyword.
|
||||
*/
|
||||
public function search(): void
|
||||
{
|
||||
try {
|
||||
if (cannot('view', PRIV_USERS)) {
|
||||
abort(403, 'Forbidden');
|
||||
}
|
||||
|
||||
$keyword = request('keyword', '');
|
||||
|
||||
$order_by = request('order_by', 'update_datetime DESC');
|
||||
|
||||
$limit = request('limit', 1000);
|
||||
|
||||
$offset = (int) request('offset', '0');
|
||||
|
||||
$providers = $this->providers_model->search($keyword, $limit, $offset, $order_by);
|
||||
|
||||
json_response($providers);
|
||||
} catch (Throwable $e) {
|
||||
json_exception($e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Store a new provider.
|
||||
*/
|
||||
public function store(): void
|
||||
{
|
||||
try {
|
||||
if (cannot('add', PRIV_USERS)) {
|
||||
abort(403, 'Forbidden');
|
||||
}
|
||||
|
||||
$provider = request('provider');
|
||||
|
||||
$this->providers_model->only($provider, $this->allowed_provider_fields);
|
||||
|
||||
$this->providers_model->only($provider['settings'], $this->allowed_provider_setting_fields);
|
||||
|
||||
$this->providers_model->optional($provider, $this->optional_provider_fields);
|
||||
|
||||
$this->providers_model->optional($provider['settings'], $this->optional_provider_setting_fields);
|
||||
|
||||
$provider_id = $this->providers_model->save($provider);
|
||||
|
||||
$provider = $this->providers_model->find($provider_id);
|
||||
|
||||
$this->webhooks_client->trigger(WEBHOOK_PROVIDER_SAVE, $provider);
|
||||
|
||||
json_response([
|
||||
'success' => true,
|
||||
'id' => $provider_id,
|
||||
]);
|
||||
} catch (Throwable $e) {
|
||||
json_exception($e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Find a provider.
|
||||
*/
|
||||
public function find(): void
|
||||
{
|
||||
try {
|
||||
if (cannot('view', PRIV_USERS)) {
|
||||
abort(403, 'Forbidden');
|
||||
}
|
||||
|
||||
$provider_id = request('provider_id');
|
||||
|
||||
$provider = $this->providers_model->find($provider_id);
|
||||
|
||||
json_response($provider);
|
||||
} catch (Throwable $e) {
|
||||
json_exception($e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Update a provider.
|
||||
*/
|
||||
public function update(): void
|
||||
{
|
||||
try {
|
||||
if (cannot('edit', PRIV_USERS)) {
|
||||
abort(403, 'Forbidden');
|
||||
}
|
||||
|
||||
$provider = request('provider');
|
||||
|
||||
$this->providers_model->only($provider, $this->allowed_provider_fields);
|
||||
|
||||
$this->providers_model->only($provider['settings'], $this->allowed_provider_setting_fields);
|
||||
|
||||
$this->providers_model->optional($provider, $this->optional_provider_fields);
|
||||
|
||||
$this->providers_model->optional($provider['settings'], $this->optional_provider_setting_fields);
|
||||
|
||||
$provider_id = $this->providers_model->save($provider);
|
||||
|
||||
$provider = $this->providers_model->find($provider_id);
|
||||
|
||||
$this->webhooks_client->trigger(WEBHOOK_PROVIDER_SAVE, $provider);
|
||||
|
||||
json_response([
|
||||
'success' => true,
|
||||
'id' => $provider_id,
|
||||
]);
|
||||
} catch (Throwable $e) {
|
||||
json_exception($e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove a provider.
|
||||
*/
|
||||
public function destroy(): void
|
||||
{
|
||||
try {
|
||||
if (cannot('delete', PRIV_USERS)) {
|
||||
abort(403, 'Forbidden');
|
||||
}
|
||||
|
||||
$provider_id = request('provider_id');
|
||||
|
||||
$provider = $this->providers_model->find($provider_id);
|
||||
|
||||
$this->providers_model->delete($provider_id);
|
||||
|
||||
$this->webhooks_client->trigger(WEBHOOK_PROVIDER_DELETE, $provider);
|
||||
|
||||
json_response([
|
||||
'success' => true,
|
||||
]);
|
||||
} catch (Throwable $e) {
|
||||
json_exception($e);
|
||||
}
|
||||
}
|
||||
}
|
90
application/controllers/Recovery.php
Normal file
90
application/controllers/Recovery.php
Normal file
@ -0,0 +1,90 @@
|
||||
<?php defined('BASEPATH') or exit('No direct script access allowed');
|
||||
|
||||
/* ----------------------------------------------------------------------------
|
||||
* Easy!Appointments - Online Appointment Scheduler
|
||||
*
|
||||
* @package EasyAppointments
|
||||
* @author A.Tselegidis <alextselegidis@gmail.com>
|
||||
* @copyright Copyright (c) Alex Tselegidis
|
||||
* @license https://opensource.org/licenses/GPL-3.0 - GPLv3
|
||||
* @link https://easyappointments.org
|
||||
* @since v1.0.0
|
||||
* ---------------------------------------------------------------------------- */
|
||||
|
||||
/**
|
||||
* Recovery controller.
|
||||
*
|
||||
* Handles the recovery page functionality.
|
||||
*
|
||||
* @package Controllers
|
||||
*/
|
||||
class Recovery extends EA_Controller
|
||||
{
|
||||
/**
|
||||
* User constructor.
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
parent::__construct();
|
||||
|
||||
$this->load->library('accounts');
|
||||
$this->load->library('email_messages');
|
||||
}
|
||||
|
||||
/**
|
||||
* Display the password recovery page.
|
||||
*/
|
||||
public function index(): void
|
||||
{
|
||||
$company_name = setting('company_name');
|
||||
|
||||
html_vars([
|
||||
'page_title' => lang('forgot_your_password'),
|
||||
'dest_url' => session('dest_url', site_url('backend')),
|
||||
'company_name' => $company_name,
|
||||
]);
|
||||
|
||||
$this->load->view('pages/recovery');
|
||||
}
|
||||
|
||||
/**
|
||||
* Recover the user password and notify the user via email.
|
||||
*/
|
||||
public function perform(): void
|
||||
{
|
||||
try {
|
||||
$username = request('username');
|
||||
|
||||
if (empty($username)) {
|
||||
throw new InvalidArgumentException('No username value provided.');
|
||||
}
|
||||
|
||||
$email = request('email');
|
||||
|
||||
if (empty($email)) {
|
||||
throw new InvalidArgumentException('No email value provided.');
|
||||
}
|
||||
|
||||
$new_password = $this->accounts->regenerate_password($username, $email);
|
||||
|
||||
$company_color = setting('company_color');
|
||||
|
||||
if ($new_password) {
|
||||
$settings = [
|
||||
'company_name' => setting('company_name'),
|
||||
'company_link' => setting('company_link'),
|
||||
'company_email' => setting('company_email'),
|
||||
'company_color' => !empty($company_color) && $company_color != DEFAULT_COMPANY_COLOR ? $company_color : null,
|
||||
];
|
||||
|
||||
$this->email_messages->send_password($new_password, $email, $settings);
|
||||
}
|
||||
|
||||
json_response([
|
||||
'success' => true,
|
||||
]);
|
||||
} catch (Throwable $e) {
|
||||
json_exception($e);
|
||||
}
|
||||
}
|
||||
}
|
254
application/controllers/Secretaries.php
Normal file
254
application/controllers/Secretaries.php
Normal file
@ -0,0 +1,254 @@
|
||||
<?php defined('BASEPATH') or exit('No direct script access allowed');
|
||||
|
||||
/* ----------------------------------------------------------------------------
|
||||
* Easy!Appointments - Online Appointment Scheduler
|
||||
*
|
||||
* @package EasyAppointments
|
||||
* @author A.Tselegidis <alextselegidis@gmail.com>
|
||||
* @copyright Copyright (c) Alex Tselegidis
|
||||
* @license https://opensource.org/licenses/GPL-3.0 - GPLv3
|
||||
* @link https://easyappointments.org
|
||||
* @since v1.0.0
|
||||
* ---------------------------------------------------------------------------- */
|
||||
|
||||
/**
|
||||
* Secretaries controller.
|
||||
*
|
||||
* Handles the secretaries related operations.
|
||||
*
|
||||
* @package Controllers
|
||||
*/
|
||||
class Secretaries extends EA_Controller
|
||||
{
|
||||
public array $allowed_provider_fields = ['id', 'first_name', 'last_name'];
|
||||
public array $allowed_secretary_fields = [
|
||||
'id',
|
||||
'first_name',
|
||||
'last_name',
|
||||
'email',
|
||||
'alt_number',
|
||||
'phone_number',
|
||||
'address',
|
||||
'city',
|
||||
'state',
|
||||
'zip_code',
|
||||
'notes',
|
||||
'timezone',
|
||||
'language',
|
||||
'is_private',
|
||||
'ldap_dn',
|
||||
'id_roles',
|
||||
'settings',
|
||||
'providers',
|
||||
];
|
||||
public array $allowed_secretary_setting_fields = ['username', 'password', 'notifications', 'calendar_view'];
|
||||
public array $optional_secretary_fields = [
|
||||
'providers' => [],
|
||||
];
|
||||
|
||||
/**
|
||||
* Secretaries constructor.
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
parent::__construct();
|
||||
|
||||
$this->load->model('secretaries_model');
|
||||
$this->load->model('providers_model');
|
||||
$this->load->model('roles_model');
|
||||
|
||||
$this->load->library('accounts');
|
||||
$this->load->library('timezones');
|
||||
$this->load->library('webhooks_client');
|
||||
}
|
||||
|
||||
/**
|
||||
* Render the backend secretaries page.
|
||||
*
|
||||
* On this page secretary users will be able to manage secretaries, which are eventually selected by customers during the
|
||||
* booking process.
|
||||
*/
|
||||
public function index(): void
|
||||
{
|
||||
session(['dest_url' => site_url('secretaries')]);
|
||||
|
||||
$user_id = session('user_id');
|
||||
|
||||
if (cannot('view', PRIV_USERS)) {
|
||||
if ($user_id) {
|
||||
abort(403, 'Forbidden');
|
||||
}
|
||||
|
||||
redirect('login');
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$role_slug = session('role_slug');
|
||||
|
||||
$providers = $this->providers_model->get();
|
||||
|
||||
foreach ($providers as &$provider) {
|
||||
$this->providers_model->only($provider, $this->allowed_provider_fields);
|
||||
}
|
||||
|
||||
script_vars([
|
||||
'user_id' => $user_id,
|
||||
'role_slug' => $role_slug,
|
||||
'timezones' => $this->timezones->to_array(),
|
||||
'min_password_length' => MIN_PASSWORD_LENGTH,
|
||||
'providers' => $providers,
|
||||
'default_language' => setting('default_language'),
|
||||
'default_timezone' => setting('default_timezone'),
|
||||
]);
|
||||
|
||||
html_vars([
|
||||
'page_title' => lang('secretaries'),
|
||||
'active_menu' => PRIV_USERS,
|
||||
'user_display_name' => $this->accounts->get_user_display_name($user_id),
|
||||
'grouped_timezones' => $this->timezones->to_grouped_array(),
|
||||
'privileges' => $this->roles_model->get_permissions_by_slug($role_slug),
|
||||
'providers' => $this->providers_model->get(),
|
||||
]);
|
||||
|
||||
$this->load->view('pages/secretaries');
|
||||
}
|
||||
|
||||
/**
|
||||
* Filter secretaries by the provided keyword.
|
||||
*/
|
||||
public function search(): void
|
||||
{
|
||||
try {
|
||||
if (cannot('view', PRIV_USERS)) {
|
||||
abort(403, 'Forbidden');
|
||||
}
|
||||
|
||||
$keyword = request('keyword', '');
|
||||
|
||||
$order_by = request('order_by', 'update_datetime DESC');
|
||||
|
||||
$limit = request('limit', 1000);
|
||||
|
||||
$offset = (int) request('offset', '0');
|
||||
|
||||
$secretaries = $this->secretaries_model->search($keyword, $limit, $offset, $order_by);
|
||||
|
||||
json_response($secretaries);
|
||||
} catch (Throwable $e) {
|
||||
json_exception($e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Store a new secretary.
|
||||
*/
|
||||
public function store(): void
|
||||
{
|
||||
try {
|
||||
if (cannot('add', PRIV_USERS)) {
|
||||
abort(403, 'Forbidden');
|
||||
}
|
||||
|
||||
$secretary = request('secretary');
|
||||
|
||||
$this->secretaries_model->only($secretary, $this->allowed_secretary_fields);
|
||||
|
||||
$this->secretaries_model->only($secretary['settings'], $this->allowed_secretary_setting_fields);
|
||||
|
||||
$this->secretaries_model->optional($secretary, $this->optional_secretary_fields);
|
||||
|
||||
$secretary_id = $this->secretaries_model->save($secretary);
|
||||
|
||||
$secretary = $this->secretaries_model->find($secretary_id);
|
||||
|
||||
$this->webhooks_client->trigger(WEBHOOK_SECRETARY_SAVE, $secretary);
|
||||
|
||||
json_response([
|
||||
'success' => true,
|
||||
'id' => $secretary_id,
|
||||
]);
|
||||
} catch (Throwable $e) {
|
||||
json_exception($e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Find a secretary.
|
||||
*/
|
||||
public function find(): void
|
||||
{
|
||||
try {
|
||||
if (cannot('view', PRIV_USERS)) {
|
||||
abort(403, 'Forbidden');
|
||||
}
|
||||
|
||||
$secretary_id = request('secretary_id');
|
||||
|
||||
$secretary = $this->secretaries_model->find($secretary_id);
|
||||
|
||||
json_response($secretary);
|
||||
} catch (Throwable $e) {
|
||||
json_exception($e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Update a secretary.
|
||||
*/
|
||||
public function update(): void
|
||||
{
|
||||
try {
|
||||
if (cannot('edit', PRIV_USERS)) {
|
||||
abort(403, 'Forbidden');
|
||||
}
|
||||
|
||||
$secretary = request('secretary');
|
||||
|
||||
$this->secretaries_model->only($secretary, $this->allowed_secretary_fields);
|
||||
|
||||
$this->secretaries_model->only($secretary['settings'], $this->allowed_secretary_setting_fields);
|
||||
|
||||
$this->secretaries_model->optional($secretary, $this->optional_secretary_fields);
|
||||
|
||||
$secretary_id = $this->secretaries_model->save($secretary);
|
||||
|
||||
$secretary = $this->secretaries_model->find($secretary_id);
|
||||
|
||||
$this->webhooks_client->trigger(WEBHOOK_SECRETARY_SAVE, $secretary);
|
||||
|
||||
json_response([
|
||||
'success' => true,
|
||||
'id' => $secretary_id,
|
||||
]);
|
||||
} catch (Throwable $e) {
|
||||
json_exception($e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove a secretary.
|
||||
*/
|
||||
public function destroy(): void
|
||||
{
|
||||
try {
|
||||
if (cannot('delete', PRIV_USERS)) {
|
||||
abort(403, 'Forbidden');
|
||||
}
|
||||
|
||||
$secretary_id = request('secretary_id');
|
||||
|
||||
$secretary = $this->secretaries_model->find($secretary_id);
|
||||
|
||||
$this->secretaries_model->delete($secretary_id);
|
||||
|
||||
$this->webhooks_client->trigger(WEBHOOK_SECRETARY_DELETE, $secretary);
|
||||
|
||||
json_response([
|
||||
'success' => true,
|
||||
]);
|
||||
} catch (Throwable $e) {
|
||||
json_exception($e);
|
||||
}
|
||||
}
|
||||
}
|
215
application/controllers/Service_categories.php
Normal file
215
application/controllers/Service_categories.php
Normal file
@ -0,0 +1,215 @@
|
||||
<?php defined('BASEPATH') or exit('No direct script access allowed');
|
||||
|
||||
/* ----------------------------------------------------------------------------
|
||||
* Easy!Appointments - Online Appointment Scheduler
|
||||
*
|
||||
* @package EasyAppointments
|
||||
* @author A.Tselegidis <alextselegidis@gmail.com>
|
||||
* @copyright Copyright (c) Alex Tselegidis
|
||||
* @license https://opensource.org/licenses/GPL-3.0 - GPLv3
|
||||
* @link https://easyappointments.org
|
||||
* @since v1.0.0
|
||||
* ---------------------------------------------------------------------------- */
|
||||
|
||||
/**
|
||||
* Service-categories controller.
|
||||
*
|
||||
* Handles the service-categories related operations.
|
||||
*
|
||||
* @package Controllers
|
||||
*/
|
||||
class Service_categories extends EA_Controller
|
||||
{
|
||||
public array $allowed_service_category_fields = ['id', 'name', 'description'];
|
||||
|
||||
public array $optional_service_category_fields = [];
|
||||
|
||||
/**
|
||||
* Service-categories constructor.
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
parent::__construct();
|
||||
|
||||
$this->load->model('service_categories_model');
|
||||
$this->load->model('roles_model');
|
||||
|
||||
$this->load->library('accounts');
|
||||
$this->load->library('timezones');
|
||||
$this->load->library('webhooks_client');
|
||||
}
|
||||
|
||||
/**
|
||||
* Render the backend service-categories page.
|
||||
*
|
||||
* On this page admin users will be able to manage service-categories, which are eventually selected by customers during the
|
||||
* booking process.
|
||||
*/
|
||||
public function index(): void
|
||||
{
|
||||
session(['dest_url' => site_url('service_categories')]);
|
||||
|
||||
$user_id = session('user_id');
|
||||
|
||||
if (cannot('view', PRIV_SERVICES)) {
|
||||
if ($user_id) {
|
||||
abort(403, 'Forbidden');
|
||||
}
|
||||
|
||||
redirect('login');
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$role_slug = session('role_slug');
|
||||
|
||||
script_vars([
|
||||
'user_id' => $user_id,
|
||||
'role_slug' => $role_slug,
|
||||
]);
|
||||
|
||||
html_vars([
|
||||
'page_title' => lang('service_categories'),
|
||||
'active_menu' => PRIV_SERVICES,
|
||||
'user_display_name' => $this->accounts->get_user_display_name($user_id),
|
||||
'timezones' => $this->timezones->to_array(),
|
||||
'privileges' => $this->roles_model->get_permissions_by_slug($role_slug),
|
||||
]);
|
||||
|
||||
$this->load->view('pages/service_categories');
|
||||
}
|
||||
|
||||
/**
|
||||
* Filter service-categories by the provided keyword.
|
||||
*/
|
||||
public function search(): void
|
||||
{
|
||||
try {
|
||||
if (cannot('view', PRIV_SERVICES)) {
|
||||
abort(403, 'Forbidden');
|
||||
}
|
||||
|
||||
$keyword = request('keyword', '');
|
||||
|
||||
$order_by = request('order_by', 'update_datetime DESC');
|
||||
|
||||
$limit = request('limit', 1000);
|
||||
|
||||
$offset = (int) request('offset', '0');
|
||||
|
||||
$service_categories = $this->service_categories_model->search($keyword, $limit, $offset, $order_by);
|
||||
|
||||
json_response($service_categories);
|
||||
} catch (Throwable $e) {
|
||||
json_exception($e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Store a new service-category.
|
||||
*/
|
||||
public function store(): void
|
||||
{
|
||||
try {
|
||||
if (cannot('add', PRIV_SERVICES)) {
|
||||
abort(403, 'Forbidden');
|
||||
}
|
||||
|
||||
$service_category = request('service_category');
|
||||
|
||||
$this->service_categories_model->only($service_category, $this->allowed_service_category_fields);
|
||||
|
||||
$this->service_categories_model->optional($service_category, $this->optional_service_category_fields);
|
||||
|
||||
$service_category_id = $this->service_categories_model->save($service_category);
|
||||
|
||||
$service_category = $this->service_categories_model->find($service_category_id);
|
||||
|
||||
$this->webhooks_client->trigger(WEBHOOK_SERVICE_CATEGORY_SAVE, $service_category);
|
||||
|
||||
json_response([
|
||||
'success' => true,
|
||||
'id' => $service_category_id,
|
||||
]);
|
||||
} catch (Throwable $e) {
|
||||
json_exception($e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Find a service-category.
|
||||
*/
|
||||
public function find(): void
|
||||
{
|
||||
try {
|
||||
if (cannot('view', PRIV_SERVICES)) {
|
||||
abort(403, 'Forbidden');
|
||||
}
|
||||
|
||||
$service_category_id = request('service_category_id');
|
||||
|
||||
$service_category = $this->service_categories_model->find($service_category_id);
|
||||
|
||||
json_response($service_category);
|
||||
} catch (Throwable $e) {
|
||||
json_exception($e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Update a service-category.
|
||||
*/
|
||||
public function update(): void
|
||||
{
|
||||
try {
|
||||
if (cannot('edit', PRIV_SERVICES)) {
|
||||
abort(403, 'Forbidden');
|
||||
}
|
||||
|
||||
$service_category = request('service_category');
|
||||
|
||||
$this->service_categories_model->only($service_category, $this->allowed_service_category_fields);
|
||||
|
||||
$this->service_categories_model->optional($service_category, $this->optional_service_category_fields);
|
||||
|
||||
$service_category_id = $this->service_categories_model->save($service_category);
|
||||
|
||||
$service_category = $this->service_categories_model->find($service_category_id);
|
||||
|
||||
$this->webhooks_client->trigger(WEBHOOK_SERVICE_CATEGORY_SAVE, $service_category);
|
||||
|
||||
json_response([
|
||||
'success' => true,
|
||||
'id' => $service_category_id,
|
||||
]);
|
||||
} catch (Throwable $e) {
|
||||
json_exception($e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove a service-category.
|
||||
*/
|
||||
public function destroy(): void
|
||||
{
|
||||
try {
|
||||
if (cannot('delete', PRIV_SERVICES)) {
|
||||
abort(403, 'Forbidden');
|
||||
}
|
||||
|
||||
$service_category_id = request('service_category_id');
|
||||
|
||||
$service_category = $this->service_categories_model->find($service_category_id);
|
||||
|
||||
$this->service_categories_model->delete($service_category_id);
|
||||
|
||||
$this->webhooks_client->trigger(WEBHOOK_SERVICE_CATEGORY_DELETE, $service_category);
|
||||
|
||||
json_response([
|
||||
'success' => true,
|
||||
]);
|
||||
} catch (Throwable $e) {
|
||||
json_exception($e);
|
||||
}
|
||||
}
|
||||
}
|
230
application/controllers/Services.php
Normal file
230
application/controllers/Services.php
Normal file
@ -0,0 +1,230 @@
|
||||
<?php defined('BASEPATH') or exit('No direct script access allowed');
|
||||
|
||||
/* ----------------------------------------------------------------------------
|
||||
* Easy!Appointments - Online Appointment Scheduler
|
||||
*
|
||||
* @package EasyAppointments
|
||||
* @author A.Tselegidis <alextselegidis@gmail.com>
|
||||
* @copyright Copyright (c) Alex Tselegidis
|
||||
* @license https://opensource.org/licenses/GPL-3.0 - GPLv3
|
||||
* @link https://easyappointments.org
|
||||
* @since v1.0.0
|
||||
* ---------------------------------------------------------------------------- */
|
||||
|
||||
/**
|
||||
* Services controller.
|
||||
*
|
||||
* Handles the services related operations.
|
||||
*
|
||||
* @package Controllers
|
||||
*/
|
||||
class Services extends EA_Controller
|
||||
{
|
||||
public array $allowed_service_fields = [
|
||||
'id',
|
||||
'name',
|
||||
'duration',
|
||||
'price',
|
||||
'currency',
|
||||
'description',
|
||||
'color',
|
||||
'location',
|
||||
'availabilities_type',
|
||||
'attendants_number',
|
||||
'is_private',
|
||||
'id_service_categories',
|
||||
];
|
||||
public array $optional_service_fields = [
|
||||
'id_service_categories' => null,
|
||||
];
|
||||
|
||||
/**
|
||||
* Services constructor.
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
parent::__construct();
|
||||
|
||||
$this->load->model('services_model');
|
||||
$this->load->model('roles_model');
|
||||
|
||||
$this->load->library('accounts');
|
||||
$this->load->library('timezones');
|
||||
$this->load->library('webhooks_client');
|
||||
}
|
||||
|
||||
/**
|
||||
* Render the backend services page.
|
||||
*
|
||||
* On this page admin users will be able to manage services, which are eventually selected by customers during the
|
||||
* booking process.
|
||||
*/
|
||||
public function index(): void
|
||||
{
|
||||
session(['dest_url' => site_url('services')]);
|
||||
|
||||
$user_id = session('user_id');
|
||||
|
||||
if (cannot('view', PRIV_SERVICES)) {
|
||||
if ($user_id) {
|
||||
abort(403, 'Forbidden');
|
||||
}
|
||||
|
||||
redirect('login');
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$role_slug = session('role_slug');
|
||||
|
||||
script_vars([
|
||||
'user_id' => $user_id,
|
||||
'role_slug' => $role_slug,
|
||||
'event_minimum_duration' => EVENT_MINIMUM_DURATION,
|
||||
]);
|
||||
|
||||
html_vars([
|
||||
'page_title' => lang('services'),
|
||||
'active_menu' => PRIV_SERVICES,
|
||||
'user_display_name' => $this->accounts->get_user_display_name($user_id),
|
||||
'timezones' => $this->timezones->to_array(),
|
||||
'privileges' => $this->roles_model->get_permissions_by_slug($role_slug),
|
||||
]);
|
||||
|
||||
$this->load->view('pages/services');
|
||||
}
|
||||
|
||||
/**
|
||||
* Filter services by the provided keyword.
|
||||
*/
|
||||
public function search(): void
|
||||
{
|
||||
try {
|
||||
if (cannot('view', PRIV_SERVICES)) {
|
||||
abort(403, 'Forbidden');
|
||||
}
|
||||
|
||||
$keyword = request('keyword', '');
|
||||
|
||||
$order_by = request('order_by', 'update_datetime DESC');
|
||||
|
||||
$limit = request('limit', 1000);
|
||||
|
||||
$offset = (int) request('offset', '0');
|
||||
|
||||
$services = $this->services_model->search($keyword, $limit, $offset, $order_by);
|
||||
|
||||
json_response($services);
|
||||
} catch (Throwable $e) {
|
||||
json_exception($e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Store a new service.
|
||||
*/
|
||||
public function store(): void
|
||||
{
|
||||
try {
|
||||
if (cannot('add', PRIV_SERVICES)) {
|
||||
abort(403, 'Forbidden');
|
||||
}
|
||||
|
||||
$service = request('service');
|
||||
|
||||
$this->services_model->only($service, $this->allowed_service_fields);
|
||||
|
||||
$this->services_model->optional($service, $this->optional_service_fields);
|
||||
|
||||
$service_id = $this->services_model->save($service);
|
||||
|
||||
$service = $this->services_model->find($service_id);
|
||||
|
||||
$this->webhooks_client->trigger(WEBHOOK_SERVICE_SAVE, $service);
|
||||
|
||||
json_response([
|
||||
'success' => true,
|
||||
'id' => $service_id,
|
||||
]);
|
||||
} catch (Throwable $e) {
|
||||
json_exception($e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Find a service.
|
||||
*/
|
||||
public function find(): void
|
||||
{
|
||||
try {
|
||||
if (cannot('delete', PRIV_SERVICES)) {
|
||||
abort(403, 'Forbidden');
|
||||
}
|
||||
|
||||
$service_id = request('service_id');
|
||||
|
||||
$service = $this->services_model->find($service_id);
|
||||
|
||||
json_response($service);
|
||||
} catch (Throwable $e) {
|
||||
json_exception($e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Update a service.
|
||||
*/
|
||||
public function update(): void
|
||||
{
|
||||
try {
|
||||
if (cannot('edit', PRIV_SERVICES)) {
|
||||
abort(403, 'Forbidden');
|
||||
}
|
||||
|
||||
$service = request('service');
|
||||
|
||||
$this->services_model->only($service, $this->allowed_service_fields);
|
||||
|
||||
$this->services_model->optional($service, $this->optional_service_fields);
|
||||
|
||||
$service_id = $this->services_model->save($service);
|
||||
|
||||
$service = $this->services_model->find($service_id);
|
||||
|
||||
$this->webhooks_client->trigger(WEBHOOK_SERVICE_SAVE, $service);
|
||||
|
||||
json_response([
|
||||
'success' => true,
|
||||
'id' => $service_id,
|
||||
]);
|
||||
} catch (Throwable $e) {
|
||||
json_exception($e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove a service.
|
||||
*/
|
||||
public function destroy(): void
|
||||
{
|
||||
try {
|
||||
if (cannot('delete', PRIV_SERVICES)) {
|
||||
abort(403, 'Forbidden');
|
||||
}
|
||||
|
||||
$service_id = request('service_id');
|
||||
|
||||
$service = $this->services_model->find($service_id);
|
||||
|
||||
$this->services_model->delete($service_id);
|
||||
|
||||
$this->webhooks_client->trigger(WEBHOOK_SERVICE_DELETE, $service);
|
||||
|
||||
json_response([
|
||||
'success' => true,
|
||||
]);
|
||||
} catch (Throwable $e) {
|
||||
json_exception($e);
|
||||
}
|
||||
}
|
||||
}
|
39
application/controllers/Test.php
Normal file
39
application/controllers/Test.php
Normal file
@ -0,0 +1,39 @@
|
||||
<?php defined('BASEPATH') or exit('No direct script access allowed');
|
||||
|
||||
/* ----------------------------------------------------------------------------
|
||||
* Easy!Appointments - Online Appointment Scheduler
|
||||
*
|
||||
* @package EasyAppointments
|
||||
* @author A.Tselegidis <alextselegidis@gmail.com>
|
||||
* @copyright Copyright (c) Alex Tselegidis
|
||||
* @license https://opensource.org/licenses/GPL-3.0 - GPLv3
|
||||
* @link https://easyappointments.org
|
||||
* @since v1.5.0
|
||||
* ---------------------------------------------------------------------------- */
|
||||
|
||||
/*
|
||||
* This file can only be used in a testing environment and only from the terminal.
|
||||
*/
|
||||
|
||||
if (ENVIRONMENT !== 'testing' || !is_cli()) {
|
||||
show_404();
|
||||
}
|
||||
|
||||
/**
|
||||
* Test controller.
|
||||
*
|
||||
* This controller does not have or need any logic, it is just used so that CI can be loaded properly during the test
|
||||
* execution.
|
||||
*/
|
||||
class Test extends EA_Controller
|
||||
{
|
||||
/**
|
||||
* Placeholder callback.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function index(): void
|
||||
{
|
||||
//
|
||||
}
|
||||
}
|
193
application/controllers/Unavailabilities.php
Normal file
193
application/controllers/Unavailabilities.php
Normal file
@ -0,0 +1,193 @@
|
||||
<?php defined('BASEPATH') or exit('No direct script access allowed');
|
||||
|
||||
/* ----------------------------------------------------------------------------
|
||||
* Easy!Appointments - Online Appointment Scheduler
|
||||
*
|
||||
* @package EasyAppointments
|
||||
* @author A.Tselegidis <alextselegidis@gmail.com>
|
||||
* @copyright Copyright (c) Alex Tselegidis
|
||||
* @license https://opensource.org/licenses/GPL-3.0 - GPLv3
|
||||
* @link https://easyappointments.org
|
||||
* @since v1.0.0
|
||||
* ---------------------------------------------------------------------------- */
|
||||
|
||||
/**
|
||||
* Unavailabilities controller.
|
||||
*
|
||||
* Handles the unavailabilities related operations.
|
||||
*
|
||||
* @package Controllers
|
||||
*/
|
||||
class Unavailabilities extends EA_Controller
|
||||
{
|
||||
public array $allowed_unavailability_fields = [
|
||||
'id',
|
||||
'start_datetime',
|
||||
'end_datetime',
|
||||
'location',
|
||||
'notes',
|
||||
'is_unavailability',
|
||||
'id_users_provider',
|
||||
];
|
||||
|
||||
public array $optional_unavailability_fields = [
|
||||
//
|
||||
];
|
||||
|
||||
/**
|
||||
* Unavailabilities constructor.
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
parent::__construct();
|
||||
|
||||
$this->load->model('unavailabilities_model');
|
||||
$this->load->model('roles_model');
|
||||
|
||||
$this->load->library('accounts');
|
||||
$this->load->library('timezones');
|
||||
$this->load->library('webhooks_client');
|
||||
}
|
||||
|
||||
/**
|
||||
* Filter unavailabilities by the provided keyword.
|
||||
*/
|
||||
public function search(): void
|
||||
{
|
||||
try {
|
||||
if (cannot('view', PRIV_APPOINTMENTS)) {
|
||||
abort(403, 'Forbidden');
|
||||
}
|
||||
|
||||
$keyword = request('keyword', '');
|
||||
|
||||
$order_by = request('order_by', 'update_datetime DESC');
|
||||
|
||||
$limit = request('limit', 1000);
|
||||
|
||||
$offset = (int) request('offset', '0');
|
||||
|
||||
$unavailabilities = $this->unavailabilities_model->search($keyword, $limit, $offset, $order_by);
|
||||
|
||||
json_response($unavailabilities);
|
||||
} catch (Throwable $e) {
|
||||
json_exception($e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Store a new unavailability.
|
||||
*/
|
||||
public function store(): void
|
||||
{
|
||||
try {
|
||||
if (cannot('add', PRIV_APPOINTMENTS)) {
|
||||
abort(403, 'Forbidden');
|
||||
}
|
||||
|
||||
$unavailability = request('unavailability');
|
||||
|
||||
$this->unavailabilities_model->only($unavailability, $this->allowed_unavailability_fields);
|
||||
|
||||
$this->unavailabilities_model->optional($unavailability, $this->optional_unavailability_fields);
|
||||
|
||||
$unavailability_id = $this->unavailabilities_model->save($unavailability);
|
||||
|
||||
$unavailability = $this->unavailabilities_model->find($unavailability_id);
|
||||
|
||||
$provider = $this->providers_model->find($unavailability['id_users_provider']);
|
||||
|
||||
$this->synchronization->sync_unavailability_saved($unavailability, $provider);
|
||||
|
||||
$this->webhooks_client->trigger(WEBHOOK_UNAVAILABILITY_SAVE, $unavailability);
|
||||
|
||||
json_response([
|
||||
'success' => true,
|
||||
'id' => $unavailability_id,
|
||||
]);
|
||||
} catch (Throwable $e) {
|
||||
json_exception($e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Find an unavailability.
|
||||
*/
|
||||
public function find(): void
|
||||
{
|
||||
try {
|
||||
if (cannot('view', PRIV_APPOINTMENTS)) {
|
||||
abort(403, 'Forbidden');
|
||||
}
|
||||
|
||||
$unavailability_id = request('unavailability_id');
|
||||
|
||||
$unavailability = $this->unavailabilities_model->find($unavailability_id);
|
||||
|
||||
json_response($unavailability);
|
||||
} catch (Throwable $e) {
|
||||
json_exception($e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Update a unavailability.
|
||||
*/
|
||||
public function update(): void
|
||||
{
|
||||
try {
|
||||
if (cannot('edit', PRIV_APPOINTMENTS)) {
|
||||
abort(403, 'Forbidden');
|
||||
}
|
||||
|
||||
$unavailability = request('unavailability');
|
||||
|
||||
$this->unavailabilities_model->only($unavailability, $this->allowed_unavailability_fields);
|
||||
|
||||
$this->unavailabilities_model->optional($unavailability, $this->optional_unavailability_fields);
|
||||
|
||||
$unavailability_id = $this->unavailabilities_model->save($unavailability);
|
||||
|
||||
$unavailability = $this->unavailabilities_model->find($unavailability_id);
|
||||
|
||||
$provider = $this->providers_model->find($unavailability['id_users_provider']);
|
||||
|
||||
$this->synchronization->sync_unavailability_saved($unavailability, $provider);
|
||||
|
||||
$this->webhooks_client->trigger(WEBHOOK_UNAVAILABILITY_SAVE, $unavailability);
|
||||
|
||||
json_response([
|
||||
'success' => true,
|
||||
'id' => $unavailability_id,
|
||||
]);
|
||||
} catch (Throwable $e) {
|
||||
json_exception($e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove a unavailability.
|
||||
*/
|
||||
public function destroy(): void
|
||||
{
|
||||
try {
|
||||
if (cannot('delete', PRIV_APPOINTMENTS)) {
|
||||
abort(403, 'Forbidden');
|
||||
}
|
||||
|
||||
$unavailability_id = request('unavailability_id');
|
||||
|
||||
$unavailability = $this->unavailabilities_model->find($unavailability_id);
|
||||
|
||||
$this->unavailabilities_model->delete($unavailability_id);
|
||||
|
||||
$this->webhooks_client->trigger(WEBHOOK_UNAVAILABILITY_DELETE, $unavailability);
|
||||
|
||||
json_response([
|
||||
'success' => true,
|
||||
]);
|
||||
} catch (Throwable $e) {
|
||||
json_exception($e);
|
||||
}
|
||||
}
|
||||
}
|
74
application/controllers/Update.php
Normal file
74
application/controllers/Update.php
Normal file
@ -0,0 +1,74 @@
|
||||
<?php defined('BASEPATH') or exit('No direct script access allowed');
|
||||
|
||||
/* ----------------------------------------------------------------------------
|
||||
* Easy!Appointments - Online Appointment Scheduler
|
||||
*
|
||||
* @package EasyAppointments
|
||||
* @author A.Tselegidis <alextselegidis@gmail.com>
|
||||
* @copyright Copyright (c) Alex Tselegidis
|
||||
* @license https://opensource.org/licenses/GPL-3.0 - GPLv3
|
||||
* @link https://easyappointments.org
|
||||
* @since v1.1.0
|
||||
* ---------------------------------------------------------------------------- */
|
||||
|
||||
/**
|
||||
* Update controller.
|
||||
*
|
||||
* Handles the update related operations.
|
||||
*
|
||||
* @package Controllers
|
||||
*/
|
||||
class Update extends EA_Controller
|
||||
{
|
||||
/**
|
||||
* Update constructor.
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
parent::__construct();
|
||||
|
||||
$this->load->model('admins_model');
|
||||
$this->load->model('settings_model');
|
||||
$this->load->model('services_model');
|
||||
$this->load->model('providers_model');
|
||||
$this->load->model('customers_model');
|
||||
|
||||
$this->load->library('instance');
|
||||
}
|
||||
|
||||
/**
|
||||
* This method will update the instance to the latest available version in the server.
|
||||
*
|
||||
* IMPORTANT: The code files must exist in the server, this method will not fetch any new files but will update
|
||||
* the database schema.
|
||||
*
|
||||
* This method can be used either by loading the page in the browser or by an ajax request. But it will answer with
|
||||
* JSON encoded data.
|
||||
*/
|
||||
public function index(): void
|
||||
{
|
||||
try {
|
||||
$user_id = session('user_id');
|
||||
|
||||
if (cannot('edit', PRIV_SYSTEM_SETTINGS)) {
|
||||
if ($user_id) {
|
||||
abort(403, 'Forbidden');
|
||||
}
|
||||
|
||||
redirect('login');
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$this->instance->migrate();
|
||||
|
||||
$view = ['success' => true];
|
||||
} catch (Throwable $e) {
|
||||
$view = ['success' => false, 'exception' => $e->getMessage()];
|
||||
}
|
||||
|
||||
html_vars($view);
|
||||
|
||||
$this->load->view('pages/update');
|
||||
}
|
||||
}
|
71
application/controllers/User.php
Normal file
71
application/controllers/User.php
Normal file
@ -0,0 +1,71 @@
|
||||
<?php defined('BASEPATH') or exit('No direct script access allowed');
|
||||
|
||||
/* ----------------------------------------------------------------------------
|
||||
* Easy!Appointments - Online Appointment Scheduler
|
||||
*
|
||||
* @package EasyAppointments
|
||||
* @author A.Tselegidis <alextselegidis@gmail.com>
|
||||
* @copyright Copyright (c) Alex Tselegidis
|
||||
* @license https://opensource.org/licenses/GPL-3.0 - GPLv3
|
||||
* @link https://easyappointments.org
|
||||
* @since v1.0.0
|
||||
* ---------------------------------------------------------------------------- */
|
||||
|
||||
/**
|
||||
* User controller.
|
||||
*
|
||||
* Handles the user related operations.
|
||||
*
|
||||
* @package Controllers
|
||||
*/
|
||||
class User extends EA_Controller
|
||||
{
|
||||
/**
|
||||
* User constructor.
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
parent::__construct();
|
||||
|
||||
$this->load->library('accounts');
|
||||
$this->load->library('email_messages');
|
||||
}
|
||||
|
||||
/**
|
||||
* Redirect to the login page.
|
||||
*/
|
||||
public function index(): void
|
||||
{
|
||||
redirect('login');
|
||||
}
|
||||
|
||||
/**
|
||||
* Display the login page.
|
||||
*
|
||||
* @deprecated Since 1.5 Use the Login controller instead.
|
||||
*/
|
||||
public function login(): void
|
||||
{
|
||||
redirect('login');
|
||||
}
|
||||
|
||||
/**
|
||||
* Display the logout page.
|
||||
*
|
||||
* @deprecated Since 1.5 Use the Logout controller instead.
|
||||
*/
|
||||
public function logout(): void
|
||||
{
|
||||
redirect('logout');
|
||||
}
|
||||
|
||||
/**
|
||||
* Display the password recovery page.
|
||||
*
|
||||
* @deprecated Since 1.5 Use the Logout controller instead.
|
||||
*/
|
||||
public function forgot_password(): void
|
||||
{
|
||||
redirect('recovery');
|
||||
}
|
||||
}
|
233
application/controllers/Webhooks.php
Normal file
233
application/controllers/Webhooks.php
Normal file
@ -0,0 +1,233 @@
|
||||
<?php defined('BASEPATH') or exit('No direct script access allowed');
|
||||
|
||||
/* ----------------------------------------------------------------------------
|
||||
* Easy!Appointments - Online Appointment Scheduler
|
||||
*
|
||||
* @package EasyAppointments
|
||||
* @author A.Tselegidis <alextselegidis@gmail.com>
|
||||
* @copyright Copyright (c) Alex Tselegidis
|
||||
* @license https://opensource.org/licenses/GPL-3.0 - GPLv3
|
||||
* @link https://easyappointments.org
|
||||
* @since v1.0.0
|
||||
* ---------------------------------------------------------------------------- */
|
||||
|
||||
/**
|
||||
* Webhooks controller.
|
||||
*
|
||||
* Handles the webhooks related operations.
|
||||
*
|
||||
* @package Controllers
|
||||
*/
|
||||
class Webhooks extends EA_Controller
|
||||
{
|
||||
public array $allowed_webhook_fields = [
|
||||
'id',
|
||||
'name',
|
||||
'url',
|
||||
'actions',
|
||||
'secret_header',
|
||||
'secret_token',
|
||||
'is_ssl_verified',
|
||||
'notes',
|
||||
];
|
||||
|
||||
public array $optional_webhook_fields = [
|
||||
//
|
||||
];
|
||||
|
||||
/**
|
||||
* Webhooks constructor.
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
parent::__construct();
|
||||
|
||||
$this->load->model('webhooks_model');
|
||||
$this->load->model('roles_model');
|
||||
|
||||
$this->load->library('accounts');
|
||||
$this->load->library('timezones');
|
||||
}
|
||||
|
||||
/**
|
||||
* Render the backend webhooks page.
|
||||
*
|
||||
* On this page admin users will be able to manage webhooks, which are eventually selected by customers during the
|
||||
* booking process.
|
||||
*/
|
||||
public function index(): void
|
||||
{
|
||||
session(['dest_url' => site_url('webhooks')]);
|
||||
|
||||
$user_id = session('user_id');
|
||||
|
||||
if (cannot('view', PRIV_WEBHOOKS)) {
|
||||
if ($user_id) {
|
||||
abort(403, 'Forbidden');
|
||||
}
|
||||
|
||||
redirect('login');
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$role_slug = session('role_slug');
|
||||
|
||||
script_vars([
|
||||
'user_id' => $user_id,
|
||||
'role_slug' => $role_slug,
|
||||
]);
|
||||
|
||||
html_vars([
|
||||
'page_title' => lang('webhooks'),
|
||||
'active_menu' => PRIV_SYSTEM_SETTINGS,
|
||||
'user_display_name' => $this->accounts->get_user_display_name($user_id),
|
||||
'timezones' => $this->timezones->to_array(),
|
||||
'privileges' => $this->roles_model->get_permissions_by_slug($role_slug),
|
||||
'available_actions' => [
|
||||
WEBHOOK_APPOINTMENT_SAVE,
|
||||
WEBHOOK_APPOINTMENT_DELETE,
|
||||
WEBHOOK_UNAVAILABILITY_SAVE,
|
||||
WEBHOOK_UNAVAILABILITY_DELETE,
|
||||
WEBHOOK_BLOCKED_PERIOD_SAVE,
|
||||
WEBHOOK_BLOCKED_PERIOD_DELETE,
|
||||
WEBHOOK_CUSTOMER_SAVE,
|
||||
WEBHOOK_CUSTOMER_DELETE,
|
||||
WEBHOOK_SERVICE_SAVE,
|
||||
WEBHOOK_SERVICE_DELETE,
|
||||
WEBHOOK_SERVICE_CATEGORY_SAVE,
|
||||
WEBHOOK_SERVICE_CATEGORY_DELETE,
|
||||
WEBHOOK_PROVIDER_SAVE,
|
||||
WEBHOOK_PROVIDER_DELETE,
|
||||
WEBHOOK_SECRETARY_SAVE,
|
||||
WEBHOOK_SECRETARY_DELETE,
|
||||
WEBHOOK_ADMIN_SAVE,
|
||||
WEBHOOK_ADMIN_DELETE,
|
||||
],
|
||||
]);
|
||||
|
||||
$this->load->view('pages/webhooks');
|
||||
}
|
||||
|
||||
/**
|
||||
* Filter webhooks by the provided keyword.
|
||||
*/
|
||||
public function search(): void
|
||||
{
|
||||
try {
|
||||
if (cannot('view', PRIV_WEBHOOKS)) {
|
||||
abort(403, 'Forbidden');
|
||||
}
|
||||
|
||||
$keyword = request('keyword', '');
|
||||
|
||||
$order_by = request('order_by', 'update_datetime DESC');
|
||||
|
||||
$limit = request('limit', 1000);
|
||||
|
||||
$offset = (int) request('offset', '0');
|
||||
|
||||
$webhooks = $this->webhooks_model->search($keyword, $limit, $offset, $order_by);
|
||||
|
||||
json_response($webhooks);
|
||||
} catch (Throwable $e) {
|
||||
json_exception($e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Store a new webhook.
|
||||
*/
|
||||
public function store(): void
|
||||
{
|
||||
try {
|
||||
if (cannot('add', PRIV_WEBHOOKS)) {
|
||||
abort(403, 'Forbidden');
|
||||
}
|
||||
|
||||
$webhook = request('webhook');
|
||||
|
||||
$this->webhooks_model->only($webhook, $this->allowed_webhook_fields);
|
||||
|
||||
$this->webhooks_model->optional($webhook, $this->optional_webhook_fields);
|
||||
|
||||
$webhook_id = $this->webhooks_model->save($webhook);
|
||||
|
||||
json_response([
|
||||
'success' => true,
|
||||
'id' => $webhook_id,
|
||||
]);
|
||||
} catch (Throwable $e) {
|
||||
json_exception($e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Update a webhook.
|
||||
*/
|
||||
public function update(): void
|
||||
{
|
||||
try {
|
||||
if (cannot('edit', PRIV_WEBHOOKS)) {
|
||||
abort(403, 'Forbidden');
|
||||
}
|
||||
|
||||
$webhook = request('webhook');
|
||||
|
||||
$this->webhooks_model->only($webhook, $this->allowed_webhook_fields);
|
||||
|
||||
$this->webhooks_model->optional($webhook, $this->optional_webhook_fields);
|
||||
|
||||
$webhook_id = $this->webhooks_model->save($webhook);
|
||||
|
||||
json_response([
|
||||
'success' => true,
|
||||
'id' => $webhook_id,
|
||||
]);
|
||||
} catch (Throwable $e) {
|
||||
json_exception($e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove a webhook.
|
||||
*/
|
||||
public function destroy(): void
|
||||
{
|
||||
try {
|
||||
if (cannot('delete', PRIV_WEBHOOKS)) {
|
||||
abort(403, 'Forbidden');
|
||||
}
|
||||
|
||||
$webhook_id = request('webhook_id');
|
||||
|
||||
$this->webhooks_model->delete($webhook_id);
|
||||
|
||||
json_response([
|
||||
'success' => true,
|
||||
]);
|
||||
} catch (Throwable $e) {
|
||||
json_exception($e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Find a webhook.
|
||||
*/
|
||||
public function find(): void
|
||||
{
|
||||
try {
|
||||
if (cannot('view', PRIV_WEBHOOKS)) {
|
||||
abort(403, 'Forbidden');
|
||||
}
|
||||
|
||||
$webhook_id = request('webhook_id');
|
||||
|
||||
$webhook = $this->webhooks_model->find($webhook_id);
|
||||
|
||||
json_response($webhook);
|
||||
} catch (Throwable $e) {
|
||||
json_exception($e);
|
||||
}
|
||||
}
|
||||
}
|
10
application/controllers/api/index.html
Normal file
10
application/controllers/api/index.html
Normal file
@ -0,0 +1,10 @@
|
||||
<html lang="en">
|
||||
<head>
|
||||
<title>403 Forbidden</title>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<p>Directory access is forbidden.</p>
|
||||
|
||||
</body>
|
||||
</html>
|
202
application/controllers/api/v1/Admins_api_v1.php
Normal file
202
application/controllers/api/v1/Admins_api_v1.php
Normal file
@ -0,0 +1,202 @@
|
||||
<?php defined('BASEPATH') or exit('No direct script access allowed');
|
||||
|
||||
/* ----------------------------------------------------------------------------
|
||||
* Easy!Appointments - Online Appointment Scheduler
|
||||
*
|
||||
* @package EasyAppointments
|
||||
* @author A.Tselegidis <alextselegidis@gmail.com>
|
||||
* @copyright Copyright (c) Alex Tselegidis
|
||||
* @license https://opensource.org/licenses/GPL-3.0 - GPLv3
|
||||
* @link https://easyappointments.org
|
||||
* @since v1.5.0
|
||||
* ---------------------------------------------------------------------------- */
|
||||
|
||||
/**
|
||||
* Admins API v1 controller.
|
||||
*
|
||||
* @package Controllers
|
||||
*/
|
||||
class Admins_api_v1 extends EA_Controller
|
||||
{
|
||||
/**
|
||||
* Admins_api_v1 constructor.
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
parent::__construct();
|
||||
|
||||
$this->load->model('admins_model');
|
||||
|
||||
$this->load->library('api');
|
||||
|
||||
$this->api->auth();
|
||||
|
||||
$this->api->model('admins_model');
|
||||
}
|
||||
|
||||
/**
|
||||
* Get an admin collection.
|
||||
*/
|
||||
public function index(): void
|
||||
{
|
||||
try {
|
||||
$keyword = $this->api->request_keyword();
|
||||
|
||||
$limit = $this->api->request_limit();
|
||||
|
||||
$offset = $this->api->request_offset();
|
||||
|
||||
$order_by = $this->api->request_order_by();
|
||||
|
||||
$fields = $this->api->request_fields();
|
||||
|
||||
$with = $this->api->request_with();
|
||||
|
||||
$admins = empty($keyword)
|
||||
? $this->admins_model->get(null, $limit, $offset, $order_by)
|
||||
: $this->admins_model->search($keyword, $limit, $offset, $order_by);
|
||||
|
||||
foreach ($admins as &$admin) {
|
||||
$this->admins_model->api_encode($admin);
|
||||
|
||||
if (!empty($fields)) {
|
||||
$this->admins_model->only($admin, $fields);
|
||||
}
|
||||
|
||||
if (!empty($with)) {
|
||||
$this->admins_model->load($admin, $with);
|
||||
}
|
||||
}
|
||||
|
||||
json_response($admins);
|
||||
} catch (Throwable $e) {
|
||||
json_exception($e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a single admin.
|
||||
*
|
||||
* @param int|null $id Admin ID.
|
||||
*/
|
||||
public function show(?int $id = null): void
|
||||
{
|
||||
try {
|
||||
$occurrences = $this->admins_model->get(['id' => $id]);
|
||||
|
||||
if (empty($occurrences)) {
|
||||
response('', 404);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$fields = $this->api->request_fields();
|
||||
|
||||
$with = $this->api->request_with();
|
||||
|
||||
$admin = $this->admins_model->find($id);
|
||||
|
||||
$this->admins_model->api_encode($admin);
|
||||
|
||||
if (!empty($fields)) {
|
||||
$this->admins_model->only($admin, $fields);
|
||||
}
|
||||
|
||||
if (!empty($with)) {
|
||||
$this->admins_model->load($admin, $with);
|
||||
}
|
||||
|
||||
json_response($admin);
|
||||
} catch (Throwable $e) {
|
||||
json_exception($e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Store a new admin.
|
||||
*/
|
||||
public function store(): void
|
||||
{
|
||||
try {
|
||||
$admin = request();
|
||||
|
||||
$this->admins_model->api_decode($admin);
|
||||
|
||||
if (array_key_exists('id', $admin)) {
|
||||
unset($admin['id']);
|
||||
}
|
||||
|
||||
if (!array_key_exists('settings', $admin)) {
|
||||
throw new InvalidArgumentException('No settings property provided.');
|
||||
}
|
||||
|
||||
$admin_id = $this->admins_model->save($admin);
|
||||
|
||||
$created_admin = $this->admins_model->find($admin_id);
|
||||
|
||||
$this->admins_model->api_encode($created_admin);
|
||||
|
||||
json_response($created_admin, 201);
|
||||
} catch (Throwable $e) {
|
||||
json_exception($e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Update an admin.
|
||||
*
|
||||
* @param int $id Admin ID.
|
||||
*/
|
||||
public function update(int $id): void
|
||||
{
|
||||
try {
|
||||
$occurrences = $this->admins_model->get(['id' => $id]);
|
||||
|
||||
if (empty($occurrences)) {
|
||||
response('', 404);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$original_admin = $occurrences[0];
|
||||
|
||||
$admin = request();
|
||||
|
||||
$this->admins_model->api_decode($admin, $original_admin);
|
||||
|
||||
$admin_id = $this->admins_model->save($admin);
|
||||
|
||||
$updated_admin = $this->admins_model->find($admin_id);
|
||||
|
||||
$this->admins_model->api_encode($updated_admin);
|
||||
|
||||
json_response($updated_admin);
|
||||
} catch (Throwable $e) {
|
||||
json_exception($e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete an admin.
|
||||
*
|
||||
* @param int $id Admin ID.
|
||||
*/
|
||||
public function destroy(int $id): void
|
||||
{
|
||||
try {
|
||||
$occurrences = $this->admins_model->get(['id' => $id]);
|
||||
|
||||
if (empty($occurrences)) {
|
||||
response('', 404);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$this->admins_model->delete($id);
|
||||
|
||||
response('', 204);
|
||||
} catch (Throwable $e) {
|
||||
json_exception($e);
|
||||
}
|
||||
}
|
||||
}
|
383
application/controllers/api/v1/Appointments_api_v1.php
Normal file
383
application/controllers/api/v1/Appointments_api_v1.php
Normal file
@ -0,0 +1,383 @@
|
||||
<?php defined('BASEPATH') or exit('No direct script access allowed');
|
||||
|
||||
/* ----------------------------------------------------------------------------
|
||||
* Easy!Appointments - Online Appointment Scheduler
|
||||
*
|
||||
* @package EasyAppointments
|
||||
* @author A.Tselegidis <alextselegidis@gmail.com>
|
||||
* @copyright Copyright (c) Alex Tselegidis
|
||||
* @license https://opensource.org/licenses/GPL-3.0 - GPLv3
|
||||
* @link https://easyappointments.org
|
||||
* @since v1.5.0
|
||||
* ---------------------------------------------------------------------------- */
|
||||
|
||||
/**
|
||||
* Appointments API v1 controller.
|
||||
*
|
||||
* @package Controllers
|
||||
*/
|
||||
class Appointments_api_v1 extends EA_Controller
|
||||
{
|
||||
/**
|
||||
* Appointments_api_v1 constructor.
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
parent::__construct();
|
||||
|
||||
$this->load->model('appointments_model');
|
||||
$this->load->model('customers_model');
|
||||
$this->load->model('providers_model');
|
||||
$this->load->model('services_model');
|
||||
$this->load->model('settings_model');
|
||||
|
||||
$this->load->library('api');
|
||||
$this->load->library('synchronization');
|
||||
$this->load->library('notifications');
|
||||
|
||||
$this->api->auth();
|
||||
|
||||
$this->api->model('appointments_model');
|
||||
}
|
||||
|
||||
/**
|
||||
* Get an appointment collection.
|
||||
*/
|
||||
public function index(): void
|
||||
{
|
||||
try {
|
||||
$keyword = $this->api->request_keyword();
|
||||
|
||||
$limit = $this->api->request_limit();
|
||||
|
||||
$offset = $this->api->request_offset();
|
||||
|
||||
$order_by = $this->api->request_order_by();
|
||||
|
||||
$fields = $this->api->request_fields();
|
||||
|
||||
$with = $this->api->request_with();
|
||||
|
||||
$where = null;
|
||||
|
||||
// Date query param.
|
||||
|
||||
$date = request('date');
|
||||
|
||||
if (!empty($date)) {
|
||||
$where['DATE(start_datetime)'] = new DateTime($date)->format('Y-m-d');
|
||||
}
|
||||
|
||||
// From query param.
|
||||
|
||||
$from = request('from');
|
||||
|
||||
if (!empty($from)) {
|
||||
$where['DATE(start_datetime) >='] = new DateTime($from)->format('Y-m-d');
|
||||
}
|
||||
|
||||
// Till query param.
|
||||
|
||||
$till = request('till');
|
||||
|
||||
if (!empty($till)) {
|
||||
$where['DATE(end_datetime) <='] = new DateTime($till)->format('Y-m-d');
|
||||
}
|
||||
|
||||
// Service ID query param.
|
||||
|
||||
$service_id = request('serviceId');
|
||||
|
||||
if (!empty($service_id)) {
|
||||
$where['id_services'] = $service_id;
|
||||
}
|
||||
|
||||
// Provider ID query param.
|
||||
|
||||
$provider_id = request('providerId');
|
||||
|
||||
if (!empty($provider_id)) {
|
||||
$where['id_users_provider'] = $provider_id;
|
||||
}
|
||||
|
||||
// Customer ID query param.
|
||||
|
||||
$customer_id = request('customerId');
|
||||
|
||||
if (!empty($customer_id)) {
|
||||
$where['id_users_customer'] = $customer_id;
|
||||
}
|
||||
|
||||
$appointments = empty($keyword)
|
||||
? $this->appointments_model->get($where, $limit, $offset, $order_by)
|
||||
: $this->appointments_model->search($keyword, $limit, $offset, $order_by);
|
||||
|
||||
foreach ($appointments as &$appointment) {
|
||||
$this->appointments_model->api_encode($appointment);
|
||||
|
||||
$this->aggregates($appointment);
|
||||
|
||||
if (!empty($fields)) {
|
||||
$this->appointments_model->only($appointment, $fields);
|
||||
}
|
||||
|
||||
if (!empty($with)) {
|
||||
$this->appointments_model->load($appointment, $with);
|
||||
}
|
||||
}
|
||||
|
||||
json_response($appointments);
|
||||
} catch (Throwable $e) {
|
||||
json_exception($e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Load the relations of the current appointment if the "aggregates" query parameter is present.
|
||||
*
|
||||
* This is a compatibility addition to the appointment resource which was the only one to support it.
|
||||
*
|
||||
* Use the "attach" query parameter instead as this one will be removed.
|
||||
*
|
||||
* @param array $appointment Appointment data.
|
||||
*
|
||||
* @deprecated Since 1.5
|
||||
*/
|
||||
private function aggregates(array &$appointment): void
|
||||
{
|
||||
$aggregates = request('aggregates') !== null;
|
||||
|
||||
if ($aggregates) {
|
||||
$appointment['service'] = $this->services_model->find(
|
||||
$appointment['id_services'] ?? ($appointment['serviceId'] ?? null),
|
||||
);
|
||||
$appointment['provider'] = $this->providers_model->find(
|
||||
$appointment['id_users_provider'] ?? ($appointment['providerId'] ?? null),
|
||||
);
|
||||
$appointment['customer'] = $this->customers_model->find(
|
||||
$appointment['id_users_customer'] ?? ($appointment['customerId'] ?? null),
|
||||
);
|
||||
$this->services_model->api_encode($appointment['service']);
|
||||
$this->providers_model->api_encode($appointment['provider']);
|
||||
$this->customers_model->api_encode($appointment['customer']);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a single appointment.
|
||||
*
|
||||
* @param int|null $id Appointment ID.
|
||||
*/
|
||||
public function show(?int $id = null): void
|
||||
{
|
||||
try {
|
||||
$occurrences = $this->appointments_model->get(['id' => $id]);
|
||||
|
||||
if (empty($occurrences)) {
|
||||
response('', 404);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$fields = $this->api->request_fields();
|
||||
|
||||
$with = $this->api->request_with();
|
||||
|
||||
$appointment = $this->appointments_model->find($id);
|
||||
|
||||
$this->appointments_model->api_encode($appointment);
|
||||
|
||||
if (!empty($fields)) {
|
||||
$this->appointments_model->only($appointment, $fields);
|
||||
}
|
||||
|
||||
if (!empty($with)) {
|
||||
$this->appointments_model->load($appointment, $with);
|
||||
}
|
||||
|
||||
json_response($appointment);
|
||||
} catch (Throwable $e) {
|
||||
json_exception($e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Store a new appointment.
|
||||
*/
|
||||
public function store(): void
|
||||
{
|
||||
try {
|
||||
$appointment = request();
|
||||
|
||||
$this->appointments_model->api_decode($appointment);
|
||||
|
||||
if (array_key_exists('id', $appointment)) {
|
||||
unset($appointment['id']);
|
||||
}
|
||||
|
||||
if (!array_key_exists('end_datetime', $appointment)) {
|
||||
$appointment['end_datetime'] = $this->calculate_end_datetime($appointment);
|
||||
}
|
||||
|
||||
$appointment_id = $this->appointments_model->save($appointment);
|
||||
|
||||
$created_appointment = $this->appointments_model->find($appointment_id);
|
||||
|
||||
$this->notify_and_sync_appointment($created_appointment);
|
||||
|
||||
$this->appointments_model->api_encode($created_appointment);
|
||||
|
||||
json_response($created_appointment, 201);
|
||||
} catch (Throwable $e) {
|
||||
json_exception($e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate the end date time of an appointment based on the selected service.
|
||||
*
|
||||
* @param array $appointment Appointment data.
|
||||
*
|
||||
* @return string Returns the end date time value.
|
||||
*
|
||||
* @throws Exception
|
||||
*/
|
||||
private function calculate_end_datetime(array $appointment): string
|
||||
{
|
||||
$duration = $this->services_model->value($appointment['id_services'], 'duration');
|
||||
|
||||
$end = new DateTime($appointment['start_datetime']);
|
||||
|
||||
$end->add(new DateInterval('PT' . $duration . 'M'));
|
||||
|
||||
return $end->format('Y-m-d H:i:s');
|
||||
}
|
||||
|
||||
/**
|
||||
* Send the required notifications and trigger syncing after saving an appointment.
|
||||
*
|
||||
* @param array $appointment Appointment data.
|
||||
* @param string $action Performed action ("store" or "update").
|
||||
*/
|
||||
private function notify_and_sync_appointment(array $appointment, string $action = 'store'): void
|
||||
{
|
||||
$manage_mode = $action === 'update';
|
||||
|
||||
$service = $this->services_model->find($appointment['id_services']);
|
||||
|
||||
$provider = $this->providers_model->find($appointment['id_users_provider']);
|
||||
|
||||
$customer = $this->customers_model->find($appointment['id_users_customer']);
|
||||
|
||||
$company_color = setting('company_color');
|
||||
|
||||
$settings = [
|
||||
'company_name' => setting('company_name'),
|
||||
'company_email' => setting('company_email'),
|
||||
'company_link' => setting('company_link'),
|
||||
'company_color' => !empty($company_color) && $company_color != DEFAULT_COMPANY_COLOR ? $company_color : null,
|
||||
'date_format' => setting('date_format'),
|
||||
'time_format' => setting('time_format'),
|
||||
];
|
||||
|
||||
$this->synchronization->sync_appointment_saved($appointment, $service, $provider, $customer, $settings);
|
||||
|
||||
$this->notifications->notify_appointment_saved(
|
||||
$appointment,
|
||||
$service,
|
||||
$provider,
|
||||
$customer,
|
||||
$settings,
|
||||
$manage_mode,
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Update an appointment.
|
||||
*
|
||||
* @param int $id Appointment ID.
|
||||
*/
|
||||
public function update(int $id): void
|
||||
{
|
||||
try {
|
||||
$occurrences = $this->appointments_model->get(['id' => $id]);
|
||||
|
||||
if (empty($occurrences)) {
|
||||
response('', 404);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$original_appointment = $occurrences[0];
|
||||
|
||||
$appointment = request();
|
||||
|
||||
$this->appointments_model->api_decode($appointment, $original_appointment);
|
||||
|
||||
$appointment_id = $this->appointments_model->save($appointment);
|
||||
|
||||
$updated_appointment = $this->appointments_model->find($appointment_id);
|
||||
|
||||
$this->notify_and_sync_appointment($updated_appointment, 'update');
|
||||
|
||||
$this->appointments_model->api_encode($updated_appointment);
|
||||
|
||||
json_response($updated_appointment);
|
||||
} catch (Throwable $e) {
|
||||
json_exception($e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete an appointment.
|
||||
*
|
||||
* @param int $id Appointment ID.
|
||||
*/
|
||||
public function destroy(int $id): void
|
||||
{
|
||||
try {
|
||||
$occurrences = $this->appointments_model->get(['id' => $id]);
|
||||
|
||||
if (empty($occurrences)) {
|
||||
response('', 404);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$deleted_appointment = $occurrences[0];
|
||||
|
||||
$service = $this->services_model->find($deleted_appointment['id_services']);
|
||||
|
||||
$provider = $this->providers_model->find($deleted_appointment['id_users_provider']);
|
||||
|
||||
$customer = $this->customers_model->find($deleted_appointment['id_users_customer']);
|
||||
|
||||
$company_color = setting('company_color');
|
||||
|
||||
$settings = [
|
||||
'company_name' => setting('company_name'),
|
||||
'company_email' => setting('company_email'),
|
||||
'company_link' => setting('company_link'),
|
||||
'company_color' => !empty($company_color) && $company_color != DEFAULT_COMPANY_COLOR ? $company_color : null,
|
||||
'date_format' => setting('date_format'),
|
||||
'time_format' => setting('time_format'),
|
||||
];
|
||||
|
||||
$this->appointments_model->delete($id);
|
||||
|
||||
$this->synchronization->sync_appointment_deleted($deleted_appointment, $provider);
|
||||
|
||||
$this->notifications->notify_appointment_deleted(
|
||||
$deleted_appointment,
|
||||
$service,
|
||||
$provider,
|
||||
$customer,
|
||||
$settings,
|
||||
);
|
||||
|
||||
response('', 204);
|
||||
} catch (Throwable $e) {
|
||||
json_exception($e);
|
||||
}
|
||||
}
|
||||
}
|
81
application/controllers/api/v1/Availabilities_api_v1.php
Normal file
81
application/controllers/api/v1/Availabilities_api_v1.php
Normal file
@ -0,0 +1,81 @@
|
||||
<?php defined('BASEPATH') or exit('No direct script access allowed');
|
||||
|
||||
/* ----------------------------------------------------------------------------
|
||||
* Easy!Appointments - Online Appointment Scheduler
|
||||
*
|
||||
* @package EasyAppointments
|
||||
* @author A.Tselegidis <alextselegidis@gmail.com>
|
||||
* @copyright Copyright (c) Alex Tselegidis
|
||||
* @license https://opensource.org/licenses/GPL-3.0 - GPLv3
|
||||
* @link https://easyappointments.org
|
||||
* @since v1.5.0
|
||||
* ---------------------------------------------------------------------------- */
|
||||
|
||||
/**
|
||||
* Availabilities API v1 controller.
|
||||
*
|
||||
* @package Controllers
|
||||
*/
|
||||
class Availabilities_api_v1 extends EA_Controller
|
||||
{
|
||||
/**
|
||||
* Availabilities_api_v1 constructor.
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
parent::__construct();
|
||||
|
||||
$this->load->library('api');
|
||||
|
||||
$this->api->auth();
|
||||
|
||||
$this->load->model('appointments_model');
|
||||
$this->load->model('providers_model');
|
||||
$this->load->model('services_model');
|
||||
$this->load->model('settings_model');
|
||||
|
||||
$this->load->library('availability');
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate the available hours based on the selected date, service and provider.
|
||||
*
|
||||
* This resource requires the following query parameters:
|
||||
*
|
||||
* - serviceId
|
||||
* - providerI
|
||||
* - date
|
||||
*
|
||||
* Based on those values it will generate the available hours, just like how the booking page works.
|
||||
*
|
||||
* You can then safely create a new appointment starting on one of the selected hours.
|
||||
*
|
||||
* Notice: The returned hours are in the provider's timezone.
|
||||
*
|
||||
* If no date parameter is provided then the current date will be used.
|
||||
*/
|
||||
public function get(): void
|
||||
{
|
||||
try {
|
||||
$provider_id = request('providerId');
|
||||
|
||||
$service_id = request('serviceId');
|
||||
|
||||
$date = request('date');
|
||||
|
||||
if (!$date) {
|
||||
$date = date('Y-m-d');
|
||||
}
|
||||
|
||||
$provider = $this->providers_model->find($provider_id);
|
||||
|
||||
$service = $this->services_model->find($service_id);
|
||||
|
||||
$available_hours = $this->availability->get_available_hours($date, $service, $provider);
|
||||
|
||||
json_response($available_hours);
|
||||
} catch (Throwable $e) {
|
||||
json_exception($e);
|
||||
}
|
||||
}
|
||||
}
|
190
application/controllers/api/v1/Customers_api_v1.php
Normal file
190
application/controllers/api/v1/Customers_api_v1.php
Normal file
@ -0,0 +1,190 @@
|
||||
<?php defined('BASEPATH') or exit('No direct script access allowed');
|
||||
|
||||
/* ----------------------------------------------------------------------------
|
||||
* Easy!Appointments - Online Appointment Scheduler
|
||||
*
|
||||
* @package EasyAppointments
|
||||
* @author A.Tselegidis <alextselegidis@gmail.com>
|
||||
* @copyright Copyright (c) Alex Tselegidis
|
||||
* @license https://opensource.org/licenses/GPL-3.0 - GPLv3
|
||||
* @link https://easyappointments.org
|
||||
* @since v1.5.0
|
||||
* ---------------------------------------------------------------------------- */
|
||||
|
||||
/**
|
||||
* Customers API v1 controller.
|
||||
*
|
||||
* @package Controllers
|
||||
*/
|
||||
class Customers_api_v1 extends EA_Controller
|
||||
{
|
||||
/**
|
||||
* Customers_api_v1 constructor.
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
parent::__construct();
|
||||
|
||||
$this->load->library('api');
|
||||
|
||||
$this->api->auth();
|
||||
|
||||
$this->api->model('customers_model');
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a customer collection.
|
||||
*/
|
||||
public function index(): void
|
||||
{
|
||||
try {
|
||||
$keyword = $this->api->request_keyword();
|
||||
|
||||
$limit = $this->api->request_limit();
|
||||
|
||||
$offset = $this->api->request_offset();
|
||||
|
||||
$order_by = $this->api->request_order_by();
|
||||
|
||||
$fields = $this->api->request_fields();
|
||||
|
||||
$with = $this->api->request_with();
|
||||
|
||||
$customers = empty($keyword)
|
||||
? $this->customers_model->get(null, $limit, $offset, $order_by)
|
||||
: $this->customers_model->search($keyword, $limit, $offset, $order_by);
|
||||
|
||||
foreach ($customers as &$customer) {
|
||||
$this->customers_model->api_encode($customer);
|
||||
|
||||
if (!empty($fields)) {
|
||||
$this->customers_model->only($customer, $fields);
|
||||
}
|
||||
|
||||
if (!empty($with)) {
|
||||
$this->customers_model->load($customer, $with);
|
||||
}
|
||||
}
|
||||
|
||||
json_response($customers);
|
||||
} catch (Throwable $e) {
|
||||
json_exception($e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a single customer.
|
||||
*
|
||||
* @param int|null $id Customer ID.
|
||||
*/
|
||||
public function show(?int $id = null): void
|
||||
{
|
||||
try {
|
||||
$occurrences = $this->customers_model->get(['id' => $id]);
|
||||
|
||||
if (empty($occurrences)) {
|
||||
response('', 404);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$fields = $this->api->request_fields();
|
||||
|
||||
$customer = $this->customers_model->find($id);
|
||||
|
||||
$this->customers_model->api_encode($customer);
|
||||
|
||||
if (!empty($fields)) {
|
||||
$this->customers_model->only($customer, $fields);
|
||||
}
|
||||
|
||||
json_response($customer);
|
||||
} catch (Throwable $e) {
|
||||
json_exception($e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Store a new customer.
|
||||
*/
|
||||
public function store(): void
|
||||
{
|
||||
try {
|
||||
$customer = request();
|
||||
|
||||
$this->customers_model->api_decode($customer);
|
||||
|
||||
if (array_key_exists('id', $customer)) {
|
||||
unset($customer['id']);
|
||||
}
|
||||
|
||||
$customer_id = $this->customers_model->save($customer);
|
||||
|
||||
$created_customer = $this->customers_model->find($customer_id);
|
||||
|
||||
$this->customers_model->api_encode($created_customer);
|
||||
|
||||
json_response($created_customer, 201);
|
||||
} catch (Throwable $e) {
|
||||
json_exception($e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Update a customer.
|
||||
*
|
||||
* @param int $id Customer ID.
|
||||
*/
|
||||
public function update(int $id): void
|
||||
{
|
||||
try {
|
||||
$occurrences = $this->customers_model->get(['id' => $id]);
|
||||
|
||||
if (empty($occurrences)) {
|
||||
response('', 404);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$original_customer = $occurrences[0];
|
||||
|
||||
$customer = request();
|
||||
|
||||
$this->customers_model->api_decode($customer, $original_customer);
|
||||
|
||||
$customer_id = $this->customers_model->save($customer);
|
||||
|
||||
$updated_customer = $this->customers_model->find($customer_id);
|
||||
|
||||
$this->customers_model->api_encode($updated_customer);
|
||||
|
||||
json_response($updated_customer);
|
||||
} catch (Throwable $e) {
|
||||
json_exception($e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete a customer.
|
||||
*
|
||||
* @param int $id Customer ID.
|
||||
*/
|
||||
public function destroy(int $id): void
|
||||
{
|
||||
try {
|
||||
$occurrences = $this->customers_model->get(['id' => $id]);
|
||||
|
||||
if (empty($occurrences)) {
|
||||
response('', 404);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$this->customers_model->delete($id);
|
||||
|
||||
response('', 204);
|
||||
} catch (Throwable $e) {
|
||||
json_exception($e);
|
||||
}
|
||||
}
|
||||
}
|
212
application/controllers/api/v1/Providers_api_v1.php
Normal file
212
application/controllers/api/v1/Providers_api_v1.php
Normal file
@ -0,0 +1,212 @@
|
||||
<?php defined('BASEPATH') or exit('No direct script access allowed');
|
||||
|
||||
/* ----------------------------------------------------------------------------
|
||||
* Easy!Appointments - Online Appointment Scheduler
|
||||
*
|
||||
* @package EasyAppointments
|
||||
* @author A.Tselegidis <alextselegidis@gmail.com>
|
||||
* @copyright Copyright (c) Alex Tselegidis
|
||||
* @license https://opensource.org/licenses/GPL-3.0 - GPLv3
|
||||
* @link https://easyappointments.org
|
||||
* @since v1.5.0
|
||||
* ---------------------------------------------------------------------------- */
|
||||
|
||||
/**
|
||||
* Providers API v1 controller.
|
||||
*
|
||||
* @package Controllers
|
||||
*/
|
||||
class Providers_api_v1 extends EA_Controller
|
||||
{
|
||||
/**
|
||||
* Providers_api_v1 constructor.
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
parent::__construct();
|
||||
|
||||
$this->load->library('api');
|
||||
|
||||
$this->api->auth();
|
||||
|
||||
$this->api->model('providers_model');
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a provider collection.
|
||||
*/
|
||||
public function index(): void
|
||||
{
|
||||
try {
|
||||
$keyword = $this->api->request_keyword();
|
||||
|
||||
$limit = $this->api->request_limit();
|
||||
|
||||
$offset = $this->api->request_offset();
|
||||
|
||||
$order_by = $this->api->request_order_by();
|
||||
|
||||
$fields = $this->api->request_fields();
|
||||
|
||||
$with = $this->api->request_with();
|
||||
|
||||
$providers = empty($keyword)
|
||||
? $this->providers_model->get(null, $limit, $offset, $order_by)
|
||||
: $this->providers_model->search($keyword, $limit, $offset, $order_by);
|
||||
|
||||
foreach ($providers as &$provider) {
|
||||
$this->providers_model->api_encode($provider);
|
||||
|
||||
if (!empty($fields)) {
|
||||
$this->providers_model->only($provider, $fields);
|
||||
}
|
||||
|
||||
if (!empty($with)) {
|
||||
$this->providers_model->load($provider, $with);
|
||||
}
|
||||
}
|
||||
|
||||
json_response($providers);
|
||||
} catch (Throwable $e) {
|
||||
json_exception($e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a single provider.
|
||||
*
|
||||
* @param int|null $id Provider ID.
|
||||
*/
|
||||
public function show(?int $id = null): void
|
||||
{
|
||||
try {
|
||||
$occurrences = $this->providers_model->get(['id' => $id]);
|
||||
|
||||
if (empty($occurrences)) {
|
||||
response('', 404);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$fields = $this->api->request_fields();
|
||||
|
||||
$with = $this->api->request_with();
|
||||
|
||||
$provider = $this->providers_model->find($id);
|
||||
|
||||
$this->providers_model->api_encode($provider);
|
||||
|
||||
if (!empty($fields)) {
|
||||
$this->providers_model->only($provider, $fields);
|
||||
}
|
||||
|
||||
if (!empty($with)) {
|
||||
$this->providers_model->load($provider, $with);
|
||||
}
|
||||
|
||||
json_response($provider);
|
||||
} catch (Throwable $e) {
|
||||
json_exception($e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Store a new provider.
|
||||
*/
|
||||
public function store(): void
|
||||
{
|
||||
try {
|
||||
$provider = request();
|
||||
|
||||
$this->providers_model->api_decode($provider);
|
||||
|
||||
if (array_key_exists('id', $provider)) {
|
||||
unset($provider['id']);
|
||||
}
|
||||
|
||||
if (!array_key_exists('services', $provider)) {
|
||||
throw new InvalidArgumentException('No services property provided.');
|
||||
}
|
||||
|
||||
if (!array_key_exists('settings', $provider)) {
|
||||
throw new InvalidArgumentException('No settings property provided.');
|
||||
}
|
||||
|
||||
if (!array_key_exists('working_plan', $provider['settings'])) {
|
||||
$provider['settings']['working_plan'] = setting('company_working_plan');
|
||||
}
|
||||
|
||||
if (!array_key_exists('working_plan_exceptions', $provider['settings'])) {
|
||||
$provider['settings']['working_plan_exceptions'] = '{}';
|
||||
}
|
||||
|
||||
$provider_id = $this->providers_model->save($provider);
|
||||
|
||||
$created_provider = $this->providers_model->find($provider_id);
|
||||
|
||||
$this->providers_model->api_encode($created_provider);
|
||||
|
||||
json_response($created_provider, 201);
|
||||
} catch (Throwable $e) {
|
||||
json_exception($e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Update a provider.
|
||||
*
|
||||
* @param int $id Provider ID.
|
||||
*/
|
||||
public function update(int $id): void
|
||||
{
|
||||
try {
|
||||
$occurrences = $this->providers_model->get(['id' => $id]);
|
||||
|
||||
if (empty($occurrences)) {
|
||||
response('', 404);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$original_provider = $occurrences[0];
|
||||
|
||||
$provider = request();
|
||||
|
||||
$this->providers_model->api_decode($provider, $original_provider);
|
||||
|
||||
$provider_id = $this->providers_model->save($provider);
|
||||
|
||||
$updated_provider = $this->providers_model->find($provider_id);
|
||||
|
||||
$this->providers_model->api_encode($updated_provider);
|
||||
|
||||
json_response($updated_provider);
|
||||
} catch (Throwable $e) {
|
||||
json_exception($e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete a provider.
|
||||
*
|
||||
* @param int $id Provider ID.
|
||||
*/
|
||||
public function destroy(int $id): void
|
||||
{
|
||||
try {
|
||||
$occurrences = $this->providers_model->get(['id' => $id]);
|
||||
|
||||
if (empty($occurrences)) {
|
||||
response('', 404);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$this->providers_model->delete($id);
|
||||
|
||||
response('', 204);
|
||||
} catch (Throwable $e) {
|
||||
json_exception($e);
|
||||
}
|
||||
}
|
||||
}
|
198
application/controllers/api/v1/Secretaries_api_v1.php
Normal file
198
application/controllers/api/v1/Secretaries_api_v1.php
Normal file
@ -0,0 +1,198 @@
|
||||
<?php defined('BASEPATH') or exit('No direct script access allowed');
|
||||
|
||||
/* ----------------------------------------------------------------------------
|
||||
* Easy!Appointments - Online Appointment Scheduler
|
||||
*
|
||||
* @package EasyAppointments
|
||||
* @author A.Tselegidis <alextselegidis@gmail.com>
|
||||
* @copyright Copyright (c) Alex Tselegidis
|
||||
* @license https://opensource.org/licenses/GPL-3.0 - GPLv3
|
||||
* @link https://easyappointments.org
|
||||
* @since v1.5.0
|
||||
* ---------------------------------------------------------------------------- */
|
||||
|
||||
/**
|
||||
* Secretaries API v1 controller.
|
||||
*
|
||||
* @package Controllers
|
||||
*/
|
||||
class Secretaries_api_v1 extends EA_Controller
|
||||
{
|
||||
/**
|
||||
* Secretaries_api_v1 constructor.
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
parent::__construct();
|
||||
|
||||
$this->load->library('api');
|
||||
|
||||
$this->api->auth();
|
||||
|
||||
$this->api->model('secretaries_model');
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a secretary collection.
|
||||
*/
|
||||
public function index(): void
|
||||
{
|
||||
try {
|
||||
$keyword = $this->api->request_keyword();
|
||||
|
||||
$limit = $this->api->request_limit();
|
||||
|
||||
$offset = $this->api->request_offset();
|
||||
|
||||
$order_by = $this->api->request_order_by();
|
||||
|
||||
$fields = $this->api->request_fields();
|
||||
|
||||
$with = $this->api->request_with();
|
||||
|
||||
$secretaries = empty($keyword)
|
||||
? $this->secretaries_model->get(null, $limit, $offset, $order_by)
|
||||
: $this->secretaries_model->search($keyword, $limit, $offset, $order_by);
|
||||
|
||||
foreach ($secretaries as &$secretary) {
|
||||
$this->secretaries_model->api_encode($secretary);
|
||||
|
||||
if (!empty($fields)) {
|
||||
$this->secretaries_model->only($secretary, $fields);
|
||||
}
|
||||
|
||||
if (!empty($with)) {
|
||||
$this->secretaries_model->load($secretary, $with);
|
||||
}
|
||||
}
|
||||
|
||||
json_response($secretaries);
|
||||
} catch (Throwable $e) {
|
||||
json_exception($e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a single secretary.
|
||||
*
|
||||
* @param int|null $id Secretary ID.
|
||||
*/
|
||||
public function show(?int $id = null): void
|
||||
{
|
||||
try {
|
||||
$occurrences = $this->secretaries_model->get(['id' => $id]);
|
||||
|
||||
if (empty($occurrences)) {
|
||||
response('', 404);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$fields = $this->api->request_fields();
|
||||
|
||||
$secretary = $this->secretaries_model->find($id);
|
||||
|
||||
$this->secretaries_model->api_encode($secretary);
|
||||
|
||||
if (!empty($fields)) {
|
||||
$this->secretaries_model->only($secretary, $fields);
|
||||
}
|
||||
|
||||
json_response($secretary);
|
||||
} catch (Throwable $e) {
|
||||
json_exception($e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Store a new secretary.
|
||||
*/
|
||||
public function store(): void
|
||||
{
|
||||
try {
|
||||
$secretary = request();
|
||||
|
||||
$this->secretaries_model->api_decode($secretary);
|
||||
|
||||
if (array_key_exists('id', $secretary)) {
|
||||
unset($secretary['id']);
|
||||
}
|
||||
|
||||
if (!array_key_exists('providers', $secretary)) {
|
||||
throw new InvalidArgumentException('No providers property provided.');
|
||||
}
|
||||
|
||||
if (!array_key_exists('settings', $secretary)) {
|
||||
throw new InvalidArgumentException('No settings property provided.');
|
||||
}
|
||||
|
||||
$secretary_id = $this->secretaries_model->save($secretary);
|
||||
|
||||
$created_secretary = $this->secretaries_model->find($secretary_id);
|
||||
|
||||
$this->secretaries_model->api_encode($created_secretary);
|
||||
|
||||
json_response($created_secretary, 201);
|
||||
} catch (Throwable $e) {
|
||||
json_exception($e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Update a secretary.
|
||||
*
|
||||
* @param int $id Secretary ID.
|
||||
*/
|
||||
public function update(int $id): void
|
||||
{
|
||||
try {
|
||||
$occurrences = $this->secretaries_model->get(['id' => $id]);
|
||||
|
||||
if (empty($occurrences)) {
|
||||
response('', 404);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$original_secretary = $occurrences[0];
|
||||
|
||||
$secretary = request();
|
||||
|
||||
$this->secretaries_model->api_decode($secretary, $original_secretary);
|
||||
|
||||
$secretary_id = $this->secretaries_model->save($secretary);
|
||||
|
||||
$updated_secretary = $this->secretaries_model->find($secretary_id);
|
||||
|
||||
$this->secretaries_model->api_encode($updated_secretary);
|
||||
|
||||
json_response($updated_secretary);
|
||||
} catch (Throwable $e) {
|
||||
json_exception($e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete a secretary.
|
||||
*
|
||||
* @param int $id Secretary ID.
|
||||
*/
|
||||
public function destroy(int $id): void
|
||||
{
|
||||
try {
|
||||
$occurrences = $this->secretaries_model->get(['id' => $id]);
|
||||
|
||||
if (empty($occurrences)) {
|
||||
response('', 404);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$this->secretaries_model->delete($id);
|
||||
|
||||
response('', 204);
|
||||
} catch (Throwable $e) {
|
||||
json_exception($e);
|
||||
}
|
||||
}
|
||||
}
|
196
application/controllers/api/v1/Service_categories_api_v1.php
Normal file
196
application/controllers/api/v1/Service_categories_api_v1.php
Normal file
@ -0,0 +1,196 @@
|
||||
<?php defined('BASEPATH') or exit('No direct script access allowed');
|
||||
|
||||
/* ----------------------------------------------------------------------------
|
||||
* Easy!Appointments - Online Appointment Scheduler
|
||||
*
|
||||
* @package EasyAppointments
|
||||
* @author A.Tselegidis <alextselegidis@gmail.com>
|
||||
* @copyright Copyright (c) Alex Tselegidis
|
||||
* @license https://opensource.org/licenses/GPL-3.0 - GPLv3
|
||||
* @link https://easyappointments.org
|
||||
* @since v1.5.0
|
||||
* ---------------------------------------------------------------------------- */
|
||||
|
||||
/**
|
||||
* Service-categories API v1 controller.
|
||||
*
|
||||
* @package Controllers
|
||||
*/
|
||||
class Service_categories_api_v1 extends EA_Controller
|
||||
{
|
||||
/**
|
||||
* Service_categories_api_v1 constructor.
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
parent::__construct();
|
||||
|
||||
$this->load->library('api');
|
||||
|
||||
$this->api->auth();
|
||||
|
||||
$this->api->model('service_categories_model');
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a service-category collection.
|
||||
*/
|
||||
public function index(): void
|
||||
{
|
||||
try {
|
||||
$keyword = $this->api->request_keyword();
|
||||
|
||||
$limit = $this->api->request_limit();
|
||||
|
||||
$offset = $this->api->request_offset();
|
||||
|
||||
$order_by = $this->api->request_order_by();
|
||||
|
||||
$fields = $this->api->request_fields();
|
||||
|
||||
$with = $this->api->request_with();
|
||||
|
||||
$service_categories = empty($keyword)
|
||||
? $this->service_categories_model->get(null, $limit, $offset, $order_by)
|
||||
: $this->service_categories_model->search($keyword, $limit, $offset, $order_by);
|
||||
|
||||
foreach ($service_categories as &$service_category) {
|
||||
$this->service_categories_model->api_encode($service_category);
|
||||
|
||||
if (!empty($fields)) {
|
||||
$this->service_categories_model->only($service_category, $fields);
|
||||
}
|
||||
|
||||
if (!empty($with)) {
|
||||
$this->service_categories_model->load($service_category, $with);
|
||||
}
|
||||
}
|
||||
|
||||
json_response($service_categories);
|
||||
} catch (Throwable $e) {
|
||||
json_exception($e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a single service-category.
|
||||
*
|
||||
* @param int|null $id Service-category ID.
|
||||
*/
|
||||
public function show(?int $id = null): void
|
||||
{
|
||||
try {
|
||||
$occurrences = $this->service_categories_model->get(['id' => $id]);
|
||||
|
||||
if (empty($occurrences)) {
|
||||
response('', 404);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$fields = $this->api->request_fields();
|
||||
|
||||
$with = $this->api->request_with();
|
||||
|
||||
$service_category = $this->service_categories_model->find($id);
|
||||
|
||||
$this->service_categories_model->api_encode($service_category);
|
||||
|
||||
if (!empty($fields)) {
|
||||
$this->service_categories_model->only($service_category, $fields);
|
||||
}
|
||||
|
||||
if (!empty($with)) {
|
||||
$this->service_categories_model->load($service_category, $with);
|
||||
}
|
||||
|
||||
json_response($service_category);
|
||||
} catch (Throwable $e) {
|
||||
json_exception($e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Store a new service-category.
|
||||
*/
|
||||
public function store(): void
|
||||
{
|
||||
try {
|
||||
$service_category = request();
|
||||
|
||||
$this->service_categories_model->api_decode($service_category);
|
||||
|
||||
if (array_key_exists('id', $service_category)) {
|
||||
unset($service_category['id']);
|
||||
}
|
||||
|
||||
$service_category_id = $this->service_categories_model->save($service_category);
|
||||
|
||||
$created_service_category = $this->service_categories_model->find($service_category_id);
|
||||
|
||||
$this->service_categories_model->api_encode($created_service_category);
|
||||
|
||||
json_response($created_service_category, 201);
|
||||
} catch (Throwable $e) {
|
||||
json_exception($e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Update a service-category.
|
||||
*
|
||||
* @param int $id Service-category ID.
|
||||
*/
|
||||
public function update(int $id): void
|
||||
{
|
||||
try {
|
||||
$occurrences = $this->service_categories_model->get(['id' => $id]);
|
||||
|
||||
if (empty($occurrences)) {
|
||||
response('', 404);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$original_category = $occurrences[0];
|
||||
|
||||
$service_category = request();
|
||||
|
||||
$this->service_categories_model->api_decode($service_category, $original_category);
|
||||
|
||||
$service_category_id = $this->service_categories_model->save($service_category);
|
||||
|
||||
$updated_service_category = $this->service_categories_model->find($service_category_id);
|
||||
|
||||
$this->service_categories_model->api_encode($updated_service_category);
|
||||
|
||||
json_response($updated_service_category);
|
||||
} catch (Throwable $e) {
|
||||
json_exception($e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete a service-category.
|
||||
*
|
||||
* @param int $id Service-category ID.
|
||||
*/
|
||||
public function destroy(int $id): void
|
||||
{
|
||||
try {
|
||||
$occurrences = $this->service_categories_model->get(['id' => $id]);
|
||||
|
||||
if (empty($occurrences)) {
|
||||
response('', 404);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$this->service_categories_model->delete($id);
|
||||
|
||||
response('', 204);
|
||||
} catch (Throwable $e) {
|
||||
json_exception($e);
|
||||
}
|
||||
}
|
||||
}
|
196
application/controllers/api/v1/Services_api_v1.php
Normal file
196
application/controllers/api/v1/Services_api_v1.php
Normal file
@ -0,0 +1,196 @@
|
||||
<?php defined('BASEPATH') or exit('No direct script access allowed');
|
||||
|
||||
/* ----------------------------------------------------------------------------
|
||||
* Easy!Appointments - Online Appointment Scheduler
|
||||
*
|
||||
* @package EasyAppointments
|
||||
* @author A.Tselegidis <alextselegidis@gmail.com>
|
||||
* @copyright Copyright (c) Alex Tselegidis
|
||||
* @license https://opensource.org/licenses/GPL-3.0 - GPLv3
|
||||
* @link https://easyappointments.org
|
||||
* @since v1.5.0
|
||||
* ---------------------------------------------------------------------------- */
|
||||
|
||||
/**
|
||||
* Services API v1 controller.
|
||||
*
|
||||
* @package Controllers
|
||||
*/
|
||||
class Services_api_v1 extends EA_Controller
|
||||
{
|
||||
/**
|
||||
* Services_api_v1 constructor.
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
parent::__construct();
|
||||
|
||||
$this->load->library('api');
|
||||
|
||||
$this->api->auth();
|
||||
|
||||
$this->api->model('services_model');
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a service collection.
|
||||
*/
|
||||
public function index(): void
|
||||
{
|
||||
try {
|
||||
$keyword = $this->api->request_keyword();
|
||||
|
||||
$limit = $this->api->request_limit();
|
||||
|
||||
$offset = $this->api->request_offset();
|
||||
|
||||
$order_by = $this->api->request_order_by();
|
||||
|
||||
$fields = $this->api->request_fields();
|
||||
|
||||
$with = $this->api->request_with();
|
||||
|
||||
$services = empty($keyword)
|
||||
? $this->services_model->get(null, $limit, $offset, $order_by)
|
||||
: $this->services_model->search($keyword, $limit, $offset, $order_by);
|
||||
|
||||
foreach ($services as &$service) {
|
||||
$this->services_model->api_encode($service);
|
||||
|
||||
if (!empty($fields)) {
|
||||
$this->services_model->only($service, $fields);
|
||||
}
|
||||
|
||||
if (!empty($with)) {
|
||||
$this->services_model->load($service, $with);
|
||||
}
|
||||
}
|
||||
|
||||
json_response($services);
|
||||
} catch (Throwable $e) {
|
||||
json_exception($e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a single service.
|
||||
*
|
||||
* @param int|null $id Service ID.
|
||||
*/
|
||||
public function show(?int $id = null): void
|
||||
{
|
||||
try {
|
||||
$occurrences = $this->services_model->get(['id' => $id]);
|
||||
|
||||
if (empty($occurrences)) {
|
||||
response('', 404);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$fields = $this->api->request_fields();
|
||||
|
||||
$with = $this->api->request_with();
|
||||
|
||||
$service = $this->services_model->find($id);
|
||||
|
||||
$this->services_model->api_encode($service);
|
||||
|
||||
if (!empty($fields)) {
|
||||
$this->services_model->only($service, $fields);
|
||||
}
|
||||
|
||||
if (!empty($with)) {
|
||||
$this->services_model->load($service, $with);
|
||||
}
|
||||
|
||||
json_response($service);
|
||||
} catch (Throwable $e) {
|
||||
json_exception($e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Store a new service.
|
||||
*/
|
||||
public function store(): void
|
||||
{
|
||||
try {
|
||||
$service = request();
|
||||
|
||||
$this->services_model->api_decode($service);
|
||||
|
||||
if (array_key_exists('id', $service)) {
|
||||
unset($service['id']);
|
||||
}
|
||||
|
||||
$service_id = $this->services_model->save($service);
|
||||
|
||||
$created_service = $this->services_model->find($service_id);
|
||||
|
||||
$this->services_model->api_encode($created_service);
|
||||
|
||||
json_response($created_service, 201);
|
||||
} catch (Throwable $e) {
|
||||
json_exception($e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Update a service.
|
||||
*
|
||||
* @param int $id Service ID.
|
||||
*/
|
||||
public function update(int $id): void
|
||||
{
|
||||
try {
|
||||
$occurrences = $this->services_model->get(['id' => $id]);
|
||||
|
||||
if (empty($occurrences)) {
|
||||
response('', 404);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$original_service = $occurrences[0];
|
||||
|
||||
$service = request();
|
||||
|
||||
$this->services_model->api_decode($service, $original_service);
|
||||
|
||||
$service_id = $this->services_model->save($service);
|
||||
|
||||
$updated_service = $this->services_model->find($service_id);
|
||||
|
||||
$this->services_model->api_encode($updated_service);
|
||||
|
||||
json_response($updated_service);
|
||||
} catch (Throwable $e) {
|
||||
json_exception($e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete a service.
|
||||
*
|
||||
* @param int $id Service ID.
|
||||
*/
|
||||
public function destroy(int $id): void
|
||||
{
|
||||
try {
|
||||
$occurrences = $this->services_model->get(['id' => $id]);
|
||||
|
||||
if (empty($occurrences)) {
|
||||
response('', 404);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$this->services_model->delete($id);
|
||||
|
||||
response('', 204);
|
||||
} catch (Throwable $e) {
|
||||
json_exception($e);
|
||||
}
|
||||
}
|
||||
}
|
108
application/controllers/api/v1/Settings_api_v1.php
Normal file
108
application/controllers/api/v1/Settings_api_v1.php
Normal file
@ -0,0 +1,108 @@
|
||||
<?php defined('BASEPATH') or exit('No direct script access allowed');
|
||||
|
||||
/* ----------------------------------------------------------------------------
|
||||
* Easy!Appointments - Online Appointment Scheduler
|
||||
*
|
||||
* @package EasyAppointments
|
||||
* @author A.Tselegidis <alextselegidis@gmail.com>
|
||||
* @copyright Copyright (c) Alex Tselegidis
|
||||
* @license https://opensource.org/licenses/GPL-3.0 - GPLv3
|
||||
* @link https://easyappointments.org
|
||||
* @since v1.5.0
|
||||
* ---------------------------------------------------------------------------- */
|
||||
|
||||
/**
|
||||
* Settings API v1 controller.
|
||||
*
|
||||
* @package Controllers
|
||||
*/
|
||||
class Settings_api_v1 extends EA_Controller
|
||||
{
|
||||
/**
|
||||
* Settings_api_v1 constructor.
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
parent::__construct();
|
||||
|
||||
$this->load->library('api');
|
||||
|
||||
$this->api->auth();
|
||||
|
||||
$this->api->model('settings_model');
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a setting collection.
|
||||
*/
|
||||
public function index(): void
|
||||
{
|
||||
try {
|
||||
$keyword = $this->api->request_keyword();
|
||||
|
||||
$limit = $this->api->request_limit();
|
||||
|
||||
$offset = $this->api->request_offset();
|
||||
|
||||
$order_by = $this->api->request_order_by();
|
||||
|
||||
$fields = $this->api->request_fields();
|
||||
|
||||
$settings = empty($keyword)
|
||||
? $this->settings_model->get(null, $limit, $offset, $order_by)
|
||||
: $this->settings_model->search($keyword, $limit, $offset, $order_by);
|
||||
|
||||
foreach ($settings as &$setting) {
|
||||
$this->settings_model->api_encode($setting);
|
||||
|
||||
if (!empty($fields)) {
|
||||
$this->settings_model->only($setting, $fields);
|
||||
}
|
||||
}
|
||||
|
||||
json_response($settings);
|
||||
} catch (Throwable $e) {
|
||||
json_exception($e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a setting value by name.
|
||||
*
|
||||
* @param string $name Setting name.
|
||||
*/
|
||||
public function show(string $name): void
|
||||
{
|
||||
try {
|
||||
$value = setting($name);
|
||||
|
||||
json_response([
|
||||
'name' => $name,
|
||||
'value' => $value,
|
||||
]);
|
||||
} catch (Throwable $e) {
|
||||
json_exception($e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set a setting value by name.
|
||||
*
|
||||
* @param string $name Setting name.
|
||||
*/
|
||||
public function update(string $name): void
|
||||
{
|
||||
try {
|
||||
$value = request('value');
|
||||
|
||||
setting([$name => $value]);
|
||||
|
||||
json_response([
|
||||
'name' => $name,
|
||||
'value' => $value,
|
||||
]);
|
||||
} catch (Throwable $e) {
|
||||
json_exception($e);
|
||||
}
|
||||
}
|
||||
}
|
196
application/controllers/api/v1/Unavailabilities_api_v1.php
Normal file
196
application/controllers/api/v1/Unavailabilities_api_v1.php
Normal file
@ -0,0 +1,196 @@
|
||||
<?php defined('BASEPATH') or exit('No direct script access allowed');
|
||||
|
||||
/* ----------------------------------------------------------------------------
|
||||
* Easy!Appointments - Online Appointment Scheduler
|
||||
*
|
||||
* @package EasyAppointments
|
||||
* @author A.Tselegidis <alextselegidis@gmail.com>
|
||||
* @copyright Copyright (c) Alex Tselegidis
|
||||
* @license https://opensource.org/licenses/GPL-3.0 - GPLv3
|
||||
* @link https://easyappointments.org
|
||||
* @since v1.5.0
|
||||
* ---------------------------------------------------------------------------- */
|
||||
|
||||
/**
|
||||
* Unavailabilities API v1 controller.
|
||||
*
|
||||
* @package Controllers
|
||||
*/
|
||||
class Unavailabilities_api_v1 extends EA_Controller
|
||||
{
|
||||
/**
|
||||
* Unavailabilities_api_v1 constructor.
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
parent::__construct();
|
||||
|
||||
$this->load->library('api');
|
||||
|
||||
$this->api->auth();
|
||||
|
||||
$this->api->model('unavailabilities_model');
|
||||
}
|
||||
|
||||
/**
|
||||
* Get an unavailability collection.
|
||||
*/
|
||||
public function index(): void
|
||||
{
|
||||
try {
|
||||
$keyword = $this->api->request_keyword();
|
||||
|
||||
$limit = $this->api->request_limit();
|
||||
|
||||
$offset = $this->api->request_offset();
|
||||
|
||||
$order_by = $this->api->request_order_by();
|
||||
|
||||
$fields = $this->api->request_fields();
|
||||
|
||||
$with = $this->api->request_with();
|
||||
|
||||
$unavailabilities = empty($keyword)
|
||||
? $this->unavailabilities_model->get(null, $limit, $offset, $order_by)
|
||||
: $this->unavailabilities_model->search($keyword, $limit, $offset, $order_by);
|
||||
|
||||
foreach ($unavailabilities as &$unavailability) {
|
||||
$this->unavailabilities_model->api_encode($unavailability);
|
||||
|
||||
if (!empty($fields)) {
|
||||
$this->unavailabilities_model->only($unavailability, $fields);
|
||||
}
|
||||
|
||||
if (!empty($with)) {
|
||||
$this->unavailabilities_model->load($unavailability, $with);
|
||||
}
|
||||
}
|
||||
|
||||
json_response($unavailabilities);
|
||||
} catch (Throwable $e) {
|
||||
json_exception($e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a single unavailability.
|
||||
*
|
||||
* @param int|null $id Unavailability ID.
|
||||
*/
|
||||
public function show(?int $id = null): void
|
||||
{
|
||||
try {
|
||||
$occurrences = $this->unavailabilities_model->get(['id' => $id]);
|
||||
|
||||
if (empty($occurrences)) {
|
||||
response('', 404);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$fields = $this->api->request_fields();
|
||||
|
||||
$with = $this->api->request_with();
|
||||
|
||||
$unavailability = $this->unavailabilities_model->find($id);
|
||||
|
||||
$this->unavailabilities_model->api_encode($unavailability);
|
||||
|
||||
if (!empty($fields)) {
|
||||
$this->unavailabilities_model->only($unavailability, $fields);
|
||||
}
|
||||
|
||||
if (!empty($with)) {
|
||||
$this->unavailabilities_model->load($unavailability, $with);
|
||||
}
|
||||
|
||||
json_response($unavailability);
|
||||
} catch (Throwable $e) {
|
||||
json_exception($e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Store a new unavailability.
|
||||
*/
|
||||
public function store(): void
|
||||
{
|
||||
try {
|
||||
$unavailability = request();
|
||||
|
||||
$this->unavailabilities_model->api_decode($unavailability);
|
||||
|
||||
if (array_key_exists('id', $unavailability)) {
|
||||
unset($unavailability['id']);
|
||||
}
|
||||
|
||||
$unavailability_id = $this->unavailabilities_model->save($unavailability);
|
||||
|
||||
$created_unavailability = $this->unavailabilities_model->find($unavailability_id);
|
||||
|
||||
$this->unavailabilities_model->api_encode($created_unavailability);
|
||||
|
||||
json_response($created_unavailability, 201);
|
||||
} catch (Throwable $e) {
|
||||
json_exception($e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Update an unavailability.
|
||||
*
|
||||
* @param int $id Unavailability ID.
|
||||
*/
|
||||
public function update(int $id): void
|
||||
{
|
||||
try {
|
||||
$occurrences = $this->unavailabilities_model->get(['id' => $id]);
|
||||
|
||||
if (empty($occurrences)) {
|
||||
response('', 404);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$original_unavailability = $occurrences[0];
|
||||
|
||||
$unavailability = request();
|
||||
|
||||
$this->unavailabilities_model->api_decode($unavailability, $original_unavailability);
|
||||
|
||||
$unavailability_id = $this->unavailabilities_model->save($unavailability);
|
||||
|
||||
$updated_unavailability = $this->unavailabilities_model->find($unavailability_id);
|
||||
|
||||
$this->unavailabilities_model->api_encode($updated_unavailability);
|
||||
|
||||
json_response($updated_unavailability);
|
||||
} catch (Throwable $e) {
|
||||
json_exception($e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete an unavailability.
|
||||
*
|
||||
* @param int $id Unavailability ID.
|
||||
*/
|
||||
public function destroy(int $id): void
|
||||
{
|
||||
try {
|
||||
$occurrences = $this->unavailabilities_model->get(['id' => $id]);
|
||||
|
||||
if (empty($occurrences)) {
|
||||
response('', 404);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$this->unavailabilities_model->delete($id);
|
||||
|
||||
response('', 204);
|
||||
} catch (Throwable $e) {
|
||||
json_exception($e);
|
||||
}
|
||||
}
|
||||
}
|
196
application/controllers/api/v1/Webhooks_api_v1.php
Normal file
196
application/controllers/api/v1/Webhooks_api_v1.php
Normal file
@ -0,0 +1,196 @@
|
||||
<?php defined('BASEPATH') or exit('No direct script access allowed');
|
||||
|
||||
/* ----------------------------------------------------------------------------
|
||||
* Easy!Appointments - Online Appointment Scheduler
|
||||
*
|
||||
* @package EasyAppointments
|
||||
* @author A.Tselegidis <alextselegidis@gmail.com>
|
||||
* @copyright Copyright (c) Alex Tselegidis
|
||||
* @license https://opensource.org/licenses/GPL-3.0 - GPLv3
|
||||
* @link https://easyappointments.org
|
||||
* @since v1.5.0
|
||||
* ---------------------------------------------------------------------------- */
|
||||
|
||||
/**
|
||||
* Webhooks API v1 controller.
|
||||
*
|
||||
* @package Controllers
|
||||
*/
|
||||
class Webhooks_api_v1 extends EA_Controller
|
||||
{
|
||||
/**
|
||||
* Webhooks_api_v1 constructor.
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
parent::__construct();
|
||||
|
||||
$this->load->library('api');
|
||||
|
||||
$this->api->auth();
|
||||
|
||||
$this->api->model('webhooks_model');
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a webhook collection.
|
||||
*/
|
||||
public function index(): void
|
||||
{
|
||||
try {
|
||||
$keyword = $this->api->request_keyword();
|
||||
|
||||
$limit = $this->api->request_limit();
|
||||
|
||||
$offset = $this->api->request_offset();
|
||||
|
||||
$order_by = $this->api->request_order_by();
|
||||
|
||||
$fields = $this->api->request_fields();
|
||||
|
||||
$with = $this->api->request_with();
|
||||
|
||||
$webhooks = empty($keyword)
|
||||
? $this->webhooks_model->get(null, $limit, $offset, $order_by)
|
||||
: $this->webhooks_model->search($keyword, $limit, $offset, $order_by);
|
||||
|
||||
foreach ($webhooks as &$webhook) {
|
||||
$this->webhooks_model->api_encode($webhook);
|
||||
|
||||
if (!empty($fields)) {
|
||||
$this->webhooks_model->only($webhook, $fields);
|
||||
}
|
||||
|
||||
if (!empty($with)) {
|
||||
$this->webhooks_model->load($webhook, $with);
|
||||
}
|
||||
}
|
||||
|
||||
json_response($webhooks);
|
||||
} catch (Throwable $e) {
|
||||
json_exception($e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a single webhook.
|
||||
*
|
||||
* @param int|null $id Webhook ID.
|
||||
*/
|
||||
public function show(?int $id = null): void
|
||||
{
|
||||
try {
|
||||
$occurrences = $this->webhooks_model->get(['id' => $id]);
|
||||
|
||||
if (empty($occurrences)) {
|
||||
response('', 404);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$fields = $this->api->request_fields();
|
||||
|
||||
$with = $this->api->request_with();
|
||||
|
||||
$webhook = $this->webhooks_model->find($id);
|
||||
|
||||
$this->webhooks_model->api_encode($webhook);
|
||||
|
||||
if (!empty($fields)) {
|
||||
$this->webhooks_model->only($webhook, $fields);
|
||||
}
|
||||
|
||||
if (!empty($with)) {
|
||||
$this->webhooks_model->load($webhook, $with);
|
||||
}
|
||||
|
||||
json_response($webhook);
|
||||
} catch (Throwable $e) {
|
||||
json_exception($e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Store a new webhook.
|
||||
*/
|
||||
public function store(): void
|
||||
{
|
||||
try {
|
||||
$webhook = request();
|
||||
|
||||
$this->webhooks_model->api_decode($webhook);
|
||||
|
||||
if (array_key_exists('id', $webhook)) {
|
||||
unset($webhook['id']);
|
||||
}
|
||||
|
||||
$webhook_id = $this->webhooks_model->save($webhook);
|
||||
|
||||
$created_webhook = $this->webhooks_model->find($webhook_id);
|
||||
|
||||
$this->webhooks_model->api_encode($created_webhook);
|
||||
|
||||
json_response($created_webhook, 201);
|
||||
} catch (Throwable $e) {
|
||||
json_exception($e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Update a webhook.
|
||||
*
|
||||
* @param int $id Webhook ID.
|
||||
*/
|
||||
public function update(int $id): void
|
||||
{
|
||||
try {
|
||||
$occurrences = $this->webhooks_model->get(['id' => $id]);
|
||||
|
||||
if (empty($occurrences)) {
|
||||
response('', 404);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$original_webhook = $occurrences[0];
|
||||
|
||||
$webhook = request();
|
||||
|
||||
$this->webhooks_model->api_decode($webhook, $original_webhook);
|
||||
|
||||
$webhook_id = $this->webhooks_model->save($webhook);
|
||||
|
||||
$updated_webhook = $this->webhooks_model->find($webhook_id);
|
||||
|
||||
$this->webhooks_model->api_encode($updated_webhook);
|
||||
|
||||
json_response($updated_webhook);
|
||||
} catch (Throwable $e) {
|
||||
json_exception($e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete a webhook.
|
||||
*
|
||||
* @param int $id Webhook ID.
|
||||
*/
|
||||
public function destroy(int $id): void
|
||||
{
|
||||
try {
|
||||
$occurrences = $this->webhooks_model->get(['id' => $id]);
|
||||
|
||||
if (empty($occurrences)) {
|
||||
response('', 404);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$this->webhooks_model->delete($id);
|
||||
|
||||
response('', 204);
|
||||
} catch (Throwable $e) {
|
||||
json_exception($e);
|
||||
}
|
||||
}
|
||||
}
|
10
application/controllers/api/v1/index.html
Normal file
10
application/controllers/api/v1/index.html
Normal file
@ -0,0 +1,10 @@
|
||||
<html lang="en">
|
||||
<head>
|
||||
<title>403 Forbidden</title>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<p>Directory access is forbidden.</p>
|
||||
|
||||
</body>
|
||||
</html>
|
10
application/controllers/index.html
Normal file
10
application/controllers/index.html
Normal file
@ -0,0 +1,10 @@
|
||||
<html lang="en">
|
||||
<head>
|
||||
<title>403 Forbidden</title>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<p>Directory access is forbidden.</p>
|
||||
|
||||
</body>
|
||||
</html>
|
45
application/core/EA_Benchmark.php
Normal file
45
application/core/EA_Benchmark.php
Normal file
@ -0,0 +1,45 @@
|
||||
<?php defined('BASEPATH') or exit('No direct script access allowed');
|
||||
|
||||
/* ----------------------------------------------------------------------------
|
||||
* Easy!Appointments - Online Appointment Scheduler
|
||||
*
|
||||
* @package EasyAppointments
|
||||
* @author A.Tselegidis <alextselegidis@gmail.com>
|
||||
* @copyright Copyright (c) Alex Tselegidis
|
||||
* @license https://opensource.org/licenses/GPL-3.0 - GPLv3
|
||||
* @link https://easyappointments.org
|
||||
* @since v1.4.0
|
||||
* ---------------------------------------------------------------------------- */
|
||||
|
||||
/**
|
||||
* Easy!Appointments benchmark.
|
||||
*
|
||||
* @property EA_Benchmark $benchmark
|
||||
* @property EA_Cache $cache
|
||||
* @property EA_Calendar $calendar
|
||||
* @property EA_Config $config
|
||||
* @property EA_DB_forge $dbforge
|
||||
* @property EA_DB_query_builder $db
|
||||
* @property EA_DB_utility $dbutil
|
||||
* @property EA_Email $email
|
||||
* @property EA_Encrypt $encrypt
|
||||
* @property EA_Encryption $encryption
|
||||
* @property EA_Exceptions $exceptions
|
||||
* @property EA_Hooks $hooks
|
||||
* @property EA_Input $input
|
||||
* @property EA_Lang $lang
|
||||
* @property EA_Loader $load
|
||||
* @property EA_Log $log
|
||||
* @property EA_Migration $migration
|
||||
* @property EA_Output $output
|
||||
* @property EA_Profiler $profiler
|
||||
* @property EA_Router $router
|
||||
* @property EA_Security $security
|
||||
* @property EA_Session $session
|
||||
* @property EA_Upload $upload
|
||||
* @property EA_URI $uri
|
||||
*/
|
||||
class EA_Benchmark extends CI_Benchmark
|
||||
{
|
||||
//
|
||||
}
|
45
application/core/EA_Cache.php
Normal file
45
application/core/EA_Cache.php
Normal file
@ -0,0 +1,45 @@
|
||||
<?php defined('BASEPATH') or exit('No direct script access allowed');
|
||||
|
||||
/* ----------------------------------------------------------------------------
|
||||
* Easy!Appointments - Online Appointment Scheduler
|
||||
*
|
||||
* @package EasyAppointments
|
||||
* @author A.Tselegidis <alextselegidis@gmail.com>
|
||||
* @copyright Copyright (c) Alex Tselegidis
|
||||
* @license https://opensource.org/licenses/GPL-3.0 - GPLv3
|
||||
* @link https://easyappointments.org
|
||||
* @since v1.4.0
|
||||
* ---------------------------------------------------------------------------- */
|
||||
|
||||
/**
|
||||
* Easy!Appointments cache.
|
||||
*
|
||||
* @property EA_Benchmark $benchmark
|
||||
* @property EA_Cache $cache
|
||||
* @property EA_Calendar $calendar
|
||||
* @property EA_Config $config
|
||||
* @property EA_DB_forge $dbforge
|
||||
* @property EA_DB_query_builder $db
|
||||
* @property EA_DB_utility $dbutil
|
||||
* @property EA_Email $email
|
||||
* @property EA_Encrypt $encrypt
|
||||
* @property EA_Encryption $encryption
|
||||
* @property EA_Exceptions $exceptions
|
||||
* @property EA_Hooks $hooks
|
||||
* @property EA_Input $input
|
||||
* @property EA_Lang $lang
|
||||
* @property EA_Loader $load
|
||||
* @property EA_Log $log
|
||||
* @property EA_Migration $migration
|
||||
* @property EA_Output $output
|
||||
* @property EA_Profiler $profiler
|
||||
* @property EA_Router $router
|
||||
* @property EA_Security $security
|
||||
* @property EA_Session $session
|
||||
* @property EA_Upload $upload
|
||||
* @property EA_URI $uri
|
||||
*/
|
||||
class EA_Cache extends CI_Cache
|
||||
{
|
||||
//
|
||||
}
|
45
application/core/EA_Calendar.php
Normal file
45
application/core/EA_Calendar.php
Normal file
@ -0,0 +1,45 @@
|
||||
<?php defined('BASEPATH') or exit('No direct script access allowed');
|
||||
|
||||
/* ----------------------------------------------------------------------------
|
||||
* Easy!Appointments - Online Appointment Scheduler
|
||||
*
|
||||
* @package EasyAppointments
|
||||
* @author A.Tselegidis <alextselegidis@gmail.com>
|
||||
* @copyright Copyright (c) Alex Tselegidis
|
||||
* @license https://opensource.org/licenses/GPL-3.0 - GPLv3
|
||||
* @link https://easyappointments.org
|
||||
* @since v1.4.0
|
||||
* ---------------------------------------------------------------------------- */
|
||||
|
||||
/**
|
||||
* Easy!Appointments calendar.
|
||||
*
|
||||
* @property EA_Benchmark $benchmark
|
||||
* @property EA_Cache $cache
|
||||
* @property EA_Calendar $calendar
|
||||
* @property EA_Config $config
|
||||
* @property EA_DB_forge $dbforge
|
||||
* @property EA_DB_query_builder $db
|
||||
* @property EA_DB_utility $dbutil
|
||||
* @property EA_Email $email
|
||||
* @property EA_Encrypt $encrypt
|
||||
* @property EA_Encryption $encryption
|
||||
* @property EA_Exceptions $exceptions
|
||||
* @property EA_Hooks $hooks
|
||||
* @property EA_Input $input
|
||||
* @property EA_Lang $lang
|
||||
* @property EA_Loader $load
|
||||
* @property EA_Log $log
|
||||
* @property EA_Migration $migration
|
||||
* @property EA_Output $output
|
||||
* @property EA_Profiler $profiler
|
||||
* @property EA_Router $router
|
||||
* @property EA_Security $security
|
||||
* @property EA_Session $session
|
||||
* @property EA_Upload $upload
|
||||
* @property EA_URI $uri
|
||||
*/
|
||||
class EA_Calendar extends CI_Calendar
|
||||
{
|
||||
//
|
||||
}
|
45
application/core/EA_Config.php
Normal file
45
application/core/EA_Config.php
Normal file
@ -0,0 +1,45 @@
|
||||
<?php defined('BASEPATH') or exit('No direct script access allowed');
|
||||
|
||||
/* ----------------------------------------------------------------------------
|
||||
* Easy!Appointments - Online Appointment Scheduler
|
||||
*
|
||||
* @package EasyAppointments
|
||||
* @author A.Tselegidis <alextselegidis@gmail.com>
|
||||
* @copyright Copyright (c) Alex Tselegidis
|
||||
* @license https://opensource.org/licenses/GPL-3.0 - GPLv3
|
||||
* @link https://easyappointments.org
|
||||
* @since v1.4.0
|
||||
* ---------------------------------------------------------------------------- */
|
||||
|
||||
/**
|
||||
* Easy!Appointments config.
|
||||
*
|
||||
* @property EA_Benchmark $benchmark
|
||||
* @property EA_Cache $cache
|
||||
* @property EA_Calendar $calendar
|
||||
* @property EA_Config $config
|
||||
* @property EA_DB_forge $dbforge
|
||||
* @property EA_DB_query_builder $db
|
||||
* @property EA_DB_utility $dbutil
|
||||
* @property EA_Email $email
|
||||
* @property EA_Encrypt $encrypt
|
||||
* @property EA_Encryption $encryption
|
||||
* @property EA_Exceptions $exceptions
|
||||
* @property EA_Hooks $hooks
|
||||
* @property EA_Input $input
|
||||
* @property EA_Lang $lang
|
||||
* @property EA_Loader $load
|
||||
* @property EA_Log $log
|
||||
* @property EA_Migration $migration
|
||||
* @property EA_Output $output
|
||||
* @property EA_Profiler $profiler
|
||||
* @property EA_Router $router
|
||||
* @property EA_Security $security
|
||||
* @property EA_Session $session
|
||||
* @property EA_Upload $upload
|
||||
* @property EA_URI $uri
|
||||
*/
|
||||
class EA_Config extends CI_Config
|
||||
{
|
||||
//
|
||||
}
|
157
application/core/EA_Controller.php
Normal file
157
application/core/EA_Controller.php
Normal file
@ -0,0 +1,157 @@
|
||||
<?php defined('BASEPATH') or exit('No direct script access allowed');
|
||||
|
||||
/* ----------------------------------------------------------------------------
|
||||
* Easy!Appointments - Online Appointment Scheduler
|
||||
*
|
||||
* @package EasyAppointments
|
||||
* @author A.Tselegidis <alextselegidis@gmail.com>
|
||||
* @copyright Copyright (c) Alex Tselegidis
|
||||
* @license https://opensource.org/licenses/GPL-3.0 - GPLv3
|
||||
* @link https://easyappointments.org
|
||||
* @since v1.4.0
|
||||
* ---------------------------------------------------------------------------- */
|
||||
|
||||
/**
|
||||
* Easy!Appointments controller.
|
||||
*
|
||||
* @property EA_Benchmark $benchmark
|
||||
* @property EA_Cache $cache
|
||||
* @property EA_Calendar $calendar
|
||||
* @property EA_Config $config
|
||||
* @property EA_DB_forge $dbforge
|
||||
* @property EA_DB_query_builder $db
|
||||
* @property EA_DB_utility $dbutil
|
||||
* @property EA_Email $email
|
||||
* @property EA_Encrypt $encrypt
|
||||
* @property EA_Encryption $encryption
|
||||
* @property EA_Exceptions $exceptions
|
||||
* @property EA_Hooks $hooks
|
||||
* @property EA_Input $input
|
||||
* @property EA_Lang $lang
|
||||
* @property EA_Loader $load
|
||||
* @property EA_Log $log
|
||||
* @property EA_Migration $migration
|
||||
* @property EA_Output $output
|
||||
* @property EA_Profiler $profiler
|
||||
* @property EA_Router $router
|
||||
* @property EA_Security $security
|
||||
* @property EA_Session $session
|
||||
* @property EA_Upload $upload
|
||||
* @property EA_URI $uri
|
||||
*
|
||||
* @property Admins_model $admins_model
|
||||
* @property Appointments_model $appointments_model
|
||||
* @property Service_categories_model $service_categories_model
|
||||
* @property Consents_model $consents_model
|
||||
* @property Customers_model $customers_model
|
||||
* @property Providers_model $providers_model
|
||||
* @property Roles_model $roles_model
|
||||
* @property Secretaries_model $secretaries_model
|
||||
* @property Services_model $services_model
|
||||
* @property Settings_model $settings_model
|
||||
* @property Unavailabilities_model $unavailabilities_model
|
||||
* @property Users_model $users_model
|
||||
* @property Webhooks_model $webhooks_model
|
||||
* @property Blocked_periods_model $blocked_periods_model
|
||||
*
|
||||
* @property Accounts $accounts
|
||||
* @property Api $api
|
||||
* @property Availability $availability
|
||||
* @property Email_messages $email_messages
|
||||
* @property Captcha_builder $captcha_builder
|
||||
* @property Google_Sync $google_sync
|
||||
* @property Caldav_Sync $caldav_sync
|
||||
* @property Ics_file $ics_file
|
||||
* @property Instance $instance
|
||||
* @property Ldap_client $ldap_client
|
||||
* @property Notifications $notifications
|
||||
* @property Permissions $permissions
|
||||
* @property Synchronization $synchronization
|
||||
* @property Timezones $timezones
|
||||
* @property Webhooks_client $webhooks_client
|
||||
*/
|
||||
class EA_Controller extends CI_Controller
|
||||
{
|
||||
/**
|
||||
* EA_Controller constructor.
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
parent::__construct();
|
||||
|
||||
$this->load->library('accounts');
|
||||
|
||||
$this->ensure_user_exists();
|
||||
|
||||
$this->configure_language();
|
||||
|
||||
$this->load_common_html_vars();
|
||||
|
||||
$this->load_common_script_vars();
|
||||
|
||||
rate_limit($this->input->ip_address());
|
||||
}
|
||||
|
||||
private function ensure_user_exists()
|
||||
{
|
||||
$user_id = session('user_id');
|
||||
|
||||
if (!$user_id || !$this->db->table_exists('users')) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!$this->accounts->does_account_exist($user_id)) {
|
||||
session_destroy();
|
||||
|
||||
abort(403, 'Forbidden');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Configure the language.
|
||||
*/
|
||||
private function configure_language()
|
||||
{
|
||||
$session_language = session('language');
|
||||
|
||||
if ($session_language) {
|
||||
$language_codes = config('language_codes');
|
||||
|
||||
config([
|
||||
'language' => $session_language,
|
||||
'language_code' => array_search($session_language, $language_codes) ?: 'en',
|
||||
]);
|
||||
}
|
||||
|
||||
$this->lang->load('translations');
|
||||
}
|
||||
|
||||
/**
|
||||
* Load common script vars for all requests.
|
||||
*/
|
||||
private function load_common_html_vars()
|
||||
{
|
||||
html_vars([
|
||||
'base_url' => config('base_url'),
|
||||
'index_page' => config('index_page'),
|
||||
'available_languages' => config('available_languages'),
|
||||
'language' => $this->lang->language,
|
||||
'csrf_token' => $this->security->get_csrf_hash(),
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Load common script vars for all requests.
|
||||
*/
|
||||
private function load_common_script_vars()
|
||||
{
|
||||
script_vars([
|
||||
'base_url' => config('base_url'),
|
||||
'index_page' => config('index_page'),
|
||||
'available_languages' => config('available_languages'),
|
||||
'csrf_token' => $this->security->get_csrf_hash(),
|
||||
'language' => config('language'),
|
||||
'language_code' => config('language_code'),
|
||||
]);
|
||||
}
|
||||
}
|
45
application/core/EA_DB_forge.php
Normal file
45
application/core/EA_DB_forge.php
Normal file
@ -0,0 +1,45 @@
|
||||
<?php defined('BASEPATH') or exit('No direct script access allowed');
|
||||
|
||||
/* ----------------------------------------------------------------------------
|
||||
* Easy!Appointments - Online Appointment Scheduler
|
||||
*
|
||||
* @package EasyAppointments
|
||||
* @author A.Tselegidis <alextselegidis@gmail.com>
|
||||
* @copyright Copyright (c) Alex Tselegidis
|
||||
* @license https://opensource.org/licenses/GPL-3.0 - GPLv3
|
||||
* @link https://easyappointments.org
|
||||
* @since v1.4.0
|
||||
* ---------------------------------------------------------------------------- */
|
||||
|
||||
/**
|
||||
* Easy!Appointments DB forge.
|
||||
*
|
||||
* @property EA_Benchmark $benchmark
|
||||
* @property EA_Cache $cache
|
||||
* @property EA_Calendar $calendar
|
||||
* @property EA_Config $config
|
||||
* @property EA_DB_forge $dbforge
|
||||
* @property EA_DB_query_builder $db
|
||||
* @property EA_DB_utility $dbutil
|
||||
* @property EA_Email $email
|
||||
* @property EA_Encrypt $encrypt
|
||||
* @property EA_Encryption $encryption
|
||||
* @property EA_Exceptions $exceptions
|
||||
* @property EA_Hooks $hooks
|
||||
* @property EA_Input $input
|
||||
* @property EA_Lang $lang
|
||||
* @property EA_Loader $load
|
||||
* @property EA_Log $log
|
||||
* @property EA_Migration $migration
|
||||
* @property EA_Output $output
|
||||
* @property EA_Profiler $profiler
|
||||
* @property EA_Router $router
|
||||
* @property EA_Security $security
|
||||
* @property EA_Session $session
|
||||
* @property EA_Upload $upload
|
||||
* @property EA_URI $uri
|
||||
*/
|
||||
class EA_DB_forge extends CI_DB_forge
|
||||
{
|
||||
//
|
||||
}
|
47
application/core/EA_DB_query_builder.php
Normal file
47
application/core/EA_DB_query_builder.php
Normal file
@ -0,0 +1,47 @@
|
||||
<?php defined('BASEPATH') or exit('No direct script access allowed');
|
||||
|
||||
/* ----------------------------------------------------------------------------
|
||||
* Easy!Appointments - Online Appointment Scheduler
|
||||
*
|
||||
* @package EasyAppointments
|
||||
* @author A.Tselegidis <alextselegidis@gmail.com>
|
||||
* @copyright Copyright (c) Alex Tselegidis
|
||||
* @license https://opensource.org/licenses/GPL-3.0 - GPLv3
|
||||
* @link https://easyappointments.org
|
||||
* @since v1.4.0
|
||||
* ---------------------------------------------------------------------------- */
|
||||
|
||||
/**
|
||||
* Easy!Appointments DB query builder.
|
||||
*
|
||||
* @property EA_Benchmark $benchmark
|
||||
* @property EA_Cache $cache
|
||||
* @property EA_Calendar $calendar
|
||||
* @property EA_Config $config
|
||||
* @property EA_DB_forge $dbforge
|
||||
* @property EA_DB_query_builder $db
|
||||
* @property EA_DB_utility $dbutil
|
||||
* @property EA_Email $email
|
||||
* @property EA_Encrypt $encrypt
|
||||
* @property EA_Encryption $encryption
|
||||
* @property EA_Exceptions $exceptions
|
||||
* @property EA_Hooks $hooks
|
||||
* @property EA_Input $input
|
||||
* @property EA_Lang $lang
|
||||
* @property EA_Loader $load
|
||||
* @property EA_Log $log
|
||||
* @property EA_Migration $migration
|
||||
* @property EA_Output $output
|
||||
* @property EA_Profiler $profiler
|
||||
* @property EA_Router $router
|
||||
* @property EA_Security $security
|
||||
* @property EA_Session $session
|
||||
* @property EA_Upload $upload
|
||||
* @property EA_URI $uri
|
||||
*
|
||||
* @method int insert_id()
|
||||
*/
|
||||
class EA_DB_query_builder extends CI_DB_query_builder
|
||||
{
|
||||
//
|
||||
}
|
45
application/core/EA_DB_utility.php
Normal file
45
application/core/EA_DB_utility.php
Normal file
@ -0,0 +1,45 @@
|
||||
<?php defined('BASEPATH') or exit('No direct script access allowed');
|
||||
|
||||
/* ----------------------------------------------------------------------------
|
||||
* Easy!Appointments - Online Appointment Scheduler
|
||||
*
|
||||
* @package EasyAppointments
|
||||
* @author A.Tselegidis <alextselegidis@gmail.com>
|
||||
* @copyright Copyright (c) Alex Tselegidis
|
||||
* @license https://opensource.org/licenses/GPL-3.0 - GPLv3
|
||||
* @link https://easyappointments.org
|
||||
* @since v1.4.0
|
||||
* ---------------------------------------------------------------------------- */
|
||||
|
||||
/**
|
||||
* Easy!Appointments DB utility.
|
||||
*
|
||||
* @property EA_Benchmark $benchmark
|
||||
* @property EA_Cache $cache
|
||||
* @property EA_Calendar $calendar
|
||||
* @property EA_Config $config
|
||||
* @property EA_DB_forge $dbforge
|
||||
* @property EA_DB_query_builder $db
|
||||
* @property EA_DB_utility $dbutil
|
||||
* @property EA_Email $email
|
||||
* @property EA_Encrypt $encrypt
|
||||
* @property EA_Encryption $encryption
|
||||
* @property EA_Exceptions $exceptions
|
||||
* @property EA_Hooks $hooks
|
||||
* @property EA_Input $input
|
||||
* @property EA_Lang $lang
|
||||
* @property EA_Loader $load
|
||||
* @property EA_Log $log
|
||||
* @property EA_Migration $migration
|
||||
* @property EA_Output $output
|
||||
* @property EA_Profiler $profiler
|
||||
* @property EA_Router $router
|
||||
* @property EA_Security $security
|
||||
* @property EA_Session $session
|
||||
* @property EA_Upload $upload
|
||||
* @property EA_URI $uri
|
||||
*/
|
||||
class EA_DB_utility extends CI_DB_utility
|
||||
{
|
||||
//
|
||||
}
|
45
application/core/EA_Email.php
Normal file
45
application/core/EA_Email.php
Normal file
@ -0,0 +1,45 @@
|
||||
<?php defined('BASEPATH') or exit('No direct script access allowed');
|
||||
|
||||
/* ----------------------------------------------------------------------------
|
||||
* Easy!Appointments - Online Appointment Scheduler
|
||||
*
|
||||
* @package EasyAppointments
|
||||
* @author A.Tselegidis <alextselegidis@gmail.com>
|
||||
* @copyright Copyright (c) Alex Tselegidis
|
||||
* @license https://opensource.org/licenses/GPL-3.0 - GPLv3
|
||||
* @link https://easyappointments.org
|
||||
* @since v1.4.0
|
||||
* ---------------------------------------------------------------------------- */
|
||||
|
||||
/**
|
||||
* Easy!Appointments email.
|
||||
*
|
||||
* @property EA_Benchmark $benchmark
|
||||
* @property EA_Cache $cache
|
||||
* @property EA_Calendar $calendar
|
||||
* @property EA_Config $config
|
||||
* @property EA_DB_forge $dbforge
|
||||
* @property EA_DB_query_builder $db
|
||||
* @property EA_DB_utility $dbutil
|
||||
* @property EA_Email $email
|
||||
* @property EA_Encrypt $encrypt
|
||||
* @property EA_Encryption $encryption
|
||||
* @property EA_Exceptions $exceptions
|
||||
* @property EA_Hooks $hooks
|
||||
* @property EA_Input $input
|
||||
* @property EA_Lang $lang
|
||||
* @property EA_Loader $load
|
||||
* @property EA_Log $log
|
||||
* @property EA_Migration $migration
|
||||
* @property EA_Output $output
|
||||
* @property EA_Profiler $profiler
|
||||
* @property EA_Router $router
|
||||
* @property EA_Security $security
|
||||
* @property EA_Session $session
|
||||
* @property EA_Upload $upload
|
||||
* @property EA_URI $uri
|
||||
*/
|
||||
class EA_Email extends CI_Email
|
||||
{
|
||||
//
|
||||
}
|
45
application/core/EA_Encrypt.php
Normal file
45
application/core/EA_Encrypt.php
Normal file
@ -0,0 +1,45 @@
|
||||
<?php defined('BASEPATH') or exit('No direct script access allowed');
|
||||
|
||||
/* ----------------------------------------------------------------------------
|
||||
* Easy!Appointments - Online Appointment Scheduler
|
||||
*
|
||||
* @package EasyAppointments
|
||||
* @author A.Tselegidis <alextselegidis@gmail.com>
|
||||
* @copyright Copyright (c) Alex Tselegidis
|
||||
* @license https://opensource.org/licenses/GPL-3.0 - GPLv3
|
||||
* @link https://easyappointments.org
|
||||
* @since v1.4.0
|
||||
* ---------------------------------------------------------------------------- */
|
||||
|
||||
/**
|
||||
* Easy!Appointments encrypt.
|
||||
*
|
||||
* @property EA_Benchmark $benchmark
|
||||
* @property EA_Cache $cache
|
||||
* @property EA_Calendar $calendar
|
||||
* @property EA_Config $config
|
||||
* @property EA_DB_forge $dbforge
|
||||
* @property EA_DB_query_builder $db
|
||||
* @property EA_DB_utility $dbutil
|
||||
* @property EA_Email $email
|
||||
* @property EA_Encrypt $encrypt
|
||||
* @property EA_Encryption $encryption
|
||||
* @property EA_Exceptions $exceptions
|
||||
* @property EA_Hooks $hooks
|
||||
* @property EA_Input $input
|
||||
* @property EA_Lang $lang
|
||||
* @property EA_Loader $load
|
||||
* @property EA_Log $log
|
||||
* @property EA_Migration $migration
|
||||
* @property EA_Output $output
|
||||
* @property EA_Profiler $profiler
|
||||
* @property EA_Router $router
|
||||
* @property EA_Security $security
|
||||
* @property EA_Session $session
|
||||
* @property EA_Upload $upload
|
||||
* @property EA_URI $uri
|
||||
*/
|
||||
class EA_Encrypt extends CI_Encrypt
|
||||
{
|
||||
//
|
||||
}
|
45
application/core/EA_Encryption.php
Normal file
45
application/core/EA_Encryption.php
Normal file
@ -0,0 +1,45 @@
|
||||
<?php defined('BASEPATH') or exit('No direct script access allowed');
|
||||
|
||||
/* ----------------------------------------------------------------------------
|
||||
* Easy!Appointments - Online Appointment Scheduler
|
||||
*
|
||||
* @package EasyAppointments
|
||||
* @author A.Tselegidis <alextselegidis@gmail.com>
|
||||
* @copyright Copyright (c) Alex Tselegidis
|
||||
* @license https://opensource.org/licenses/GPL-3.0 - GPLv3
|
||||
* @link https://easyappointments.org
|
||||
* @since v1.4.0
|
||||
* ---------------------------------------------------------------------------- */
|
||||
|
||||
/**
|
||||
* Easy!Appointments encryption.
|
||||
*
|
||||
* @property EA_Benchmark $benchmark
|
||||
* @property EA_Cache $cache
|
||||
* @property EA_Calendar $calendar
|
||||
* @property EA_Config $config
|
||||
* @property EA_DB_forge $dbforge
|
||||
* @property EA_DB_query_builder $db
|
||||
* @property EA_DB_utility $dbutil
|
||||
* @property EA_Email $email
|
||||
* @property EA_Encrypt $encrypt
|
||||
* @property EA_Encryption $encryption
|
||||
* @property EA_Exceptions $exceptions
|
||||
* @property EA_Hooks $hooks
|
||||
* @property EA_Input $input
|
||||
* @property EA_Lang $lang
|
||||
* @property EA_Loader $load
|
||||
* @property EA_Log $log
|
||||
* @property EA_Migration $migration
|
||||
* @property EA_Output $output
|
||||
* @property EA_Profiler $profiler
|
||||
* @property EA_Router $router
|
||||
* @property EA_Security $security
|
||||
* @property EA_Session $session
|
||||
* @property EA_Upload $upload
|
||||
* @property EA_URI $uri
|
||||
*/
|
||||
class EA_Encryption extends CI_Encryption
|
||||
{
|
||||
//
|
||||
}
|
45
application/core/EA_Exceptions.php
Normal file
45
application/core/EA_Exceptions.php
Normal file
@ -0,0 +1,45 @@
|
||||
<?php defined('BASEPATH') or exit('No direct script access allowed');
|
||||
|
||||
/* ----------------------------------------------------------------------------
|
||||
* Easy!Appointments - Online Appointment Scheduler
|
||||
*
|
||||
* @package EasyAppointments
|
||||
* @author A.Tselegidis <alextselegidis@gmail.com>
|
||||
* @copyright Copyright (c) Alex Tselegidis
|
||||
* @license https://opensource.org/licenses/GPL-3.0 - GPLv3
|
||||
* @link https://easyappointments.org
|
||||
* @since v1.4.0
|
||||
* ---------------------------------------------------------------------------- */
|
||||
|
||||
/**
|
||||
* Easy!Appointments exceptions.
|
||||
*
|
||||
* @property EA_Benchmark $benchmark
|
||||
* @property EA_Cache $cache
|
||||
* @property EA_Calendar $calendar
|
||||
* @property EA_Config $config
|
||||
* @property EA_DB_forge $dbforge
|
||||
* @property EA_DB_query_builder $db
|
||||
* @property EA_DB_utility $dbutil
|
||||
* @property EA_Email $email
|
||||
* @property EA_Encrypt $encrypt
|
||||
* @property EA_Encryption $encryption
|
||||
* @property EA_Exceptions $exceptions
|
||||
* @property EA_Hooks $hooks
|
||||
* @property EA_Input $input
|
||||
* @property EA_Lang $lang
|
||||
* @property EA_Loader $load
|
||||
* @property EA_Log $log
|
||||
* @property EA_Migration $migration
|
||||
* @property EA_Output $output
|
||||
* @property EA_Profiler $profiler
|
||||
* @property EA_Router $router
|
||||
* @property EA_Security $security
|
||||
* @property EA_Session $session
|
||||
* @property EA_Upload $upload
|
||||
* @property EA_URI $uri
|
||||
*/
|
||||
class EA_Exceptions extends CI_Exceptions
|
||||
{
|
||||
//
|
||||
}
|
45
application/core/EA_Hooks.php
Normal file
45
application/core/EA_Hooks.php
Normal file
@ -0,0 +1,45 @@
|
||||
<?php defined('BASEPATH') or exit('No direct script access allowed');
|
||||
|
||||
/* ----------------------------------------------------------------------------
|
||||
* Easy!Appointments - Online Appointment Scheduler
|
||||
*
|
||||
* @package EasyAppointments
|
||||
* @author A.Tselegidis <alextselegidis@gmail.com>
|
||||
* @copyright Copyright (c) Alex Tselegidis
|
||||
* @license https://opensource.org/licenses/GPL-3.0 - GPLv3
|
||||
* @link https://easyappointments.org
|
||||
* @since v1.4.0
|
||||
* ---------------------------------------------------------------------------- */
|
||||
|
||||
/**
|
||||
* Easy!Appointments hooks.
|
||||
*
|
||||
* @property EA_Benchmark $benchmark
|
||||
* @property EA_Cache $cache
|
||||
* @property EA_Calendar $calendar
|
||||
* @property EA_Config $config
|
||||
* @property EA_DB_forge $dbforge
|
||||
* @property EA_DB_query_builder $db
|
||||
* @property EA_DB_utility $dbutil
|
||||
* @property EA_Email $email
|
||||
* @property EA_Encrypt $encrypt
|
||||
* @property EA_Encryption $encryption
|
||||
* @property EA_Exceptions $exceptions
|
||||
* @property EA_Hooks $hooks
|
||||
* @property EA_Input $input
|
||||
* @property EA_Lang $lang
|
||||
* @property EA_Loader $load
|
||||
* @property EA_Log $log
|
||||
* @property EA_Migration $migration
|
||||
* @property EA_Output $output
|
||||
* @property EA_Profiler $profiler
|
||||
* @property EA_Router $router
|
||||
* @property EA_Security $security
|
||||
* @property EA_Session $session
|
||||
* @property EA_Upload $upload
|
||||
* @property EA_URI $uri
|
||||
*/
|
||||
class EA_Hooks extends CI_Hooks
|
||||
{
|
||||
//
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user