Browse Source

WIP - Added services handler to BuyCoursesPlugin - Refs BT#12074

José Loguercio 8 years ago
parent
commit
cc1cf5cefa

+ 63 - 0
plugin/buycourses/database.php

@@ -209,6 +209,69 @@ $saleTable->addForeignKeyConstraint(
     ['onDelete' => 'CASCADE']
 );
 
+$servicesTable = $pluginSchema->createTable(BuyCoursesPlugin::TABLE_SERVICES);
+$servicesTable->addColumn(
+    'id',
+    \Doctrine\DBAL\Types\Type::INTEGER,
+    ['autoincrement' => true, 'unsigned' => true]
+);
+$servicesTable->addColumn('name', \Doctrine\DBAL\Types\Type::STRING);
+$servicesTable->addColumn('description', \Doctrine\DBAL\Types\Type::TEXT);
+$servicesTable->addColumn(
+    'price',
+    \Doctrine\DBAL\Types\Type::DECIMAL,
+    ['scale' => 2]
+);
+$servicesTable->addColumn('duration_days', \Doctrine\DBAL\Types\Type::INTEGER);
+$servicesTable->addColumn('applies_to', \Doctrine\DBAL\Types\Type::INTEGER);
+$servicesTable->addColumn('owner_id', \Doctrine\DBAL\Types\Type::INTEGER);
+$servicesTable->addColumn('visibility', \Doctrine\DBAL\Types\Type::INTEGER);
+$servicesTable->addColumn('video_url', \Doctrine\DBAL\Types\Type::STRING);
+$servicesTable->addColumn('image', \Doctrine\DBAL\Types\Type::STRING);
+$servicesTable->addColumn('service_information', \Doctrine\DBAL\Types\Type::TEXT);
+$servicesTable->setPrimaryKey(['id']);
+
+$servicesNodeTable = $pluginSchema->createTable(BuyCoursesPlugin::TABLE_SERVICES_SALE);
+$servicesNodeTable->addColumn(
+    'id',
+    \Doctrine\DBAL\Types\Type::INTEGER,
+    ['autoincrement' => true, 'unsigned' => true]
+);
+$servicesNodeTable->addColumn(
+    'service_id',
+    \Doctrine\DBAL\Types\Type::INTEGER,
+    ['unsigned' => true]
+);
+$servicesNodeTable->addColumn('reference', \Doctrine\DBAL\Types\Type::STRING);
+$servicesNodeTable->addColumn('currency_id', \Doctrine\DBAL\Types\Type::INTEGER);
+$servicesNodeTable->addColumn(
+    'price',
+    \Doctrine\DBAL\Types\Type::DECIMAL,
+    ['scale' => 2]
+);
+$servicesNodeTable->addColumn('node_type', \Doctrine\DBAL\Types\Type::INTEGER);
+$servicesNodeTable->addColumn('node_id', \Doctrine\DBAL\Types\Type::INTEGER);
+$servicesNodeTable->addColumn('buyer_id', \Doctrine\DBAL\Types\Type::INTEGER);
+$servicesNodeTable->addColumn('buy_date', \Doctrine\DBAL\Types\Type::DATETIME);
+$servicesNodeTable->addColumn(
+    'date_start',
+    \Doctrine\DBAL\Types\Type::DATETIME,
+    ['notnull' => false]
+);
+$servicesNodeTable->addColumn(
+    'date_end',
+    \Doctrine\DBAL\Types\Type::DATETIME
+);
+$servicesNodeTable->addColumn('status', \Doctrine\DBAL\Types\Type::INTEGER);
+$servicesNodeTable->addColumn('payment_type', \Doctrine\DBAL\Types\Type::INTEGER);
+$servicesNodeTable->setPrimaryKey(['id']);
+$servicesNodeTable->addForeignKeyConstraint(
+    $servicesTable,
+    ['service_id'],
+    ['id'],
+    ['onDelete' => 'CASCADE']
+);
+
 $queries = $pluginSchema->toSql($platform);
 
 foreach ($queries as $query) {

+ 10 - 0
plugin/buycourses/lang/spanish.php

@@ -133,3 +133,13 @@ $strings['ByUser'] = "Por usuario";
 $strings['PaymentMethod'] = "Método de pago";
 $strings['SWIFT'] = "Código SWIFT";
 $strings['SWIFT_help'] = "Formato estándar de los Códigos de Identificación Bancaria (BIC) que sirve como identificador único para un banco o institución financiera.";
+$strings['include_services'] = "Incluir Servicios";
+$strings['Services'] = "Servicios";
+$strings['Service'] = "Servicio";
+$strings['NewService'] = "Nuevo servicio";
+$strings['ServiceName'] = "Nombre de servicio";
+$strings['AppliesTo'] = "Aplicado a";
+$strings['ServiceInformation'] = "Información del servicio";
+$strings['EditService'] = "Editar servicio";
+$strings['ServiceAdded'] = "Servicio agregado";
+$strings['ServiceEdited'] = "Servicio editado";

+ 342 - 3
plugin/buycourses/src/buy_course_plugin.class.php

@@ -23,6 +23,8 @@ class BuyCoursesPlugin extends Plugin
     const TABLE_TRANSFER = 'plugin_buycourses_transfer';
     const TABLE_COMMISSION = 'plugin_buycourses_commission';
     const TABLE_PAYPAL_PAYOUTS = 'plugin_buycourses_paypal_payouts';
+    const TABLE_SERVICES = 'plugin_buycourses_services';
+    const TABLE_SERVICES_SALE = 'plugin_buycourses_service_sale';
     const PRODUCT_TYPE_COURSE = 1;
     const PRODUCT_TYPE_SESSION = 2;
     const PAYMENT_TYPE_PAYPAL = 1;
@@ -33,6 +35,9 @@ class BuyCoursesPlugin extends Plugin
     const SALE_STATUS_CANCELED = -1;
     const SALE_STATUS_PENDING = 0;
     const SALE_STATUS_COMPLETED = 1;
+    const SERVICE_STATUS_PENDING = 0;
+    const SERVICE_STATUS_COMPLETED = 1;
+    const SERVICE_STATUS_CANCELLED = -1;
 
     /**
      *
@@ -54,11 +59,12 @@ class BuyCoursesPlugin extends Plugin
                 Alex Aragón - BeezNest (Design icons and css styles) <br/>
                 Imanol Losada - BeezNest (introduction of sessions purchase) <br/>
                 Angel Fernando Quiroz Campos - BeezNest (cleanup and new reports) <br/>
-                José Loguercio Silva - BeezNest (pay teachers and commissions)
+                José Loguercio Silva - BeezNest (Payouts and buy Services)
             ",
             array(
                 'show_main_menu_tab' => 'boolean',
                 'include_sessions' => 'boolean',
+                'include_services' => 'boolean',
                 'paypal_enable' => 'boolean',
                 'transfer_enable' => 'boolean',
                 'commissions_enable' => 'boolean',
@@ -80,7 +86,9 @@ class BuyCoursesPlugin extends Plugin
             self::TABLE_SALE,
             self::TABLE_CURRENCY,
             self::TABLE_COMMISSION,
-            self::TABLE_PAYPAL_PAYOUTS
+            self::TABLE_PAYPAL_PAYOUTS,
+            self::TABLE_SERVICES,
+            self::TABLE_SERVICES_SALE
         );
         $em = Database::getManager();
         $cn = $em->getConnection();
@@ -107,7 +115,9 @@ class BuyCoursesPlugin extends Plugin
             self::TABLE_SALE,
             self::TABLE_CURRENCY,
             self::TABLE_COMMISSION,
-            self::TABLE_PAYPAL_PAYOUTS
+            self::TABLE_PAYPAL_PAYOUTS,
+            self::TABLE_SERVICES_SALE,
+            self::TABLE_SERVICES
         );
 
         foreach ($tablesToBeDeleted as $tableToBeDeleted) {
@@ -1663,4 +1673,333 @@ class BuyCoursesPlugin extends Plugin
         );
     }
 
+    /**
+     * Register addicional service
+     * @param array params $service
+     * @return mixed response
+     */
+    public function storeService($service)
+    {
+        $servicesTable = Database::get_main_table(BuyCoursesPlugin::TABLE_SERVICES);
+
+        $return = Database::insert(
+            $servicesTable,
+            [
+                'name' => Security::remove_XSS($service['name']),
+                'description' => Security::remove_XSS($service['description']),
+                'price' => $service['price'],
+                'duration_days' => intval($service['duration_days']),
+                'applies_to' => intval($service['applies_to']),
+                'owner_id' => intval($service['owner_id']),
+                'visibility' => intval($service['visibility']),
+                'image' => 'simg.png',
+                'video_url' => $service['video_url'],
+                'service_information' => $service['service_information']
+            ]
+        );
+
+        if ($return) {
+            $img = str_replace('data:image/png;base64,', '', $service['cropResult']);
+            $img = str_replace(' ', '+', $img);
+            $data = base64_decode($img);
+            $file = api_get_path(SYS_PLUGIN_PATH).'buycourses/uploads/services/images/simg-'.$return.'.png';
+            file_put_contents($file, $data);
+
+            Database::update(
+                $servicesTable,
+                ['image' => 'simg-'.$return.'.png'],
+                ['id = ?' => intval($return)]
+            );
+            return $return;
+        }
+
+        return false;
+    }
+
+    /**
+     * update a service
+     * @param array $service
+     * @param integer $id
+     * @return mixed response
+     */
+    public function updateService($service, $id)
+    {
+        $servicesTable = Database::get_main_table(BuyCoursesPlugin::TABLE_SERVICES);
+
+        if (!empty($service['cropResult'])) {
+            $img = str_replace('data:image/png;base64,', '', $service['cropResult']);
+            $img = str_replace(' ', '+', $img);
+            $data = base64_decode($img);
+            $file = api_get_path(SYS_PLUGIN_PATH).'buycourses/uploads/services/images/simg-'.$id.'.png';
+            file_put_contents($file, $data);
+        }
+
+        return Database::update(
+            $servicesTable,
+            [
+                'name' => Security::remove_XSS($service['name']),
+                'description' => Security::remove_XSS($service['description']),
+                'price' => $service['price'],
+                'duration_days' => intval($service['duration_days']),
+                'applies_to' => intval($service['applies_to']),
+                'owner_id' => intval($service['owner_id']),
+                'visibility' => intval($service['visibility']),
+                'image' => 'simg-'.$id.'.png',
+                'video_url' => $service['video_url'],
+                'service_information' => $service['service_information']
+            ],
+            ['id = ?' => intval($id)]
+        );
+    }
+
+    /**
+     * Remove a service
+     * @param int $id The transfer account ID
+     * @return int Rows affected. Otherwise return false
+     */
+    public function deleteService($id)
+    {
+        return Database::delete(
+            Database::get_main_table(self::TABLE_SERVICES),
+            ['id = ?' => intval($id)]
+        );
+    }
+
+    /**
+     * List adicional services
+     * @param integer $id service id
+     * @return array
+     */
+    public function getServices($id = null)
+    {
+        $servicesTable = Database::get_main_table(BuyCoursesPlugin::TABLE_SERVICES);
+        $userTable = Database::get_main_table(TABLE_MAIN_USER);
+
+        $conditions = null;
+        $showData = "all";
+
+        if ($id) {
+            $conditions = ['WHERE' => ['s.id = ?' => $id]];
+            $showData = "first";
+        }
+
+        $innerJoins = "INNER JOIN $userTable u ON s.owner_id = u.id";
+        $currency = $this->getSelectedCurrency();
+        $isoCode = $currency['iso_code'];
+        $return = Database::select(
+            "s.*, '$isoCode' as currency, u.firstname, u.lastname",
+            "$servicesTable s $innerJoins",
+            $conditions,
+            $showData
+        );
+
+        $services = [];
+
+        if ($id) {
+            $services['id'] = $return['id'];
+            $services['name'] = $return['name'];
+            $services['description'] = $return['description'];
+            $services['price'] = $return['price'];
+            $services['currency'] = $return['currency'];
+            $services['duration_days'] = $return['duration_days'];
+            $services['applies_to'] = $return['applies_to'];
+            $services['owner_id'] = $return['owner_id'];
+            $services['owner_name'] = api_get_person_name($return['firstname'], $return['lastname']);
+            $services['visibility'] = $return['visibility'];
+            $services['image'] = $return['image'];
+            $services['video_url'] = $return['video_url'];
+            $services['service_information'] = $return['service_information'];
+
+            return $services;
+        }
+
+        foreach ($return as $index => $service) {
+            $services[$index]['id'] = $service['id'];
+            $services[$index]['name'] = $service['name'];
+            $services[$index]['description'] = $service['description'];
+            $services[$index]['price'] = $service['price'];
+            $services[$index]['currency'] = $service['currency'];
+            $services[$index]['duration_days'] = $service['duration_days'];
+            $services[$index]['applies_to'] = $service['applies_to'];
+            $services[$index]['owner_id'] = $service['owner_id'];
+            $services[$index]['owner_name'] = api_get_person_name($service['firstname'], $service['lastname']);
+            $services[$index]['visibility'] = $service['visibility'];
+            $services[$index]['image'] = $service['image'];
+            $services[$index]['video_url'] = $service['video_url'];
+            $services[$index]['service_information'] = $service['service_information'];
+        }
+
+        return $services;
+    }
+
+    /**
+     * Get the statuses for sales
+     * @return array
+     */
+    public function getServiceSaleStatuses()
+    {
+        return [
+            self::SERVICE_STATUS_CANCELLED => $this->get_lang('SaleStatusCancelled'),
+            self::SERVICE_STATUS_PENDING => $this->get_lang('SaleStatusPending'),
+            self::SERVICE_STATUS_COMPLETED => $this->get_lang('SaleStatusCompleted')
+        ];
+    }
+
+    /**
+     * List services sales
+     * @param integer $id service id
+     * @param integer $buyerId buyer id
+     * @param integer $status status
+     * @param integer $nodeType The node Type ( User = 1 , Course = 2 , Session = 3 )
+     * @param integer $nodeId the nodeId
+     * @param boolean $hot enable hot services
+     * @return array
+     */
+    public function getServiceSale($id = null, $buyerId = null, $status = null, $nodeType = null, $nodeId = null, $hot = false)
+    {
+        $servicesTable = Database::get_main_table(BuyCoursesPlugin::TABLE_SERVICES);
+        $servicesSaleTable = Database::get_main_table(BuyCoursesPlugin::TABLE_SERVICES_SALE);
+
+        $conditions = null;
+        $showData = "all";
+        $groupBy = "";
+
+        if ($id) {
+            $conditions = ['WHERE' => ['ss.id = ?' => $id]];
+            $showData = "first";
+        }
+
+        if ($buyerId) {
+            $conditions = ['WHERE' => ['ss.buyer_id = ?' => $buyerId], 'ORDER' => 'id ASC'];
+        }
+
+        if (is_numeric($status)) {
+            $conditions = ['WHERE' => ['ss.status = ?' => $status], 'ORDER' => 'id ASC'];
+        }
+
+        if ($id && $buyerId) {
+            $conditions = ['WHERE' => ['ss.id = ? AND ss.buyer_id = ?' => [$id, $buyerId]], 'ORDER' => 'id ASC'];
+        }
+
+        if ($nodeType && $nodeId) {
+            $conditions = ['WHERE' => ['ss.node_type = ? AND ss.node_id = ?' => [$nodeType, $nodeId]], 'ORDER' => 'id ASC'];
+        }
+
+        if ($hot) {
+            $hot = "count(ss.service_id) as hot, ";
+            $conditions = ['ORDER' => 'hot DESC', 'LIMIT' => '6'];
+            $groupBy = "GROUP BY ss.service_id";
+        }
+
+        $innerJoins = "INNER JOIN $servicesTable s ON ss.service_id = s.id $groupBy";
+        $currency = $this->getSelectedCurrency();
+        $isoCode = $currency['iso_code'];
+        $return = Database::select(
+            "ss.*, s.name, s.description, s.price as service_price, s.duration_days, s.applies_to, s.owner_id, s.visibility, s.image, $hot '$isoCode' as currency",
+            "$servicesSaleTable ss $innerJoins",
+            $conditions,
+            $showData
+        );
+
+        $servicesSale = [];
+
+        if ($id) {
+
+            $owner = api_get_user_info($return['owner_id']);
+            $buyer = api_get_user_info($return['buyer_id']);
+
+            $servicesSale['id'] = $return['id'];
+            $servicesSale['service']['id'] = $return['service_id'];
+            $servicesSale['service']['name'] = $return['name'];
+            $servicesSale['service']['description'] = $return['description'];
+            $servicesSale['service']['price'] = $return['service_price'];
+            $servicesSale['service']['duration_days'] = $return['duration_days'];
+            $servicesSale['service']['applies_to'] = $return['applies_to'];
+            $servicesSale['service']['owner']['id'] = $return['owner_id'];
+            $servicesSale['service']['owner']['name'] = api_get_person_name($owner['firstname'], $owner['lastname']);
+            $servicesSale['service']['visibility'] = $return['visibility'];
+            $servicesSale['service']['image'] = $return['image'];
+            $servicesSale['reference'] = $return['reference'];
+            $servicesSale['currency_id'] = $return['currency_id'];
+            $servicesSale['currency'] = $return['currency'];
+            $servicesSale['price'] = $return['price'];
+            $servicesSale['node_type'] = $return['node_type'];
+            $servicesSale['node_id'] = $return['node_id'];
+            $servicesSale['buyer']['id'] = $buyer['user_id'];
+            $servicesSale['buyer']['name'] = api_get_person_name($buyer['firstname'], $buyer['lastname']);
+            $servicesSale['buyer']['username'] = $buyer['username'];
+            $servicesSale['buy_date'] = $return['buy_date'];
+            $servicesSale['date_start'] = $return['date_start'];
+            $servicesSale['date_end'] = $return['date_end'];
+            $servicesSale['status'] = $return['status'];
+            $servicesSale['payment_type'] = $return['payment_type'];
+
+            return $servicesSale;
+        }
+
+
+        foreach ($return as $index => $service) {
+
+            $owner = api_get_user_info($service['owner_id']);
+            $buyer = api_get_user_info($service['buyer_id']);
+
+            $servicesSale[$index]['id'] = $service['id'];
+            $servicesSale[$index]['service']['id'] = $service['service_id'];
+            $servicesSale[$index]['service']['name'] = $service['name'];
+            $servicesSale[$index]['service']['description'] = $service['description'];
+            $servicesSale[$index]['service']['price'] = $service['service_price'];
+            $servicesSale[$index]['service']['duration_days'] = $service['duration_days'];
+            $servicesSale[$index]['service']['applies_to'] = $service['applies_to'];
+            $servicesSale[$index]['service']['owner']['id'] = $service['owner_id'];
+            $servicesSale[$index]['service']['owner']['name'] = api_get_person_name($owner['firstname'], $owner['lastname']);
+            $servicesSale[$index]['service']['visibility'] = $service['visibility'];
+            $servicesSale[$index]['service']['image'] = $service['image'];
+            $servicesSale[$index]['reference'] = $service['reference'];
+            $servicesSale[$index]['currency_id'] = $service['currency_id'];
+            $servicesSale[$index]['currency'] = $service['currency'];
+            $servicesSale[$index]['price'] = $service['price'];
+            $servicesSale[$index]['node_type'] = $service['node_type'];
+            $servicesSale[$index]['node_id'] = $service['node_id'];
+            $servicesSale[$index]['buyer']['id'] = $service['buyer_id'];
+            $servicesSale[$index]['buyer']['name'] = api_get_person_name($buyer['firstname'], $buyer['lastname']);
+            $servicesSale[$index]['buyer']['username'] = $buyer['username'];
+            $servicesSale[$index]['buy_date'] = $service['buy_date'];
+            $servicesSale[$index]['date_start'] = $service['date_start'];
+            $servicesSale[$index]['date_end'] = $service['date_end'];
+            $servicesSale[$index]['status'] = $service['status'];
+            $servicesSale[$index]['payment_type'] = $service['payment_type'];
+        }
+
+        return $servicesSale;
+    }
+
+    /**
+     * Update service sale status to cancelled
+     * @param int $serviceSaleId The sale ID
+     * @return boolean
+     */
+    public function cancelServiceSale($serviceSaleId)
+    {
+        $this->updateServiceSaleStatus($serviceSaleId, self::SERVICE_STATUS_CANCELLED);
+        return true;
+    }
+
+    /**
+     * Complete service sale process. Update service sale status to completed
+     * @param int $serviceSaleId The service sale ID
+     * @return boolean
+     */
+    public function completeServiceSale($serviceSaleId)
+    {
+        $serviceSale = $this->getServiceSale($serviceSaleId);
+
+        if ($serviceSale['status'] == self::SERVICE_STATUS_COMPLETED) {
+            return true;
+        }
+
+        $this->updateServiceSaleStatus($serviceSaleId, self::SERVICE_STATUS_COMPLETED);
+
+        return true;
+    }
+
 }

