PHP code example of remp / crm-skeleton

1. Go to this page and download the library: Download remp/crm-skeleton library. Choose the download type require.

2. Extract the ZIP file and open the index.php.

3. Add this code to the index.php.
    
        
<?php
require_once('vendor/autoload.php');

/* Start to develop here. Best regards https://php-download.com/ */

    

remp / crm-skeleton example snippets


    

    namespace Crm\DemoModule;

    class DemoModule extends \Crm\ApplicationModule\CrmModule
    {
        // register your extension points based on the documentation below
    }
    

class DemoPresenter extends \Crm\ApplicationModule\Presenters\FrontendPresenter
{
    public function startup()
    {
        // in this example we want DemoPresenter be available only to logged in users
        $this->onlyLoggedIn();

        parent::startup();
    }

    public function renderDefault()
    {
        $this->template->userId = $this->getUser()->getUserId();
    }
}

class DemoAdminPresenter extends \Crm\AdminModule\Presenters\AdminPresenter
{
    public function renderDefault()
    {
        // in Admin presenters even empty render methods are necessary
    }
}

class DemoModule extends \Crm\ApplicationModule\CrmModule
{
    // ...
    public function registerAdminMenuItems(\Crm\ApplicationModule\Menu\MenuContainerInterface $menuContainer)
    {
        $mainMenu = new \Crm\ApplicationModule\Menu\MenuItem('', '#', 'fa fa-link', 800);

        $menuItem = new \Crm\ApplicationModule\Menu\MenuItem($this->translator->translate('api.menu.api_tokens'), ':Api:ApiTokensAdmin:', 'fa fa-unlink', 200);
        $mainMenu->addChild($menuItem);

        $menuContainer->attachMenuItem($mainMenu);
    }
    // ...
}

class DemoModule extends \Crm\ApplicationModule\CrmModule
{
    // ...
    public function registerFrontendMenuItems(\Crm\ApplicationModule\Menu\MenuContainerInterface $menuContainer)
    {
        $menuItem = new \Crm\ApplicationModule\Menu\MenuItem($this->translator->translate('payments.menu.payments'), ':Payments:Payments:My', '', 100);
        $menuContainer->attachMenuItem($menuItem);
    }
    // ...
}


class DemoModule extends \Crm\ApplicationModule\CrmModule
{
    // ...
    public function registerEventHandlers(\League\Event\Emitter $emitter)
    {
        $emitter->addListener(
            \Crm\UsersModule\Events\UserMetaEvent::class,
            $this->getInstance(\Crm\ApplicationModule\Events\RefreshUserDataTokenHandler::class)
        );
    }
    // ...
}

class DemoUserEvent extends \League\Event\AbstractEvent
{
    private $userId;

    public function __construct(int $userId, $value)
    {
        $this->userId = $userId;
    }

    public function getUserId(): int
    {
        return $this->userId;
    }
}

class DemoRepository extends \Crm\ApplicationModule\Repository
{
    private $emitter;

    protected $tableName = 'demo';

    public function __construct(
        \Nette\Database\Context $database,
        \League\Event\Emitter $emitter,
        \Nette\Caching\IStorage $cacheStorage = null
    ) {
        parent::__construct($database, $cacheStorage);
        $this->emitter = $emitter;
    }

    public function save(\Nette\Database\Table\ActiveRow $user, string $demoValue)
    {
        $result = $this->insert([
            'value' => $demoValue,
        ]);
        if ($result) {
            // HERE'S THE EXAMPLE OF EMITTING YOUR EVENT
            $this->emitter->emit(new DemoUserEvent($user->id, $demoValue));
        }
        return $result;
    }
}

class DemoHandler extends \League\Event\AbstractListener
{
    public function handle(\League\Event\EventInterface $event)
    {
        $userId = $event->getUserId();
        // do whathever handling you want to do
    }
}

class DemoModule extends \Crm\ApplicationModule\CrmModule
{
    // ...
    public function registerLazyWidgets(\Crm\ApplicationModule\Widget\LazyWidgetManagerInterface $widgetManager)
    {
        $widgetManager->registerWidget(
            'admin.users.header',
            \Crm\SubscriptionsModule\Components\MonthSubscriptionsSmallBarGraphWidget::class
        );
    }
    // ...
}

