Download the PHP package remp/crm-skeleton without Composer

On this page you can find all versions of the php package remp/crm-skeleton. It is possible to download/install these versions without Composer. Possible dependencies are resolved automatically.

FAQ

After the download, you have to make one include require_once('vendor/autoload.php');. After that you have to import the classes with use statements.

Example:
If you use only one package a project is not needed. But if you use more then one package, without a project it is not possible to import the classes with use statements.

In general, it is recommended to use always a project to download your libraries. In an application normally there is more than one library needed.
Some PHP packages are not free to download and because of that hosted in private repositories. In this case some credentials are needed to access such packages. Please use the auth.json textarea to insert credentials, if a package is coming from a private repository. You can look here for more information.

  • Some hosting areas are not accessible by a terminal or SSH. Then it is not possible to use Composer.
  • To use Composer is sometimes complicated. Especially for beginners.
  • Composer needs much resources. Sometimes they are not available on a simple webspace.
  • If you are using private repositories you don't need to share your credentials. You can set up everything on our site and then you provide a simple download link to your team member.
  • Simplify your Composer build process. Use our own command line tool to download the vendor folder as binary. This makes your build process faster and you don't need to expose your credentials for private repositories.
Please rate this library. Is it a good library?

Informations about the package crm-skeleton

CRM Skeleton

This is a pre-configured skeleton of CRM application with simple installation.

Translation status @ Weblate

CRM is currently available in _skSK, _csCZ, _enUS and partially _huHU locales. If you're interested in translating the open-source modules to your language, you can approach us and contribute via Weblate.

Installation

CRM Skeleton

To create skeleton application which will be run directly on the host machine, use:

If you plan to use our Docker Compose appliance to run application, skip vendor installation as you might not have all extensions installed on your host machine.

Docker

Simplest posible way is to run this application in docker containers. Docker Compose is used for orchestrating. Except of these two application, there is no need to install anything on host machine.

Recommended (tested) versions are:

Steps to install application within docker

  1. Prepare environment & configuration files

    No changes are required if you want to run application as it is.

  2. Setup host

    Default host used by application is http://crm.press. It should by pointing to localhost (127.0.0.1).

  3. Start docker compose

    You should see log of starting containers.

  4. Enter application docker container

    Following commands will be run inside container.

  5. Update permissions for docker application

    Owner of folders temp, log and content is user on host machine. Application needs to have right to write there.

  6. Install composer packages.

  7. Initialize and migrate database.

  8. Initialize random application key (CRM_KEY value) in your .env file.

  9. Generate user access resources to control access rights to features in CRM admin.

  10. Generate API access resources to control access rights of API tokens to specific endpoints.

  11. Seed database with required data

  12. Copy module's assets to your www folder. This is part of composer.json and it's handled automatically for you in subsequent updates.

  13. All done

    Access application via web browser. Default configuration:

IMPORTANT: Please update steps 7-11 every time you update the CRM - every time you run composer update.

Available modules

Not all the modules that we open sourced are directly included in this repository. You can explore other modules and see, whether there are some scratching your itch.

Or you can dive into the docs below and extend the CRM with your own module.

Custom module implementation

Definition of the module

All modules have to implement Crm\ApplicationModule\ApplicationModuleInterface and therefore all are dependent on an application module we provide.

We have prepared abstract class CrmModule to extend that implements this interface. It includes all extension points the ApplicationManager can work with. This section will help you to understand each integration point - what it its purpose and how to use it. You can always use any of the provided modules as a referece for how to structure the code.

Your module implementation should be placed within app/modules folder. To create DemoModule you'd need to:

Presenter mapping

By default the configuration uses wildcard presenter mapping to ease the learning curve of working with the CRM. You can find this snippet in config.neon.

This configuration means, that all scanned classes mapping the pattern will be used as presenters - even if the module is not enabled. This occasionally creates unwanted side effects.

If you want to have full control over the presenter mapping, for each enabled custom module replace the mapping entry with explicit mapping:

Note: All modules installed as packages are already mapped explicitly.

