# Repository: Leantime/leantime # Stars: 9516 ## CLAUDE.md # CLAUDE.md This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. ## About Leantime Leantime is an open source project management system designed for non-project managers. It combines strategy, planning, and execution in an easy-to-use interface. The application is built with PHP (Laravel), MySQL, and a JS frontend. Current version: 3.6.2. ## Current State & Active Migrations These are ongoing architectural efforts. None need to be fixed proactively -- they provide context for understanding why the codebase has mixed patterns. ### 1. HTMX Migration (In Progress) **Goal**: Replace jQuery AJAX and full-page reloads with HTMX partial updates. **Status**: 8 of 56 domains have dedicated `Hxcontrollers/` with 19 total HxControllers. ~57 Blade templates and ~14 tpl.php files use HTMX attributes. **Domains with HxControllers**: Tickets, Projects, Timesheets, Widgets, Menu, Notifications, Plugins, Help. **Pattern**: Main page controllers load minimal data + skeleton; content loads via HTMX partials. New async work should use HTMX, not jQuery AJAX. ### 2. Template Migration (In Progress) **Goal**: Move from legacy `.tpl.php` to Laravel Blade `.blade.php`. **Status**: ~198 `.tpl.php` files (legacy) vs ~91 `.blade.php` files in domains + ~33 in shared Views. About 30% migrated. - **Fully modernized (Blade-only)**: Dashboard, Gamecenter, Goalcanvas, Menu, Notifications, Plugins, Widgets - **Partially modernized (mix)**: Auth, Calendar, Comments, Help, Projects, Tickets, Timesheets, Users - **Fully legacy (TPL-only)**: All other canvas variants, Clients, Files, Ideas, Wiki, Sprints, Setting, etc. **Pattern**: Main page views tend to stay `.tpl.php` while new partials and HTMX fragments use `.blade.php`. When touching templates, prefer Blade for new work. ### 3. Service Layer / JSON-RPC **Current state**: Services are the business logic layer AND the JSON-RPC API surface. Any public method on a service class can be called via `leantime.rpc.{domain}.{service}.{method}`. The `@api` annotation marks intended API methods but is NOT enforced at runtime. ### 4. Plugin System (Private Submodule) **Current state**: `app/Plugins/` is a git submodule pointing to a private repository for commercial plugins. In the OSS repo this directory is essentially empty. Three plugin types: system (env config, loads at boot), custom (folder), marketplace (phar + license key). ### 5. Event System (String-Based, Moving to Class-Based) **Current state**: 100% string-based event names dynamically generated from class namespace (e.g., `leantime.domain.tickets.services.tickets.updateTicket.ticket_updated`). Only one class-based event exists: `Files/Events/FileUploaded.php` (boilerplate). The `DispatchesEvents` trait is mixed into nearly every core class. Future work should prefer class-based events where practical. ### 6. JavaScript Architecture (Outdated, Needs Componentization) **Current state**: All JS uses a global `leantime` namespace with IIFE module pattern. ~7-8MB of JS loaded on every page (no code splitting). jQuery 3.7.1 + Bootstrap 2.x (ancient) still in use. TinyMCE 5.10.9 is 3.6MB alone. Both Moment.js and Luxon included (redundant). A file-based per-domain loading system was planned but remains commented out in `webpack.mix.js`. Eventually needs componentized architecture with code splitting. ## Development Environment Setup ### Requirements - PHP 8.2+ - MySQL 8.0+ or MariaDB 10.6+ - Required PHP extensions: BC Math, Ctype, cURL, DOM, Exif, Fileinfo, Filter, GD, Hash, LDAP, Multibyte String, MySQL, OPcache, OpenSSL, PCNTL, PCRE, PDO, Phar, Session, Tokenizer, Zip, SimpleXML ### Local Development with Docker (Recommended) ```bash # First build the development environment make clean build # Start the development server make run-dev ``` This starts a development server on port 8090 with: - Leantime app: http://localhost:8090 - MailDev (for email testing): http://localhost:8081 - phpMyAdmin: http://localhost:8082 (auth: leantime/leantime) - S3Ninja (for S3 testing): http://localhost:8083 ### Manual Local Development ```bash # Install dependencies make install-deps-dev # Build for development make build-dev # Point your web server to the public/ directory # Create MySQL database # Copy config/.env.sample to config/.env and configure your database # Navigate to /install ``` ## Common Commands ### Build Commands ```bash make install-deps-dev # Install development dependencies make install-deps # Install production dependencies make build-dev # Build for development (with source maps) make build # Build for production make clear-cache # Clear cache make package # Package for release npx mix # Build js/css using webpack (run in root or within a plugin) ``` ### Testing Commands ```bash make phpstan # Run static analysis (level 0) make test-code-style # Run code style checks (Laravel Pint) make fix-code-style # Fix code style issues (Laravel Pint) make unit-test # Run unit tests (Docker) make acceptance-test # Run acceptance tests (Docker) # Run specific acceptance test groups (inside Docker): docker compose --file .dev/docker-compose.yaml --file .dev/docker-compose.tests.yaml exec leantime-dev php vendor/bin/codecept run -g api --steps docker compose --file .dev/docker-compose.yaml --file .dev/docker-compose.tests.yaml exec leantime-dev php vendor/bin/codecept run -g timesheet --steps # Available groups: api, timesheet, login, ticket, user ``` ### CLI Commands Leantime extended the standard Laravel artisan command and includes several command-line tools located in the `app/Command` directory that can be executed via: ```bash php bin/leantime [command] ``` Common commands: - `system:update` - Update the Leantime installation - `plugin:enable [pluginname]` - Enable a specific plugin - `plugin:disable [pluginname]` - Disable a specific plugin - `plugin:install [pluginname]` - Install a plugin from the marketplace - `plugin:list` - List all installed plugins - `user:add` - Add a new user - `setting:save [key] [value]` - Save a system setting ## Code Architecture ### Directory Structure - `app/` - Main application code - `Core/` - Framework core components - `Application/` - Application service providers - `Auth/` - Authentication services (guards, Sanctum tokens) - `Bootstrap/` - Custom bootstrap (LoadConfig) - `Configuration/` - App configuration (Environment, DefaultConfig, AppSettings, laravelConfig) - `Console/` - Console kernel - `Controller/` - Base controllers (Controller, HtmxController, Frontcontroller, Composer) - `Db/` - Database abstraction (Db, Repository, DbColumn, DatabaseHelper) - `Domains/` - Base domain interfaces (DomainService, DomainRepository, DomainModel) - `Events/` - Event system (EventDispatcher, DispatchesEvents trait) - `Exceptions/` - Exception handling - `Files/` - File management - `Http/` - HTTP handling (HttpKernel, IncomingRequest, ApiRequest, HtmxRequest) - `Middleware/` - Request middleware (16 middlewares) - `Plugins/` - Plugin infrastructure - `Routing/` - Route loading - `Support/` - Helper utilities (CarbonMacros, DateTimeHelper, Format, Cast) - `UI/` - Template handling (Template, Theme, ViewsServiceProvider) - `Domain/` - Application domains (56 modules), organized by feature - Each domain typically contains: - `Controllers/` - HTTP endpoints - `Hxcontrollers/` - HTMX-specific controllers - `Repositories/` - Data access - `Services/` - Business logic - `Models/` - Data structures - `Templates/` - View templates (`.tpl.php`, `.blade.php`, `partials/`) - `Js/` - Domain-specific JavaScript - `Composers/` - View composers - `Listeners/` - Event listeners - `Htmx/` - HTMX event enums - `Middleware/` - Domain middleware - `register.php` - Event/filter listener registration - `Views/` - Shared view files - `Templates/layouts/` - Layout skeletons (app, entry, blank, error, registration) - `Templates/components/` - Shared Blade components (``) - `Templates/sections/` - Header, footer, nav sections - `Composers/` - Shared view composers (App, Header, Footer, Entry, PageBottom) - `Plugins/` - Extension plugins (git submodule to private repo) - `Language/` - Internationalization files (INI-based) - `bootstrap/` - Application bootstrap files - `config/` - Configuration files (.env, .env.sample) - `public/` - Web root directory - `assets/` - Static assets (CSS, JS, images, fonts) - `dist/` - Built/compiled assets (output of `npx mix`) - `theme/` - Theme files (default, minimal) - `storage/` - Storage for logs, cache, and sessions - `tests/` - Test files (Codeception v5.1) - `Acceptance/` - Acceptance tests (Cest format, WebDriver + Selenium) - `Unit/` - Unit tests (extending Laravel TestCase) ### General Architecture Overview The application is built on Laravel 11 with significant custom components. It uses a plugin system for extensibility. Leantime follows a domain-driven architecture: **Core** Framework code (Laravel) and any extended classes are in the `app/Core` folder. Core manages all shared functionality and base features. **Domain** 56 domain modules in `app/Domain/`. Each module has several layers representing one domain: 1. **Controllers** handle HTTP requests and delegate to services 2. **Services** contain business logic and orchestrate operations 3. **Repositories** access and manipulate data storage 4. **Models** represent data structures 5. **Templates** represent view files (Blade and legacy PHP) 6. **Listeners** contain event listeners 7. **Jobs** are queueing jobs **Plugins** Plugins are installable domain modules living in `app/Plugins/`. Each plugin follows the same structure as domain modules but also contains a `composer.json` file for plugin identification. Plugins can be managed as folders or pre-packaged phar files. ### Domain Module Reference **Core Feature Domains**: Tickets, Projects, Users, Sprints, Timesheets, Calendar, Comments, Files, Wiki, Ideas, Reports, Notifications, Dashboard, Widgets, Menu, Tags, Reactions, Entityrelations, Audit, Read **Canvas Domains** (14 variants extending `Canvas` base): Canvas (base), Cpcanvas, Dbmcanvas, Eacanvas, Emcanvas, Goalcanvas, Insightscanvas, Lbmcanvas, Leancanvas, Minempathycanvas, Obmcanvas, Retroscanvas, Riskscanvas, Sbcanvas, Smcanvas, Sqcanvas, Swotcanvas, Valuecanvas **System Domains**: Api, Auth, Cron, CsvImport, Connector, Environment, Errors, Install, Ldap, Modulemanager, Oidc, Plugins, Queue, Setting, Strategy, TwoFA **Backend-only Domains** (no UI): Audit, Entityrelations, Ldap, Reactions, Read, Tags, Queue **Canvas Inheritance Pattern**: The `Canvas` base domain provides generic controllers, services, and repositories. Each variant extends the base with minimal code -- typically just overriding a `CANVAS_NAME` constant: ```php class ShowCanvas extends \Leantime\Domain\Canvas\Controllers\ShowCanvas { protected const CANVAS_NAME = 'cp'; } ``` Goalcanvas is the exception, having been fully modernized to Blade with its own service. ### Architecture Details #### Application Boot Sequence 1. `public/index.php` loads helpers, autoloader, creates Application via `bootstrap/app.php` 2. `bootstrap/app.php` creates `Leantime\Core\Application` (extends Laravel's), binds HttpKernel, ConsoleKernel, ExceptionHandler, IncomingRequest 3. `Bootloader::getInstance()->boot($app)` captures request and routes to HttpKernel or ConsoleKernel 4. **HttpKernel bootstrappers** (in order): LoadEnvironmentVariables, **LoadConfig** (custom -- loads `laravelConfig.php` + Environment), HandleExceptions, RegisterFacades, RegisterProviders, BootProviders 5. **Middleware pipeline** processes the request (see Middleware section) 6. **Routing**: Tries Laravel routes first, falls back to Frontcontroller if no match #### Config System Administrators can configure Leantime using .env files or Environment variables. These need to be stored in the `config/` folder. **Custom config loader** (`app/Core/Bootstrap/LoadConfig.php`): - Creates `Environment` instance as config repository (NOT Laravel's standard Repository) - Loads from `app/Core/Configuration/laravelConfig.php` (NOT from `config/` PHP files) - Priority order: Environment Variables > .env file > PHP config file > DefaultConfig defaults - Maps `#[LaravelConfig('dotted.key')]` attributes on `DefaultConfig` properties to Laravel config **Important**: The list of ServiceProviders is stored in `laravelConfig.php`. ALL Laravel config (database, cache, session, auth, etc.) lives in this single file, not in separate `config/*.php` files. Standard `artisan publish` will NOT work correctly. User-editable variables should be added to `config/.env.sample` and exposed via `LEAN_*` prefix. #### Data Layer Architecture (To Be Refactored) 1. **Repository Classes**: Located in domain-specific `/Repositories` folders, these classes extend `Leantime\Core\Db\Repository` and provide the data access layer. They mix raw SQL queries with Laravel Query Builder depending on when code was written. The `dbcall()` method provides a wrapper that dispatches events around SQL execution. 2. **Models**: Located in domain-specific `/Models` folders, these are simple data structures with public properties. No ORM annotations, no validation, no encapsulation. Properties typically use `mixed` type hints. Some use `#[DbColumn('name')]` attributes for column mapping. 3. **Database Abstraction**: `Core/Db/Db.php` wraps Laravel's `DatabaseManager` (not raw PDO anymore). `Core/Db/DatabaseHelper.php` provides cross-database compatibility helpers for MySQL, PostgreSQL, and MS SQL Server. 4. **Table Naming Convention**: Database tables use a `zp_` prefix (e.g., `zp_projects`, `zp_users`). The following areas will need refactoring for Doctrine integration: - **Repository Pattern**: Current repositories mix domain logic with data access. They need to be refactored to use Doctrine's EntityManager. - **Entity Definition**: Current models need to be converted to proper Doctrine entities with annotations/attributes for mapping. - **SQL Statements**: Raw SQL queries need to be replaced with Doctrine's DQL or QueryBuilder. - **Column Attributes**: The current `DbColumn` attribute will need to be replaced with Doctrine's mapping annotations. - **Transactions**: Current manual transaction handling would be replaced with Doctrine's transaction management. - **Relationship Management**: Current manual relationship handling would be replaced with Doctrine's relationship mappings. #### HTTP Layer Architecture **Dual Routing System**: 1. **Laravel Routes** (new, preferred): Standard `routes.php` files in domains and plugins, loaded by `RouteLoader` 2. **Frontcontroller** (legacy, deprecated but still handles most requests): Convention-based URL-to-class mapping **Frontcontroller URL Convention** (`Core/Controller/Frontcontroller.php`): ``` /module/action -> Domain\{Module}\Controllers\{Action}::get()|post() /module/action/id -> Domain\{Module}\Controllers\{Action}::get()|post() with id param /module/action/id/method -> Domain\{Module}\Controllers\{Action}::method() /hx/module/action -> Domain\{Module}\Hxcontrollers\{Action} ``` Resolution order: Domain Controllers > Domain Hxcontrollers > Plugin Controllers > Plugin Hxcontrollers **Two Controller Method Patterns** (both coexist): 1. **`run()` method (legacy, ~55 controllers)**: Single method handles GET and POST with inline `$_POST`/`$_GET` checks 2. **`get($params)` / `post($params)` (modern, ~83 controllers)**: Separate methods per HTTP verb. Returns `Response`. **Prefer this pattern for new code.** **Request Types** (auto-detected via `RequestTypeDetector`): - `IncomingRequest` - Standard web requests - `ApiRequest` - API requests (adds `getAuthorizationHeader()`, `getAPIKey()`, `getBearerToken()`) - `HtmxRequest` - HTMX requests (adds `isBoosted()`, `getTarget()`, `getTriggerName()`, etc.) **Middleware Stack** (exact order in `HttpKernel.php`): 1. `TrustProxies` - Proxy trust validation 2. `StartSession` - Session init with locking and exponential backoff 3. `Installed` - Redirects to `/install` if not installed 4. `Updated` - Redirects to update if DB version behind 5. `LoadPlugins` - Fires events that trigger user plugin `register.php` loading 6. `InitialHeaders` - Security headers (CSP, X-Frame-Options) -- filterable by plugins 7. `AuthCheck` - Authentication (web guards + API guards, 2FA check, public route bypass) 8. `AuthenticateSession` - Password hash validation, Leantime user session data 9. `RequestRateLimiter` - Rate limits: login 20/min, API 100/min, general 10000/min 10. `HandleCors` - CORS handling 11. `ValidatePostSize` - POST size validation 12. `TrimStrings` - Whitespace trimming (except passwords) 13. `ConvertEmptyStringsToNull` - Empty string to null 14. `SetCacheHeaders` - Cache control with etag support 15. `Localization` - Language, timezone, date/time formats, CarbonImmutable macros 16. `CurrentProject` (domain middleware) - Sets active project context for non-HTMX/API requests **Two-Pipeline Architecture**: After the core middleware stack, a second pipeline runs for plugin-registered middleware: ```php // Core middleware -> Plugin middleware -> Router dispatch ``` Plugins register into this second pipeline via `Registration::registerMiddleware()`. #### Event System Leantime has a custom event system in `Core/Events/` that implements Laravel's `Dispatcher` interface but provides two parallel mechanisms (similar to WordPress hooks): **Events** (fire-and-forget): ```php self::dispatch_event('ticket_created', $payload); ``` **Filters** (modify data through a pipeline): ```php $result = self::dispatch_filter('beforeReturnAllPlugins', $installedPlugins, ['enabledOnly' => $enabledOnly]); ``` **Event Name Convention**: Names are auto-generated from class namespace + method: ``` leantime.domain.tickets.services.tickets.updateTicket.ticket_updated ``` Moving a class changes all its event names -- this is why class-based events are the desired direction. **Listener Registration** (in `register.php` files): ```php // Class-based listener (calls handle() method) EventDispatcher::add_event_listener( 'leantime.domain.projects.services.projects.notifyProjectUsers.notifyProjectUsers', NotifyProjectUsers::class ); // Closure listener with wildcard EventDispatcher::addEventListener('leantime.domain.auth.*.userSignUpSuccess', function ($params) { $helperService = app()->make(\Leantime\Domain\Help\Services\Helper::class); $helperService->createDefaultProject(session('userdata.id'), session('userdata.role')); }); // Filter listener with priority EventDispatcher::add_filter_listener( 'leantime.domain.menu.repositories.menu.getMenuStructure.menuStructures.project', function ($menu) { $menu['newItem'] = [...]; return $menu; }, 50 // lower = earlier execution ); ``` **Pattern Matching**: Supports `*` (any string), `?` (any char), `{RGX:pattern:RGX}` (inline regex). **Blade Directives**: `@dispatchEvent('eventName')`, `@dispatchFilter('filterName', $data)` **Event Discovery** (`discoverListeners()`): Called at boot, scans all `app/Domain/*/register.php` files + system plugin `register.php` files. User-enabled plugin register files load later via `LoadPlugins` middleware event. **register.php Pattern Guide**: Domains that have `register.php`: Auth, CsvImport, Help, Install, Notifications, Plugins, Queue, Reports. These files: - Register event/filter listeners via `EventDispatcher` - Schedule cron jobs via Laravel Scheduler - Hook into application lifecycle events - All currently use string-based event names #### Service Layer Architecture The service layer implements business logic and follows these principles: 1. **Domain Services**: Located in domain-specific `/Services` folders, these classes implement the `Leantime\Core\Domains\DomainService` interface. 2. **Responsibility**: Service classes encapsulate business rules and coordinate between repositories, often combining data from multiple repositories. 3. **Implementation Pattern**: - Services delegate data access to repositories - Services handle domain-specific validation rules - Services trigger events when important state changes occur - Services implement permission checks and authorization logic - Use constructor-based DI with PHP 8 promoted properties - Use `DispatchesEvents` trait for event integration - Use `dispatch_filter()` for plugin hook points 4. **Filter System**: Services use a filter system to allow plugins to modify data before and after processing. 5. **API Exposure**: Most public methods in service classes are marked with `@api` annotation to indicate they are part of the stable API. Any public service method can be called via JSON-RPC at `leantime.rpc.{Domain}.{Service}.{method}` -- the `@api` annotation is documentation only, not enforced at runtime. When refactoring for Doctrine: - Services will need to work with Doctrine entities instead of array structures - Transaction handling would be moved from repositories to services - Hydration logic can be simplified using Doctrine's entity manager #### JSONRPC API Architecture Leantime provides its users with a JSON-RPC 2.0 API. The API is a thin wrapper accessible through the API domain (`app/Domain/Api/Controllers/Jsonrpc.php`) and provides structured access to the service layers of all domains. **Method routing convention**: ``` leantime.rpc.{domain}.{methodname} # 4 segments (service = domain name) leantime.rpc.{domain}.{servicename}.{methodname} # 5 segments ``` **How it works**: The controller uses PHP Reflection to introspect service method parameters, matches request params by name, validates required params, and attempts type casting. Services are resolved via `app()->make()`. **Authentication**: Two types: 1. **Leantime API Keys** (`x-api-key` header): Format `lt_{user}_{key}`, acts as service account 2. **Laravel Sanctum** (Bearer tokens): Personal access tokens (requires AdvancedAuth plugin) **Deprecated API controllers**: The `app/Domain/Api/Controllers/` directory contains legacy REST-like controllers (Tickets.php, Projects.php, etc.) that return JSON. These are deprecated -- all new JS API calls should go through the JSON-RPC endpoint. #### Template System Leantime uses a dual template system actively migrating from PHP to Blade: **Template types**: - `.tpl.php` (~198 files) - Legacy PHP templates using `$tpl->get('variable')` pattern - `.blade.php` (~91 in domains, ~33 in Views) - Modern Laravel Blade - `.sub.php` (~19 files) - Reusable legacy template fragments via `$tpl->displaySubmodule()` - `.inc.php` (~10 files) - Canvas base includes **Shared View Folder** (`app/Views/`): - `Templates/layouts/` - Layout skeletons: `app.blade.php` (main), `entry.blade.php` (login), `blank.blade.php`, `error.blade.php`, `registration.blade.php` - `Templates/components/` - Shared Blade components: accordion, badge, button, dropdownPill, emojiinput, inlineLinks, inlineSelect, loader, loadingText, pageheader, selectable, tabs, undrawSvg, plus kanban sub-components - `Templates/sections/` - header, footer, pageBottom, appAnnouncement - `Composers/` - App, Header, Footer, Entry, PageBottom **Component syntax**: `` for shared, `` for domain-specific. **Template rendering methods** (on `Template` class): - `display($template, $layout, $code)` - Full page render with layout - `displayPartial($template)` - Render without layout - `displayFragment($viewPath, $fragment)` - HTMX fragment rendering - `displaySubmodule($alias)` - Render legacy submodule - `emptyResponse()` - Empty HTTP response **HTMX for asynchronous calls** Leantime is using HTMX for elements that should update asynchronously. The process is ongoing. The goal is that the main page controllers are loading minimal amounts of data to show the page and some shared components (think filters or similar) and all content is being loaded via htmx. All htmx controllers are inside the HxControllers folder. Templates for htmx calls should be in `templates/partials` as they only represent a small part of the page content. If a partial or htmx call represents an entity that may be used in various other places (ticket cards, project cards, user cards etc) a component should be created. **HTMX Pattern Guide**: URL convention: `/hx/{module}/{controller}/{action}` Creating an HxController: ```php namespace Leantime\Domain\{Module}\Hxcontrollers; use Leantime\Core\Controller\HtmxController; class MyController extends HtmxController { // Required: points to a Blade partial protected static string $view = '{module}::partials.myPartial'; // DI via init(), NOT __construct() public function init(MyService $service): void { $this->service = $service; } // Action methods are named semantically, not by HTTP verb public function get($params): void { $this->tpl->assign('data', $this->service->getData($params['id'])); } public function save(): void { // Process $_POST $this->tpl->setNotification('Saved!', 'success'); $this->setHTMXEvent('HTMX.ShowNotification'); } } ``` HTMX event coordination between components: ```php // PHP: Define events as an enum for type-safety enum HtmxTicketEvents: string { case UPDATE = 'ticket_update'; case SUBTASK_UPDATE = 'subtasks_update'; } // PHP: Trigger event in HxController $this->setHTMXEvent(HtmxTicketEvents::UPDATE->value); ``` ```html
``` Common HTMX patterns used: - Lazy loading: `hx-trigger="revealed"` (widgets load when scrolled into view) - Cross-component updates: `hx-trigger="ticket_update from:body"` - Loading indicators: `hx-indicator=".htmx-indicator"` with `` - Preloading: `preload="mouseover"` (hover-preload for dropdowns) - Notifications: `HTMX.ShowNotification` event triggers jQuery growl via global listener in `app.js` **Batch template variable assignment** (common pattern in HxControllers): ```php array_map([$this->tpl, 'assign'], array_keys($tplVars), array_values($tplVars)); ``` #### Role Management, Authorization and Authentication - Leantime uses a combination of Laravel's standard Authentication and Sanctum and Custom auth providers. - Three auth guards: `leantime` (web, session-based), `sanctum` (token), `jsonRpc` (API) - Each user can have a role which is currently hard coded - Each user can be assigned to 1 client - Users can be assigned to projects - Roles give users access to data or parts of the system - Additionally each user has specific project access. - Projects can be either "Accessible to everyone", "accessible only by users within a client" or accessible by users directly assigned to the project only. - Admins and Owners can access all projects **API Authentication** System admins and users can create API Keys. There are 2 types of keys: 1. Leantime API Keys which act as service accounts and are handled like a regular user. Format: `lt_{user}_{key}`. The username is the api key name and password is the api-secret 2. Personal Access Tokens can be created by users (if the AdvancedAuth plugin is installed). Tokens can be used to authenticate the user owning them. We use Laravel Sanctum for this. **Additional Auth providers** Leantime supports LDAP and OIDC authentication natively but can also integrate additional providers via Laravel Socialite (Authentik, Auth0, Gitea, GitHub, GitLab, Google, Keycloak, Microsoft, Okta, PropelAuth, EduID, SAML2). ## Frontend Architecture ### Build System Laravel Mix 6.x (Webpack 5.x) -- configured in `webpack.mix.js`. Output goes to `public/dist/` with version-stamped filenames. **JS bundles** (ALL loaded on every page via `header.blade.php`): - `compiled-htmx` + `compiled-htmx-extensions` - HTMX core + head-support, preload, SSE extensions - `compiled-frameworks` - jQuery 3.7.1 + Bootstrap 2.x - `compiled-framework-plugins` - jQuery UI, Chosen.js, growl, tags input, nestedSortable - `compiled-global-component` - Luxon, Moment, Tippy.js, Uppy, Croppie, Packery, Shepherd.js, Isotope, GridStack, jsTree, Mermaid, Marked - `compiled-editor-component` - TinyMCE 5.10.9 + ~20 custom plugins (3.6MB) - `compiled-calendar-component` - FullCalendar + iCal.js - `compiled-table-component` - DataTables + plugins - `compiled-gantt-component` - Snap.svg + custom Frappe Gantt - `compiled-chart-component` - Chart.js + Luxon adapter - `compiled-app` - Core app + ALL domain JS files via glob `./app/Domain/**/*.js` ### JavaScript Architecture **Global namespace**: All JS uses `leantime` namespace with IIFE module pattern: ```javascript leantime.ticketsController = (function () { function doSomething() { ... } return { doSomething: doSomething }; })(); ``` **Domain JS files** (46 total in `app/Domain/*/Js/`): Pattern mirrors backend -- `{domain}Repository.js` for AJAX, `{domain}Service.js` for logic, `{domain}Controller.js` for UI/DOM. **Guidance**: Use HTMX for data loading/updates. Use JS only for interactivity (editors, drag-and-drop, etc.). Use JSON-RPC endpoint when fetch is needed. When using fetch: ```javascript fetch(url, { credentials: "include", headers: { 'X-Requested-With': 'XMLHttpRequest' } }) ``` ### CSS Architecture **Three-layer system**: 1. **Third-party**: Bootstrap 2.x, jQuery UI, Font Awesome 6.5.2, library-specific CSS 2. **Custom components**: `public/assets/css/components/` -- structure.css, style.default.css, nav.css, kanban.css, forms.css, mobile.css, tables.css, etc. 3. **Tailwind 3.4.x**: Available with `tw-` prefix to avoid Bootstrap conflicts. Only `@tailwind components` and `@tailwind utilities` active (base disabled). Moving towards Tailwind for new CSS. **CSS Variables (Design Tokens)**: The theme system is built on 100+ CSS custom properties. Always use these instead of hardcoded values: - Colors: `--accent1`, `--accent2`, `--primary-color`, `--primary-font-color`, `--primary-background`, `--secondary-background`, `--layered-background` - Typography: `--primary-font-family`, `--base-font-size`, `--font-size-xs` through `--font-size-xxxl` - Layout: `--box-radius`, `--box-radius-small`, `--box-radius-large`, `--element-radius`, `--input-radius` - Shadows: `--min-shadow`, `--regular-shadow`, `--large-shadow`, `--input-shadow` - Z-index: `--zlayer-1` through `--zlayer-9` - Glass: `--glass-blur`, `--glass-background`, `--glass-border` ### Theme System Themes in `public/theme/{name}/` with `theme.ini`, `css/light.css`, `css/dark.css`. Two built-in themes: **default** ("More") and **minimal** ("Less"), both with light/dark mode. Fonts: Roboto (default), Atkinson Hyperlegible (accessibility), Shantell Sans. ## Coding Guidelines ### Task Approach Hierarchy When handling user requests, follow this priority order: 1. **Simple Queries**: For straightforward questions about existing code, use Read/Grep tools directly 2. **Code Modifications**: For changes to existing functionality, analyze the current implementation first 3. **New Features**: For new functionality, research similar existing patterns before implementing 4. **Debugging**: For bug fixes, reproduce the issue first, then implement the fix 5. **Complex Tasks**: For multi-step operations, use TodoWrite to plan before executing ### Context Management #### Working with Large Codebases - Use search tools (Grep, Glob) strategically to find relevant code before reading files - When multiple files might be relevant, batch tool calls to read them efficiently - Focus on understanding the specific area of code related to the user's request #### Clarifying Requirements - Ask clarifying questions when the user's request is ambiguous - When multiple implementation approaches are possible, present options to the user - If unsure about existing patterns or conventions, research the codebase first #### Efficient Tool Usage - Use Task tool for complex searches that might require multiple rounds - Batch independent tool calls in single responses - Read related files together when working on connected functionality ### Testing Strategy **Framework**: Codeception v5.1 (wraps PHPUnit). All test targets are Docker-first via `make` commands. #### When to Run Tests - **Before making changes**: Run relevant tests to establish baseline - **During development**: Run unit tests for the specific domain being modified - **After implementation**: Run full test suite for the affected areas - **Before committing**: Always run code style checks and static analysis #### Test Selection Guidelines - For API changes: Run API-specific acceptance tests (`-g api`) - For domain-specific changes: Run tests for that domain (e.g., `-g timesheet`) - For core changes: Run full test suite - For frontend changes: Test both functionality and styling #### Test Failure Handling - Never ignore test failures - Fix failing tests before proceeding with new functionality - If tests are legitimately outdated, update them as part of the task ### Security Guidelines #### Data Protection - Never log sensitive user data (passwords, API keys, personal information) - Use proper input validation and sanitization for all user inputs - Follow the existing authentication and authorization patterns - Be mindful of SQL injection prevention when working with database queries #### Plugin Development Security - When working with plugins, ensure they follow the same security standards - Validate plugin inputs and outputs - Don't expose internal system information through plugin APIs - Follow the principle of least privilege for plugin permissions #### Code Security Practices - Use parameterized queries through the existing Repository pattern - Validate file uploads and handle them securely - Ensure proper session management - Follow OWASP guidelines for web application security ### Performance Guidelines #### Database Operations - Use the existing Repository pattern instead of direct queries - Be mindful of N+1 query problems when working with related data - Consider database indexes when adding new query patterns - Use pagination for large result sets #### File Operations - Use batch tool calls when reading multiple related files - Avoid reading large files unnecessarily - use targeted searches first - Consider memory usage when processing large datasets #### Frontend Performance - Minimize JavaScript bundle size when adding new features - Use HTMX for efficient partial page updates - Optimize images and assets appropriately - Follow existing patterns for lazy loading and caching - Use htmx for information updates and reloads, use javascript for interactivity #### Caching - Leantime uses the Laravel cache either file-based or Redis. - Cache should be used wherever expensive operations are happening. - When Redis is available, check if admin has chosen Redis and auto-load config via respective ServiceProvider. ## Development Practices ### Plugin System Leantime has a comprehensive plugin system that allows extending core functionality: 1. **Plugin Architecture**: - Plugins reside in the `app/Plugins` directory (git submodule to private repo for commercial plugins) - Each plugin is a self-contained package with its own domain structure - Plugins can have their own vendors through Composer - Two plugin formats: folder-based and phar-based (for marketplace plugins) 2. **Plugin Registration** (`register.php`): - Registers event listeners and filters via `EventDispatcher` - The `Registration` service (`Domain\Plugins\Services\Registration`) provides a fluent API: ```php $registration = new Registration('MyPlugin'); $registration->registerMiddleware([MyMiddleware::class]); $registration->registerLanguageFiles(['en-US', 'de-DE']); $registration->addMenuItem([...], 'project', ['main', 'submenu-key']); $registration->addCss(['app.css']); $registration->addHeaderJs(['vendor.js']); $registration->addFooterJs(['app.js']); ``` 3. **Plugin Loading Order**: - **System plugins** (from `LEAN_PLUGINS` env): Loaded at boot during `discoverListeners()`, before middleware. Cannot be disabled via UI. - **User plugins**: Loaded when `LoadPlugins` middleware fires (after session, install check, update check) - Plugin `routes.php` files are loaded via `RouteLoader` 4. **Plugin Types**: - System: Core enabled plugins defined in config. Always loaded, cannot be disabled via UI, load earlier in the stack - Marketplace: From marketplace.leantime.io. Delivered as phar packages, require license key validation - Custom Folders: Regular plugins or plugins in development 5. **Plugin Lifecycle**: `discoverNewPlugins()` -> `installPlugin()` -> `enablePlugin()` -> `disablePlugin()` -> `removePlugin()`. Each plugin service class can implement `install()`, `uninstall()`, `enable()`, `disable()` hooks. 6. **Plugin license keys and validation**: - Plugins can be purchased from the marketplace (marketplace.leantime.io). - Each plugin needs to be installed with a license key which is stored in the database - License Keys are perpetual however they are restricted by the number of users. - Leantime checks number of active users in the system regularly and against the server. - If a system has more users than allowed for a plugin the plugin is disabled. Data remains in the database - Daily cron validates all marketplace plugin licenses 7. **Refactoring Considerations**: - Move away from string-based event hooks to class-based events - Implement a more robust dependency management system - Standardize plugin activation/deactivation hooks - Add versioning and compatibility checking - Plugin updates should be handled automatically without having to enter license keys ### Event System Features should use the event system to maintain loose coupling between components. Event System is custom extension of Laravel events and also includes options for filters. See the Event System section under Architecture Details for full documentation. ### Routing Leantime has dual routing: Laravel routes (preferred for new code) and legacy Frontcontroller (convention-based URL-to-class mapping). See HTTP Layer Architecture for details. ### Testing Code should be tested using: - PHPStan for static analysis (currently level 0) - Laravel Pint for code style (primary tool; PHPCS also configured but Pint is preferred) - Codeception v5.1 for both unit and acceptance testing - Test groups: `api`, `timesheet`, `login`, `ticket`, `user` ## Specific Code Style Guidelines ### Code Style We use Laravel Pint for code style (config at `.pint/pint.json`). ### Backwards Compatibility Unless specifically called out DO NOT keep any old code or build any sort of backwards compatibility. ### Configs All laravel configs need to be stored in the laravelConfig file inside the core configuration folder. Any variables that should be editable by the user should be added to the sample.env file and exposed via `LEAN_*`. We do not load any custom php configs from the root config folder and as such things like artisan publish will not publish configs correctly. Instead the content needs to be added to laravelConfig. When redis is available for a certain service (queue, cache, sessions etc) we should check if the admin has chosen to use redis and then automatically load the redis config via the respective serviceProvider. ### Error Logging When logging errors ALWAYS use the Log Facade (ensure it's included in the use statements). Example: `Log::error($exception)` DO NOT use the helper functions error_log() ### Strict types - Use strict types where ever possible (for returns and for parameters) - When creating arrays evaluate whether a model/object should be used and create one if deemed appropriate ### Comments - Add valid phpDoc comments to all methods and classes. - For each method that is changed verify that PhpDoc comment exists and is aligned - Methods in services that should be available to our jsonRPC should include the @api doc comment ### DateTime Handling Always use `CarbonImmutable` or the `dtHelper()` function class for all things datetime and have various macros to help with common date formats. As a general rule all dates from the database are assumed to be in UTC and in the format YYYY-MM-DD HH:MM:SS Dates coming from the frontend/user are assumed in the user's timezone and their respective date format. We have a DateTimeHelper class to parse common datetime formats we find, the dateTimeHelper should be used in most cases. ### Layer enforcement - Controllers should only call services NOT repositories. If a repo call is detected it should be refactored. - Services can call repositories - Be careful when calling domain services in other domain services as circular references can happen - Services should validate input and throw exceptions when validation fails ## README.md
Leantime Logo # Leantime® โญ If you find Leantime useful, please star us on GitHub! โญ Leantime is an open source project management system for non-project managers.
We combine strategy, planning and execution while making it easy for everyone on the team to use.
Built with ADHD, dyslexia and autism in mind. ๐Ÿง 
๐Ÿ’ช As simple as Trello but as feature-rich as Jira
๐Ÿ”„ A perfect alternative to ClickUp, Monday, or Asana
๐ŸŒ [https://leantime.io](https://leantime.io)
A screenshot of Leantime's my work dashboard showing a few boxes with large metrics represnting todos complete, goals contributing to, scheduled todos. Also shows a day calendar with one task on it and a list of tasks grouped by Overdue, Due this week and Due Later [![License Badge](https://img.shields.io/github/license/leantime/leantime?style=flat-square)](https://www.gnu.org/licenses/agpl-3.0.en.html) [![Docker Hub Badge](https://img.shields.io/docker/pulls/leantime/leantime?style=flat-square)](https://hub.docker.com/r/leantime/leantime) ![Github Downloads](https://img.shields.io/github/downloads/leantime/leantime/total) [![Discord Badge](https://img.shields.io/discord/990001288026677318?label=Discord&style=flat-square)](https://discord.gg/4zMzJtAq9z) [![Crowdin](https://badges.crowdin.net/leantime/localized.svg)](https://crowdin.com/project/leantime) ![GitHub Sponsors](https://img.shields.io/github/sponsors/leantime)
![alt text](public/assets/images/Screenshots/mywork-v3.5.png "Home Screen")


## ๐Ÿš€ Features* ## | Task Management | Project Planning | Information/Knowledge Management | Administration | | -------------------------------------------------------------------- | -------------------------------------------- | --------------------------------------- |-------------------------------------------------| | Task management via
kanban boards, gantt, table, list and calendar views | Project Dashboards, reports & status updates | Wikis / Docs | Easy installation | | Unlimited subtasks and dependencies | Goal & metrics tracking | Idea Boards | Multiple user roles and per project permissions | | Milestone management | Lean & Business Model Canvas | Retrospectives | Two factor authentication | | Sprint Management | SWOT Analysis canvas | File Storage via S3 or local filesystem | LDAP, OIDC integration | | Timetracking & timesheets | Risk Analysis | Screen & webcam recording | Extendable via plugins and API | | | ... and more | Comments/discussions on everything | Integrates with Slack, Mattermost, Discord | | | ... and more | | Available in over 20 languages | *yes, all of these features are included in the OSS version

### ๐Ÿ“ธ Screenshots ###
Screenshot of Leantime's my work dashboard but with a dark color scheme. All colors are darkened or reverted Screenshot of Leantime's project dashboard showing a project checklist that has the first box checked, the latest tasks (1 right now), a progress donut chart at 0% Screenshot of Leantime's todo screen in table format. Tasks are grouped by status where each Status has a different color
alt text Screenshot of Leantime's timeline or gantt feature showing a timeline with various milestone boxes different in length representing how long these take. Each milestone has a different color and they are connected with an arrow Screenshot of Leantime's personal calendar screen showing a month overview with a few tasks
alt text Screenshot of Leantime's wiki page showing one template article of a product requirements document formatted with lists and tables Screenshot of Leantime's timesheet feature with a table a one week overview and input boxes for each day. Tasks are organized in rows


### โ—System Requirements ### * PHP 8.2+ * MySQL 8.0+ or MariaDB 10.6+ * Apache or Nginx (IIS works with some modifications) * PHP Extensions: * BC Math (bcmath) * Ctype * cURL * DOM * Exif * Fileinfo * Filter * GD * Hash * LDAP * Multibyte String (mbstring) * MySQL * OPcache * OpenSSL * PCNTL * PCRE * PDO * Phar * Session * Tokenizer * Zip * SimpleXML

### ๏ธโšก๏ธ Installation (Production) ### There are two main ways to install LeanTime for production. The first of which is to install all needed pieces of the system locally. The second is to use the officially supported Docker image. #### Local Production Installation #### * Download latest release package (file is called: Leantime-vx.x.x.zip) from the release page * Create an empty MySQL database * Upload the entire directory to your server * Point your domain root to the `public/` directory * Rename `config/sample.env` to `config/.env` * Fill in your database credentials (username, password, host, dbname) in `config/.env` * Navigate to `/install` * Follow instructions to install database and set up first user account ##### IIS Installation Notes ##### Whilst the steps above are applicable to Internet Information Services (IIS), there is an additional configuration change that may be required in IIS to ensure full functionality - you need to allow the PATCH method: * Open IIS * Expand the server and sites on the left and select the LeanTime site * Double click on `Handler Mappings` * Double click on the PHP handler mapping that is used by the site * Click `Request Restrictionsโ€ฆ` * Click the `Verbs` tab * In the `One of the following verbs` text box, add `PATCH` - for example: `GET,HEAD,POST,PATCH` * Click `OK` * In the `Executable (optional)` text box, put a double quote character (`โ€œ`) at the start and at the end of the path to the `php-cgi.exe` file (_this isn't needed if the path doesn't have a space in it_) * Click `OK` * A popup will appear asking if you want to create a FastCGI application - click `Yes` Note: You may need to repeat this when you upgrade PHP. #### Production Installation via Docker #### We maintain an official Docker image on dockerhub. To run the image enter your MySQL credentials and execute. You can pass in all the configuration variables from .env ``` docker run -d --restart unless-stopped -p 8080:8080 --network leantime-net \ -e LEAN_DB_HOST=mysql_leantime \ -e LEAN_DB_USER=admin \ -e LEAN_DB_PASSWORD=321.qwerty \ -e LEAN_DB_DATABASE=leantime \ -e LEAN_EMAIL_RETURN=changeme@local.local \ --name leantime leantime/leantime:latest ``` Unless you have a database defined somewhere else you should use our [docker-compose file](https://github.com/Leantime/docker-leantime/blob/master/docker-compose.yml). Once started you can go to `/install` and run the installation script. **Important: If you are planning to use plugins you need to mount the plugin folder `plugins:/var/www/html/app/Plugins` and ensure the www-data user has access to it. Otherwise installation may fail or plugins will be removed after a restart** ##### Docker Installation Notes ##### If you intend to place Leantime behind a reverse proxy (nginx, etc.) to handle custom domain name resolution and SSL offloading, you will need to set the following environment variable in docker ``` -e LEAN_APP_URL=https://yourdomain.com \ ``` * Update yourdomain.com to your custom domain name.

### ๐Ÿค“ Installation (Development) ### There are two ways to install a development setup of LeanTime. The first (but most technical) is to install all pieces of the system locally. The second (and preferred method) is to use a docker containerized development environment. #### Local Development Installation #### * Clone repository to your local server * Create MySQL database * Run webpack builder via `make build-dev` * Point your local domain to the `public/` directory * Rename `config/sample.env` to `config/.env` * Fill in your database credentials (username, password, host, dbname) in `config/.env` * Navigate to `/install` * Follow instructions to install database and user account #### Development Installation via Docker #### For development, we use a dockerized development environment. You will need to have ``docker``, ``docker compose``, ``make``, ``composer``, ``git`` and ``npm`` installed. * Notes for Windows Environments: - Run all commands within the git bash terminal in order to utilize unix specific commands - If installing php from a zip file, make sure to configure php.ini It does not exist initially, so copy C:\php\php.ini-development to C:\php\php.ini. You will also need to edit php.ini in a text editor and enable all needed extensions for the build process. You can find these by running the make commands and looking for any extensions that error out as missing. You can enable them by searching php.ini for the extension that will look like: `;extension=gd` and removing the semicolon. In order to build the development docker image, in the root of this repository, run a primer with ```make clean build``` afterwards, run ```make run-dev``` this will start the development server on port 5080. The dev environment provides a MySQL server, mail server, s3 server, and should be good to go for your needs out of the box. The basic configuration of the development environment is already defined in the composer file. You can create .env file inside of `config/.env` to augment the base configuration by setting some of the configs out of sample.env). **Important: Don't update the database information as this will disconnect the app from the docker database**. The applications you get are as follows * [http://localhost:8090](http://localhost:5080) : leantime * [http://localhost:8081](http://localhost:8081) : maildev - to check emails sent * [http://localhost:8082](http://localhost:8082) : phpMyAdmin(authentication ``leantime:leantime``) to check the DB schema and data * [http://localhost:8083](http://localhost:8083) : s3ninja - to check s3 uploads. You need to enable this in the ``.dev/.env`` file by enabling s3 Additionally, Xdebug is enabled, but you will have to modify your IDE key in the ``.dev/xdebug.ini`` file(or alternatively, on your IDE). You also need to have port 9003 temporarily open on your firewall so you can utilize it effectively. This is because connections from docker to the host will count as external inbound connections

### Run Tests Static Analysis `make phpstan`
Code Style `make test-code-style` (to fix code style automatically use `make fix-code-style`)
Unit Tests `make unit-test`
Acceptance Tests `make acceptance-test`
(requires docker) You can test individual acceptance test groups directly using:
For api:
`docker compose --file .dev/docker-compose.yaml --file .dev/docker-compose.tests.yaml exec leantime-dev php vendor/bin/codecept run -g api --steps`
For timesheets:
`docker compose --file .dev/docker-compose.yaml --file .dev/docker-compose.tests.yaml exec leantime-dev php vendor/bin/codecept run -g timesheet --steps`
### ๐Ÿ— Update ### #### Manual * Make sure to take a backup of your database and files * Replace all files in your directory with the updated version * If there were any database changes, the system will redirect you to `/update` #### CLI * Run `php bin/leantime system:update` #### Docker * Before updating, make sure your mysql container was started using a mounted volume, otherwise your content will be deleted * Delete/Stop existing container * Pull the latest docker image and rebuild using your compose file ### Common Issues Please refer to our [documentation](https://docs.leantime.io/installation/common-issues) about common issues found when installing or updating Leantime ## ๐Ÿ”Œ Extend Leantime ## #### You can extend Leantime by: - building your own plugin: [Plugin Docs](https://docs.leantime.io/development/plugin-development) - using our json-rpc API: [API Docs](https://docs.leantime.io/api/usage) - or by purchasing a plugin from our [marketplace](https://marketplace.leantime.io) ## ๐Ÿ›Ÿ Let us install it for you. ## Hassle free installation service in your environments. We can do full installations, updates, configurations or plugin installations. See our [Marketplace](https://marketplace.leantime.io/product-category/services/technical/) for details.

## โ˜๏ธ Not interested in hosting yourself? Let us do it for you ## We offer [managed hosting plans](https://leantime.io/managed-hosting/) as well as a [SaaS product](https://leantime.io/pricing/) so you can get all the benefits of Leantime without the hassle. Head to [leantime.io](https://leantime.io/) for more information.

## ๐Ÿค™ Need technical support? ## We can help you set up Leantime in your environment and customize it to your needs. Our support plans are [outlined on our website](https://leantime.io/priority-support/). Please note: We currently only support the official Leantime docker compose and standard installations. We only offer support for the most recent version. We do not offer support for Cloudron, Elestio, Turnkey, or other external distribution platforms sharing unofficial versions of Leantime. ## ๐Ÿซด Contributing We're excited you are interested in contributing to Leantime. We want to make sure you have a great experience contributing to Leantime and that the new features you build will make it into core.
### ๐Ÿชฒ Bugs Find an issue on Github (or create a new one) add your name to it or comment that you will be working on it. Once fixed, create a Pull Request. ### New Features in Core If you have an idea about new features please reach out to us on Discord. This is where we coordinate feature development and discuss whether core is the right place to add your new features (Plugins is the alternative). ### ๐ŸŒ Translations Language files and translations are stored in `app/Language/* `. Once updates please create a Pull Request. ### ๐Ÿ‘ฅ Community Support * Documentation [https://docs.leantime.io](https://docs.leantime.io) * Community Chat [Discord](https://discord.gg/4zMzJtAq9z) * File a bug report [https://github.com/Leantime/leantime/issues/new](https://github.com/Leantime/leantime/issues/new) * Translations [https://crowdin.com/project/leantime](https://crowdin.com/project/leantime)

## โš–๏ธ LICENSE Exceptions Leantime is licensed under AGPLv3. This file forms part of the Leantime Software for which the following exception is added: Plugins within the `/app/Plugins` directory which may contain plugins licensed under other licenses including our enterprise license.