class DemoWidget extends \Crm\ApplicationModule\Widget\BaseLazyWidget
{
    private $templateName = 'demo.latte';

    public function identifier()
    {
        return 'demowidget';
    }

    public function render()
    {
        $this->template->setFile(__DIR__ . DIRECTORY_SEPARATOR . $this->templateName);
        $this->template->render();
    }
}

class CacheCommand extends \Symfony\Component\Console\Command\Command
{
    // ...
    protected function configure()
    {
        $this->setName('application:cache')
            ->setDescription('Resets application cache (per-module)')
            ->addOption(
                'tags',
                null,
                \Symfony\Component\Console\Input\InputOption::VALUE_OPTIONAL | \Symfony\Component\Console\Input\InputOption::VALUE_IS_ARRAY,
                'Tag specifies which group of cache values should be reset.'
            );
    }
    // ...
}

class CacheCommand extends \Symfony\Component\Console\Command\Command
{
    // ...
    protected function execute(\Symfony\Component\Console\Input\InputInterface $input, \Symfony\Component\Console\Output\OutputInterface $output)
    {
        $tags = $input->getOption('tags');

        foreach ($this->moduleManager->getModules() as $module) {
            $className = get_class($module);
            $output->writeln("Caching module <info>{$className}</info>");
            $module->cache($output, $tags);
        }
    }
    // ...
}

class DemoModule extends \Crm\ApplicationModule\CrmModule
{
    // ...
    public function registerCommands(\Crm\ApplicationModule\Commands\CommandsContainerInterface $commandsContainer)
    {
        $commandsContainer->registerCommand($this->getInstance(\Crm\ApplicationModule\Commands\CacheCommand::class));
    }
    // ...
}

class FooHandler extends \Crm\ApiModule\Api\ApiHandler
{
    // ...
    public function params()
    {
        return [
            new \Crm\ApiModule\Params\InputParam(\Crm\ApiModule\Params\InputParam::TYPE_POST, 'email', \Crm\ApiModule\Params\InputParam::REQUIRED),
            new \Crm\ApiModule\Params\InputParam(\Crm\ApiModule\Params\InputParam::TYPE_POST, 'type', \Crm\ApiModule\Params\InputParam::OPTIONAL),
        ];
    }
    // ...
}

class FooHandler extends \Crm\ApiModule\Api\ApiHandler
{
    // ...
    public function handle(\Crm\ApiModule\Authorization\ApiAuthorizationInterface $authorization)
    {
        // read provided params
        $paramsProcessor = new \Crm\ApiModule\Params\ParamsProcessor($this->params());
        $error = $paramsProcessor->isError();
        if ($error) {
            $response = new \Crm\ApiModule\Api\JsonResponse(['status' => 'error', 'message' => $error]);
            $response->setHttpCode(\Nette\Http\Response::S400_BAD_REQUEST);
            return $response;
        }
        $params = $paramsProcessor->getValues();

        // read provided token
        $tokenParser = new \Crm\ApiModule\Authorization\TokenParser();
        if (!$tokenParser->isOk()) {
            $this->errorMessage = $tokenParser->errorMessage();
            $response = new \Crm\ApiModule\Api\JsonResponse(['status' => 'error', 'message' => $tokenParser->errorMessage()]);
            $response->setHttpCode(\Nette\Http\Response::S400_BAD_REQUEST);
            return $response;
        }
        $token = $this->userData->getUserToken($tokenParser->getToken());
        if (!$token) {
            $response = new \Crm\ApiModule\Api\JsonResponse(['status' => 'error', 'message' => 'Token not found']);
            $response->setHttpCode(\Nette\Http\Response::S404_NOT_FOUND);
            return $response;
        }

        // generate sample response
        $response = new \Crm\ApiModule\Api\JsonResponse([
            "token" => $token,
            "email" => $params["email"]
        ]);
        $response->setHttpCode(\Nette\Http\Response::S200_OK);
        return $response;
    }
    // ...
}

$request = file_get_contents("php://input");
if (empty($request)) {
    $response = new \Crm\ApiModule\Api\JsonResponse(['status' => 'error', 'message' => 'Empty request body, JSON expected']);
    $response->setHttpCode(\Nette\Http\Response::S400_BAD_REQUEST);
    return $response;
}