Configuration ready

Your module is now ready and registered within application. You can start extending the application via following extension points or by implementing your own presenters.

Implementing presenters

Frontend presenters

One of the main reasons to create your own module is to present/receive new information from/by user. This section provides brief introduction of the dummy presenter creation in Nette within CRM. If you're not familiar with Nette framework, it's highly recommended to read about presenters and components on official Nette documentation page.

The application by default scans the source code and match all presenters matching the mapping configured in config.neon. You're free to extend the mapping as needed:

The definition will include all presenters whose fully resolved class name matches the pattern above (asterisk standing for wildcard). See that the application doesn't care where the files are stored - the namespace and class name is important.

The standard we used for storing files is to store Presenters within one subdirectory of each module, Templates within their own directory and Components within their own directory. This might be the structure of your new DemoModule. We recommend to follow this standard just to keep the code easily browsable.

Frontend (end-user-facing) presenters should always extend Crm\ApplicationModule\Presenters\FrontendPresenter which provides extra variables to the layout, sets the layout based on the application configuration and does unified execution that's common for all frontend presenters - feel free to explore the code or extend it further.

In the example we created the minimal version of Presenter (without any extra external dependencies) and passed userId parameter to the template. Let's see how app/modules/DemoModule/templates/DemoPresenter/default.latte might look like:

Couple of things happened in the example template:

As the presenters are being scanned and matched by the wildcard pattern, there's no need to register them further in your config.neon file. The DI container is able to provide any dependencies to the Presenters either via constructor or via @inject annotation.

If the action should be accessible within fronted menu, don't forget to registerFrontendMenuItems.

Admin presenter (extra steps)

Administration presenters are similar to Frontend presenters, however they provide some extra features - mainly ACL.

All administration presenters should extend Crm\AdminModule\Presenters\AdminPresenter. The rest works the same way as frontend presenters.

Nette by default allows you not to include render* method if it doesn't do anything and will directly pass the execution to the template. In Admin presenters that's not the case. Due to the way how ACL works - it scans render* methods in classes inheriting from AdminPresenter - all access points should have their render method implemented even if it should be empty.

When you create new Admin presenter or even new action within Admin presenter, you need to refresh the ACL rules to include the new action:

You can then assign the access to admin actions to specific roles (and create new roles) at /users/admin-group-admin/.

If the action should be accessible within admin menu, don't forget to registerAdminMenuItems.

Translations

to be delivered in the near future

Module configuration

Module configuration is kept in app/config/config.neon and app/config/config.local.neon files. The second file provides a local configuration (only applied for local environment) which overrides global configuration defined in config.neon.

config.neon contains a configuration of modules loaded by your module and basic configuration values such as as database connection strings. In addition, if you want your class to be managed and injected by dependency injection container, you need to specify it in services section here. Example:

ExampleClass will be instantiated as a singleton instance and can injected to other classes by requiring it in their constructor functions (for more details, see Nette DI documentation).

Custom repositories

Here is an example of how to use config.neon file to override default implementation of repository file used by UsersModule. UsersModule registers its repository service in config.neon file like this:

When a service registration is prefixed with a key (usersRepository), you can refer to this key in your configuration file and provide your own implementation of the service. Your repository should extend the original repository (since other services may rely on functions defined there) plus implement your custom functionality. Next step is to re-register your repository with the same key:

Following this, MyUsersRepository will be injected in all services instead of UsersRepository.

Integration with the application

Each of the following subheadings represent method of ApplicationModuleInterface that can be extended by your module. Example snippets in this section display the primary usage of the integration points, you're free to explore and use other parameters of the call.

registerAdminMenuItems

CRM provides you with two separate user interfaces - one for end users and one for administrators of the system (CRM admin). Each module can register its own items which are then injected into the top menu.

Usually each module registers it's own main menu item and inserts all its features as subitems of this main menu item. It is also possible to inject submenu items to other main menu items if they're present.

