diff --git a/public/plugin/BuyCourses/lang/en_US.php b/public/plugin/BuyCourses/lang/en_US.php index 9a72902f8d9..2e496488230 100644 --- a/public/plugin/BuyCourses/lang/en_US.php +++ b/public/plugin/BuyCourses/lang/en_US.php @@ -211,6 +211,9 @@ $strings['YouNeedToBeRegisteredInAtLeastOneSession'] = 'You need to be registered in at least one session'; $strings['IfYouWantToGetTheCertificateAndOrSkillsAsociatedToThisCourseYouNeedToBuyTheCertificateServiceYouCanGoToServiceCatalogClickingHere'] = "To obtain the certificate and/or the skills associated to this course, you need to buy the Certificate service. Go to the services catalogue to buy it by clicking here"; $strings['ServiceDeleted'] = 'Service deleted'; +$strings['ServiceCopied'] = 'Service copied successfully.'; +$strings['ServiceCopyFailed'] = 'The service could not be copied.'; +$strings['ServiceCopyInvalidSecurityToken'] = 'The copy request is no longer valid. Please try again.'; $strings['YourCoursesNeedAtLeastOneLearningPath'] = 'The courses to which you are subscribed need at least one learning path that contains a final certificate item'; $strings['GlobalTaxPerc'] = 'Global tax rate'; $strings['GlobalTaxPercDescription'] = 'Default tax rate that will be used unless there is a specific tax rate for the course, session or service.'; @@ -596,4 +599,4 @@ $strings['MaxMindAccountId'] = 'MaxMind account ID'; $strings['MaxMindAccountIdHelp'] = 'Required only when MaxMind web service is selected.'; $strings['MaxMindLicenseKey'] = 'MaxMind license key'; -$strings['MaxMindLicenseKeyHelp'] = 'Required only when MaxMind web service is selected. Keep this value private.'; \ No newline at end of file +$strings['MaxMindLicenseKeyHelp'] = 'Required only when MaxMind web service is selected. Keep this value private.'; diff --git a/public/plugin/BuyCourses/lang/es.php b/public/plugin/BuyCourses/lang/es.php index a9afb4f2938..b324a37b890 100644 --- a/public/plugin/BuyCourses/lang/es.php +++ b/public/plugin/BuyCourses/lang/es.php @@ -211,6 +211,9 @@ $strings['YouNeedToBeRegisteredInAtLeastOneSession'] = 'Necesitas estar registrado en al menos una sesión'; $strings['IfYouWantToGetTheCertificateAndOrSkillsAsociatedToThisCourseYouNeedToBuyTheCertificateServiceYouCanGoToServiceCatalogClickingHere'] = "Si quieres obtener el certificado y/o las competencias asociadas a este curso, necesitas comprar el servicio de Certificado , puedes ir al catálogo de servicios para comprarlo haciendo click AQUÍ"; $strings['ServiceDeleted'] = 'Servicio eliminado'; +$strings['ServiceCopied'] = 'Servicio copiado correctamente.'; +$strings['ServiceCopyFailed'] = 'No se pudo copiar el servicio.'; +$strings['ServiceCopyInvalidSecurityToken'] = 'La solicitud de copia ya no es válida. Inténtalo nuevamente.'; $strings['YourCoursesNeedAtLeastOneLearningPath'] = 'Los cursos en los que estás registrado necesitan tener al menos una lección que contenga un item de cerficado final'; $strings['GlobalTaxPerc'] = 'Porcentaje del impuesto global'; $strings['GlobalTaxPercDescription'] = 'Porcentaje por defecto que se usará, excepto si existe un impuesto específico en el curso, sesión o servicio.'; diff --git a/public/plugin/BuyCourses/lang/fr_FR.php b/public/plugin/BuyCourses/lang/fr_FR.php index da15316d65b..1d748b0eb41 100644 --- a/public/plugin/BuyCourses/lang/fr_FR.php +++ b/public/plugin/BuyCourses/lang/fr_FR.php @@ -211,6 +211,9 @@ $strings['YouNeedToBeRegisteredInAtLeastOneSession'] = 'You need to be registered in at least one session'; $strings['IfYouWantToGetTheCertificateAndOrSkillsAsociatedToThisCourseYouNeedToBuyTheCertificateServiceYouCanGoToServiceCatalogClickingHere'] = "Pour obtenir le certificat et/ou les compétences associés à ce cours, vous devez acheter le service Certificat . Allez au catalogue des services pour l'acheter en cliquant ici"; $strings['ServiceDeleted'] = 'Service deleted'; +$strings['ServiceCopied'] = 'Service copié avec succès.'; +$strings['ServiceCopyFailed'] = 'Le service n’a pas pu être copié.'; +$strings['ServiceCopyInvalidSecurityToken'] = 'La demande de copie n’est plus valide. Veuillez réessayer.'; $strings['YourCoursesNeedAtLeastOneLearningPath'] = 'The courses to which you are subscribed need at least one learning path that contains a final certificate item'; $strings['GlobalTaxPerc'] = 'Global tax rate'; $strings['GlobalTaxPercDescription'] = 'Default tax rate that will be used unless there is a specific tax rate for the course, session or service.'; diff --git a/public/plugin/BuyCourses/src/buy_course_plugin.class.php b/public/plugin/BuyCourses/src/buy_course_plugin.class.php index e7207ed697a..07f881e90c5 100644 --- a/public/plugin/BuyCourses/src/buy_course_plugin.class.php +++ b/public/plugin/BuyCourses/src/buy_course_plugin.class.php @@ -3927,6 +3927,140 @@ public function saveServiceBenefitConfigurations(int $serviceId, array $serviceD } } + public function copyService(int $id) + { + if ($id <= 0) { + return false; + } + + $servicesTable = Database::get_main_table(self::TABLE_SERVICES); + $service = Database::select( + '*', + $servicesTable, + [ + 'where' => ['id = ?' => $id], + ], + 'first' + ); + + if (empty($service) || !is_array($service)) { + return false; + } + + $sourceName = trim((string) ($service['name'] ?? '')); + if ('' === $sourceName) { + $sourceName = $this->get_lang('Service'); + } + + $sourceImageName = (string) ($service['image'] ?? ''); + + $copiedServiceId = Database::insert( + $servicesTable, + [ + 'name' => $sourceName.' (copy)', + 'description' => (string) ($service['description'] ?? ''), + 'price' => isset($service['price']) ? (float) $service['price'] : 0.0, + 'tax_perc' => '' !== (string) ($service['tax_perc'] ?? '') ? (int) $service['tax_perc'] : null, + 'duration_days' => isset($service['duration_days']) ? (int) $service['duration_days'] : 0, + 'renewable' => !empty($service['renewable']) ? 1 : 0, + 'total_charges' => isset($service['total_charges']) ? (int) $service['total_charges'] : 0, + 'allow_trial' => !empty($service['allow_trial']) ? 1 : 0, + 'trial_period' => (string) ($service['trial_period'] ?? ''), + 'trial_frequency' => isset($service['trial_frequency']) ? (int) $service['trial_frequency'] : 0, + 'trial_total_charges' => isset($service['trial_total_charges']) ? (int) $service['trial_total_charges'] : 0, + 'max_subscribers' => isset($service['max_subscribers']) ? (int) $service['max_subscribers'] : 0, + 'subscription_behavior_json' => (string) ($service['subscription_behavior_json'] ?? ''), + 'stripe_price_id' => (string) ($service['stripe_price_id'] ?? ''), + 'display_on_course_creation_page' => !empty($service['display_on_course_creation_page']) ? 1 : 0, + 'applies_to' => isset($service['applies_to']) ? (int) $service['applies_to'] : self::SERVICE_TYPE_NONE, + 'owner_id' => isset($service['owner_id']) ? (int) $service['owner_id'] : api_get_user_id(), + 'visibility' => !empty($service['visibility']) ? 1 : 0, + 'image' => '', + 'video_url' => (string) ($service['video_url'] ?? ''), + 'service_information' => (string) ($service['service_information'] ?? ''), + ] + ); + + if (!$copiedServiceId) { + return false; + } + + $copiedImageName = 'simg-'.(int) $copiedServiceId.'.png'; + if ($this->copyServiceImage($sourceImageName, $copiedImageName)) { + Database::update( + $servicesTable, + ['image' => $copiedImageName], + ['id = ?' => (int) $copiedServiceId] + ); + } + + $this->copyServiceBenefitConfigurations($id, (int) $copiedServiceId); + + return (int) $copiedServiceId; + } + + private function copyServiceBenefitConfigurations(int $sourceServiceId, int $targetServiceId): void + { + if ($sourceServiceId <= 0 || $targetServiceId <= 0) { + return; + } + + if (!$this->hasPluginTable(self::TABLE_SERVICE_REL_EXTRA_FIELD)) { + return; + } + + $serviceRelTable = Database::get_main_table(self::TABLE_SERVICE_REL_EXTRA_FIELD); + $rows = Database::select( + '*', + $serviceRelTable, + [ + 'where' => ['service_id = ?' => $sourceServiceId], + ] + ); + + foreach ($rows as $row) { + $extraFieldId = isset($row['extra_field_id']) ? (int) $row['extra_field_id'] : 0; + $grantedValue = isset($row['granted_value']) ? (int) $row['granted_value'] : 0; + + if ($extraFieldId <= 0 || $grantedValue <= 0) { + continue; + } + + Database::insert($serviceRelTable, [ + 'service_id' => $targetServiceId, + 'extra_field_id' => $extraFieldId, + 'granted_value' => $grantedValue, + ]); + } + } + + private function copyServiceImage(string $sourceImageName, string $targetImageName): bool + { + if ('' === $sourceImageName || '' === $targetImageName) { + return false; + } + + if (!$this->serviceImageExists($sourceImageName)) { + return false; + } + + try { + $pluginsFilesystem = Container::getPluginsFileSystem(); + $directory = $this->getServiceImagesDirectory(); + + if (!$pluginsFilesystem->directoryExists($directory)) { + $pluginsFilesystem->createDirectory($directory); + } + + $content = $pluginsFilesystem->read($this->getServiceImageStoragePath($sourceImageName)); + $pluginsFilesystem->write($this->getServiceImageStoragePath($targetImageName), $content); + } catch (\Throwable) { + return false; + } + + return true; + } + /** * update a service. * diff --git a/public/plugin/BuyCourses/src/list_service.php b/public/plugin/BuyCourses/src/list_service.php index 087c9c90848..324ea4fe8d6 100644 --- a/public/plugin/BuyCourses/src/list_service.php +++ b/public/plugin/BuyCourses/src/list_service.php @@ -16,6 +16,11 @@ $plugin = BuyCoursesPlugin::create(); $httpRequest = Container::getRequest(); +$copyServiceTokenKey = 'buycourses_copy_service_token'; +if (empty($_SESSION[$copyServiceTokenKey])) { + $_SESSION[$copyServiceTokenKey] = bin2hex(random_bytes(32)); +} + $includeSession = 'true' === $plugin->get('include_sessions'); $includeServices = 'true' === $plugin->get('include_services'); @@ -27,6 +32,40 @@ api_protect_admin_script(true); +if ('POST' === $httpRequest->getMethod() && 'copy_service' === $httpRequest->request->get('action')) { + $serviceId = $httpRequest->request->getInt('id'); + $postedToken = (string) $httpRequest->request->get('copy_service_token', ''); + $sessionToken = (string) ($_SESSION[$copyServiceTokenKey] ?? ''); + + if ('' === $sessionToken || !hash_equals($sessionToken, $postedToken)) { + Display::addFlash( + Display::return_message($plugin->get_lang('ServiceCopyInvalidSecurityToken'), 'warning') + ); + + header('Location: list_service.php'); + exit; + } + + $_SESSION[$copyServiceTokenKey] = bin2hex(random_bytes(32)); + + $copiedServiceId = $plugin->copyService($serviceId); + if (false === $copiedServiceId) { + Display::addFlash( + Display::return_message($plugin->get_lang('ServiceCopyFailed'), 'error') + ); + + header('Location: list_service.php'); + exit; + } + + Display::addFlash( + Display::return_message($plugin->get_lang('ServiceCopied'), 'success') + ); + + header('Location: services_edit.php?id='.(int) $copiedServiceId); + exit; +} + $pageSize = BuyCoursesPlugin::PAGINATION_PAGE_SIZE; $currentPage = max(1, $httpRequest->query->getInt('page', 1)); $first = $pageSize * ($currentPage - 1); @@ -72,6 +111,7 @@ $tpl->assign('courses', []); $tpl->assign('sessions', []); $tpl->assign('services', $services); +$tpl->assign('copy_service_token', (string) ($_SESSION[$copyServiceTokenKey] ?? '')); $tpl->assign('course_pagination', ''); $tpl->assign('session_pagination', ''); diff --git a/public/plugin/BuyCourses/view/list.tpl b/public/plugin/BuyCourses/view/list.tpl index 3dc353c0e9d..0ca354c3359 100644 --- a/public/plugin/BuyCourses/view/list.tpl +++ b/public/plugin/BuyCourses/view/list.tpl @@ -603,13 +603,32 @@ {% endif %} - - - {{ 'Edit'|get_lang }} - +
+ + + {{ 'Edit'|get_lang }} + + +
+ + + + +
+
{% else %}