try {
    $params = \Nette\Utils\Json::decode($request, \Nette\Utils\Json::FORCE_ARRAY);
} catch (\Nette\Utils\JsonException $e) {
    $response = new \Crm\ApiModule\Api\JsonResponse(['status' => 'error', 'message' => "Malformed JSON: " . $e->getMessage()]);
    $response->setHttpCode(\Nette\Http\Response::S400_BAD_REQUEST);
    return $response;
}

// use $params as necessary

class DemoModule extends \Crm\ApplicationModule\CrmModule
{
    // ...
    public function registerApiCalls(\Crm\ApiModule\Api\ApiRoutersContainerInterface $apiRoutersContainer)
    {
        $apiRoutersContainer->attachRouter(new \Crm\ApiModule\Router\ApiRoute(
            new \Crm\ApiModule\Router\ApiIdentifier('1', 'demo', 'foo'),
            \Crm\DemoModule\Api\FooHandler::class,
            \Crm\ApiModule\Authorization\NoAuthorization::class
        ));
    }
    // ...
}

class UsersModule extends \Crm\ApplicationModule\CrmModule
{
    // ...
    public function registerCleanupFunction(\Crm\ApplicationModule\CallbackManagerInterface $cleanUpManager)
    {
        $cleanUpManager->add(function (\Nette\DI\Container $container) {
            /** @var \Crm\UsersModule\Repository\ChangePasswordsLogsRepository $changePasswordLogsRepository */
            $changePasswordLogsRepository = $container->getByType(\Crm\UsersModule\Repository\ChangePasswordsLogsRepository::class);
            $changePasswordLogsRepository->removeOldData('-12 months');
        });
        $cleanUpManager->add(function (\Nette\DI\Container $container) {
            /** @var \Crm\UsersModule\Repository\UserActionsLogRepository $changePasswordLogsRepository */
            $userActionsLogRepository = $container->getByType(\Crm\UsersModule\Repository\UserActionsLogRepository::class);
            $userActionsLogRepository->removeOldData('-12 months');
        });
    }
    // ...
}

class DemoModule extends \Crm\ApplicationModule\CrmModule
{
    // ...
    public function registerHermesHandlers(\Tomaj\Hermes\Dispatcher $dispatcher)
    {
        $dispatcher->registerHandler(
            'dummy-event',
            $this->getInstance(\Crm\DemoModule\Hermes\FooHandler::class)
        );
    }
    // ...
}



class DemoPresenter extends \Crm\ApplicationModule\Presenters\FrontendPresenter
{
    /** @var Tomaj\Hermes\Emitter @inject */
    public $hermesEmitter;

    public function renderDefault($id)
    {
        $this->emitter->emit(new \Crm\ApplicationModule\Hermes\HermesMessage('dummy-event', [
            'fooId' => $id,
            'userId' => $this->getUser()->getId(),
        ]));
    }
}

class FooHandler implements \Tomaj\Hermes\Handler\HandlerInterface
{
    public function handle(\Tomaj\Hermes\MessageInterface $message): bool
    {
        $payload = $message->getPayload();

        // your processing code

        // return true if the execution went correctly, false other
        return true;
    }
}

class DemoModule extends \Crm\ApplicationModule\CrmModule
{
    // ...
    public function registerAuthenticators(\Crm\ApplicationModule\Authenticator\AuthenticatorManagerInterface $authenticatorManager)
    {
        $authenticatorManager->registerAuthenticator(
            $this->getInstance(\Crm\DemoModule\Authenticator\FooAuthenticator::class)
        );
    }
    // ...
}

class FooAuthenticator extends \Crm\ApplicationModule\Authenticator\BaseAuthenticator
{
    // ...
    private $token;

    public function setCredentials(array $credentials) : \Crm\ApplicationModule\Authenticator\AuthenticatorInterface
    {
        parent::setCredentials($credentials);
        $this->token = $credentials['fooQuery'] ?? null;
        return $this;
    }