When you register the menu item, you can provide the label, Nette route link (you can read more about Nette routing here, CSS class to be used to prefix the label (primarily for using Font Awesome icons) and the sorting index to control the order of items.

If any of the items are restricted to currently logged user by ACL, the menu items will not be shown for them.

registerFrontendMenuItems

CRM allows each module to register main menu and submenu items on frontend side of the application - available to end users. It works the same way registerAdminMenuItems does, it just affects different part of the system.

As there's no ACL on the frontend side, each registered item will be shown to the end user.

registerEventHandlers

Each module is able to trigger any avaiable implementation of League's \League\Event\AbstractEvent during the execution throught League's Emitter. These are all synchronous events and all handlers are executed immediatelly when the event is emitted.

Application lets you add the listener to any event type. As the event type is essentially only the string (even when we use full class name for that), it's safe to listen to other module events - if the target module containing execution code emitting the event won\t be registered in config.neon, your listeners will not be triggered, but the rest of your module will remain unaffected.

Here's the example of registering the listener to one of the events provided by UsersModule.

To emit your own events, you need to create an event class - possibly holding any arbitrary data that could be interesting for listeners. For example if you're emitting user-related event, you probably want to include reference to the user into the event you emit. Here's the example of event class:

You can then emit the event anywhere in your code - for example in Presenters (when handling user actions), Repositories (when updating the database) or success handlers of your forms:

Once the event is emitted, the listeners will pick it up. Here's the example implementation of handler that gets to listen to events:

See, that at no point the application checked whether the handler did really receive the event it expects to receive - whether the event really has getUserId() method. It's up to your implementation to decide whether and when to check this.

Nette provides you with DI in the constructor to include any dependencies you need. Due to that, don't forget to register your listener class in the config.neon file.

For more information about Event's implementation possibilities, please visit documentation page of ThePhpLeague.

registerWidgets

Widgets are placeholders in the application layout and within the templates of presenter actions. Module can provide widget placeholders for other modules to display any kind of arbitrary content that might be interesting in the viewing context.

The base example is the detail page of the user owned by UsersModule. By default, it displays primary information about user (email, addresses), but other modules can register their own widgets to provide data they store about the users: SubscriptionsModule displays list of subscriptions with links for their management, PaymentsModule displays list of actual users payments and also total amount of money user spent within the system.

As a module developer, you're free to provide as many placeholders for widgets and register as many widgets as you want. Then, by simply enabling and disabling modules, whole blocks of website can be displayed/removed without affecting the rest of the application.

When registering a widget, you specify a placeholder where the widget will be shown and the implementation of your widget class. Optionally you can pass the priority of the widget which affects which widgets will be rendered sooner and which later. Here's the example of registering the widget in your module class:

Providing a placeholder for widgets is straight-forward. In your .latte template file, include the control macro to the space where you want the widgets to be displayed:

Now for the actual implementation of widgets. In it's simplest form, the widget implementation is similar to presenters and actions. The widget's responsibility is to either render the output (usually by using .latte template) or decide that there's nothing to display and return nothing. Here's the example implementation of the bare widget:

The widget referred to demo.latte placed within the same folder. Here's how it might look like:

Nette provides you with DI in the constructor to include any dependencies you need. Due to that, don't forget to register your widget class in the config.neon file.

registerCommands

Console commands are used to run scheduled or one-time tasks from the CLI. They're primarily targeted to be run by system's scheduler (CRON) or by service managing running of system services (systemd, Supervisor).

All command classes should extend Symfony\Component\Console\Command\Command class and override configure() and execute() methods. We're using Crm\ApplicationModule\Commands\CacheCommand command as the example class in the following snippets.

Configure method serves to define command namespace, name, arguments and options (and whether they're mandatory or not).

When you have the definition ready, you can start implementing the handler. See that any input is readable from InputInterface $input and any of your output writable to OutputInterface $output provided by the execute method.

You can color your output or use completely custom output formatting if you want. See additional information about outputting at Symfony's documentation.

Nette provides you with DI in the constructor to include any dependencies you need. Due to that, don't forget to register your command execution class in the config.neon file. Afterwards you need to register it in your Module class.

When it's registered, you can either list all commands by running php bin/command.php or execute the command directly by using the name you defined in the configure method:

registerApiCalls

API calls provide a way how external application or your frontend can reach your system's backend. Each API call handler has a separate class implementation. Following is an example implementation of API handler:

The params() method allows you to define GET/POST params and to flag them whether they should be required or not. These parameters can be later validated by Crm\ApiModule\Params\ParamsProcessor.

The handle() method allows you to implement your business logic and return the output. By default all API handlers return text output. We used Crm\ApiModule\Api\JsonResponse in our example to indicate application/json header and automatic JSON encoding of response by application.

You might want to use JSON input instead of GET/POST parameters. To read JSON input, we advise to put following snippet to the beginning of handle() method:

Nette provides you with DI in the constructor to include any dependencies you need. Due to that, don't forget to register your API handler class in the config.neon file. Afterwards you need to register it in your Module class.

When you create new handler, you should call console command to refresh ACL rules so they include this new API handler:

Created handlers are not assigned to any API key by default, you need to whitelist them manually by visiting /api/api-access-admin/.

This implementation then has to be linked to the specific API route in your Module class.

The definition will make an endpoint accessible at /api/v3/demo/foo. The NoAuthorization class will allow everyone to use the API call.

By default the API module provides these type of authorizations:

You can always make your own implementation of Crm\ApiModule\Authorization\ApiAuthorizationInterface and use that in your Module class when registering API handler to the route.

All registered API endpoints are listed at /api/api-calls-admin/.

Requests to API are logged to api_logs MySQL table. The logging can be disabled in application configuration page, Other section (/admin/config-admin/?categoryId=6).

registerCleanupFunction

ApplicationModule provides a console command to clean up unnecessary data from within the system by running:

It's supposed to be run periodically by your system scheduler (CRON).

Each module can register its own cleanup function with the cleanup implementation. See what UsersModule gets to clean up:

In the example we requested the instance of ChangePasswordsLogsRepository and UserActionsLogRepository from the DI container and executed prepared method to remove old data from the database.

registerHermesHandlers

Hermes is a tool (console command) for asynchronous processing of events. The application can generate an event with any arbitrary payload to be processed by registered handlers later.

The asynchronous event processor is a console worker that should be run once and keep running forever (until the code changes). It automatically checks for new tasks and perform them. To run the worker, execute:

You can start listening to other module's events by adding following definition to your module:

Every time the application (effectively any module) emits the dummy-event event, your FooHandler will get executed. To emit such event, you need to call emit() method on the instance of Tomaj\Hermes\Emitter:

The DemoPresenter created a new HermesMessage that will get stored in the storage and picked by the console worker later.

By default, the messages are being processed in the same order as they're emitted, however it's possible to delay the execution of message to the specified time - see optional parameters of HermesMessage constructor.

The Handler is implemented as a standalone class that gets the Tomaj\Hermes\MessageInterface as an input:

The handler can get the array payload from the message and do the processing. Handler is responsible for returning result value indicating whether the processing was done or not. If the handler returns true, processing is marked as OK. If the handler returns false or doesn't return a value, processing is marked as failed.

Nette provides you with DI in the constructor to include any dependencies you need. Due to that, don't forget to register your handler class in the config.neon file.

Restarting the worker

Remember that you should restart the worker(s) when the code changes, otherwise it would still use the old version of code that's loaded in the memory.

If you use systemd or supervisor you can configure the tools to start the worker automatically when it stops and trigger the graceful stop of worker. There are two configuration options:

registerAuthenticators

Application allows you to authenticate via custom channels to complement standard email-password authentication. User might be authenticated by a cookie set by some other service, by a special one-time token in URL that was sent to you in an email or by an email and password verified against 3rd party API.

CRM is shipped only with the standard Crm\UsersModule\Authenticator\UsersAuthenticator. You can register your own authenticator if necessary:

Here the DemoModule registers its own authenticator. For the sake of example, let's pretend that FooAuthenticator will authenticate people based on the fooQuery parameter, that was source from the GET parameter in the URL.

The FooAuthenticator extracted the necessary fooQuery value from $credentials parameter. When the authenticate() method is then called, authentication is based on this token (or halted if the token is not present). If the authentication is OK, $user should be returned.

You can prioritize authenticators when registering them in Module classes by using optional parameter of registerAuthenticator call.

Nette provides you with DI in the constructor to include any dependencies you need. Due to that, don't forget to register your authenticator class in the config.neon file.

You might wonder who's responsible for populating $credentials parameter so that your parameter is included there. The data can come from different parts of the system, but mainly it's:

The idea is that the application will populate $credentials array with any relevant values and each authenticator implementation will read only values that it understands. If the values are not there, the authenticator should pass the execution to the next authenticator in the queue.

registerUserData

Due to GDPR compliance, system is required to provide user with any kind of user-related data that's stored within the system and also be able to delete it if requested. If your module works with / stores user data (if your data has reference to user), you must implement a UserDataProviderInterface and register the implementation here.

The purpose of each method within the interface is described within the PHPDoc of an UserDataProviderInterface, following are implementation excerpts with their descriptions:

The data method returns array of public user data usable by the third party applications or services. We recommend to put here any often-accessed user-related data that might be cached for faster future access.

The download method returns array of all related user data. In general, return value of data is always subset of download.

The downloadAttachments method returns list of paths to files to include. In the example the PaymentUserDataProvider generates user invoices into the temporary files and returns list of paths to the caller.

The protect method returns IDs of protected instances and targets this information to the UserDataProvider responsible for possible deletion of those instances. The implementation implies the dependency between the protector and protectee. In this example the UserDataProvider of ProductsModule (our internal module) protects addresses related to the orders for future claim possibilities.

The canBeDeleted returns information whether the user can be deleted or not. There might be cases, where it's not possible (e.g. due to active subscription) and manual intervention is needed before the deletion of user can happen. This method returns such flag also with information "why" the user cannot be yet deleted. In this case, his last active subscription ended within the last three months.

The delete method non-reversibly deletes or anonymizes everything related to the user.

registerSegmentCriteria

The SegmentsModule allows admin users to create segments of user based on specified set of conditions that are selectable in a user-friendly UI. Each module can add its own criteria and parameters that get registered to this UI. These criteria with parameters should be translatable to the query, that gets generated and executed by the SegmentsModule.

To register your own criteria implementation to application, call:

When registering the criteria, you have to provide target table on which you do the selection (the first parameter) - in the most cases it's segments of users. The implementation has to honor this selection and always select unique list of users with arbitrary fields.

The implementation class should implement Crm\ApplicationModule\Criteria\CriteriaInterface. The methods are documented within the interface, but we include the brief description of the most important methods also here based on a Criteria implementation we use:

The params method defined list of available parameters for ActiveSubscriptionCriteria - parametrized condition based on a subset of users having active subscription. The parameters can further descibe "when" the user had an active subscription, filter only users with access to specific content type, with specific type of subscription or with recurrent payment.

The join method generates actual subquery, that will be used to join on by the master segment query generator. You can see that each parameter defined previously should alter the final query in its own way and it's up to developer to handle the conditions and their values correctly.

You can check the rest of the implementation of Crm\SubscriptionsModule\Segment\ActiveSubscriptionCriteria here to get full picture.

Nette provides you with DI in the constructor to include any dependencies you need. Due to that, don't forget to register your criteria class in the config.neon file.

When the implementation is ready, you can check your new Criteria in action at /segment/stored-segments/new.

registerRoutes

CRM by default uses couple of default route patterns provided Crm\ApplicationModule\Router\RouterFactory. The very default route - <module>/<presenter>/<action>[/<id>] - is matching module/presenter/action in the URL and passing any string provided after the action as an $id parameter to action handler.

If no action is provided, default action is executed (renderDefault method of matched presenter), if no presenter is provided, DefaultPresenter is used. For any other caveats, please see the Nette framework routing documentation.

Each module can register its own routes that would be matched before the default ones. Custom routes can be used for nice URLs of special promotions or just so the most used paths of the system have a URL that's not generated from module/presenter/action combination. See the route defined by UsersModule:

It has registered sign/in/ route to be forwarded to UsersModule / SignPresenter / renderIn() method. All URLs generated by framework to this specific action (in the latte templates) will be now rendered as sign/in instead of default users/sign/in.

cache

ApplicationModule provides a console command to periodically cache any kind of data the module would wanted to have cached (php bin/command.php application:cache). It can be temporary data that gets refreshed periodically (e.g. statistical pre-calculations) or persistent data that should be cached only after the release (e.g. application routes).

You can use $tags to filter different type of cached data or different periodicallity. If you for example wanted to have your data cached only once in an hour, you could run the cache in your CRON with hourly tag (php bin/command.php application:cache --tags=hourly) and then search for this tag in $tags parameter.

registerLayouts

Modules can register multiple layouts, that are used when rendering the customer-facing frontend. CRM ships with the very simple frontend layout registered by ApplicationModule. You're free to register new layouts (created based on the frontend as reference implementation) and use them across the application.

The default application layout can be changed in application configuration (/admin/config-admin/?categoryId=1). Be aware that using layout that's not registered will cause application to crash immediatelly.

Any action or presenter can use their own layout and override default layout if necessary. You can force the change by calling $this->setLayout('foo_layout'); within your presenter. You might want to use custom layouts for views that are supposed to be displayed in iframes or modal windows and you don't want to render full header/footer that gets usually rendered.

Every layout should includ content snippet, that gets replaced by the content rendered by executed action. See @frontend_layout_plain.latte for reference:

registerSeeders

Seeders provide application with data necessary running the system. This data is usually stored in the database. Each module can seed its own data into the database or extend already existing data (of modules, that your module will depend on) - for example MailModule can seed the default templates and layouts, ApiModule can seed extra configuration options to the existing application config.

Each seeder has to be implemented as a separate class implementing \Crm\ApplicationModule\Seeders\ISeeder interface.

See the ConfigsSeeder of ApiModule as an example:

The implementation checks whether the config category already exists or not. If it doesn't, it creates it and retrieves the reference. Afterwards it creates new enable_api_log config option that can be used within the application to check whether the API calls should be logged or not - see the usage in \Crm\ApiModule\Presenters\ApiPresenter. Every step is written to the $output just for the sake of logging and visibility of changes.

Nette provides you with DI in the constructor to include any dependencies you need. Due to that, don't forget to register your seeder class in the config.neon file. Afterwards you need to register it in your Module class.

registerAccessProvider

to be delivered in the future

registerDataProviders

Idea of data providers is similar to widgets. Instead of displaying the data (like widgets do) they only "provide" the data to the execution function which can process them further. It's main purpose is to provide a way for extending modules to pass data to one of the generic modules without generic modules knowing something about the data.

For example the form for listing the users in admin provided by AdminForm has by default filtering fields directly related to the user. By using data providers, SubscriptionModule (if enabled) can extend this form and add additional filteing fields to the form and then also use the values to alter the results of the filtering query.

The other nice example are charts. Generic implementation of chart can contain line chart with number of all registered users. When PaymentsModule is included, it can provide another dataset for the chart - number of "paying users". UsersModule will receive this data (without any semantic knowledge of what they consist of) and display them within the chart.

Here's the example of registering data provider within your module class:

Here's the example of data provider extension point - the itegration part where the provided data is eventually consumed:

The extension points can provide any arbitrary data to providers - such as parameters for date filtering so that the data provider can return chart data to the same period of time as the original chart.

Here's the example of implementation of the data provider.

Nette provides you with DI in the constructor to include any dependencies you need. Due to that, don't forget to register your widget class in the config.neon file.


All versions of crm-skeleton with dependencies

PHP Build Version
Package Version
Composer command for our command line client (download client) This client runs in each environment. You don't need a specific PHP version etc. The first 20 API calls are free. Standard composer command

The package remp/crm-skeleton contains the following files

Loading the files please wait ....