-
Notifications
You must be signed in to change notification settings - Fork 510
Development Guide
This guide is for developers only. If you just want to install FreeScout, please follow Installation Guide.
- Architecture & Philosophy
- Contributing
- GitHub Workflow
- Step-by-Step Instruction
- Development Guidelines
- Additional Information
The main idea is to keep FreeScout core neat and clean and not to turn application into a monster by stuffing with all kinds of functionality, which some users may never need. Before adding features to the core we are making sure that they are really needed by analysing GitHub Forum, feature requests and emails to the support. We don't include in the core features required for example by 1% of users, such functionality can be implemented as custom modules.
FreeScout has modular architecture (same idea as WordPress, Redmine, etc), which allows to keep the dist as small as possible and install & update the app on shared hostings. FreeScout core is always lightweight, free and open source. Modules can be free or paid.
To think that updating Laravel to the latest version makes your application much better and secure is a BIG illusion - every framework update brings new bugs, new security issues, tons of useless never-ending work and extra megabytes of code making dist of the app bigger and heavier. Laravel version of the FreeScout always stays the same. In order to fix bugs or make FreeScout compatible with newer PHP version we are patching and overriding vendor
files. This way FreeScout always stays secure, stable and lightweight.
Audit tools may show some vulnerabilities in dependencies, but it does not mean they exist in the app and can be exploited. If some issues are discovered - we are fixing them right away by overriding individual package files (see /overrides
folder). Since the beginning of FreeScout in 2018 we have not heard of a single hacked FreeScout instance.
New features are not added to the core just in case or when just someone just wants to have them. There is a system for tracking and analyzing feature requests.
Keep in mind, that not all your feature requests, ideas and pull requests will be accepted. Stay humble and remember that any aspect of the application can be adjusted using custom modules and filters & actions. See an example of how contribution should not be done.
Don't forget that you are not a project manager. Try to be polite, friendly and positive. Otherwise your account may be banned.
"I've bought a lot of modules (or I've spent $$$ on modules), now all my needs have to be fulfilled"
- this kind of attitude is a mistake which may lead to being banned.
Here is the FreeScout Development Process described in details.
-
dist branch is set as a default branch that people see when they visit project's Github page and download a ready to go build (
/vendor
directory is committed in this branch). - Contributors are using master branch, where
/vendor
directory is added to .gitignore - Composer lock file is committed, so dependencies can be quickly installed with
composer install --ignore-platform-reqs
.
When running composer commands always add --ignore-platform-reqs
flag.
When creating a pull request to master, double check the branch name, as GitHub may automatically pre-select a default branch (dist).
- Create an issue to get an approval on implementing this or that feature or comment on the existing issue to let others know that you are working on it.
- Install the application and switch into master branch.
- Run
composer install --ignore-platform-reqs
- Run the following command to populate test data into your database:
php artisan db:seed
- Enable debugging via
.env
file:
APP_ENV=local
APP_DEBUG=true
- Fork master branch of the project.
- Implement/fix your feature (comment your code, follow the code style of the project, make sure that design is mobile-friendly).
- Write or adapt tests as needed.
- Perform a pull request into master branch.
After pulling updates from master
branch always run the following commands afterwards:
composer install --ignore-platform-reqs
php artisan freescout:after-app-update
- Node.js, VueJS are not used at all.
- See tasks in Todo List.
- Before implementing some functionality please create an issue to discuss it.
- Do not use any HelpScout's files (images, icons, fonts, etc) or code (HTML, CSS, JS, etc) or texts from the help pages. Write and create everything by yourself or use free libraries, etc.
- FreeScout API must be completely equal to HelpScout's API
- All strings must be translatable (except system emails/alerts and
Manage » Translate
page). Do not edit manually files in/resources/lang/
, use Translate tool. - Design must be mobile friendly.
- Always use
User::dateFormat($date, $format)
function to display dates to take user's timezone and time format into account. - Use only Bootstrap Icons.
- Always specify length for strings in migrations.
- After changing routes, make sure to run
php artisan freescout:build
to make new routes available in JS vialaroute.url()
function. - Scripts and styles must be included using
Minify::stylesheet
orMinify::javascript
, as in .htaccess browser caching is enabled incss
andjs
folders. - Class for miscellaneous functions -
App\Misc\Helper
, available via\Helper
alias. - In
composer.json
make sure to specify only exact versions of packages (example: 1.0.2) - DB query caching is implemented using Watson\Rememberable trait. It uses
array
as a cache driver, i.e. queries are cached in memory for each request. - Custom settings are stored using
\Option::get()
and\Option::set()
, default values can be set inconfig/app.php
inoptions
parameter (don't forget to clear cache after editingapp.php
) - Polycast JS script regularly sends ajax requests, which you can see in Laravel debugbar. To stop these requests you can type
poly.disconnect();
in the browser console. - In forms make sure to have same size units for label and input container (for example,
col-sm-2
andcol-sm-6
), if they are different (for example,col-sm-2
andcol-md-6
), the form will be broken when resized. - Do not use
env()
function to get config parameters, useconfig('app.url')
instead, as config is cached. - After changing config files in
/config
don't forget to runphp artisan config:clear
. - If using
select('field')
andorderBy()
in query make sure that all fields from order clause are present inselect()
. - Every time you create some route, make sure to add it to the \Helper::$menu to properly highlight menu item.
- Keep in mind that user can be deleted. Deleted user still visible in the past threads, notifications, etc, but does not receive notifications, etc.
- To perform ajax request create an
ajax
method in controller (see other controllers) and usefsAjax
function in JavaScript. Each controller has oneajax()
method. - To log exceptions use
\Helper::logException($e);
- You can write messages to the App Log using standard
\Log::error()
function or you can write to activity log visible inManage » Logs
using\Helper::log()
function. - By default Storage::put() saves files in app/storage/public, to save in app/storage use
Storage::disk('private')->put()
. - Application must be able to work in a subdirectory.
- In templates and PHP all assets must be included using
asset('favicon.ico')
function application to be able to work in subdirectory. - Do not use
enum
fields in DB, instead useunsignedTinyInteger
type and constants. - Instead of
json_encode()
use\Helper::jsonEncodeUtf8()
to avoid converting symbols into\u0411
. - Instead of
json_decode()
use\Helper::jsonDecode()
. - DB tables should always have
id
field to avoid this. - Custom data for Mailboxes, Customers and Threads can be stored in meta fields:
$mailbox->setMetaParam('eup', $meta_settings )
and$meta_settings = $mailbox->meta['eup'] ?? []
- When running
\Artisan::call()
not in the console make sure to pass'--force' => true
to avoid this issue. - When using
curl
orGuzzleHttp
ALWAYS use\Helper::setCurlDefaultOptions()
or\Helper::setGuzzleDefaultOptions()
functions. - Always add "id" to DB tables as some DBs require a primary key.
- When using
sync()
function for models make sure you are passing an array to it:$mailbox->users()->sync($request->users) ?: []);
. Otherwise PostgreSQL will through an error. - Datepicker with
enableTime
option enabled may return non-standard value (2023-12-14T11:25
), standard is2023-12-14 11:25
. To avoid Carbon "unexpected data found" error\Helper::sanitizeDatepickerDatetime()
function should be used. -
shell_exect()
should be executed via\Helper::shellExec()
. - In commands executed by cron better to avoid
$this->confirm()
as in some cases it may get stuck if vendor/symfony/console/Application.php does not determine that the command is executed by cton.
-
where('field', '<>', '')
does not work in PostgreSQL (https://github.com/freescout-help-desk/freescout/commit/ed15907e69ff9e285d5903617674ab8f6b13a3a1). Fortimestamp
field the following works: `where('field', '!=', null); -
where()
conditions are always case-sensitive in PostgreSQL and case-insensitive in MySQL; - When a string containing
\u0000
symbol is saved to DB, PostgreSQL truncates the string starting from this symbol (#3485). To avoid this the string need to be sanitized before saving to DB:\Helper::sqlSatinizeString($string)
;
Conventions: https://github.com/alexeymezenin/laravel-best-practices#follow-laravel-naming-conventions
Regular variables: snake_case.
FreeScout sets Content Security Policy (CSP) via meta tag to script-src 'self'
mode in order to strengthen security.
With this CSP configuration events can't be attached to DOM elements directly via HTML (for example <button onclick="someFunction" />
or <a href="javascript:..."></a>
). All events should be attached via jQuery.
If you need to insert JavaScript tag <script>
directly into the HTML, do it like this (this way your script will not be blocked by CSP):
<script type="text/javascript" {!! \Helper::cspNonceAttr() !!}>
// Some JS code
</script>
If you need to add some external JavaScript files to the page you need to list them in .env file:
APP_CSP_SCRIPT_SRC="example.org/js/script.js example.org/js/another-script.js"
Or you can list your scripts via the hook:
\Eventy::addFilter('csp.script_src', function ($value) {
return $value.' example.org/js/script.js example.org/js/another-script.js';
});
Hint: It's very convenient to use Firefox browser console to debug CSP errors on the page.
- Add strings or variables to
/resources/views/js/vars.blade.php
- Run
php artisan freescout:build
Retrieving localized strings in JS:
Lang.get('messages.ajax_error');
Lang.get('messages.ajax_error', { name: 'Joe' });
Retrieving variables in JS:
alert(Vars.sample);
In order to have access to the route in JS, add laroute => true
to the route:
Route::get('/conversation/{id}', ['uses' => 'ConversationsController@view', 'laroute' => true])->name('conversations.view');
Using in JS:
laroute.route('conversations.view', { id: 7' });
Project JSON specifiction:
{
"status":"success",
"msg": "Error occured",
"msg_success": "Done"
...
}
- status - Status code: "success" or "error".
- msg - Error message.
- msg_success - Success message.
Queues:
- email (sending outgoing emails)
- default (queue for general tasks)
FreeScout uses schedule:run
to process queued jobs. queue:work
command uses cache mutex which uses cache to prevent process overlapping. If you run schedule:run
in console and terminate it, the cache mutex will not be removed, so you need to clear cache to remove mutex: cache:clear
For testing to process queued jobs in foreground you may change queue driver to sync
in .env
file:
QUEUE_DRIVER=sync
After making changes to the code in order changes to take effect for background jobs you need to clear cache:
php artisan cache:clear
Failed background jobs are cleaned once a week in scheduler.
Modified version of https://github.com/leemason/polycast is used to pass notifications to JavaScript in real time:
-
App\Subscription
creates BroadcastNotification notifications usingnotify()
and sets a delay to allow undoing. - BroadcastNotification via custom channel RealtimeBroadcastChannel fires RealtimeBroadcastNotificationCreated broadcastable event.
- RealtimeBroadcastNotificationCreated event is being processed immediately and
PolycastBroadcaster->broadcast()
method is executed. -
PolycastBroadcaster->broadcast()
saves notification data topolycast_events
table. - Polycast JS script via ajax request receives new notification and displays in the menu and if needed as browser push notification.
Actions and filters are hooks which allow to extend application functionality. Actions and filters system is implemented via Eventy (documentation is available here). See how to use actions & filters in the Modules Development Guide.
Actions are pieces of code you want to execute at certain points in your code. Actions never return anything but merely serve as the option to hook in to your existing code without having to mess things up. (Examples)
Filters are made to modify entities. They always return some kind of value. By default they return their first parameter and you should too. (Examples)
There are two types of folders:
- Common folders (Unassigned, Drafts, Assigned, Closed, Spam, Deleted).
- Personal folders (Mine, Starred), they are counting conversations on per user basis.
FreeScout — Help desk & shared mailbox, free Zendesk & Help Scout alternative.
About
Installation
Configuration
- Sending Emails
- Fetching Emails
- Connect G Suite & Microsoft 365
- Console Commands
- Backup
- Update
- Upgrade PHP
Troubleshooting
Tools & Integrations
- API
- Migrate to FreeScout
- Zapier
- Make (Integromat)
- MacOS Menu Bar App
Development