    public function authenticate()
    {
        if ($this->token === null) {
            return false;
        }

        $email = $this->tokenChecker->getEmailFromToken($this->token);
        if (!$email) {
            throw new \Nette\Security\AuthenticationException('invalid token', , \Crm\UsersModule\Auth\UserAuthenticator::IDENTITY_NOT_FOUND);
        }

        $user = $this->userManager->loadUserByEmail($email);
        if (!$user) {
            throw new \Nette\Security\AuthenticationException('invalid token', , \Crm\UsersModule\Auth\UserAuthenticator::IDENTITY_NOT_FOUND);
        }

        $this->addAttempt($user->email, $user, $this->source, \Crm\UsersModule\Repository\LoginAttemptsRepository::STATUS_TOKEN_OK);
        return $user;
    }
    // ...
}

class UserMetaUserDataProvider implements \Crm\ApplicationModule\User\UserDataProviderInterface
{
    // ...
    public function data($userId)
    {
        $result = [];
        foreach ($this->userMetaRepository->userMetaRows($userId)->where(['is_public' => true]) as $row) {
            $result[] = [$row->key => $row->value];
        }
        return $result;
    }
    // ...
}

class UserMetaUserDataProvider implements \Crm\ApplicationModule\User\UserDataProviderInterface
{
    // ...
    public function download($userId)
    {
        $result = [];
        foreach ($this->userMetaRepository->userMetaRows($userId) as $row) {
            $result[] = [$row->key => $row->value];
        }
        return $result;
    }
    // ...
}

class PaymentsUserDataProvider implements \Crm\ApplicationModule\User\UserDataProviderInterface
{
    // ...
    public function downloadAttachments($userId)
    {
        $payments = $this->paymentsRepository->userPayments($userId)->where('invoice_id IS NOT NULL');

        $files = [];
        foreach ($payments as $payment) {
            $invoiceFile = tempnam(sys_get_temp_dir(), 'invoice');
            $this->invoiceGenerator->renderInvoicePDFToFile($invoiceFile, $payment->user, $payment);
            $fileName = $payment->invoice->invoice_number->number . '.pdf';
            $files[$fileName] = $invoiceFile;
        }

        return $files;
    }
    // ...
}

class OrdersUserDataProvider implements \Crm\ApplicationModule\User\UserDataProviderInterface
{
    // ...
    public function protect($userId): array
    {
        $exclude = [];
        foreach ($this->ordersRepository->getByUser($userId)->fetchAll() as $order) {
            $exclude[] = $order->shipping_address_id;
            $exclude[] = $order->licence_address_id;
            $exclude[] = $order->billing_address_id;
        }

        return [\Crm\UsersModule\User\AddressesUserDataProvider::identifier() => array_unique(array_filter($exclude), SORT_NUMERIC)];
    }
    // ...
}

class SubscriptionsUserDataProvider implements \Crm\ApplicationModule\User\UserDataProviderInterface
{
    // ...
    public function canBeDeleted($userId): array
    {
        $threeMonthsAgo = DateTime::from(strtotime('-3 months'));
        if ($this->subscriptionsRepository->hasSubscriptionEndAfter($userId, $threeMonthsAgo)) {
            return [false, $this->translator->translate('subscriptions.data_provider.delete.three_months_active')];
        }

        return [true, null];
    }
    // ...
}

class AddressChangeRequestsUserDataProvider implements \Crm\ApplicationModule\User\UserDataProviderInterface
{
    // ...
    public function delete($userId, $protectedData = [])
    {
        $this->addressChangeRequestsRepository->deleteAll($userId);
    }
    // ...
}

class DemoModule extends \Crm\ApplicationModule\CrmModule
{
    // ...
    public function registerSegmentCriteria(\Crm\ApplicationModule\Criteria\CriteriaStorage $criteriaStorage)
    {
        $criteriaStorage->register(
            'users',
            'foo_criteria',
            $this->getInstance(\Crm\DemoModule\Segment\FooCriteria::class)
        );
    }
    // ...
}

