init(); $this->initServices(); } public function onNewRequest($request) { parent::onNewRequest($request); $this->initServices(true); } /** * Init installed services * @param bool $force * @return void */ protected function initServices($force = false) { if ($this->services === null || $force) { $this->services = $this->getServiceModel() ->where('group_key', '=', $this->getGroupKey()) ->get() ->keyBy('service_key') ->toArray(); } } /** * Get registered service instance * @param string|Service $service key / class name / instance * @return Service|null */ public function getService($service): ?Service { if ($service instanceof Service) { # Service should match manager group key if ($service->getGroupKey() !== $this->getGroupKey()) { return null; } return $service; } $className = false; $key = false; if (isset($this->services[$service]['class_name'])) { # get service by key $className = $this->services[$service]['class_name']; $key = $service; } else { # get service by class name foreach ($this->services as $k => $v) { if ($v['class_name'] == $service) { $className = $v['class_name']; $key = $k; break; } } if (! $className) { $className = $service; } } if (! class_exists($className)) { return null; } $service = new $className($this, $this->services[$key] ?? []); if ($service instanceof Service) { return $service; } return null; } /** * Install service * @param string $serviceClass * @param array $opts * @return bool */ public function installService(string $serviceClass, array $opts = []): bool { $service = $this->getService($serviceClass); if (! $service) { $this->log(__FUNCTION__ . ' can\'t get service: ' . $serviceClass); return false; } $serviceKey = $service->getKey(); if (empty($serviceKey) || $this->loadService($serviceKey, ['id'])) { return false; } $data = $service->getInstallSettings(); $data['group_key'] = $this->getGroupKey(); $data['service_key'] = $serviceKey; $data['service_type'] = $service->getType(); $data['class_name'] = $serviceClass; $id = $this->saveService(0, $data); if ($id > 0) { $opts['id'] = $id; $service->onInstall($opts); return true; } return false; } /** * Enable service * @param string $serviceClass * @param array $opts * @return bool */ public function enableService(string $serviceClass, array $opts = []): bool { $service = $this->getService($serviceClass); if (! $service) { return false; } $service->onEnable(); $this->getServiceModel() ->where('class_name', '=', $serviceClass) ->update(['enabled' => 1]) ; return true; } /** * Disable service * @param string $serviceClass * @param array $opts * @return bool */ public function disableService(string $serviceClass, array $opts = []): bool { $service = $this->getService($serviceClass); if (! $service) { return false; } $service->onDisable(); $this->getServiceModel() ->where('class_name', '=', $serviceClass) ->update(['enabled' => 0]) ; return true; } /** * Install service images * @param Module $controller * @param int $id * @param array $images ['field_name' => 'path to image'] * @return bool */ public function installServiceImages(Module $controller, int $id, array $images) { if (empty($id)) { return false; } if (empty($images)) { return false; } $this->adminSettings($controller, __FUNCTION__, [ 'onBeforeView' => function ($params) use ($id, $images) { do { if (empty($params['form'])) { break; } /** @var Form $form */ $form = $params['form']; $form->setRecordID($id); $settings = $form->getUnion('settings'); foreach ($images as $k => $v) { /** @var Images $f */ $f = $settings->field($k); if (! $f) { continue; } $f->uploadFromFile($v); } } while (false); return ''; } ]); return true; } /** * Uninstall service * @param string $serviceClass * @param array $opts * @return bool */ public function uninstallService(string $serviceClass, array $opts = []): bool { $service = $this->getService($serviceClass); if (! $service) { return false; } $deleted = $this->deleteService([ 'group_key' => $this->getGroupKey(), 'service_key' => $service->getKey(), ]); if ($deleted) { $service->onUninstall($opts); } return $deleted; } /** * Save/create service * @param int $id service ID * @param array $data * @return int|bool */ protected function saveService(int $id, array $data) { $model = $this->getServiceModel(); $data['modified_by'] = $data['modified_by'] ?? User::id(); $lang = $model->langColumns(); foreach ($lang as $l) { if (isset($data[$l])) { $data[$l] = $this->locale->fill($data[$l]); } } if ($id > 0) { return $model->find($id)->fill($data)->save(); } $model->fill($data)->save(); return $model->getKey(); } /** * Load service data * @param int|string|array $service id/key/filter * @param array $columns * @return array|ServiceModel */ protected function loadService($service, $columns = ['*']) { $filter = []; if (is_array($service)) { $filter = $service; } elseif (is_int($service)) { $filter['id'] = $service; } elseif (is_string($service)) { $filter['service_key'] = $service; } $filter['group_key'] = $this->getGroupKey(); return $this->getServiceModel()->one($filter, $columns); } /** * Delete service * @param int|string|array $service id/key/filter * @return bool */ protected function deleteService($service): bool { $service = $this->loadService($service); if ($service) { return $service->delete(); } return false; } /** * Set icon implementation * @param string $className icon implementation */ public function setIconClass(string $className) { $this->iconClass = $className; } /** * Can create packs * @return bool */ public function hasPacks(): bool { return property_exists($this, 'packClass') && !empty($this->packClass) && class_exists($this->packClass); } /** * Set service model implementation * @param string $className */ public function setServiceModelClass(string $className) { $this->serviceModelClass = $className; } /** * Service model * @param array $attributes * @return ServiceModel */ public function getServiceModel(array $attributes = []) { $class = $this->serviceModelClass; return new $class($attributes); } /** * Manage services settings * @param Module $controller * @param string $action * @param array $opts * @return mixed */ public function adminSettings(Module $controller, string $action, array $opts = []): string { $type = $opts['type'] ?? Service::TYPE_SERVICE; $isPack = ($type == Service::TYPE_SERVICEPACK); $opts['data']['type'] = $type; $opts['action'] = $opts['action'] ?? $action; $opts['templateDir'] = $opts['templateDir'] ?? Svc::i()->module_dir_tpl_core; # List: $list = Admin::list($controller, 'admin/service.manager.listing', $opts); $list->wrapper()->title($opts['title'] ?? ($isPack ? _t('@svc', 'Service Packages') : _t('@svc', 'Services'))); $model = $this->getServiceModel(); # Form: $form = Admin::form($controller, 'admin/service.manager.form', $opts); $this->onAdminSettings($list, $form); if (! $list->getModel()) { $list->useModel($model); } if ($this->hasPacks() && $isPack) { $service = $this->getService($this->packClass); $service->onAdminSettings($form); # form + service pack $form->onSubmit(function ($id, $params) use ($service) { if (! $id) { $params['data']['group_key'] = $this->getGroupKey(); $params['data']['service_key'] = $service->generateServicePackKey(); $params['data']['service_type'] = Service::TYPE_SERVICEPACK; $params['data']['class_name'] = rtrim(get_class($service), '_'); # with aliases support } }); $list->onDelete(); } else { $form->onRecordID(function ($id) use ($form) { if (! empty($id)) { $data = $form->_data(); if (empty($data)) { $form->load(); } } return $id; }); } if (! $form->getModel()) { $loaded = false; $form->useModel($model, [ 'afterLoad' => function ($data, $id, $fields) use ($form, &$loaded, $isPack) { if (! $isPack && ! empty($data['class_name'])) { $service = $this->getService($data['class_name']); if ($service && ! $loaded) { $loaded = true; $service->onAdminSettings($form); # form + service } } return $data; } ]); } $list->formAdd($form, ['add' => $isPack]); $list->onToggle(); $list->onRotate(true, 'num', [ 'additionalQuery' => ' AND group_key=' . $this->db->str2sql($this->getGroupKey()) . ' AND service_type=' . $this->db->str2sql($type) ]); $query = $model ->where('group_key', '=', $this->getGroupKey()) ->where('service_type', '=', $type) ->orderBy('num') ; $data = $query->get()->toArray(); foreach ($data as $k => $v) { if (! empty($v['class_name'])) { $s = $this->getService($v['class_name']); if (! $s || ! $s->isAllowed() || ! $s->isEditable()) { unset($data[$k]); } } } $payways = Bills::getPayWaysList(false); if (empty($payways)) { $list->alert(_t('svc', 'Add [a]payment method[/a].', [ 'a' => '', '/a' => '', ]), 'warning'); } $list->rows($data); return $list->view(); } /** * Check if any active (enabled) service exist * @return bool */ public function hasAnyActiveService(): bool { foreach ($this->services as $k => $v) { if (empty($v['enabled'])) { continue; } if (empty($v['service_type']) || $v['service_type'] != Service::TYPE_SERVICE) { continue; } return true; } return false; } /** * Admin settings form * @param mixed $list * @param mixed $form * @return mixed|void */ public function onAdminSettings($list, $form) { } /** * Is service enabled * @param string $serviceKey * @return bool */ public function isServiceEnabled(string $serviceKey): bool { return !empty($this->services[$serviceKey]['enabled']); } /** * Activate service or pay * @param array|string|Service $services service key / class name / object * @param int|string $itemID item ID to apply service to * @param int|string $userID user ID * @param string $payWay pay way * @param array $opts * @return array */ public function activateOrPay($services, $itemID, $userID, string $payWay, array $opts = []) { if (! is_array($services)) { $services = [$services]; } foreach ($services as $k => &$v) { $v = $this->getService($v); if (! $v || ! $v->isEnabled()) { unset($services[$k]); } } unset($v); if (empty($services)) { $this->errors->set(_t('svc', 'Service activation error')); return []; } $servicesSettings = []; $sum = 0; foreach ($services as $service) { $service->setActivationSettings($itemID, $userID, $opts['settings'] ?? []); $sum += $this->app->filter('svc.service.activate.cost', $service->getCost(), $service); $s = $service->getActivationSettings(); if (! $userID && ! empty($s['user'])) { $userID = $s['user']; } $servicesSettings[] = $s; } $pay = Bills::getPayAmount($sum, $payWay); $response = []; # активируем услугу бесплатно if (! empty($opts['free']) && $payWay === 'balance') { $sum = 0; foreach ($services as $service) { $service->updateBalance(false); } } $userBalance = 0; if (!empty($userID)) { $userBalance = Users::model()->userBalance($userID); } if ($userBalance >= $sum && $payWay === 'balance') { # активируем услугу (списываем со счета пользователя) foreach ($services as $service) { $service->activate(); } if (isset($opts['redirect'])) { $response['redirect'] = $opts['redirect']; } return $response; } $payWays = Bills::getPayWaysList(); if (! isset($payWays[$payWay])) { $this->errors->set(_t('svc', 'Service activation error')); return []; } # создаем счет для оплаты $billID = Bills::createBill_InPay( $userID, $userBalance, $sum, $pay['amount'], $pay['currency'], Bills::STATUS_WAITING, $payWays[$payWay]['key'], $payWays[$payWay]['way'], _t('bills', 'Balance Top Up via [payway]', [ 'payway' => $payWays[$payWay]['title'], ]), '', '', $itemID, $servicesSettings ); if (! $billID) { $this->errors->set(_t('bills', 'Error while bill creation')); return []; } # строим форму оплаты $response['pay'] = true; $response['form'] = Bills::buildPayRequestForm($payWays[$payWay]['key'], $payWays[$payWay]['way'], $billID, $pay['amount']); return $response; } /** * Create bill * @param Service $service * @param bool $updateBalance * @param int * @return int */ public function createBill(Service $service, $updateBalance = true) { $data = $service->getActivationSettings(); $balance = 0; if ($data['user']) { $balance = Users::model()->userBalance($data['user']); } $cost = $this->app->filter('svc.service.bill.amount', $service->getCost(), $service); $description = $this->app->filter('svc.service.bill.description', $service->getBillDescription(), $service); $billID = Bills::createBill_OutService( $this->getGroupKey(), $service->getKey(), $data['item'] ?? 0, $data['user'] ?? 0, $updateBalance ? $balance - $cost : $balance, $updateBalance ? $cost : 0, 0, Bills::STATUS_COMPLETED, $description ); if ($updateBalance) { Bills::updateUserBalance($data['user'] ?? 0, $cost, false); } return $billID; } /** * Cron event * @return void */ public function onCron() { foreach ($this->services as $serviceKey => $serviceData) { $service = $this->getService($serviceKey); if ($service) { $service->onCron(); } } } /** * List active services * @param int|string $itemID * @param array $itemData * @return array */ public function itemActiveServices($itemID, array $itemData = []) { $status = $this->getServiceStatusModel(); $data = $status->where('item_id', $itemID) ->where('service_status', Service::STATUS_ACTIVE) ->get() ->toArray(); foreach ($data as & $v) { $service = $this->getService($v['service_key']); if (! $service) { continue; } $v['service_title'] = $service->getTitle(); if (method_exists($service, 'adminItemServices')) { $v['admin_form_html'] = $service->adminItemServices($v, $itemData); } } unset($v); return $data; } }