+ 13 - 7
plugin/buycourses/src/configuration.php

@@ -13,6 +13,7 @@ require_once __DIR__.'/../../../main/inc/global.inc.php';
 
 $plugin = BuyCoursesPlugin::create();
 $includeSession = $plugin->get('include_sessions') === 'true';
+$includeServices = $plugin->get('include_services') === 'true';
 
 api_protect_admin_script(true);
 
@@ -20,22 +21,21 @@ Display::addFlash(Display::return_message(get_lang('Info').' - '.$plugin->get_la
 
 $courses = $plugin->getCoursesForConfiguration();
 
-//view
+// breadcrumbs
 $interbreadcrumb[] = [
-    'url' => 'course_catalog.php',
-    'name' => $plugin->get_lang('CourseListOnSale')
-];
-$interbreadcrumb[] = [
-    'url' => 'paymentsetup.php',
-    'name' => get_lang('Configuration')
+    'url' => api_get_path(WEB_PLUGIN_PATH) . 'buycourses/index.php',
+    'name' => $plugin->get_lang('Home')
 ];
 
 $templateName = $plugin->get_lang('AvailableCourses');
+
 $tpl = new Template($templateName);
+
 $tpl->assign('product_type_course', BuyCoursesPlugin::PRODUCT_TYPE_COURSE);
 $tpl->assign('product_type_session', BuyCoursesPlugin::PRODUCT_TYPE_SESSION);
 $tpl->assign('courses', $courses);
 $tpl->assign('sessions_are_included', $includeSession);
+$tpl->assign('services_are_included', $includeServices);
 
 if ($includeSession) {
     $sessions = $plugin->getSessionsForConfiguration();
@@ -43,6 +43,12 @@ if ($includeSession) {
     $tpl->assign('sessions', $sessions);
 }
 
+if ($includeServices) {
+    $services = $plugin->getServices();
+
+    $tpl->assign('services', $services);
+}
+
 $content = $tpl->fetch('buycourses/view/configuration.tpl');
 
 $tpl->assign('header', $templateName);

+ 2 - 0
plugin/buycourses/src/sales_report.php

@@ -16,6 +16,7 @@ $plugin = BuyCoursesPlugin::create();
 
 $paypalEnable = $plugin->get('paypal_enable');
 $commissionsEnable = $plugin->get('commissions_enable');
+$includeServices = $plugin->get('include_services');
 
 if (isset($_GET['order'])) {
     $sale = $plugin->getSale($_GET['order']);
@@ -170,6 +171,7 @@ if ($commissionsEnable == "true") {
 $template->assign('form', $form->returnForm());
 $template->assign('selected_sale', $selectedSale);
 $template->assign('selected_status', $selectedStatus);
+$template->assign('services_are_included', $includeServices);
 $template->assign('sale_list', $saleList);
 $template->assign('sale_status_canceled', BuyCoursesPlugin::SALE_STATUS_CANCELED);
 $template->assign('sale_status_pending', BuyCoursesPlugin::SALE_STATUS_PENDING);

+ 35 - 0
plugin/buycourses/src/service_error.php

@@ -0,0 +1,35 @@
+<?php
+/* For license terms, see /license.txt */
+/**
+ * Errors management for the Buy Courses plugin - Redirects to service_catalog.php with a error msg
+ * @package chamilo.plugin.buycourses
+ */
+/**
+ * Config
+ */
+
+require_once '../config.php';
+
+if (isset($_SESSION['bc_service_sale_id'])) {
+
+    $plugin = BuyCoursesPlugin::create();
+    $serviceSaleId = $_SESSION['bc_service_sale_id'];
+    unset($_SESSION['bc_service_sale_id']);
+    $serviceSale = $plugin->getServiceSale($serviceSaleId);
+
+    $plugin->cancelServiceSale(intval($serviceSaleId));
+    Display::addFlash(
+        Display::return_message($plugin->get_lang('OrderCancelled'), 'error', false)
+    );
+
+    header('Location: '. api_get_path(WEB_PLUGIN_PATH) . 'buycourses/src/service_catalog.php');
+    exit;
+}
+
+Display::addFlash(
+    Display::return_message($plugin->get_lang('ErrorOccurred'), 'error', false)
+);
+
+header('Location: '. api_get_path(WEB_PLUGIN_PATH) . 'buycourses/src/service_catalog.php');
+
+exit;

+ 99 - 0
plugin/buycourses/src/service_sales_report.php

@@ -0,0 +1,99 @@
+<?php
+
+/* For license terms, see /license.txt */
+/**
+ * List of pending payments of the Buy Courses plugin
+ * @package chamilo.plugin.buycourses
+ */
+//Initialization
+$cidReset = true;
+
+require_once '../config.php';
+
+api_protect_admin_script();
+
+$plugin = BuyCoursesPlugin::create();
+
+$paypalEnable = $plugin->get('paypal_enable');
+$commissionsEnable = $plugin->get('commissions_enable');
+$includeServices = $plugin->get('include_services');
+$servicesOnly = $plugin->get('show_services_only');
+
+$saleStatuses = $plugin->getServiceSaleStatuses();
+$paymentTypes = $plugin->getPaymentTypes();
+
+$form = new FormValidator('search', 'get');
+
+$form->addSelect('status', $plugin->get_lang('OrderStatus'), $saleStatuses, ['cols-size' => [0, 0, 0]]);
+$form->addText('user', get_lang('User'), false, ['cols-size' => [0, 0, 0]]);
+$form->addButtonSearch(get_lang('Search'), 'search');
+
+$servicesSales = $plugin->getServiceSale();
+$serviceSaleList = [];
+
+foreach ($servicesSales as $sale) {
+    $serviceSaleList[] = [
+        'id' => $sale['id'],
+        'reference' => $sale['reference'],
+        'status' => $sale['status'],
+        'date' => api_format_date($sale['buy_date'], DATE_TIME_FORMAT_LONG_24H),
+        'currency' => $sale['currency'],
+        'price' => $sale['price'],
+        'service_type' => $sale['service']['applies_to'],
+        'service_name' => $sale['service']['name'],
+        'complete_user_name' => $sale['buyer']['name'],
+        'recurring_payment' => $sale['recurring_payment'],
+        'payment_type' => $paymentTypes[$sale['payment_type']]
+    ];
+}
+
+//View
+$interbreadcrumb[] = ['url' => '../index.php', 'name' => $plugin->get_lang('plugin_title')];
+
+$templateName = $plugin->get_lang('SalesReport');
+
+$template = new Template($templateName);
+
+$toolbar = '';
+
+if ($paypalEnable == 'true' && $commissionsEnable == 'true') {
+
+    $toolbar .= Display::toolbarButton(
+        $plugin->get_lang('PaypalPayoutCommissions'),
+        api_get_path(WEB_PLUGIN_PATH) . 'buycourses/src/paypal_payout.php',
+        'paypal',
+        'primary',
+        ['title' => $plugin->get_lang('PaypalPayoutCommissions')]
+    );
+    
+    $template->assign('actions', $toolbar);
+    
+}
+
+if ($commissionsEnable == 'true') {
+
+    $toolbar .= Display::toolbarButton(
+        $plugin->get_lang('PayoutReport'),
+        api_get_path(WEB_PLUGIN_PATH) . 'buycourses/src/payout_report.php',
+        'money',
+        'info',
+        ['title' => $plugin->get_lang('PayoutReport')]
+    );
+    
+    $template->assign('actions', $toolbar);
+    
+}
+$template->assign('form', $form->returnForm());
+$template->assign('showing_services', true);
+$template->assign('show_services_only', $servicesOnly);
+$template->assign('services_are_included', $includeServices);
+$template->assign('sale_list', $serviceSaleList);
+$template->assign('sale_status_cancelled', BuyCoursesPlugin::SERVICE_STATUS_CANCELLED);
+$template->assign('sale_status_pending', BuyCoursesPlugin::SERVICE_STATUS_PENDING);
+$template->assign('sale_status_completed', BuyCoursesPlugin::SERVICE_STATUS_COMPLETED);
+
+$content = $template->fetch('buycourses/view/service_sales_report.tpl');
+
+
+$template->assign('content', $content);
+$template->display_one_col_template();

+ 123 - 0
plugin/buycourses/src/services_add.php

@@ -0,0 +1,123 @@
+<?php
+/* For license terms, see /license.txt */
+/**
+ * Create new Services for the Buy Courses plugin
+ * @package chamilo.plugin.buycourses
+ */
+/**
+ * Init
+ */
+
+$cidReset = true;
+
+require_once '../../../main/inc/global.inc.php';
+
+$plugin = BuyCoursesPlugin::create();
+$currency = $plugin->getSelectedCurrency();
+$em = Database::getManager();
+$users = $em->getRepository('ChamiloUserBundle:User')->findAll();
+$userOptions = [];
+if (!empty($users)) {
+    foreach ($users as $user) {
+        $userOptions[$user->getId()] = $user->getCompleteNameWithUsername();
+    }
+}
+
+api_protect_admin_script(true);
+
+$htmlHeadXtra[] = api_get_css_asset('cropper/dist/cropper.min.css');
+$htmlHeadXtra[] = api_get_asset('cropper/dist/cropper.min.js');
+
+//view
+$interbreadcrumb[] = [
+    'url' => 'configuration.php',
+    'name' => $plugin->get_lang('Configuration')
+];
+
+$formDefaultValues = [
+    'price' => 0,
+    'duration_days' => 0,
+    'applies_to' => 0,
+    'visibility' => true
+];
+
+$form = new FormValidator('Services');
+$form->addText('name', $plugin->get_lang('ServiceName'));
+$form->addTextarea('description', $plugin->get_lang('Description'));
+$form->addElement(
+    'number',
+    'price',
+    [$plugin->get_lang('Price'), null, $currency['iso_code']],
+    ['step' => 0.01]
+);
+$form->addElement(
+    'number',
+    'duration_days',
+    [$plugin->get_lang('Duration'), null, get_lang('Days')],
+    ['step' => 1]
+);
+$form->addElement(
+    'radio',
+    'applies_to',
+    $plugin->get_lang('AppliesTo'),
+    get_lang('None'),
+    0
+);
+$form->addElement(
+    'radio',
+    'applies_to',
+    null,
+    get_lang('User'),
+    1
+);
+$form->addElement(
+    'radio',
+    'applies_to',
+    null,
+    get_lang('Course'),
+    2
+);
+$form->addElement(
+    'radio',
+    'applies_to',
+    null,
+    get_lang('Session'),
+    3
+);
+$form->addSelect(
+    'owner_id',
+    get_lang('Owner'),
+    $userOptions
+);
+$form->addCheckBox('visibility', $plugin->get_lang('VisibleInCatalog'));
+$form->addFile(
+    'picture',
+    (get_lang(
+        'AddImage'
+    )),
+    array('id' => 'picture', 'class' => 'picture-form', 'crop_image' => true, 'crop_ratio' => '4 / 3')
+);
+$form->addText('video_url', get_lang('VideoUrl'), false);
+$form->addHtmlEditor('service_information', $plugin->get_lang('ServiceInformation'), false);
+$form->addButtonSave(get_lang('Add'));
+$form->setDefaults($formDefaultValues);
+
+if ($form->validate()) {
+    $values = $form->getSubmitValues();
+    
+    $plugin->storeService($values);
+
+    Display::addFlash(
+        Display::return_message($plugin->get_lang('ServiceAdded'), 'success')
+    );
+    
+    header('Location: configuration.php');
+    exit;
+}
+
+$templateName = $plugin->get_lang('NewService');
+$tpl = new Template($templateName);
+
+$tpl->assign('header', $templateName);
+$tpl->assign('content', $form->returnForm());
+$tpl->display_one_col_template();

+ 140 - 0
plugin/buycourses/src/services_edit.php

@@ -0,0 +1,140 @@
+<?php
+/* For license terms, see /license.txt */
+/**
+ * Create new Services for the Buy Courses plugin
+ * @package chamilo.plugin.buycourses
+ */
+/**
+ * Init
+ */
+
+$cidReset = true;
+
+require_once '../../../main/inc/global.inc.php';
+
+$serviceId = isset($_REQUEST['id']) ? intval($_REQUEST['id']) : null;
+
+if (!$serviceId) {
+    header('Location: configuration.php');
+}
+
+$plugin = BuyCoursesPlugin::create();
+$currency = $plugin->getSelectedCurrency();
+$em = Database::getManager();
+$users = $em->getRepository('ChamiloUserBundle:User')->findAll();
+$userOptions = [];
+if (!empty($users)) {
+    foreach ($users as $user) {
+        $userOptions[$user->getId()] = $user->getCompleteNameWithUsername();
+    }
+}
+
+api_protect_admin_script(true);
+$htmlHeadXtra[] = api_get_css_asset('cropper/dist/cropper.min.css');
+$htmlHeadXtra[] = api_get_asset('cropper/dist/cropper.min.js');
+
+//view
+$interbreadcrumb[] = [
+    'url' => 'configuration.php',
+    'name' => $plugin->get_lang('Configuration')
+];
+
+$service = $plugin->getServices($serviceId);
+
+$formDefaultValues = [
+    'name' => $service['name'],
+    'description' => $service['description'],
+    'price' => $service['price'],
+    'duration_days' => $service['duration_days'],
+    'owner_id' => intval($service['owner_id']),
+    'applies_to' => intval($service['applies_to']),
+    'visibility' => ($service['visibility'] == 1) ? true : false,
+    'image' =>
+    is_file(api_get_path(SYS_PLUGIN_PATH).'buycourses/uploads/services/images/simg-'.$serviceId.'.png')
+        ?
+    api_get_path(WEB_PLUGIN_PATH).'buycourses/uploads/services/images/simg-'.$serviceId.'.png'
+        :
+    api_get_path(WEB_CODE_PATH).'img/session_default.png',
+    'video_url' => $service['video_url'],
+    'service_information' => $service['service_information']
+];
+
+$form = new FormValidator('Services');
+$form->addText('name', $plugin->get_lang('ServiceName'));
+$form->addTextarea('description', $plugin->get_lang('Description'));
+$form->addElement(
+    'number',
+    'price',
+    [$plugin->get_lang('Price'), null, $currency['iso_code']],
+    ['step' => 0.01]
+);
+$form->addElement(
+    'number',
+    'duration_days',
+    [$plugin->get_lang('Duration'), null, get_lang('Days')],
+    ['step' => 1]
+);
+$form->addElement(
+    'radio',
+    'applies_to',
+    $plugin->get_lang('AppliesTo'),
+    get_lang('None'),
+    0
+);
+$form->addElement(
+    'radio',
+    'applies_to',
+    null,
+    get_lang('User'),
+    1
+);
+$form->addElement(
+    'radio',
+    'applies_to',
+    null,
+    get_lang('Course'),
+    2
+);
+$form->addElement(
+    'radio',
+    'applies_to',
+    null,
+    get_lang('Session'),
+    3
+);
+$form->addSelect(
+    'owner_id',
+    get_lang('Owner'),
+    $userOptions
+);
+$form->addCheckBox('visibility', $plugin->get_lang('VisibleInCatalog'));
+$form->addFile(
+    'picture',
+    ($formDefaultValues['image'] != '' ? get_lang('UpdateImage') : get_lang(
+        'AddImage'
+    )),
+    array('id' => 'picture', 'class' => 'picture-form', 'crop_image' => true, 'crop_ratio' => '4 / 3')
+);
+$form->addText('video_url', get_lang('VideoUrl'), false);
+$form->addHtmlEditor('service_information', $plugin->get_lang('ServiceInformation'), false);
+$form->addHidden('id', $serviceId);
+$form->addButtonSave(get_lang('Edit'));
+$form->setDefaults($formDefaultValues);
+if ($form->validate()) {
+    $values = $form->getSubmitValues();
+    $plugin->updateService($values, $serviceId);
+
+    Display::addFlash(
+        Display::return_message($plugin->get_lang('ServiceEdited'), 'success')
+    );
+
+    header('Location: configuration.php');
+    exit;
+}
+
+$templateName = $plugin->get_lang('EditService');
+$tpl = new Template($templateName);
+
+$tpl->assign('header', $templateName);
+$tpl->assign('content', $form->returnForm());
+$tpl->display_one_col_template();

+ 66 - 2
plugin/buycourses/view/configuration.tpl

@@ -1,5 +1,3 @@
-<script type='text/javascript' src="../js/buycourses.js"></script>
-
 <link rel="stylesheet" type="text/css" href="../resources/css/style.css"/>
 
 {% if sessions_are_included %}
@@ -10,6 +8,11 @@
         <li role="presentation">
             <a href="#sessions" aria-controls="sessions" role="tab" data-toggle="tab">{{ 'Sessions'|get_lang }}</a>
         </li>
+        {% if services_are_included %}
+            <li role="presentation">
+                <a href="#services" aria-controls="services" role="tab" data-toggle="tab">{{ 'Services'|get_plugin_lang('BuyCoursesPlugin') }}</a>
+            </li>
+        {% endif %}
     </ul>
 {% endif %}
 
@@ -131,4 +134,65 @@
             </div>
         </div>
     {% endif %}
+    {% if services_are_included %}
+        <div role="tabpanel" class="tab-pane" id="services">
+            <div class="table-responsive">
+                <a href="{{ _p.web_plugin ~ 'buycourses/src/services_add.php' }}" class="btn btn-primary">
+                    <em class="fa fa-cart-plus fa-fw"></em> {{ 'NewService'| get_plugin_lang('BuyCoursesPlugin') }}
+                </a>
+                </br>
+                </br>
+                <table id="services_table" class="table">
+                    <thead>
+                    <tr>
+                        <th>{{ 'Service'|get_plugin_lang('BuyCoursesPlugin') }}</th>
+                        <th>{{ 'Description'|get_lang }}</th>
+                        <th class="text-center">{{ 'Duration'|get_plugin_lang('BuyCoursesPlugin') }}</th>
+                        <th class="text-center">{{ 'VisibleInCatalog'|get_plugin_lang('BuyCoursesPlugin') }}</th>
+                        <th class="text-center">{{ 'Owner'|get_lang }}</th>
+                        <th class="text-right">{{ 'Price'|get_plugin_lang('BuyCoursesPlugin') }}</th>
+                        <th class="text-right">{{ 'Options'|get_lang }}</th>
+                    </tr>
+                    </thead>
+                    <tbody>
+                    {% for item in services %}
+                        <tr data-item="{{ item.id }}" data-type="service">
+                            <td>
+                                {{ item.name }}
+                            </td>
+                            <td>
+                                {{ item.description }}
+                            </td>
+                            <td class="text-center">
+                                {% if item.duration_days == 0 %}
+                                    {{ 'NoLimit'|get_lang }}
+                                {% else %}
+                                    {{ item.duration_days }} {{ 'Days'|get_lang }}
+                                {% endif %}
+                            </td>
+                            <td class="text-center">
+                                {% if item.visibility == 1 %}
+                                    <em class="fa fa-fw fa-check-square-o"></em>
+                                {% else %}
+                                    <em class="fa fa-fw fa-square-o"></em>
+                                {% endif %}
+                            </td>
+                            <td class="text-center">
+                                {{ item.owner_name }}
+                            </td>
+                            <td class="text-right" width="200">
+                                {{ "#{item.price} #{tem.currency ?: item.currency}" }}
+                            </td>
+                            <td class="text-right">
+                                <a href="{{ _p.web_plugin ~ 'buycourses/src/services_edit.php?' ~ {'id': item.id}|url_encode() }}" class="btn btn-info btn-sm">
+                                    <em class="fa fa-wrench fa-fw"></em> {{ 'Edit'|get_lang }}
+                                </a>
+                            </td>
+                        </tr>
+                    {% endfor %}
+                    </tbody>
+                </table>
+            </div>
+        </div>
+    {% endif %}
 </div>

+ 13 - 0
plugin/buycourses/view/sales_report.tpl

@@ -1,3 +1,16 @@
+<link rel="stylesheet" type="text/css" href="../resources/css/style.css"/>
+<ul class="nav nav-tabs buy-courses-sessions-tabs" role="tablist">
+    <li id="buy-courses-sessions-tab" class="active" role="presentation">
+        <a href="sales_report.php" aria-controls="buy-courses_sessions" role="tab">{{ 'CourseSessionBlock'|get_lang }}</a>
+    </li>
+    {% if services_are_included %}
+        <li id="buy-services-tab" class="{{ showing_services ? 'active' : '' }}" role="presentation">
+            <a href="service_sales_report.php" aria-controls="buy-services" role="tab">{{ 'Services'|get_plugin_lang('BuyCoursesPlugin') }}</a>
+        </li>
+    {% endif %}
+</ul>
+</br>
+</br>
 {{ form }}
 
 <div class="table-responsive">

+ 94 - 0
plugin/buycourses/view/service_sales_report.tpl

@@ -0,0 +1,94 @@
+<link rel="stylesheet" type="text/css" href="../resources/css/style.css"/>
+<script type="text/javascript" src="../resources/js/modals.js"></script>
+<div class="row">
+    <div class="col-md-3 col-sm-12 col-xs-12">
+        {{ form }}
+    </div>
+    <div class="col-md-9 col-sm-12 col-xs-12">
+        <div class="table-responsive">
+            <table class="table table-striped table-hover">
+                <thead>
+                <tr>
+                    <th class="text-center">{{ 'ServiceName'|get_plugin_lang('BuyCoursesPlugin') }}</th>
+                    <th class="text-center">{{ 'OrderReference'|get_plugin_lang('BuyCoursesPlugin') }}</th>
+                    <th class="text-center">{{ 'OrderStatus'|get_plugin_lang('BuyCoursesPlugin') }}</th>
+                    <th class="text-center">{{ 'OrderDate'|get_plugin_lang('BuyCoursesPlugin') }}</th>
+                    <th class="text-right">{{ 'Price'|get_plugin_lang('BuyCoursesPlugin') }}</th>
+                    <th class="text-center">{{ 'Renewable'|get_plugin_lang('BuyCoursesPlugin') }}</th>
+                    <th class="text-center">{{ 'ServiceSaleInfo'|get_plugin_lang('BuyCoursesPlugin')  }}</th>
+                </tr>
+                </thead>
+                <tbody>
+                {% for sale in sale_list %}
+                    <tr>
+                        <td class="text-center">{{ sale.service_name }}</td>
+                        <td class="text-center">{{ sale.reference }}</td>
+                        <td class="text-center">
+                            {% if sale.status == sale_status_cancelled %}
+                                {{ 'SaleStatusCancelled'|get_plugin_lang('BuyCoursesPlugin') }}
+                            {% elseif sale.status == sale_status_pending %}
+                                {{ 'SaleStatusPending'|get_plugin_lang('BuyCoursesPlugin') }}
+                            {% elseif sale.status == sale_status_completed %}
+                                {{ 'SaleStatusCompleted'|get_plugin_lang('BuyCoursesPlugin') }}
+                            {% endif %}
+                        </td>
+                        <td class="text-center">{{ sale.date }}</td>
+                        <td class="text-right">{{ sale.currency ~ ' ' ~ sale.price }}</td>
+                        {% if sale.recurring_payment == 0 %}
+                            <td class="text-center">{{ 'No' | get_lang }}</td>
+                        {% else %}
+                            <td class="text-center">
+                                <a id="renewable_info" tag="{{ sale.id }}" name="r_{{ sale.id }}" class="btn btn-warning btn-sm">{{ 'Info' | get_lang }}</a>
+                            </td>
+                        {% endif %}
+                        <td class="text-center">
+                            <a id="service_sale_info" tag="{{ sale.id }}" name="s_{{ sale.id }}" class="btn btn-info btn-sm">{{ 'Info' | get_lang }}</a>
+                        </td>
+                    </tr>
+                {% endfor %}
+                </tbody>
+            </table>
+        </div>
+    </div>
+</div>
+
+<script>
+    $(document).on('ready', function () {
+        $("td a").click(function() {
+            var id = $(this).attr('tag');
+            var action = $(this).attr('id');
+            $.ajax({
+                data: 'id='+id,
+                url: '{{ _p.web_plugin }}buycourses/src/buycourses.ajax.php?a='+action,
+                type: 'POST',
+                beforeSend: function() {
+                    if (action == 'renewable_info') {
+                        $('a[name=r_'+id+']').html('<em class="fa fa-spinner fa-pulse"></em> {{ 'Loading' | get_lang }}');
+                    } else if (action == 'service_sale_info') {
+                        $('a[name=s_'+id+']').html('<em class="fa fa-spinner fa-pulse"></em> {{ 'Loading' | get_lang }}');
+                    }
+                },
+                success: function(response) {
+                    $('a[name=r_'+id+']').html('{{ 'Info' | get_lang }}');
+                    $('a[name=s_'+id+']').html('{{ 'Info' | get_lang }}');
+                    var title = "";
+                    if (action == "renewable_info") {
+                        title = "{{ 'RecurringPaymentProfilePaypalInformation' | get_plugin_lang('BuyCoursesPlugin') }}";
+                    } else if (action == 'service_sale_info') {
+                        title = "{{ 'ServiceSaleInfo' | get_plugin_lang('BuyCoursesPlugin') }}";
+                    }
+                    bootbox.dialog({
+                        message: response,
+                        title: title,
+                        buttons: {
+                            main: {
+                                label: "{{ 'Close' | get_lang }}",
+                                className: "btn-default"
+                            }
+                        }
+                    });
+                }
+            })
+        });
+    });
+</script>