class ActiveSubscriptionCriteria implements \Crm\ApplicationModule\Criteria\CriteriaInterface
{
    // ...
    public function params(): array
    {
        return [
            new \Crm\SegmentModule\Params\DateTimeParam(
                "active_at",
                "Active at", 
                "Filter only subscriptions active within selected period", 
                false
            ),
            new \Crm\SegmentModule\Params\StringArrayParam(
                "contains",
                "Content types", 
                "Users who have access to selected content types", 
                false, 
                null, 
                null, 
                $this->contentAccessRepository->all()->fetchPairs(null, 'name')
            ),
            new \Crm\SegmentModule\Params\StringArrayParam(
                "type", 
                "Types of subscription", 
                "Users who have access to selected types of subscription", 
                false, 
                null, 
                null, 
                array_keys($this->subscriptionsRepository->availableTypes())
            ),
            new \Crm\SegmentModule\Params\NumberArrayParam(
                "subscription_type", 
                "Subscription types", 
                "Users who have access to selected subscription types", 
                false, 
                null, 
                null, 
                $this->subscriptionTypesRepository->all()->fetchPairs("id", "name")
            ),
            new \Crm\SegmentModule\Params\BooleanParam(
                "is_recurrent", 
                "Recurrent subscriptions", 
                "Users who had at least one recurrent subscription"
            ),
        ];
    }
    // ...
}

class ActiveSubscriptionCriteria implements \Crm\ApplicationModule\Criteria\CriteriaInterface
{
    // ...
    public function join(\Crm\SegmentModule\Params\ParamsBag $params): string
    {
        $where = [];

        if ($params->has('active_at')) {
            $where = array_merge($where, $params->datetime('active_at')->escapedConditions('subscriptions.start_time', 'subscriptions.end_time'));
        }

        if ($params->has('contains')) {
            $values = $params->stringArray('contains')->escapedString();
            $where[] = " content_access.name IN ({$values}) ";
        }

        if ($params->has('type')) {
            $values = $params->stringArray('type')->escapedString();
            $where[] = " subscriptions.type IN ({$values}) ";
        }

        if ($params->has('subscription_type')) {
            $values = $params->numberArray('subscription_type')->escapedString();
            $where[] = " subscription_types.id IN ({$values}) ";
        }

        if ($params->has('is_recurrent')) {
            $where[] = " subscriptions.is_recurrent = {$params->boolean('is_recurrent')->number()} ";
        }

        return "SELECT DISTINCT(subscriptions.user_id) AS id, " . \Crm\SegmentModule\Criteria\Fields::formatSql($this->fields()) . "
          FROM subscriptions
          INNER JOIN subscription_types ON subscription_types.id = subscriptions.subscription_type_id
          INNER JOIN subscription_type_content_access ON subscription_type_content_access.subscription_type_id = subscription_types.id
          INNER JOIN content_access ON content_access.id = subscription_type_content_access.content_access_id
          WHERE " . implode(" AND ", $where);
    }
    // ...
}

class DemoModule extends \Crm\ApplicationModule\CrmModule
{
    // ...
    public function registerRoutes(\Nette\Application\Routers\RouteList $router)
    {
        $router->prepend('/sign/in/', 'Users:Sign:in'); // use "prepend" to have your route resolved early and possibly override our routes
        $router->addRoute('/promo', 'Foo:Promo:default'); // use "addRoute" to to have your route resolve regularly
    }
    // ...
}

class DemoModule extends \Crm\ApplicationModule\CrmModule
{
    // ...
    public function cache(\Symfony\Component\Console\Output\OutputInterface $output, array $tags = [])
    {
        $output->writeln("<info>Refreshing user stats cache</info>");
        $repository = $container->getByType(\Crm\UsersModule\Repository\UsersRepository::class);
        $repository->totalCount(true, true);
    }
    // ...
}

class DemoModule extends \Crm\ApplicationModule\CrmModule
{
    // ...
    public function registerLayouts(\Crm\ApplicationModule\LayoutManager $layoutManager)
    {
        $layoutManager->registerLayout('frontend', realpath(__DIR__ . '/templates/@frontend_layout.latte'));
    }
    // ...
}

// ...
public function seed(\Symfony\Component\Console\Output\OutputInterface $output)
{
    $category = $this->configCategoriesRepository->loadByName('Other');
    if (!$category) {
        $category = $this->configCategoriesRepository->add('Other', 'fa fa-tag', 900);
        $output->writeln('  <comment>* config category <info>Other</info> created</comment>');
    } else {
        $output->writeln(' * config category <info>Other</info> exists');
    }

    $name = 'enable_api_log';
    $config = $this->configsRepository->loadByName($name);
    if (!$config) {
        $this->configBuilder->createNew()
            ->setName($name)
            ->setDisplayName('API logs')
            ->setDescription('Enable API logs in database')
            ->setType(\Crm\ApplicationModule\Config\ApplicationConfig::TYPE_BOOLEAN)
            ->setAutoload(true)
            ->setConfigCategory($category)
            ->setSorting(500)
            ->setValue(true)
            ->save();
        $output->writeln("<comment>  * config item <info>$name</info> created</comment>");
    } else {
        $output->writeln("  * config item <info>$name</info> exists");
    }
}
// ...

class DemoModule extends \Crm\ApplicationModule\CrmModule
{
    // ...
    public function registerDataProviders(\Crm\ApplicationModule\DataProvider\DataProviderManager $dataProviderManager)
    {
        $dataProviderManager->registerDataProvider(
            'subscriptions.dataprovider.ending_subscriptions',
            $this->getInstance(DemoDataProvider::class)
        );
    }
    // ...
}

public function createComponentGoogleSubscriptionsEndGraph(\Crm\ApplicationModule\Components\Graphs\GoogleLineGraphGroupControlFactoryInterface $factory)
{
    $items = [];

    // THE DEFAULT CHART ITEM PROVIDED BY GENERIC MODULE
    $graphDataItem = new \Crm\ApplicationModule\Graphs\GraphDataItem();
    $graphDataItem->setCriteria((new Criteria())
        ->setTableName('subscriptions')
        ->setTimeField('end_time')
        ->setValueField('count(*)')
        ->setStart($this->dateFrom)
        ->setEnd($this->dateTo));

    $graphDataItem->setName($this->translator->translate('dashboard.subscriptions.ending.now.title'));

    $items[] = $graphDataItem;

    // CHART ITEMS PROVIDED BY OTHER MODULES
    $providers = $this->dataProviderManager->getProviders('subscriptions.dataprovider.ending_subscriptions');
    foreach ($providers as $sorting => $provider) {
        $items[] = $provider->provide(['dateFrom' => $this->dateFrom, 'dateTo' => $this->dateTo]);
    }

    // ...
}

class DemoDataProvider
{
    public function provide(array $params): \Crm\ApplicationModule\Graphs\GraphDataItem
    {
        if (!isset($params['dateFrom'])) {
            throw new \Crm\ApplicationModule\DataProvider\DataProviderException('dateFrom param missing');
        }
        if (!isset($params['dateTo'])) {
            throw new \Crm\ApplicationModule\DataProvider\DataProviderException('dateTo param missing');
        }

        $graphDataItem = new \Crm\ApplicationModule\Graphs\GraphDataItem();
        $graphDataItem->setCriteria((new \Crm\ApplicationModule\Graphs\Criteria())
            ->setTableName('subscriptions')
            ->setJoin('LEFT JOIN payments ON payments.subscription_id=subscriptions.id
                        LEFT JOIN recurrent_payments ON payments.id = recurrent_payments.parent_payment_id')
            ->setWhere('  AND next_subscription_id IS NULL AND (recurrent_payments.id IS NULL OR recurrent_payments.state != \'active\' or recurrent_payments.status is not null or recurrent_payments.retries = 0)')
            ->setTimeField('end_time')
            ->setValueField('count(*)')
            ->setStart($params['dateFrom'])
            ->setEnd($params['dateTo']));

        $graphDataItem->setName($this->translator->translate('dashboard.subscriptions.ending.nonext.title'));

        return $graphDataItem;
    }
}

    php bin/command.php phinx:migrate
    

    php bin/command.php application:generate_key
    

    php bin/command.php user:generate_access
    

    php bin/command.php api:generate_access
    

    php bin/command.php application:seed
    

    php bin/command.php application:install_assets
    

app/
    modules/
        DemoModule/
            presenters/
                DemoPresenter.php
            templates/
                DemoPresenter/
                    default.latte
            DemoModule.php
bash
# refreshing ACL will create new ACL rule matching new admin actions
php bin/command.php user:generate_access

# seeding will assign access right to the newly generated action to superadmin role
php bin/command.php application:seed
bash
php bin/command.php application:cache
bash
php bin/command.php api:generate_access

php bin/command.php application:cleanup

php bin/command.php application:hermes_worker