Passed
Pull Request — master (#6999)
by Angel Fernando Quiroz
11:06
created

BuyCoursesPlugin::registerSale()   D

Complexity

Conditions 18
Paths 76

Size

Total Lines 106
Code Lines 74

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 18
eloc 74
nc 76
nop 3
dl 0
loc 106
rs 4.8666
c 0
b 0
f 0

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
3
declare(strict_types=1);
4
/* For license terms, see /license.txt */
5
6
use Chamilo\CoreBundle\Entity\AccessUrlRelCourse;
7
use Chamilo\CoreBundle\Entity\AccessUrlRelSession;
8
use Chamilo\CoreBundle\Entity\Course;
9
use Chamilo\CoreBundle\Entity\CourseRelUser;
10
use Chamilo\CoreBundle\Entity\Session;
11
use Chamilo\CoreBundle\Entity\SessionRelCourse;
12
use Chamilo\CoreBundle\Entity\SessionRelCourseRelUser;
13
use Chamilo\CoreBundle\Entity\User;
14
use Chamilo\CoreBundle\Framework\Container;
15
use Chamilo\CourseBundle\Entity\CCourseDescription;
16
use Doctrine\ORM\Query\Expr\Join;
17
use Doctrine\ORM\QueryBuilder;
18
use Symfony\Component\HttpFoundation\Request as HttpRequest;
19
use Symfony\Component\Intl\Currencies;
20
21
/**
22
 * Plugin class for the BuyCourses plugin.
23
 *
24
 * @author  Jose Angel Ruiz <[email protected]>
25
 * @author  Imanol Losada <[email protected]>
26
 * @author  Alex Aragón <[email protected]>
27
 * @author  Angel Fernando Quiroz Campos <[email protected]>
28
 * @author  José Loguercio Silva  <[email protected]>
29
 * @author  Julio Montoya
30
 */
31
class BuyCoursesPlugin extends Plugin
32
{
33
    public const TABLE_PAYPAL = 'plugin_buycourses_paypal_account';
34
    public const TABLE_CURRENCY = 'plugin_buycourses_currency';
35
    public const TABLE_ITEM = 'plugin_buycourses_item';
36
    public const TABLE_ITEM_BENEFICIARY = 'plugin_buycourses_item_rel_beneficiary';
37
    public const TABLE_SALE = 'plugin_buycourses_sale';
38
    public const TABLE_TRANSFER = 'plugin_buycourses_transfer';
39
    public const TABLE_COMMISSION = 'plugin_buycourses_commission';
40
    public const TABLE_PAYPAL_PAYOUTS = 'plugin_buycourses_paypal_payouts';
41
    public const TABLE_SERVICES = 'plugin_buycourses_services';
42
    public const TABLE_SERVICES_SALE = 'plugin_buycourses_service_sale';
43
    public const TABLE_CULQI = 'plugin_buycourses_culqi';
44
    public const TABLE_GLOBAL_CONFIG = 'plugin_buycourses_global_config';
45
    public const TABLE_INVOICE = 'plugin_buycourses_invoices';
46
    public const TABLE_TPV_REDSYS = 'plugin_buycourses_tpvredsys_account';
47
    public const TABLE_COUPON = 'plugin_buycourses_coupon';
48
    public const TABLE_COUPON_ITEM = 'plugin_buycourses_coupon_rel_item';
49
    public const TABLE_COUPON_SERVICE = 'plugin_buycourses_coupon_rel_service';
50
    public const TABLE_SUBSCRIPTION = 'plugin_buycourses_subscription';
51
    public const TABLE_SUBSCRIPTION_SALE = 'plugin_buycourses_subscription_rel_sale';
52
    public const TABLE_SUBSCRIPTION_PERIOD = 'plugin_buycourses_subscription_period';
53
    public const TABLE_COUPON_SALE = 'plugin_buycourses_coupon_rel_sale';
54
    public const TABLE_COUPON_SERVICE_SALE = 'plugin_buycourses_coupon_rel_service_sale';
55
    public const TABLE_COUPON_SUBSCRIPTION_SALE = 'plugin_buycourses_coupon_rel_subscription_sale';
56
    public const TABLE_STRIPE = 'plugin_buycourses_stripe_account';
57
    public const TABLE_TPV_CECABANK = 'plugin_buycourses_cecabank_account';
58
    public const PRODUCT_TYPE_COURSE = 1;
59
    public const PRODUCT_TYPE_SESSION = 2;
60
    public const PRODUCT_TYPE_SERVICE = 3;
61
    public const PAYMENT_TYPE_PAYPAL = 1;
62
    public const PAYMENT_TYPE_TRANSFER = 2;
63
    public const PAYMENT_TYPE_CULQI = 3;
64
    public const PAYMENT_TYPE_TPV_REDSYS = 4;
65
    public const PAYMENT_TYPE_STRIPE = 5;
66
    public const PAYMENT_TYPE_TPV_CECABANK = 6;
67
    public const PAYOUT_STATUS_CANCELED = 2;
68
    public const PAYOUT_STATUS_PENDING = 0;
69
    public const PAYOUT_STATUS_COMPLETED = 1;
70
    public const SALE_STATUS_CANCELED = -1;
71
    public const SALE_STATUS_PENDING = 0;
72
    public const SALE_STATUS_COMPLETED = 1;
73
    public const SERVICE_STATUS_PENDING = 0;
74
    public const SERVICE_STATUS_COMPLETED = 1;
75
    public const SERVICE_STATUS_CANCELLED = -1;
76
    public const SERVICE_TYPE_USER = 1;
77
    public const SERVICE_TYPE_COURSE = 2;
78
    public const SERVICE_TYPE_SESSION = 3;
79
    public const SERVICE_TYPE_LP_FINAL_ITEM = 4;
80
    public const CULQI_INTEGRATION_TYPE = 'INTEG';
81
    public const CULQI_PRODUCTION_TYPE = 'PRODUC';
82
    public const TAX_APPLIES_TO_ALL = 1;
83
    public const TAX_APPLIES_TO_ONLY_COURSE = 2;
84
    public const TAX_APPLIES_TO_ONLY_SESSION = 3;
85
    public const TAX_APPLIES_TO_ONLY_SERVICES = 4;
86
    public const PAGINATION_PAGE_SIZE = 6;
87
    public const COUPON_DISCOUNT_TYPE_PERCENTAGE = 1;
88
    public const COUPON_DISCOUNT_TYPE_AMOUNT = 2;
89
    public const COUPON_STATUS_ACTIVE = 1;
90
    public const COUPON_STATUS_DISABLE = 0;
91
92
    /**
93
     * @var bool
94
     */
95
    public $isAdminPlugin = true;
96
97
    public function __construct()
98
    {
99
        parent::__construct(
100
            '7.1',
101
            '
102
                Jose Angel Ruiz - NoSoloRed (original author) <br/>
103
                Francis Gonzales and Yannick Warnier - BeezNest (integration) <br/>
104
                Alex Aragón - BeezNest (Design icons and css styles) <br/>
105
                Imanol Losada - BeezNest (introduction of sessions purchase) <br/>
106
                Angel Fernando Quiroz Campos - BeezNest (cleanup and new reports) <br/>
107
                José Loguercio Silva - BeezNest (Payouts and buy Services) <br/>
108
                Julio Montoya
109
            ',
110
            [
111
                'show_main_menu_tab' => 'boolean',
112
                'public_main_menu_tab' => 'boolean',
113
                'include_sessions' => 'boolean',
114
                'include_services' => 'boolean',
115
                'paypal_enable' => 'boolean',
116
                'transfer_enable' => 'boolean',
117
                'culqi_enable' => 'boolean',
118
                'commissions_enable' => 'boolean',
119
                'unregistered_users_enable' => 'boolean',
120
                'hide_free_text' => 'boolean',
121
                'hide_shopping_cart_from_course_catalogue' => 'boolean',
122
                'invoicing_enable' => 'boolean',
123
                'tax_enable' => 'boolean',
124
                'use_currency_symbol' => 'boolean',
125
                'tpv_redsys_enable' => 'boolean',
126
                'stripe_enable' => 'boolean',
127
                'cecabank_enable' => 'boolean',
128
            ]
129
        );
130
    }
131
132
    public static function create(): self
133
    {
134
        static $result = null;
135
136
        return $result ? $result : $result = new self();
137
    }
138
139
    /**
140
     * Check if plugin is enabled.
141
     *
142
     * @param bool $checkEnabled Check if, additionnally to being installed, the plugin is enabled
143
     */
144
    public function isEnabled(bool $checkEnabled = false): bool
145
    {
146
        return $this->get('paypal_enable') || $this->get('transfer_enable') || $this->get('culqi_enable') || $this->get('stripe_enable') || $this->get('cecabank_enable');
147
    }
148
149
    /**
150
     * This method creates the tables required to this plugin.
151
     *
152
     * @throws \Doctrine\DBAL\Exception
153
     */
154
    public function install(): void
155
    {
156
        $tablesToBeCompared = [
157
            self::TABLE_PAYPAL,
158
            self::TABLE_TRANSFER,
159
            self::TABLE_CULQI,
160
            self::TABLE_ITEM_BENEFICIARY,
161
            self::TABLE_ITEM,
162
            self::TABLE_SALE,
163
            self::TABLE_CURRENCY,
164
            self::TABLE_COMMISSION,
165
            self::TABLE_PAYPAL_PAYOUTS,
166
            self::TABLE_SERVICES,
167
            self::TABLE_SERVICES_SALE,
168
            self::TABLE_GLOBAL_CONFIG,
169
            self::TABLE_INVOICE,
170
            self::TABLE_TPV_REDSYS,
171
            self::TABLE_COUPON,
172
            self::TABLE_COUPON_ITEM,
173
            self::TABLE_COUPON_SERVICE,
174
            self::TABLE_SUBSCRIPTION,
175
            self::TABLE_SUBSCRIPTION_SALE,
176
            self::TABLE_SUBSCRIPTION_PERIOD,
177
            self::TABLE_COUPON_SALE,
178
            self::TABLE_COUPON_SERVICE_SALE,
179
            self::TABLE_COUPON_SUBSCRIPTION_SALE,
180
            self::TABLE_STRIPE,
181
            self::TABLE_TPV_CECABANK,
182
        ];
183
        $em = Database::getManager();
184
        $cn = $em->getConnection();
185
        $sm = $cn->createSchemaManager();
186
        $tables = $sm->tablesExist($tablesToBeCompared);
187
188
        if ($tables) {
189
            return;
190
        }
191
192
        require_once api_get_path(SYS_PLUGIN_PATH).'BuyCourses/database.php';
193
    }
194
195
    /**
196
     * This method drops the plugin tables.
197
     *
198
     * @throws Exception
199
     */
200
    public function uninstall(): void
201
    {
202
        $tablesToBeDeleted = [
203
            self::TABLE_PAYPAL,
204
            self::TABLE_TRANSFER,
205
            self::TABLE_CULQI,
206
            self::TABLE_ITEM_BENEFICIARY,
207
            self::TABLE_ITEM,
208
            self::TABLE_SALE,
209
            self::TABLE_CURRENCY,
210
            self::TABLE_COMMISSION,
211
            self::TABLE_PAYPAL_PAYOUTS,
212
            self::TABLE_SERVICES_SALE,
213
            self::TABLE_SERVICES,
214
            self::TABLE_GLOBAL_CONFIG,
215
            self::TABLE_INVOICE,
216
            self::TABLE_TPV_REDSYS,
217
            self::TABLE_COUPON,
218
            self::TABLE_COUPON_ITEM,
219
            self::TABLE_COUPON_SERVICE,
220
            self::TABLE_SUBSCRIPTION,
221
            self::TABLE_SUBSCRIPTION_SALE,
222
            self::TABLE_SUBSCRIPTION_PERIOD,
223
            self::TABLE_COUPON_SALE,
224
            self::TABLE_COUPON_SERVICE_SALE,
225
            self::TABLE_COUPON_SUBSCRIPTION_SALE,
226
            self::TABLE_STRIPE,
227
        ];
228
229
        foreach ($tablesToBeDeleted as $tableToBeDeleted) {
230
            $table = Database::get_main_table($tableToBeDeleted);
231
            $sql = "DROP TABLE IF EXISTS $table";
232
            Database::query($sql);
233
        }
234
        $this->manageTab(false);
235
    }
236
237
    /**
238
     * @throws Exception
239
     */
240
    public function update(): void
241
    {
242
        $table = self::TABLE_GLOBAL_CONFIG;
243
        $sql = "SHOW COLUMNS FROM $table WHERE Field = 'global_tax_perc'";
244
        $res = Database::query($sql);
245
246
        if (0 === Database::num_rows($res)) {
247
            $sql = "ALTER TABLE $table ADD (
248
                sale_email varchar(255) NOT NULL,
249
                global_tax_perc int unsigned NOT NULL,
250
                tax_applies_to int unsigned NOT NULL,
251
                tax_name varchar(255) NOT NULL,
252
                seller_name varchar(255) NOT NULL,
253
                seller_id varchar(255) NOT NULL,
254
                seller_address varchar(255) NOT NULL,
255
                seller_email varchar(255) NOT NULL,
256
                next_number_invoice int unsigned NOT NULL,
257
                invoice_series varchar(255) NOT NULL
258
            )";
259
            $res = Database::query($sql);
260
            if (!$res) {
261
                echo Display::return_message($this->get_lang('ErrorUpdateFieldDB'), 'warning');
262
            }
263
        }
264
265
        $res = Database::query("SHOW COLUMNS FROM $table WHERE Field = 'info_email_extra'");
266
267
        if (0 === Database::num_rows($res)) {
268
            $res = Database::query("ALTER TABLE $table ADD (info_email_extra TEXT NOT NULL)");
269
            if (!$res) {
270
                echo Display::return_message($this->get_lang('ErrorUpdateFieldDB'), 'warning');
271
            }
272
        }
273
274
        $table = self::TABLE_ITEM;
275
        $sql = "SHOW COLUMNS FROM $table WHERE Field = 'tax_perc'";
276
        $res = Database::query($sql);
277
278
        if (0 === Database::num_rows($res)) {
279
            $sql = "ALTER TABLE $table ADD tax_perc int unsigned NULL";
280
            $res = Database::query($sql);
281
            if (!$res) {
282
                echo Display::return_message($this->get_lang('ErrorUpdateFieldDB'), 'warning');
283
            }
284
        }
285
286
        $table = self::TABLE_SERVICES;
287
        $sql = "SHOW COLUMNS FROM $table WHERE Field = 'tax_perc'";
288
        $res = Database::query($sql);
289
290
        if (0 === Database::num_rows($res)) {
291
            $sql = "ALTER TABLE $table ADD tax_perc int unsigned NULL";
292
            $res = Database::query($sql);
293
            if (!$res) {
294
                echo Display::return_message($this->get_lang('ErrorUpdateFieldDB'), 'warning');
295
            }
296
        }
297
298
        $table = self::TABLE_SALE;
299
        $sql = "SHOW COLUMNS FROM $table WHERE Field = 'tax_perc'";
300
        $res = Database::query($sql);
301
302
        if (0 === Database::num_rows($res)) {
303
            $sql = "ALTER TABLE $table ADD (
304
                price_without_tax decimal(10,2) NULL,
305
                tax_perc int unsigned NULL,
306
                tax_amount decimal(10,2) NULL,
307
                invoice int unsigned NULL
308
            )";
309
            $res = Database::query($sql);
310
            if (!$res) {
311
                echo Display::return_message($this->get_lang('ErrorUpdateFieldDB'), 'warning');
312
            }
313
        }
314
315
        $res = Database::query("SHOW COLUMNS FROM $table WHERE Field = 'price_without_discount'");
316
317
        if (0 === Database::num_rows($res)) {
318
            $res = Database::query("ALTER TABLE $table ADD (
319
                price_without_discount decimal(10,2) NULL,
320
                discount_amount decimal(10,2) NULL
321
            )");
322
            if (!$res) {
323
                echo Display::return_message($this->get_lang('ErrorUpdateFieldDB'), 'warning');
324
            }
325
        }
326
327
        $table = self::TABLE_SERVICES_SALE;
328
        $sql = "SHOW COLUMNS FROM $table WHERE Field = 'tax_perc'";
329
        $res = Database::query($sql);
330
331
        if (0 === Database::num_rows($res)) {
332
            $sql = "ALTER TABLE $table ADD (
333
                price_without_tax decimal(10,2) NULL,
334
                tax_perc int unsigned NULL,
335
                tax_amount decimal(10,2) NULL,
336
                invoice int unsigned NULL
337
            )";
338
            $res = Database::query($sql);
339
            if (!$res) {
340
                echo Display::return_message($this->get_lang('ErrorUpdateFieldDB'), 'warning');
341
            }
342
        }
343
344
        $res = Database::query("SHOW COLUMNS FROM $table WHERE Field = 'price_without_discount'");
345
346
        if (0 === Database::num_rows($res)) {
347
            $res = Database::query("ALTER TABLE $table ADD (
348
                price_without_discount decimal(10,2) NULL,
349
                discount_amount decimal(10,2) NULL
350
            )");
351
            if (!$res) {
352
                echo Display::return_message($this->get_lang('ErrorUpdateFieldDB'), 'warning');
353
            }
354
        }
355
356
        $table = self::TABLE_INVOICE;
357
        $sql = "CREATE TABLE IF NOT EXISTS $table (
358
            id int unsigned NOT NULL AUTO_INCREMENT,
359
            sale_id int unsigned NOT NULL,
360
            is_service int unsigned NOT NULL,
361
            num_invoice int unsigned NOT NULL,
362
            year int(4) unsigned NOT NULL,
363
            serie varchar(255) NOT NULL,
364
            date_invoice datetime NOT NULL,
365
            PRIMARY KEY (id)
366
        )";
367
        Database::query($sql);
368
369
        $table = self::TABLE_TPV_REDSYS;
370
        $sql = "CREATE TABLE IF NOT EXISTS $table (
371
            id int unsigned NOT NULL AUTO_INCREMENT,
372
            merchantcode varchar(255) NOT NULL,
373
            terminal varchar(255) NOT NULL,
374
            currency varchar(255) NOT NULL,
375
            kc varchar(255) NOT NULL,
376
            url_redsys varchar(255) NOT NULL,
377
            url_redsys_sandbox varchar(255) NOT NULL,
378
            sandbox int unsigned NULL,
379
            PRIMARY KEY (id)
380
        )";
381
        Database::query($sql);
382
383
        $res = Database::query("SELECT * FROM $table");
384
        if (0 == Database::num_rows($res)) {
385
            Database::insert($table, [
386
                'url_redsys' => 'https://sis.redsys.es/sis/realizarPago',
387
                'url_redsys_sandbox' => 'https://sis-t.redsys.es:25443/sis/realizarPago',
388
            ]);
389
        }
390
391
        $table = self::TABLE_COUPON;
392
        $sql = "CREATE TABLE IF NOT EXISTS $table (
393
            id int unsigned NOT NULL AUTO_INCREMENT,
394
            code varchar(255) NOT NULL,
395
            discount_type int unsigned NOT NULL,
396
            discount_amount decimal(10, 2) NOT NULL,
397
            valid_start datetime NOT NULL,
398
            valid_end datetime NOT NULL,
399
            delivered varchar(255) NOT NULL,
400
            active tinyint NOT NULL,
401
            PRIMARY KEY (id)
402
        )";
403
        Database::query($sql);
404
405
        $table = self::TABLE_COUPON_ITEM;
406
        $sql = "CREATE TABLE IF NOT EXISTS $table (
407
            id int unsigned NOT NULL AUTO_INCREMENT,
408
            coupon_id int unsigned NOT NULL,
409
            product_type int unsigned NOT NULL,
410
            product_id int unsigned NOT NULL,
411
            PRIMARY KEY (id)
412
        )";
413
        Database::query($sql);
414
415
        $table = self::TABLE_COUPON_SERVICE;
416
        $sql = "CREATE TABLE IF NOT EXISTS $table (
417
            id int unsigned NOT NULL AUTO_INCREMENT,
418
            coupon_id int unsigned NOT NULL,
419
            service_id int unsigned NOT NULL,
420
            PRIMARY KEY (id)
421
        )";
422
        Database::query($sql);
423
424
        $table = self::TABLE_SUBSCRIPTION;
425
        $sql = "CREATE TABLE IF NOT EXISTS $table (
426
            product_type int unsigned NOT NULL,
427
            product_id int unsigned NOT NULL,
428
            duration int unsigned NOT NULL,
429
            currency_id int unsigned NOT NULL,
430
            price decimal(10, 2) NOT NULL,
431
            tax_perc int unsigned,
432
            PRIMARY KEY (product_type, product_id, duration)
433
        )";
434
        Database::query($sql);
435
436
        $table = self::TABLE_SUBSCRIPTION_SALE;
437
        $sql = "CREATE TABLE IF NOT EXISTS $table (
438
            id int unsigned NOT NULL AUTO_INCREMENT,
439
            currency_id int unsigned NOT NULL,
440
            reference varchar(255) NOT NULL,
441
            date datetime NOT NULL,
442
            user_id int unsigned NOT NULL,
443
            product_type int NOT NULL,
444
            product_name varchar(255) NOT NULL,
445
            product_id int unsigned NOT NULL,
446
            price decimal(10,2) NOT NULL,
447
            price_without_tax decimal(10,2) NULL,
448
            tax_perc int unsigned NULL,
449
            tax_amount decimal(10,2) NULL,
450
            status int NOT NULL,
451
            payment_type int NOT NULL,
452
            invoice int NOT NULL,
453
            price_without_discount decimal(10,2),
454
            discount_amount decimal(10,2),
455
            subscription_end datetime NOT NULL,
456
            expired tinyint NULL,
457
            PRIMARY KEY (id)
458
        )";
459
        Database::query($sql);
460
461
        $table = self::TABLE_SUBSCRIPTION_PERIOD;
462
        $sql = "CREATE TABLE IF NOT EXISTS $table (
463
            duration int unsigned NOT NULL,
464
            name varchar(50) NOT NULL,
465
            PRIMARY KEY (duration)
466
        )";
467
        Database::query($sql);
468
469
        $table = self::TABLE_COUPON_SALE;
470
        $sql = "CREATE TABLE IF NOT EXISTS $table (
471
            id int unsigned NOT NULL AUTO_INCREMENT,
472
            coupon_id int unsigned NOT NULL,
473
            sale_id int unsigned NOT NULL,
474
            PRIMARY KEY (id)
475
        )";
476
        Database::query($sql);
477
478
        $table = self::TABLE_COUPON_SERVICE_SALE;
479
        $sql = "CREATE TABLE IF NOT EXISTS $table (
480
            id int unsigned NOT NULL AUTO_INCREMENT,
481
            coupon_id int unsigned NOT NULL,
482
            service_sale_id int unsigned NOT NULL,
483
            PRIMARY KEY (id)
484
        )";
485
        Database::query($sql);
486
487
        $table = self::TABLE_COUPON_SUBSCRIPTION_SALE;
488
        $sql = "CREATE TABLE IF NOT EXISTS $table (
489
            id int unsigned NOT NULL AUTO_INCREMENT,
490
            coupon_id int unsigned NOT NULL,
491
            sale_id int unsigned NOT NULL,
492
            PRIMARY KEY (id)
493
        )";
494
        Database::query($sql);
495
496
        $table = self::TABLE_STRIPE;
497
        $sql = "CREATE TABLE IF NOT EXISTS $table (
498
            id int unsigned NOT NULL AUTO_INCREMENT,
499
            account_id varchar(255) NOT NULL,
500
            secret_key varchar(255) NOT NULL,
501
            endpoint_secret varchar(255) NOT NULL,
502
            PRIMARY KEY (id)
503
        )";
504
        Database::query($sql);
505
506
        $sql = "SELECT * FROM $table";
507
        $res = Database::query($sql);
508
        if (0 == Database::num_rows($res)) {
509
            Database::insert($table, [
510
                'account_id' => '',
511
                'secret_key' => '',
512
                'endpoint_secret' => '',
513
            ]);
514
        }
515
516
        $table = self::TABLE_TPV_CECABANK;
517
        $sql = "CREATE TABLE IF NOT EXISTS $table (
518
            id int unsigned NOT NULL AUTO_INCREMENT,
519
            crypto_key varchar(255) NOT NULL,
520
            merchant_id varchar(255) NOT NULL,
521
            acquirer_bin varchar(255) NOT NULL,
522
            terminal_id varchar(255) NOT NULL,
523
            cypher varchar(255) NOT NULL,
524
            exponent varchar(255) NOT NULL,
525
            supported_payment varchar(255) NOT NULL,
526
            url varchar(255) NOT NULL,
527
            PRIMARY KEY (id)
528
        )";
529
        Database::query($sql);
530
531
        Display::addFlash(
532
            Display::return_message(
533
                $this->get_lang('Updated'),
534
                'info',
535
                false
536
            )
537
        );
538
539
        $fieldlabel = 'buycourses_company';
540
        $fieldtype = '1';
541
        $fieldtitle = $this->get_lang('Company');
542
        $fielddefault = '';
543
        UserManager::create_extra_field($fieldlabel, $fieldtype, $fieldtitle, $fielddefault);
544
545
        $fieldlabel = 'buycourses_vat';
546
        $fieldtype = '1';
547
        $fieldtitle = $this->get_lang('VAT');
548
        $fielddefault = '';
549
        UserManager::create_extra_field($fieldlabel, $fieldtype, $fieldtitle, $fielddefault);
550
551
        $fieldlabel = 'buycourses_address';
552
        $fieldtype = '1';
553
        $fieldtitle = $this->get_lang('Address');
554
        $fielddefault = '';
555
        UserManager::create_extra_field($fieldlabel, $fieldtype, $fieldtitle, $fielddefault);
556
557
        header('Location: '.api_get_path(WEB_PLUGIN_PATH).'BuyCourses');
558
559
        exit;
560
    }
561
562
    /**
563
     * This function verifies if the plugin is enabled and return the price info for a course or session in the new grid
564
     * catalog for 1.11.x, the main purpose is to show if a course or session is in sale it shows in the main platform
565
     * course catalog so the old BuyCourses plugin catalog can be deprecated.
566
     *
567
     * @param int $productId   course or session id
568
     * @param int $productType course or session type
569
     */
570
    public function buyCoursesForGridCatalogValidator(int $productId, int $productType): ?array
571
    {
572
        $return = [];
573
        $paypal = 'true' === $this->get('paypal_enable');
574
        $transfer = 'true' === $this->get('transfer_enable');
575
        $stripe = 'true' === $this->get('stripe_enable');
576
        $culqi = 'true' === $this->get('culqi_enable');
577
        $cecabank = 'true' === $this->get('cecabank_enable');
578
        $tpv_redsys = 'true' === $this->get('tpv_redsys_enable');
579
        $hideFree = 'true' === $this->get('hide_free_text');
580
581
        if ($paypal || $transfer || $stripe || $culqi || $cecabank || $tpv_redsys) {
582
            $item = $this->getItemByProduct($productId, $productType);
583
            $html = '<div class="buycourses-price">';
584
            if ($item) {
0 ignored issues
show
introduced by
$item is of type null, thus it always evaluated to false.
Loading history...
585
                $html .= '<span class="label label-primary label-price">
586
                            <strong>'.$item['total_price_formatted'].'</strong>
587
                          </span>';
588
                $return['verificator'] = true;
589
            } else {
590
                if (!$hideFree) {
591
                    $html .= '<span class="label label-primary label-free">
592
                                <strong>'.$this->get_lang('Free').'</strong>
593
                              </span>';
594
                }
595
                $return['verificator'] = false;
596
            }
597
            $html .= '</div>';
598
            $return['html'] = $html;
599
600
            return $return;
601
        }
602
603
        return null;
604
    }
605
606
    /**
607
     * Return the buyCourses plugin button to buy the course.
608
     */
609
    public function returnBuyCourseButton(int $productId, int $productType): string
610
    {
611
        $url = api_get_path(WEB_PLUGIN_PATH).'buycourses/src/process.php?i='.$productId.'&t='.$productType;
612
        $buyButton = Display::getMdiIcon('cart');
613
        if ('true' === $this->get('hide_shopping_cart_from_course_catalogue')) {
614
            $buyButton = Display::getMdiIcon('check').\PHP_EOL.get_lang('Subscribe');
615
        }
616
617
        return '<a class="btn btn-success btn-sm" title="'.$this->get_lang('Buy').'" href="'.$url.'">'.
618
            $buyButton.'</a>';
619
    }
620
621
    /**
622
     * Get the currency for sales.
623
     *
624
     * @return array The selected currency. Otherwise, return false
625
     *
626
     * @throws Exception
627
     */
628
    public function getSelectedCurrency(): array
629
    {
630
        return Database::select(
631
            '*',
632
            Database::get_main_table(self::TABLE_CURRENCY),
633
            [
634
                'where' => ['status = ?' => true],
635
            ],
636
            'first'
637
        );
638
    }
639
640
    /**
641
     * Get a list of currencies.
642
     */
643
    public function getCurrencies(): array
644
    {
645
        try {
646
            return Database::select(
647
                '*',
648
                Database::get_main_table(self::TABLE_CURRENCY)
649
            );
650
        } catch (Exception $e) {
651
            return [];
652
        }
653
    }
654
655
    /**
656
     * Save the selected currency.
657
     *
658
     * @param int $selectedId The currency ID
659
     *
660
     * @throws Exception
661
     */
662
    public function saveCurrency(int $selectedId): void
663
    {
664
        $currencyTable = Database::get_main_table(
665
            self::TABLE_CURRENCY
666
        );
667
668
        Database::update(
669
            $currencyTable,
670
            ['status' => 0]
671
        );
672
        Database::update(
673
            $currencyTable,
674
            ['status' => 1],
675
            ['id = ?' => $selectedId]
676
        );
677
    }
678
679
    /**
680
     * Save the PayPal configuration params.
681
     *
682
     * @return int Rows affected. Otherwise, return false
683
     *
684
     * @throws Exception
685
     */
686
    public function savePaypalParams(array $params): int
687
    {
688
        return Database::update(
689
            Database::get_main_table(self::TABLE_PAYPAL),
690
            [
691
                'username' => $params['username'],
692
                'password' => $params['password'],
693
                'signature' => $params['signature'],
694
                'sandbox' => isset($params['sandbox']),
695
            ],
696
            ['id = ?' => 1]
697
        );
698
    }
699
700
    /**
701
     * Gets the stored PayPal params.
702
     *
703
     * @return array<string, mixed>
704
     *
705
     * @throws Exception
706
     */
707
    public function getPaypalParams(): array
708
    {
709
        return Database::select(
710
            '*',
711
            Database::get_main_table(self::TABLE_PAYPAL),
712
            ['id = ?' => 1],
713
            'first'
714
        );
715
    }
716
717
    /**
718
     * Gets the stored TPV Redsys params.
719
     *
720
     * @return array<string, mixed>
721
     *
722
     * @throws Exception
723
     */
724
    public function getTpvRedsysParams(): array
725
    {
726
        return Database::select(
727
            '*',
728
            Database::get_main_table(self::TABLE_TPV_REDSYS),
729
            ['id = ?' => 1],
730
            'first'
731
        );
732
    }
733
734
    /**
735
     * Save the tpv Redsys configuration params.
736
     *
737
     * @return int Rows affected. Otherwise, return false
738
     *
739
     * @throws Exception
740
     */
741
    public function saveTpvRedsysParams(array $params): int
742
    {
743
        return Database::update(
744
            Database::get_main_table(self::TABLE_TPV_REDSYS),
745
            [
746
                'merchantcode' => $params['merchantcode'],
747
                'terminal' => $params['terminal'],
748
                'currency' => $params['currency'],
749
                'kc' => $params['kc'],
750
                'url_redsys' => $params['url_redsys'],
751
                'url_redsys_sandbox' => $params['url_redsys_sandbox'],
752
                'sandbox' => isset($params['sandbox']),
753
            ],
754
            ['id = ?' => 1]
755
        );
756
    }
757
758
    /**
759
     * Save Stripe configuration params.
760
     *
761
     * @return int Rows affected. Otherwise, return false
762
     *
763
     * @throws Exception
764
     */
765
    public function saveStripeParameters(array $params): int
766
    {
767
        return Database::update(
768
            Database::get_main_table(self::TABLE_STRIPE),
769
            [
770
                'account_id' => $params['account_id'],
771
                'secret_key' => $params['secret_key'],
772
                'endpoint_secret' => $params['endpoint_secret'],
773
            ],
774
            ['id = ?' => 1]
775
        );
776
    }
777
778
    /**
779
     * Gets the stored Stripe params.
780
     *
781
     * @throws Exception
782
     */
783
    public function getStripeParams(): array
784
    {
785
        return Database::select(
786
            '*',
787
            Database::get_main_table(self::TABLE_STRIPE),
788
            ['id = ?' => 1],
789
            'first'
790
        );
791
    }
792
793
    /**
794
     * Save transfer account information.
795
     *
796
     * @param array $params The transfer account
797
     *
798
     * @return int Rows affected. Otherwise, return false
799
     *
800
     * @throws \Doctrine\DBAL\Exception
801
     */
802
    public function saveTransferAccount(array $params): int
803
    {
804
        return Database::insert(
805
            Database::get_main_table(self::TABLE_TRANSFER),
806
            [
807
                'name' => $params['tname'],
808
                'account' => $params['taccount'],
809
                'swift' => $params['tswift'],
810
            ]
811
        );
812
    }
813
814
    /**
815
     * Save email message information in transfer.
816
     *
817
     * @param array $params The transfer message
818
     *
819
     * @return int Rows affected. Otherwise, return false
820
     *
821
     * @throws Exception
822
     */
823
    public function saveTransferInfoEmail(array $params): int
824
    {
825
        return Database::update(
826
            Database::get_main_table(self::TABLE_GLOBAL_CONFIG),
827
            ['info_email_extra' => $params['tinfo_email_extra']],
828
            ['id = ?' => 1]
829
        );
830
    }
831
832
    /**
833
     * Gets message information for transfer email.
834
     *
835
     * @throws Exception
836
     */
837
    public function getTransferInfoExtra(): array
838
    {
839
        return Database::select(
840
            'info_email_extra AS tinfo_email_extra',
841
            Database::get_main_table(self::TABLE_GLOBAL_CONFIG),
842
            ['id = ?' => 1],
843
            'first'
844
        );
845
    }
846
847
    /**
848
     * Get a list of transfer accounts.
849
     *
850
     * @return array
851
     *
852
     * @throws Exception
853
     */
854
    public function getTransferAccounts(): array
855
    {
856
        return Database::select(
857
            '*',
858
            Database::get_main_table(self::TABLE_TRANSFER)
859
        );
860
    }
861
862
    /**
863
     * Remove a transfer account.
864
     *
865
     * @param  int  $id  The transfer account ID
866
     *
867
     * @return int Rows affected. Otherwise, return false
868
     *
869
     * @throws Exception
870
     */
871
    public function deleteTransferAccount(int $id): int
872
    {
873
        return Database::delete(
874
            Database::get_main_table(self::TABLE_TRANSFER),
875
            ['id = ?' => $id]
876
        );
877
    }
878
879
    /**
880
     * Get registered item data.
881
     *
882
     * @param int $itemId The item ID
883
     */
884
    public function getItem(int $itemId): array
885
    {
886
        return Database::select(
887
            '*',
888
            Database::get_main_table(self::TABLE_ITEM),
889
            [
890
                'where' => ['id = ?' => $itemId],
891
            ],
892
            'first'
893
        );
894
    }
895
896
    /**
897
     * Get the item data.
898
     *
899
     * @param int $productId The item ID
900
     * @param int $itemType  The item type
901
     */
902
    public function getItemByProduct(int $productId, int $itemType, ?array $coupon = null): ?array
903
    {
904
        $buyItemTable = Database::get_main_table(self::TABLE_ITEM);
905
        $buyCurrencyTable = Database::get_main_table(self::TABLE_CURRENCY);
906
907
        $fakeItemFrom = "
908
            $buyItemTable i
909
            INNER JOIN $buyCurrencyTable c
910
                ON i.currency_id = c.id
911
        ";
912
913
        $product = Database::select(
914
            ['i.*', 'c.iso_code'],
915
            $fakeItemFrom,
916
            [
917
                'where' => [
918
                    'i.product_id = ? AND i.product_type = ?' => [
919
                        $productId,
920
                        $itemType,
921
                    ],
922
                ],
923
            ],
924
            'first'
925
        );
926
927
        if (empty($product)) {
928
            return null;
929
        }
930
931
        $this->setPriceSettings($product, self::TAX_APPLIES_TO_ONLY_COURSE, $coupon);
932
933
        return $product;
934
    }
935
936
    /**
937
     * Get registered item data.
938
     *
939
     * @param int $itemId      The product ID
940
     * @param int $productType The product type
941
     */
942
    public function getSubscriptionItem(int $itemId, int $productType): array
943
    {
944
        return Database::select(
945
            '*',
946
            Database::get_main_table(self::TABLE_SUBSCRIPTION),
947
            [
948
                'where' => ['product_id = ? AND product_type = ?' => [
949
                    $itemId,
950
                    $productType,
951
                ],
952
                ],
953
            ],
954
            'first'
955
        );
956
    }
957
958
    /**
959
     * Get the item data.
960
     *
961
     * @param int   $productId The item ID
962
     * @param int   $itemType  The item type
963
     * @param array $coupon    Array with at least 'discount_type' and 'discount_amount' elements
964
     */
965
    public function getSubscriptionItemByProduct(int $productId, int $itemType, ?array $coupon = null): ?array
966
    {
967
        $buySubscriptionItemTable = Database::get_main_table(self::TABLE_SUBSCRIPTION);
968
        $buyCurrencyTable = Database::get_main_table(self::TABLE_CURRENCY);
969
970
        $fakeItemFrom = "
971
            $buySubscriptionItemTable s
972
            INNER JOIN $buyCurrencyTable c
973
                ON s.currency_id = c.id
974
        ";
975
976
        $item = Database::select(
977
            ['s.*', 'c.iso_code'],
978
            $fakeItemFrom,
979
            [
980
                'where' => [
981
                    's.product_id = ? AND s.product_type = ?' => [
982
                        $productId,
983
                        $itemType,
984
                    ],
985
                ],
986
            ],
987
            'first'
988
        );
989
990
        if (empty($item)) {
991
            return null;
992
        }
993
994
        $this->setPriceSettings($item, self::TAX_APPLIES_TO_ONLY_COURSE, $coupon);
995
996
        return $item;
997
    }
998
999
    /**
1000
     * Get the item data.
1001
     *
1002
     * @param int $productId The item ID
1003
     * @param int $itemType  The item type
1004
     */
1005
    public function getSubscriptionsItemsByProduct(int $productId, int $itemType): ?array
1006
    {
1007
        $buySubscriptionItemTable = Database::get_main_table(self::TABLE_SUBSCRIPTION);
1008
        $buyCurrencyTable = Database::get_main_table(self::TABLE_CURRENCY);
1009
1010
        $fakeItemFrom = "
1011
            $buySubscriptionItemTable s
1012
            INNER JOIN $buyCurrencyTable c
1013
                ON s.currency_id = c.id
1014
        ";
1015
1016
        $items = Database::select(
1017
            ['s.*', 'c.iso_code'],
1018
            $fakeItemFrom,
1019
            [
1020
                'where' => [
1021
                    's.product_id = ? AND s.product_type = ?' => [
1022
                        $productId,
1023
                        $itemType,
1024
                    ],
1025
                ],
1026
            ]
1027
        );
1028
1029
        for ($i = 0; $i < count($items); $i++) {
0 ignored issues
show
Performance Best Practice introduced by
It seems like you are calling the size function count() as part of the test condition. You might want to compute the size beforehand, and not on each iteration.

If the size of the collection does not change during the iteration, it is generally a good practice to compute it beforehand, and not on each iteration:

for ($i=0; $i<count($array); $i++) { // calls count() on each iteration
}

// Better
for ($i=0, $c=count($array); $i<$c; $i++) { // calls count() just once
}
Loading history...
1030
            $this->setPriceSettings($items[$i], self::TAX_APPLIES_TO_ONLY_COURSE);
1031
        }
1032
1033
        if (empty($items)) {
1034
            return null;
1035
        }
1036
1037
        return $items;
1038
    }
1039
1040
    /**
1041
     * Get registered item data by duration.
1042
     *
1043
     * @param int $duration The subscription duration
1044
     */
1045
    public function getSubscriptionsItemsByDuration(int $duration): array
1046
    {
1047
        return Database::select(
1048
            '*',
1049
            Database::get_main_table(self::TABLE_SUBSCRIPTION),
1050
            [
1051
                'where' => [
1052
                    'duration = ?' => [$duration],
1053
                ],
1054
            ]
1055
        );
1056
    }
1057
1058
    /**
1059
     * Lists current user session details, including each session course detail.
1060
     *
1061
     * It can return the number of rows when $typeResult is 'count'.
1062
     *
1063
     * @param string|null $name            Optional. The name filter.
1064
     * @param int         $min             Optional. The minimum price filter.
1065
     * @param int         $max             Optional. The maximum price filter.
1066
     * @param string      $typeResult      Optional. 'all', 'first' or 'count'.
1067
     * @param int         $sessionCategory
1068
     */
1069
    public function getCatalogSessionList(int $start, int $end, ?string $name = null, int $min = 0, int $max = 0, string $typeResult = 'all', $sessionCategory = 0): array|int
1070
    {
1071
        $sessions = $this->filterSessionList($start, $end, $name, $min, $max, $typeResult, $sessionCategory);
1072
1073
        if ('count' === $typeResult) {
1074
            return $sessions;
1075
        }
1076
1077
        $sessionCatalog = [];
1078
        // loop through all sessions
1079
        foreach ($sessions as $session) {
1080
            $sessionCourses = $session->getCourses();
1081
1082
            if (empty($sessionCourses)) {
1083
                continue;
1084
            }
1085
1086
            $item = $this->getItemByProduct(
1087
                $session->getId(),
1088
                self::PRODUCT_TYPE_SESSION
1089
            );
1090
1091
            if (empty($item)) {
1092
                continue;
1093
            }
1094
1095
            $sessionData = $this->getSessionInfo($session->getId());
1096
            $sessionData['coaches'] = $session->getGeneralCoaches()->map(fn (User $coach) => $coach->getFullName());
1097
            $sessionData['enrolled'] = $this->getUserStatusForSession(
1098
                api_get_user_id(),
1099
                $session
1100
            );
1101
            $sessionData['courses'] = [];
1102
1103
            foreach ($sessionCourses as $sessionCourse) {
1104
                $course = $sessionCourse->getCourse();
1105
1106
                $sessionCourseData = [
1107
                    'title' => $course->getTitle(),
1108
                    'coaches' => [],
1109
                ];
1110
1111
                $userCourseSubscriptions = $session->getSessionRelCourseRelUsersByStatus(
1112
                    $course,
1113
                    Session::COURSE_COACH
1114
                );
1115
1116
                foreach ($userCourseSubscriptions as $userCourseSubscription) {
1117
                    $user = $userCourseSubscription->getUser();
1118
                    $sessionCourseData['coaches'][] = $user->getFullName();
1119
                }
1120
                $sessionData['courses'][] = $sessionCourseData;
1121
            }
1122
1123
            $sessionCatalog[] = $sessionData;
1124
        }
1125
1126
        return $sessionCatalog;
1127
    }
1128
1129
    /**
1130
     * Lists current user course details.
1131
     *
1132
     * @param string $name Optional. The name filter
1133
     * @param int    $min  Optional. The minimum price filter
1134
     * @param int    $max  Optional. The maximum price filter
1135
     */
1136
    public function getCatalogCourseList(int $first, int $pageSize, ?string $name = null, int $min = 0, int $max = 0, string $typeResult = 'all'): array|int
1137
    {
1138
        $courses = $this->filterCourseList($first, $pageSize, $name, $min, $max, $typeResult);
1139
1140
        if ('count' === $typeResult) {
1141
            return $courses;
1142
        }
1143
1144
        if (empty($courses)) {
1145
            return [];
1146
        }
1147
1148
        $courseCatalog = [];
1149
        foreach ($courses as $course) {
1150
            $item = $this->getItemByProduct(
1151
                $course->getId(),
1152
                self::PRODUCT_TYPE_COURSE
1153
            );
1154
1155
            if (empty($item)) {
1156
                continue;
1157
            }
1158
1159
            $courseItem = [
1160
                'id' => $course->getId(),
1161
                'title' => $course->getTitle(),
1162
                'code' => $course->getCode(),
1163
                'course_img' => null,
1164
                'item' => $item,
1165
                'teachers' => [],
1166
                'enrolled' => $this->getUserStatusForCourse(api_get_user_id(), $course),
1167
            ];
1168
1169
            foreach ($course->getTeachersSubscriptions() as $courseUser) {
1170
                /** @var CourseRelUser $courseUser */
1171
                $teacher = $courseUser->getUser();
1172
                $courseItem['teachers'][] = $teacher->getFullName();
1173
            }
1174
1175
            // Check images
1176
            $imgUrl = $this->getCourseIllustrationUrl($course);
1177
            if (!empty($imgUrl)) {
1178
                $courseItem['course_img'] = $imgUrl;
1179
            }
1180
            $courseCatalog[] = $courseItem;
1181
        }
1182
1183
        return $courseCatalog;
1184
    }
1185
1186
    /**
1187
     * Returns the course illustration URL or null if none.
1188
     */
1189
    private function getCourseIllustrationUrl(Course $course): ?string
1190
    {
1191
        $illustrationRepo = Container::getIllustrationRepository();
1192
1193
        $url = $illustrationRepo->getIllustrationUrl($course, 'course_picture_medium');
1194
1195
        if (empty($url)) {
1196
            $url = $illustrationRepo->getIllustrationUrl($course, 'course_picture_original');
1197
        }
1198
1199
        return $url ?: null;
1200
    }
1201
1202
    /**
1203
     * Lists current user subscription session details, including each session course details.
1204
     *
1205
     * It can return the number of rows when $typeResult is 'count'.
1206
     *
1207
     * @param int    $start           pagination start
1208
     * @param int    $end             pagination end
1209
     * @param string $name            Optional. The name filter.
1210
     * @param string $typeResult      Optional. 'all', 'first' or 'count'.
1211
     * @param int    $sessionCategory Optional. Session category id
1212
     *
1213
     * @return array|int
1214
     */
1215
    public function getCatalogSubscriptionSessionList(int $start, int $end, ?string $name = null, string $typeResult = 'all', int $sessionCategory = 0)
1216
    {
1217
        $sessions = $this->filterSubscriptionSessionList($start, $end, $name, $typeResult, $sessionCategory);
1218
1219
        if ('count' === $typeResult) {
1220
            return $sessions;
1221
        }
1222
1223
        $sessionCatalog = [];
1224
        // loop through all sessions
1225
        foreach ($sessions as $session) {
1226
            $sessionCourses = $session->getCourses();
1227
1228
            if (empty($sessionCourses)) {
1229
                continue;
1230
            }
1231
1232
            $item = $this->getSubscriptionItemByProduct(
1233
                $session->getId(),
1234
                self::PRODUCT_TYPE_SESSION
1235
            );
1236
1237
            if (empty($item)) {
1238
                continue;
1239
            }
1240
1241
            $sessionData = $this->getSubscriptionSessionInfo($session->getId());
1242
            $sessionData['coaches'] = $session->getGeneralCoaches()->map(fn (User $coach) => $coach->getFullName());
1243
            $sessionData['enrolled'] = $this->getUserStatusForSubscriptionSession(
1244
                api_get_user_id(),
1245
                $session
1246
            );
1247
            $sessionData['courses'] = [];
1248
1249
            foreach ($sessionCourses as $sessionCourse) {
1250
                $course = $sessionCourse->getCourse();
1251
1252
                $sessionCourseData = [
1253
                    'title' => $course->getTitle(),
1254
                    'coaches' => [],
1255
                ];
1256
1257
                $userCourseSubscriptions = $session->getSessionRelCourseRelUsersByStatus(
1258
                    $course,
1259
                    Session::COURSE_COACH
1260
                );
1261
1262
                foreach ($userCourseSubscriptions as $userCourseSubscription) {
1263
                    $user = $userCourseSubscription->getUser();
1264
                    $sessionCourseData['coaches'][] = $user->getFullName();
1265
                }
1266
                $sessionData['courses'][] = $sessionCourseData;
1267
            }
1268
1269
            $sessionCatalog[] = $sessionData;
1270
        }
1271
1272
        return $sessionCatalog;
1273
    }
1274
1275
    /**
1276
     * Lists current user subscription course details.
1277
     *
1278
     * @param string $typeResult Optional. 'all', 'first' or 'count'.
1279
     *
1280
     * @return array|int
1281
     */
1282
    public function getCatalogSubscriptionCourseList(int $first, int $pageSize, ?string $name = null, string $typeResult = 'all')
1283
    {
1284
        $courses = $this->filterSubscriptionCourseList($first, $pageSize, $name, $typeResult);
1285
1286
        if ('count' === $typeResult) {
1287
            return $courses;
1288
        }
1289
1290
        if (empty($courses)) {
1291
            return [];
1292
        }
1293
1294
        $courseCatalog = [];
1295
        foreach ($courses as $course) {
1296
            $item = $this->getSubscriptionItemByProduct(
1297
                $course->getId(),
1298
                self::PRODUCT_TYPE_COURSE
1299
            );
1300
1301
            if (empty($item)) {
1302
                continue;
1303
            }
1304
1305
            $courseItem = [
1306
                'id' => $course->getId(),
1307
                'title' => $course->getTitle(),
1308
                'code' => $course->getCode(),
1309
                'course_img' => null,
1310
                'item' => $item,
1311
                'teachers' => [],
1312
                'enrolled' => $this->getUserStatusForSubscriptionCourse(api_get_user_id(), $course),
1313
            ];
1314
1315
            foreach ($course->getTeachersSubscriptions() as $courseUser) {
1316
                $teacher = $courseUser->getUser();
1317
                $courseItem['teachers'][] = $teacher->getFullName();
1318
            }
1319
1320
            // Check images
1321
            $imgUrl = $this->getCourseIllustrationUrl($course);
1322
            if (!empty($imgUrl)) {
1323
                $courseItem['course_img'] = $imgUrl;
1324
            }
1325
            $courseCatalog[] = $courseItem;
1326
        }
1327
1328
        return $courseCatalog;
1329
    }
1330
1331
    public function getPriceWithCurrencyFromIsoCode(float $price, string $isoCode): string
1332
    {
1333
        $useSymbol = 'true' === $this->get('use_currency_symbol');
1334
1335
        $result = $isoCode.' '.$price;
1336
        if ($useSymbol) {
1337
            $symbol = 'BRL' === $isoCode ? 'R$' : Currencies::getSymbol($isoCode);
1338
            $result = $symbol.' '.$price;
1339
        }
1340
1341
        return $result;
1342
    }
1343
1344
    /**
1345
     * Get course info.
1346
     *
1347
     * @return array
1348
     * @throws \Doctrine\ORM\NonUniqueResultException
1349
     */
1350
    public function getCourseInfo(int $courseId, ?array $coupon = null)
1351
    {
1352
        $entityManager = Database::getManager();
1353
        $course = $entityManager->find(Course::class, $courseId);
1354
1355
        if (empty($course)) {
1356
            return [];
1357
        }
1358
1359
        $item = $this->getItemByProduct(
1360
            $course->getId(),
1361
            self::PRODUCT_TYPE_COURSE,
1362
            $coupon
1363
        );
1364
1365
        if (empty($item)) {
1366
            return [];
1367
        }
1368
1369
        /** @var CCourseDescription $courseDescription */
1370
        $courseDescription = Container::getCourseDescriptionRepository()
1371
            ->getResourcesByCourse($course)
1372
            ->addOrderBy('descriptionType', 'ASC')
1373
            ->setMaxResults(1)
1374
            ->getQuery()
1375
            ->getOneOrNullResult()
1376
        ;
1377
1378
        $globalParameters = $this->getGlobalParameters();
1379
        $courseInfo = [
1380
            'id' => $course->getId(),
1381
            'title' => $course->getTitle(),
1382
            'description' => $courseDescription?->getContent(),
1383
            'code' => $course->getCode(),
1384
            'visual_code' => $course->getVisualCode(),
1385
            'teachers' => [],
1386
            'item' => $item,
1387
            'tax_name' => $globalParameters['tax_name'],
1388
            'tax_enable' => $this->checkTaxEnabledInProduct(self::TAX_APPLIES_TO_ONLY_COURSE),
1389
            'course_img' => null,
1390
        ];
1391
1392
        foreach ($course->getTeachersSubscriptions() as $teachers) {
1393
            $user = $teachers->getUser();
1394
            $courseInfo['teachers'][] = [
1395
                'id' => $user->getId(),
1396
                'name' => $user->getFullName(),
1397
            ];
1398
        }
1399
1400
        $imgUrl = $this->getCourseIllustrationUrl($course);
1401
        if (!empty($imgUrl)) {
1402
            $courseInfo['course_img'] = $imgUrl;
1403
        }
1404
1405
        return $courseInfo;
1406
    }
1407
1408
    /**
1409
     * Get session info.
1410
     *
1411
     * @return array
1412
     */
1413
    public function getSessionInfo(int $sessionId, ?array $coupon = null)
1414
    {
1415
        $entityManager = Database::getManager();
1416
        $session = $entityManager->find(Session::class, $sessionId);
1417
1418
        if (empty($session)) {
1419
            return [];
1420
        }
1421
1422
        $item = $this->getItemByProduct(
1423
            $session->getId(),
1424
            self::PRODUCT_TYPE_SESSION,
1425
            $coupon
1426
        );
1427
1428
        if (empty($item)) {
1429
            return [];
1430
        }
1431
1432
        $sessionDates = SessionManager::parseSessionDates($session);
1433
1434
        $globalParameters = $this->getGlobalParameters();
1435
        $sessionInfo = [
1436
            'id' => $session->getId(),
1437
            'name' => $session->getName(),
1438
            'description' => $session->getDescription(),
1439
            'dates' => $sessionDates,
1440
            'courses' => [],
1441
            'tax_name' => $globalParameters['tax_name'],
1442
            'tax_enable' => $this->checkTaxEnabledInProduct(self::TAX_APPLIES_TO_ONLY_SESSION),
1443
            'image' => null,
1444
            'nbrCourses' => $session->getNbrCourses(),
1445
            'nbrUsers' => $session->getNbrUsers(),
1446
            'item' => $item,
1447
            'duration' => $session->getDuration(),
1448
        ];
1449
1450
        $imgUrl = $this->getSessionIllustrationUrl($session);
1451
        if (!empty($imgUrl)) {
1452
            $sessionInfo['image'] = $imgUrl;
1453
        }
1454
1455
        $sessionCourses = $session->getCourses();
1456
        foreach ($sessionCourses as $sessionCourse) {
1457
            $course = $sessionCourse->getCourse();
1458
            $sessionCourseData = [
1459
                'title' => $course->getTitle(),
1460
                'coaches' => [],
1461
            ];
1462
1463
            $userCourseSubscriptions = $session->getSessionRelCourseRelUsersByStatus(
1464
                $course,
1465
                Session::COURSE_COACH
1466
            );
1467
1468
            foreach ($userCourseSubscriptions as $userCourseSubscription) {
1469
                $user = $userCourseSubscription->getUser();
1470
                $coaches['id'] = $user->getId();
1471
                $coaches['name'] = $user->getFullName();
1472
                $sessionCourseData['coaches'][] = $coaches;
1473
            }
1474
1475
            $sessionInfo['courses'][] = $sessionCourseData;
1476
        }
1477
1478
        return $sessionInfo;
1479
    }
1480
1481
    /**
1482
     * Returns the session picture URL or null if none.
1483
     */
1484
    private function getSessionIllustrationUrl(Session $session): ?string
1485
    {
1486
        $assetRepo = Container::getAssetRepository();
1487
        $asset = $session->getImage(); // Asset|null
1488
1489
        if (!$asset) {
1490
            return null;
1491
        }
1492
1493
        $url = $assetRepo->getAssetUrl($asset);
1494
1495
        return $url ?: null;
1496
    }
1497
1498
    /**
1499
     * Get course info.
1500
     *
1501
     * @throws \Doctrine\ORM\NonUniqueResultException
1502
     */
1503
    public function getSubscriptionCourseInfo(int $courseId, ?array $coupon = null): array
1504
    {
1505
        $entityManager = Database::getManager();
1506
        $course = $entityManager->find(Course::class, $courseId);
1507
1508
        if (empty($course)) {
1509
            return [];
1510
        }
1511
1512
        $item = $this->getSubscriptionItemByProduct(
1513
            $course->getId(),
1514
            self::PRODUCT_TYPE_COURSE,
1515
            $coupon
1516
        );
1517
1518
        if (empty($item)) {
1519
            return [];
1520
        }
1521
1522
        /** @var CCourseDescription $courseDescription */
1523
        $courseDescription = Container::getCourseDescriptionRepository()
1524
            ->getResourcesByCourse($course)
1525
            ->addOrderBy('descriptionType', 'ASC')
1526
            ->setMaxResults(1)
1527
            ->getQuery()
1528
            ->getOneOrNullResult()
1529
        ;
1530
1531
        $globalParameters = $this->getGlobalParameters();
1532
        $courseInfo = [
1533
            'id' => $course->getId(),
1534
            'title' => $course->getTitle(),
1535
            'description' => $courseDescription?->getContent(),
1536
            'code' => $course->getCode(),
1537
            'visual_code' => $course->getVisualCode(),
1538
            'teachers' => [],
1539
            'item' => $item,
1540
            'tax_name' => $globalParameters['tax_name'],
1541
            'tax_enable' => $this->checkTaxEnabledInProduct(self::TAX_APPLIES_TO_ONLY_COURSE),
1542
            'course_img' => null,
1543
        ];
1544
1545
        $courseTeachers = $course->getTeachersSubscriptions();
1546
1547
        foreach ($courseTeachers as $teachers) {
1548
            $user = $teachers->getUser();
1549
            $teacher['id'] = $user->getId();
1550
            $teacher['name'] = $user->getFullName();
1551
            $courseInfo['teachers'][] = $teacher;
1552
        }
1553
1554
        $imgUrl = $this->getCourseIllustrationUrl($course);
1555
1556
        if (!empty($imgUrl)) {
1557
            $courseInfo['course_img'] = $imgUrl;
1558
        }
1559
1560
        return $courseInfo;
1561
    }
1562
1563
    /**
1564
     * Get session info.
1565
     *
1566
     * @param array $sessionId The session ID
1567
     *
1568
     * @return array
1569
     */
1570
    public function getSubscriptionSessionInfo(int $sessionId, ?array $coupon = null)
1571
    {
1572
        $entityManager = Database::getManager();
1573
        $session = $entityManager->find(Session::class, $sessionId);
1574
1575
        if (empty($session)) {
1576
            return [];
1577
        }
1578
1579
        $item = $this->getSubscriptionItemByProduct(
1580
            $session->getId(),
1581
            self::PRODUCT_TYPE_SESSION,
1582
            $coupon
1583
        );
1584
1585
        if (empty($item)) {
1586
            return [];
1587
        }
1588
1589
        $sessionDates = SessionManager::parseSessionDates($session);
1590
1591
        $globalParameters = $this->getGlobalParameters();
1592
        $sessionInfo = [
1593
            'id' => $session->getId(),
1594
            'name' => $session->getName(),
1595
            'description' => $session->getDescription(),
1596
            'dates' => $sessionDates,
1597
            'courses' => [],
1598
            'tax_name' => $globalParameters['tax_name'],
1599
            'tax_enable' => $this->checkTaxEnabledInProduct(self::TAX_APPLIES_TO_ONLY_SESSION),
1600
            'image' => null,
1601
            'nbrCourses' => $session->getNbrCourses(),
1602
            'nbrUsers' => $session->getNbrUsers(),
1603
            'item' => $item,
1604
            'duration' => $session->getDuration(),
1605
        ];
1606
1607
        $imgUrl = $this->getSessionIllustrationUrl($session);
1608
        if (!empty($imgUrl)) {
1609
            $sessionInfo['image'] = $imgUrl;
1610
        }
1611
1612
        $sessionCourses = $session->getCourses();
1613
        foreach ($sessionCourses as $sessionCourse) {
1614
            $course = $sessionCourse->getCourse();
1615
            $sessionCourseData = [
1616
                'title' => $course->getTitle(),
1617
                'coaches' => [],
1618
            ];
1619
1620
            $userCourseSubscriptions = $session->getSessionRelCourseRelUsersByStatus(
1621
                $course,
1622
                Session::COURSE_COACH
1623
            );
1624
1625
            foreach ($userCourseSubscriptions as $userCourseSubscription) {
1626
                $user = $userCourseSubscription->getUser();
1627
                $coaches['id'] = $user->getId();
1628
                $coaches['name'] = $user->getFullName();
1629
                $sessionCourseData['coaches'][] = $coaches;
1630
            }
1631
1632
            $sessionInfo['courses'][] = $sessionCourseData;
1633
        }
1634
1635
        return $sessionInfo;
1636
    }
1637
1638
    /**
1639
     * Register a sale.
1640
     *
1641
     * @param int $itemId      The product ID
1642
     * @param int $paymentType The payment type
1643
     * @param int $couponId    The coupon ID
1644
     */
1645
    public function registerSale(int $itemId, int $paymentType, ?int $couponId = null): ?int
1646
    {
1647
        if (!in_array(
1648
            $paymentType,
1649
            [
1650
                self::PAYMENT_TYPE_PAYPAL,
1651
                self::PAYMENT_TYPE_TRANSFER,
1652
                self::PAYMENT_TYPE_CULQI,
1653
                self::PAYMENT_TYPE_TPV_REDSYS,
1654
                self::PAYMENT_TYPE_STRIPE,
1655
                self::PAYMENT_TYPE_TPV_CECABANK,
1656
            ]
1657
        )
1658
        ) {
1659
            return null;
1660
        }
1661
1662
        $entityManager = Database::getManager();
1663
        $item = $this->getItem($itemId);
1664
1665
        if (empty($item)) {
1666
            return null;
1667
        }
1668
1669
        $productName = '';
1670
        if (self::PRODUCT_TYPE_COURSE == $item['product_type']) {
1671
            $course = $entityManager->find(Course::class, $item['product_id']);
1672
1673
            if (empty($course)) {
1674
                return null;
1675
            }
1676
1677
            $productName = $course->getTitle();
1678
        } elseif (self::PRODUCT_TYPE_SESSION == $item['product_type']) {
1679
            $session = $entityManager->find(Session::class, $item['product_id']);
1680
1681
            if (empty($session)) {
1682
                return null;
1683
            }
1684
1685
            $productName = $session->getName();
1686
        }
1687
1688
        $coupon = null;
1689
1690
        if (null != $couponId) {
1691
            $coupon = $this->getCoupon($couponId, $item['product_type'], $item['product_id']);
1692
        }
1693
1694
        $couponDiscount = 0;
1695
        $priceWithoutDiscount = 0;
1696
        if (null != $coupon) {
1697
            if (self::COUPON_DISCOUNT_TYPE_AMOUNT == $coupon['discount_type']) {
1698
                $couponDiscount = $coupon['discount_amount'];
1699
            } elseif (self::COUPON_DISCOUNT_TYPE_PERCENTAGE == $coupon['discount_type']) {
1700
                $couponDiscount = ($item['price'] * $coupon['discount_amount']) / 100;
1701
            }
1702
            $priceWithoutDiscount = $item['price'];
1703
        }
1704
        $item['price'] -= $couponDiscount;
1705
        $price = $item['price'];
1706
        $priceWithoutTax = null;
1707
        $taxPerc = null;
1708
        $taxAmount = 0;
1709
        $taxEnable = 'true' === $this->get('tax_enable');
1710
        $globalParameters = $this->getGlobalParameters();
1711
        $taxAppliesTo = $globalParameters['tax_applies_to'];
1712
1713
        if ($taxEnable
1714
            && (
1715
                self::TAX_APPLIES_TO_ALL == $taxAppliesTo
1716
                || (self::TAX_APPLIES_TO_ONLY_COURSE == $taxAppliesTo && self::PRODUCT_TYPE_COURSE == $item['product_type'])
1717
                || (self::TAX_APPLIES_TO_ONLY_SESSION == $taxAppliesTo && self::PRODUCT_TYPE_SESSION == $item['product_type'])
1718
            )
1719
        ) {
1720
            $priceWithoutTax = $item['price'];
1721
            $globalTaxPerc = $globalParameters['global_tax_perc'];
1722
            $precision = 2;
1723
            $taxPerc = null === $item['tax_perc'] ? $globalTaxPerc : $item['tax_perc'];
1724
            $taxAmount = round($priceWithoutTax * $taxPerc / 100, $precision);
1725
            $price = $priceWithoutTax + $taxAmount;
1726
        }
1727
1728
        $values = [
1729
            'reference' => $this->generateReference(
1730
                api_get_user_id(),
1731
                $item['product_type'],
1732
                $item['product_id']
1733
            ),
1734
            'currency_id' => $item['currency_id'],
1735
            'date' => api_get_utc_datetime(),
1736
            'user_id' => api_get_user_id(),
1737
            'product_type' => $item['product_type'],
1738
            'product_name' => $productName,
1739
            'product_id' => $item['product_id'],
1740
            'price' => $price,
1741
            'price_without_tax' => $priceWithoutTax,
1742
            'tax_perc' => $taxPerc,
1743
            'tax_amount' => $taxAmount,
1744
            'status' => self::SALE_STATUS_PENDING,
1745
            'payment_type' => $paymentType,
1746
            'price_without_discount' => $priceWithoutDiscount,
1747
            'discount_amount' => $couponDiscount,
1748
        ];
1749
1750
        return Database::insert(self::TABLE_SALE, $values);
1751
    }
1752
1753
    /**
1754
     * Update the sale reference.
1755
     *
1756
     * @return bool
1757
     */
1758
    public function updateSaleReference(int $saleId, string $saleReference)
1759
    {
1760
        $saleTable = Database::get_main_table(self::TABLE_SALE);
1761
1762
        return Database::update(
1763
            $saleTable,
1764
            ['reference' => $saleReference],
1765
            ['id = ?' => $saleId]
1766
        );
1767
    }
1768
1769
    /**
1770
     * Get sale data by ID.
1771
     *
1772
     * @return array
1773
     */
1774
    public function getSale(int $saleId)
1775
    {
1776
        return Database::select(
1777
            '*',
1778
            Database::get_main_table(self::TABLE_SALE),
1779
            [
1780
                'where' => ['id = ?' => (int) $saleId],
1781
            ],
1782
            'first'
1783
        );
1784
    }
1785
1786
    /**
1787
     * Get sale data by reference.
1788
     *
1789
     * @return array
1790
     */
1791
    public function getSaleFromReference(string $reference)
1792
    {
1793
        return Database::select(
1794
            '*',
1795
            Database::get_main_table(self::TABLE_SALE),
1796
            [
1797
                'where' => ['reference = ?' => $reference],
1798
            ],
1799
            'first'
1800
        );
1801
    }
1802
1803
    /**
1804
     * Get a list of sales by the payment type.
1805
     *
1806
     * @param int $paymentType The payment type to filter (default : Paypal)
1807
     *
1808
     * @return array The sale list. Otherwise, return false
1809
     */
1810
    public function getSaleListByPaymentType(int $paymentType = self::PAYMENT_TYPE_PAYPAL)
1811
    {
1812
        $saleTable = Database::get_main_table(self::TABLE_SALE);
1813
        $currencyTable = Database::get_main_table(self::TABLE_CURRENCY);
1814
        $userTable = Database::get_main_table(TABLE_MAIN_USER);
1815
1816
        $innerJoins = "
1817
            INNER JOIN $currencyTable c ON s.currency_id = c.id
1818
            INNER JOIN $userTable u ON s.user_id = u.id
1819
        ";
1820
1821
        return Database::select(
1822
            ['c.iso_code', 'u.firstname', 'u.lastname', 's.*'],
1823
            "$saleTable s $innerJoins",
1824
            [
1825
                'where' => [
1826
                    's.payment_type = ? AND s.status = ?' => [
1827
                        $paymentType,
1828
                        self::SALE_STATUS_COMPLETED,
1829
                    ],
1830
                ],
1831
                'order' => 'id DESC',
1832
            ]
1833
        );
1834
    }
1835
1836
    /**
1837
     * Get data of sales.
1838
     *
1839
     * @param int $saleId    The sale id
1840
     * @param int $isService Check if a service
1841
     *
1842
     * @return array The sale data
1843
     */
1844
    public function getDataSaleInvoice(int $saleId, int $isService)
1845
    {
1846
        if ($isService) {
1847
            $sale = $this->getServiceSale($saleId);
1848
            $sale['reference'] = $sale['reference'];
1849
            $sale['product_name'] = $sale['service']['name'];
1850
            $sale['payment_type'] = $sale['payment_type'];
1851
            $sale['user_id'] = $sale['buyer']['id'];
1852
            $sale['date'] = $sale['buy_date'];
1853
        } else {
1854
            $sale = $this->getSale($saleId);
1855
        }
1856
1857
        return $sale;
1858
    }
1859
1860
    /**
1861
     * Get data of invoice.
1862
     *
1863
     * @param int $saleId    The sale id
1864
     * @param int $isService Check if a service
1865
     *
1866
     * @return array The invoice data
1867
     */
1868
    public function getDataInvoice(int $saleId, int $isService)
1869
    {
1870
        return Database::select(
1871
            '*',
1872
            Database::get_main_table(self::TABLE_INVOICE),
1873
            [
1874
                'where' => [
1875
                    'sale_id = ? AND ' => (int) $saleId,
1876
                    'is_service = ?' => (int) $isService,
1877
                ],
1878
            ],
1879
            'first'
1880
        );
1881
    }
1882
1883
    /**
1884
     * Get invoice numbering.
1885
     *
1886
     * @param int $saleId    The sale id
1887
     * @param int $isService Check if a service
1888
     *
1889
     * @return string
1890
     */
1891
    public function getNumInvoice(int $saleId, int $isService)
1892
    {
1893
        $dataInvoice = $this->getDataInvoice($saleId, $isService);
1894
        if (empty($dataInvoice)) {
1895
            return '-';
1896
        }
1897
1898
        return $dataInvoice['serie'].$dataInvoice['year'].'/'.$dataInvoice['num_invoice'];
1899
    }
1900
1901
    /**
1902
     * Get currency data by ID.
1903
     *
1904
     * @param int $currencyId The currency ID
1905
     *
1906
     * @return array
1907
     */
1908
    public function getCurrency(int $currencyId)
1909
    {
1910
        return Database::select(
1911
            '*',
1912
            Database::get_main_table(self::TABLE_CURRENCY),
1913
            [
1914
                'where' => ['id = ?' => $currencyId],
1915
            ],
1916
            'first'
1917
        );
1918
    }
1919
1920
    /**
1921
     * Complete sale process. Update sale status to completed.
1922
     *
1923
     * @return bool
1924
     */
1925
    public function completeSale(int $saleId)
1926
    {
1927
        $sale = $this->getSale($saleId);
1928
1929
        if (self::SALE_STATUS_COMPLETED == $sale['status']) {
1930
            return true;
1931
        }
1932
1933
        $saleIsCompleted = false;
1934
1935
        switch ($sale['product_type']) {
1936
            case self::PRODUCT_TYPE_COURSE:
1937
                $course = api_get_course_info_by_id($sale['product_id']);
1938
                $saleIsCompleted = CourseManager::subscribeUser($sale['user_id'], $course['code']);
1939
1940
                break;
1941
1942
            case self::PRODUCT_TYPE_SESSION:
1943
                SessionManager::subscribeUsersToSession(
1944
                    $sale['product_id'],
1945
                    [$sale['user_id']],
1946
                    api_get_session_visibility($sale['product_id']),
1947
                    false
1948
                );
1949
1950
                $saleIsCompleted = true;
1951
1952
                break;
1953
        }
1954
1955
        if ($saleIsCompleted) {
1956
            $this->updateSaleStatus($sale['id'], self::SALE_STATUS_COMPLETED);
1957
            if ('true' === $this->get('invoicing_enable')) {
1958
                $this->setInvoice($sale['id']);
1959
            }
1960
        }
1961
1962
        return $saleIsCompleted;
1963
    }
1964
1965
    /**
1966
     * Update sale status to canceled.
1967
     *
1968
     * @param int $saleId The sale ID
1969
     */
1970
    public function cancelSale(int $saleId): void
1971
    {
1972
        $this->updateSaleStatus($saleId, self::SALE_STATUS_CANCELED);
1973
    }
1974
1975
    /**
1976
     * Get payment types.
1977
     */
1978
    public function getPaymentTypes(bool $onlyActive = false): array
1979
    {
1980
        $types = [
1981
            self::PAYMENT_TYPE_PAYPAL => 'PayPal',
1982
            self::PAYMENT_TYPE_TRANSFER => $this->get_lang('BankTransfer'),
1983
            self::PAYMENT_TYPE_CULQI => 'Culqi',
1984
            self::PAYMENT_TYPE_TPV_REDSYS => $this->get_lang('TpvPayment'),
1985
            self::PAYMENT_TYPE_STRIPE => 'Stripe',
1986
            self::PAYMENT_TYPE_TPV_CECABANK => $this->get_lang('TpvCecabank'),
1987
        ];
1988
1989
        if (!$onlyActive) {
1990
            return $types;
1991
        }
1992
1993
        if ('true' !== $this->get('paypal_enable')) {
1994
            unset($types[self::PAYMENT_TYPE_PAYPAL]);
1995
        }
1996
1997
        if ('true' !== $this->get('transfer_enable')) {
1998
            unset($types[self::PAYMENT_TYPE_TRANSFER]);
1999
        }
2000
2001
        if ('true' !== $this->get('culqi_enable')) {
2002
            unset($types[self::PAYMENT_TYPE_CULQI]);
2003
        }
2004
2005
        if ('true' !== $this->get('tpv_redsys_enable')
2006
            || !file_exists(api_get_path(SYS_PLUGIN_PATH).'buycourses/resources/apiRedsys.php')
2007
        ) {
2008
            unset($types[self::PAYMENT_TYPE_TPV_REDSYS]);
2009
        }
2010
2011
        if ('true' !== $this->get('stripe_enable')) {
2012
            unset($types[self::PAYMENT_TYPE_STRIPE]);
2013
        }
2014
2015
        if ('true' !== $this->get('cecabank_enable')) {
2016
            unset($types[self::PAYMENT_TYPE_TPV_CECABANK]);
2017
        }
2018
2019
        return $types;
2020
    }
2021
2022
    /**
2023
     * Register a invoice.
2024
     *
2025
     * @param int $saleId    The sale ID
2026
     * @param int $isService The service type to filter (default : 0)
2027
     */
2028
    public function setInvoice(int $saleId, int $isService = 0): void
2029
    {
2030
        $invoiceTable = Database::get_main_table(self::TABLE_INVOICE);
2031
        $year = date('Y');
2032
2033
        $globalParameters = $this->getGlobalParameters();
2034
        $numInvoice = $globalParameters['next_number_invoice'];
2035
        $serie = $globalParameters['invoice_series'];
2036
2037
        if (empty($numInvoice)) {
2038
            $item = Database::select(
2039
                ['MAX(num_invoice) AS num_invoice'],
2040
                $invoiceTable,
2041
                [
2042
                    'where' => ['year = ?' => $year],
2043
                ],
2044
                'first'
2045
            );
2046
2047
            $numInvoice = 1;
2048
            if (false !== $item) {
2049
                $numInvoice = (int) ($item['num_invoice'] + 1);
2050
            }
2051
        } else {
2052
            Database::update(
2053
                Database::get_main_table(self::TABLE_GLOBAL_CONFIG),
2054
                ['next_number_invoice' => 0],
2055
                ['id = ?' => 1]
2056
            );
2057
        }
2058
2059
        Database::insert(
2060
            $invoiceTable,
2061
            [
2062
                'sale_id' => $saleId,
2063
                'is_service' => $isService,
2064
                'num_invoice' => $numInvoice,
2065
                'year' => $year,
2066
                'serie' => $serie,
2067
                'date_invoice' => api_get_utc_datetime(),
2068
            ]
2069
        );
2070
2071
        // Record invoice in the sales table
2072
        $table = Database::get_main_table(self::TABLE_SALE);
2073
        if (!empty($isService)) {
2074
            $table = Database::get_main_table(self::TABLE_SERVICES_SALE);
2075
        }
2076
2077
        Database::update(
2078
            $table,
2079
            ['invoice' => 1],
2080
            ['id = ?' => $saleId]
2081
        );
2082
    }
2083
2084
    /**
2085
     * Get Tax's types.
2086
     *
2087
     * @return array
2088
     */
2089
    public function getTaxAppliesTo()
2090
    {
2091
        return [
2092
            self::TAX_APPLIES_TO_ALL => $this->get_lang('AllCoursesSessionsAndServices'),
2093
            self::TAX_APPLIES_TO_ONLY_COURSE => $this->get_lang('OnlyCourses'),
2094
            self::TAX_APPLIES_TO_ONLY_SESSION => $this->get_lang('OnlySessions'),
2095
            self::TAX_APPLIES_TO_ONLY_SERVICES => $this->get_lang('OnlyServices'),
2096
        ];
2097
    }
2098
2099
    /**
2100
     * Get a list of sales by the status.
2101
     *
2102
     * @param int $status The status to filter
2103
     *
2104
     * @return array The sale list. Otherwise, return false
2105
     */
2106
    public function getSaleListByStatus(int $status = self::SALE_STATUS_PENDING)
2107
    {
2108
        $saleTable = Database::get_main_table(self::TABLE_SALE);
2109
        $currencyTable = Database::get_main_table(self::TABLE_CURRENCY);
2110
        $userTable = Database::get_main_table(TABLE_MAIN_USER);
2111
2112
        $innerJoins = "
2113
            INNER JOIN $currencyTable c ON s.currency_id = c.id
2114
            INNER JOIN $userTable u ON s.user_id = u.id
2115
        ";
2116
2117
        return Database::select(
2118
            ['c.iso_code', 'u.firstname', 'u.lastname', 'u.email', 's.*'],
2119
            "$saleTable s $innerJoins",
2120
            [
2121
                'where' => ['s.status = ?' => $status],
2122
                'order' => 'id DESC',
2123
            ]
2124
        );
2125
    }
2126
2127
    /**
2128
     * Get the list statuses for sales.
2129
     *
2130
     * @return array
2131
     *
2132
     * @throws Exception
2133
     */
2134
    public function getSaleListReport(?string $dateStart = null, ?string $dateEnd = null)
2135
    {
2136
        $saleTable = Database::get_main_table(self::TABLE_SALE);
2137
        $currencyTable = Database::get_main_table(self::TABLE_CURRENCY);
2138
        $userTable = Database::get_main_table(TABLE_MAIN_USER);
2139
        $innerJoins = "
2140
            INNER JOIN $currencyTable c ON s.currency_id = c.id
2141
            INNER JOIN $userTable u ON s.user_id = u.id
2142
        ";
2143
        $list = Database::select(
2144
            ['c.iso_code', 'u.firstname', 'u.lastname', 'u.email', 's.*'],
2145
            "$saleTable s $innerJoins",
2146
            [
2147
                'order' => 'id DESC',
2148
            ]
2149
        );
2150
        $listExportTemp = [];
2151
        $listExport = [];
2152
        $textStatus = null;
2153
        $paymentTypes = $this->getPaymentTypes();
2154
        $productTypes = $this->getProductTypes();
2155
        foreach ($list as $item) {
2156
            $statusSaleOrder = $item['status'];
2157
2158
            switch ($statusSaleOrder) {
2159
                case 0:
2160
                    $textStatus = $this->get_lang('SaleStatusPending');
2161
2162
                    break;
2163
2164
                case 1:
2165
                    $textStatus = $this->get_lang('SaleStatusCompleted');
2166
2167
                    break;
2168
2169
                case -1:
2170
                    $textStatus = $this->get_lang('SaleStatusCanceled');
2171
2172
                    break;
2173
            }
2174
            $dateFilter = new DateTime($item['date']);
2175
            $listExportTemp[] = [
2176
                'id' => $item['id'],
2177
                'reference' => $item['reference'],
2178
                'status' => $textStatus,
2179
                'status_filter' => $item['status'],
2180
                'date' => $dateFilter->format('Y-m-d'),
2181
                'order_time' => $dateFilter->format('H:i:s'),
2182
                'price' => $item['iso_code'].' '.$item['price'],
2183
                'product_type' => $productTypes[$item['product_type']],
2184
                'product_name' => $item['product_name'],
2185
                'payment_type' => $paymentTypes[$item['payment_type']],
2186
                'complete_user_name' => api_get_person_name($item['firstname'], $item['lastname']),
2187
                'email' => $item['email'],
2188
            ];
2189
        }
2190
        $listExport[] = [
2191
            get_lang('Number'),
2192
            $this->get_lang('OrderStatus'),
2193
            $this->get_lang('OrderDate'),
2194
            $this->get_lang('OrderTime'),
2195
            $this->get_lang('PaymentMethod'),
2196
            $this->get_lang('SalePrice'),
2197
            $this->get_lang('ProductType'),
2198
            $this->get_lang('ProductName'),
2199
            $this->get_lang('UserName'),
2200
            get_lang('Email'),
2201
        ];
2202
        // Validation Export
2203
        $dateStart = strtotime($dateStart);
2204
        $dateEnd = strtotime($dateEnd);
2205
        foreach ($listExportTemp as $item) {
2206
            $dateFilter = strtotime($item['date']);
2207
            if (($dateFilter >= $dateStart) && ($dateFilter <= $dateEnd)) {
2208
                $listExport[] = [
2209
                    'id' => $item['id'],
2210
                    'status' => $item['status'],
2211
                    'date' => $item['date'],
2212
                    'order_time' => $item['order_time'],
2213
                    'payment_type' => $item['payment_type'],
2214
                    'price' => $item['price'],
2215
                    'product_type' => $item['product_type'],
2216
                    'product_name' => $item['product_name'],
2217
                    'complete_user_name' => $item['complete_user_name'],
2218
                    'email' => $item['email'],
2219
                ];
2220
            }
2221
        }
2222
2223
        return $listExport;
2224
    }
2225
2226
    /**
2227
     * Get the statuses for sales.
2228
     *
2229
     * @return array
2230
     */
2231
    public function getSaleStatuses()
2232
    {
2233
        return [
2234
            self::SALE_STATUS_CANCELED => $this->get_lang('SaleStatusCanceled'),
2235
            self::SALE_STATUS_PENDING => $this->get_lang('SaleStatusPending'),
2236
            self::SALE_STATUS_COMPLETED => $this->get_lang('SaleStatusCompleted'),
2237
        ];
2238
    }
2239
2240
    /**
2241
     * Get the statuses for Payouts.
2242
     *
2243
     * @return array
2244
     */
2245
    public function getPayoutStatuses()
2246
    {
2247
        return [
2248
            self::PAYOUT_STATUS_CANCELED => $this->get_lang('PayoutStatusCanceled'),
2249
            self::PAYOUT_STATUS_PENDING => $this->get_lang('PayoutStatusPending'),
2250
            self::PAYOUT_STATUS_COMPLETED => $this->get_lang('PayoutStatusCompleted'),
2251
        ];
2252
    }
2253
2254
    /**
2255
     * Get the list of product types.
2256
     *
2257
     * @return array
2258
     */
2259
    public function getProductTypes()
2260
    {
2261
        return [
2262
            self::PRODUCT_TYPE_COURSE => get_lang('Course'),
2263
            self::PRODUCT_TYPE_SESSION => get_lang('Session'),
2264
        ];
2265
    }
2266
2267
    /**
2268
     * Get the list of service types.
2269
     *
2270
     * @return array
2271
     */
2272
    public function getServiceTypes()
2273
    {
2274
        return [
2275
            self::SERVICE_TYPE_USER => get_lang('User'),
2276
            self::SERVICE_TYPE_COURSE => get_lang('Course'),
2277
            self::SERVICE_TYPE_SESSION => get_lang('Session'),
2278
            self::SERVICE_TYPE_LP_FINAL_ITEM => get_lang('TemplateTitleCertificate'),
2279
        ];
2280
    }
2281
2282
    /**
2283
     * Get the list of coupon status.
2284
     *
2285
     * @return array
2286
     */
2287
    public function getCouponStatuses()
2288
    {
2289
        return [
2290
            self::COUPON_STATUS_ACTIVE => $this->get_lang('CouponActive'),
2291
            self::COUPON_STATUS_DISABLE => $this->get_lang('CouponDisabled'),
2292
        ];
2293
    }
2294
2295
    /**
2296
     * Get the list of coupon discount types.
2297
     *
2298
     * @return array
2299
     */
2300
    public function getCouponDiscountTypes()
2301
    {
2302
        return [
2303
            self::COUPON_DISCOUNT_TYPE_PERCENTAGE => $this->get_lang('CouponPercentage'),
2304
            self::COUPON_DISCOUNT_TYPE_AMOUNT => $this->get_lang('CouponAmount'),
2305
        ];
2306
    }
2307
2308
    /**
2309
     * Generates a random text (used for order references).
2310
     *
2311
     * @param int  $length    Optional. Length of characters (defaults to 6)
2312
     * @param bool $lowercase Optional. Include lowercase characters
2313
     * @param bool $uppercase Optional. Include uppercase characters
2314
     * @param bool $numbers   Optional. Include numbers
2315
     */
2316
    public static function randomText(
2317
        int $length = 6,
2318
        bool $lowercase = true,
2319
        bool $uppercase = true,
2320
        bool $numbers = true
2321
    ): string {
2322
        $salt = $lowercase ? 'abchefghknpqrstuvwxyz' : '';
2323
        $salt .= $uppercase ? 'ACDEFHKNPRSTUVWXYZ' : '';
2324
        $salt .= $numbers ? (strlen($salt) ? '2345679' : '0123456789') : '';
2325
2326
        if (0 == strlen($salt)) {
2327
            return '';
2328
        }
2329
2330
        $str = '';
2331
2332
        srand(microtime() * 1000000);
2333
2334
        for ($i = 0; $i < $length; $i++) {
2335
            $numbers = rand(0, strlen($salt) - 1);
2336
            $str .= substr($salt, $numbers, 1);
2337
        }
2338
2339
        return $str;
2340
    }
2341
2342
    /**
2343
     * Generates an order reference.
2344
     *
2345
     * @param int $userId      The user ID
2346
     * @param int $productType The course/session type
2347
     * @param int $productId   The course/session ID
2348
     */
2349
    public function generateReference(int $userId, int $productType, int $productId): string
2350
    {
2351
        return vsprintf(
2352
            '%d-%d-%d-%s',
2353
            [$userId, $productType, $productId, self::randomText()]
2354
        );
2355
    }
2356
2357
    /**
2358
     * Get a list of sales by the user.
2359
     *
2360
     * @param string $term The search term
2361
     *
2362
     * @return array The sale list. Otherwise, return false
2363
     */
2364
    public function getSaleListByUser(string $term)
2365
    {
2366
        $term = trim($term);
2367
2368
        if (empty($term)) {
2369
            return [];
2370
        }
2371
2372
        $saleTable = Database::get_main_table(self::TABLE_SALE);
2373
        $currencyTable = Database::get_main_table(self::TABLE_CURRENCY);
2374
        $userTable = Database::get_main_table(TABLE_MAIN_USER);
2375
        $innerJoins = "
2376
            INNER JOIN $currencyTable c ON s.currency_id = c.id
2377
            INNER JOIN $userTable u ON s.user_id = u.id
2378
        ";
2379
2380
        return Database::select(
2381
            ['c.iso_code', 'u.firstname', 'u.lastname', 'u.email', 's.*'],
2382
            "$saleTable s $innerJoins",
2383
            [
2384
                'where' => [
2385
                    'u.username LIKE %?% OR ' => $term,
2386
                    'u.lastname LIKE %?% OR ' => $term,
2387
                    'u.firstname LIKE %?%' => $term,
2388
                ],
2389
                'order' => 'id DESC',
2390
            ]
2391
        );
2392
    }
2393
2394
    /**
2395
     * Get a list of sales by the user id.
2396
     *
2397
     * @param int $id The user id
2398
     *
2399
     * @return array The sale list. Otherwise, return false
2400
     */
2401
    public function getSaleListByUserId(int $id)
2402
    {
2403
        if (empty($id)) {
2404
            return [];
2405
        }
2406
2407
        $saleTable = Database::get_main_table(self::TABLE_SALE);
2408
        $currencyTable = Database::get_main_table(self::TABLE_CURRENCY);
2409
        $userTable = Database::get_main_table(TABLE_MAIN_USER);
2410
2411
        $innerJoins = "
2412
            INNER JOIN $currencyTable c ON s.currency_id = c.id
2413
            INNER JOIN $userTable u ON s.user_id = u.id
2414
        ";
2415
2416
        return Database::select(
2417
            ['c.iso_code', 'u.firstname', 'u.lastname', 's.*'],
2418
            "$saleTable s $innerJoins",
2419
            [
2420
                'where' => [
2421
                    'u.id = ? AND s.status = ?' => [(int) $id, self::SALE_STATUS_COMPLETED],
2422
                ],
2423
                'order' => 'id DESC',
2424
            ]
2425
        );
2426
    }
2427
2428
    /**
2429
     * Get a list of sales by date range.
2430
     *
2431
     * @return array The sale list. Otherwise, return false
2432
     */
2433
    public function getSaleListByDate(string $dateStart, string $dateEnd)
2434
    {
2435
        $dateStart = trim($dateStart);
2436
        $dateEnd = trim($dateEnd);
2437
        if (empty($dateStart)) {
2438
            return [];
2439
        }
2440
        if (empty($dateEnd)) {
2441
            return [];
2442
        }
2443
        $saleTable = Database::get_main_table(self::TABLE_SALE);
2444
        $currencyTable = Database::get_main_table(self::TABLE_CURRENCY);
2445
        $userTable = Database::get_main_table(TABLE_MAIN_USER);
2446
        $innerJoins = "
2447
            INNER JOIN $currencyTable c ON s.currency_id = c.id
2448
            INNER JOIN $userTable u ON s.user_id = u.id
2449
        ";
2450
2451
        return Database::select(
2452
            ['c.iso_code', 'u.firstname', 'u.lastname', 'u.email', 's.*'],
2453
            "$saleTable s $innerJoins",
2454
            [
2455
                'where' => [
2456
                    's.date BETWEEN ? AND ' => $dateStart,
2457
                    ' ? ' => $dateEnd,
2458
                ],
2459
                'order' => 'id DESC',
2460
            ]
2461
        );
2462
    }
2463
2464
    /**
2465
     * Get a list of sales by the user Email.
2466
     *
2467
     * @param string $term The search term
2468
     *
2469
     * @return array The sale list. Otherwise, return false
2470
     */
2471
    public function getSaleListByEmail(string $term)
2472
    {
2473
        $term = trim($term);
2474
        if (empty($term)) {
2475
            return [];
2476
        }
2477
        $saleTable = Database::get_main_table(self::TABLE_SALE);
2478
        $currencyTable = Database::get_main_table(self::TABLE_CURRENCY);
2479
        $userTable = Database::get_main_table(TABLE_MAIN_USER);
2480
        $innerJoins = "
2481
            INNER JOIN $currencyTable c ON s.currency_id = c.id
2482
            INNER JOIN $userTable u ON s.user_id = u.id
2483
        ";
2484
2485
        return Database::select(
2486
            ['c.iso_code', 'u.firstname', 'u.lastname', 'u.email', 's.*'],
2487
            "$saleTable s $innerJoins",
2488
            [
2489
                'where' => [
2490
                    'u.email LIKE %?% ' => $term,
2491
                ],
2492
                'order' => 'id DESC',
2493
            ]
2494
        );
2495
    }
2496
2497
    /**
2498
     * Convert the course info to array with necessary course data for save item.
2499
     *
2500
     * @param array $defaultCurrency Optional. Currency data
2501
     *
2502
     * @return array
2503
     */
2504
    public function getCourseForConfiguration(Course $course, ?array $defaultCurrency = null)
2505
    {
2506
        $courseItem = [
2507
            'item_id' => null,
2508
            'course_id' => $course->getId(),
2509
            'course_visual_code' => $course->getVisualCode(),
2510
            'course_code' => $course->getCode(),
2511
            'course_title' => $course->getTitle(),
2512
            'course_directory' => $course->getDirectory(),
2513
            'course_visibility' => $course->getVisibility(),
2514
            'visible' => false,
2515
            'currency' => empty($defaultCurrency) ? null : $defaultCurrency['iso_code'],
2516
            'price' => 0.00,
2517
            'tax_perc' => null,
2518
        ];
2519
2520
        $item = $this->getItemByProduct($course->getId(), self::PRODUCT_TYPE_COURSE);
2521
2522
        if (false !== $item) {
2523
            $courseItem['item_id'] = $item['id'];
2524
            $courseItem['visible'] = true;
2525
            $courseItem['currency'] = $item['iso_code'];
2526
            $courseItem['price'] = $item['price'];
2527
            $courseItem['tax_perc'] = $item['tax_perc'];
2528
        }
2529
2530
        return $courseItem;
2531
    }
2532
2533
    /**
2534
     * Convert the session info to array with necessary session data for save item.
2535
     *
2536
     * @param Session $session         The session data
2537
     * @param array   $defaultCurrency Optional. Currency data
2538
     *
2539
     * @return array
2540
     */
2541
    public function getSessionForConfiguration(Session $session, ?array $defaultCurrency = null)
2542
    {
2543
        $buyItemTable = Database::get_main_table(self::TABLE_ITEM);
2544
        $buyCurrencyTable = Database::get_main_table(self::TABLE_CURRENCY);
2545
2546
        $fakeItemFrom = "
2547
            $buyItemTable i
2548
            INNER JOIN $buyCurrencyTable c ON i.currency_id = c.id
2549
        ";
2550
2551
        $sessionItem = [
2552
            'item_id' => null,
2553
            'session_id' => $session->getId(),
2554
            'session_name' => $session->getName(),
2555
            'session_visibility' => $session->getVisibility(),
2556
            'session_display_start_date' => null,
2557
            'session_display_end_date' => null,
2558
            'visible' => false,
2559
            'currency' => empty($defaultCurrency) ? null : $defaultCurrency['iso_code'],
2560
            'price' => 0.00,
2561
            'tax_perc' => null,
2562
        ];
2563
2564
        $displayStartDate = $session->getDisplayStartDate();
2565
2566
        if (!empty($displayStartDate)) {
2567
            $sessionItem['session_display_start_date'] = api_format_date(
2568
                $session->getDisplayStartDate()->format('Y-m-d h:i:s')
2569
            );
2570
        }
2571
2572
        $displayEndDate = $session->getDisplayEndDate();
2573
2574
        if (!empty($displayEndDate)) {
2575
            $sessionItem['session_display_end_date'] = api_format_date(
2576
                $session->getDisplayEndDate()->format('Y-m-d h:i:s'),
2577
                DATE_TIME_FORMAT_LONG_24H
2578
            );
2579
        }
2580
2581
        $item = Database::select(
2582
            ['i.*', 'c.iso_code'],
2583
            $fakeItemFrom,
2584
            [
2585
                'where' => [
2586
                    'i.product_id = ? AND ' => $session->getId(),
2587
                    'i.product_type = ?' => self::PRODUCT_TYPE_SESSION,
2588
                ],
2589
            ],
2590
            'first'
2591
        );
2592
2593
        if (false !== $item) {
2594
            $sessionItem['item_id'] = $item['id'];
2595
            $sessionItem['visible'] = true;
2596
            $sessionItem['currency'] = $item['iso_code'];
2597
            $sessionItem['price'] = $item['price'];
2598
            $sessionItem['tax_perc'] = $item['tax_perc'];
2599
        }
2600
2601
        return $sessionItem;
2602
    }
2603
2604
    /**
2605
     * Get all beneficiaries for a item.
2606
     *
2607
     * @param int $itemId The item ID
2608
     *
2609
     * @return array The beneficiaries. Otherwise, return false
2610
     */
2611
    public function getItemBeneficiaries(int $itemId)
2612
    {
2613
        $beneficiaryTable = Database::get_main_table(self::TABLE_ITEM_BENEFICIARY);
2614
2615
        return Database::select(
2616
            '*',
2617
            $beneficiaryTable,
2618
            [
2619
                'where' => [
2620
                    'item_id = ?' => $itemId,
2621
                ],
2622
            ]
2623
        );
2624
    }
2625
2626
    /**
2627
     * Delete a item with its beneficiaries.
2628
     *
2629
     * @param int $itemId The item ID
2630
     *
2631
     * @return int The number of affected rows. Otherwise, return false
2632
     */
2633
    public function deleteItem(int $itemId)
2634
    {
2635
        $itemTable = Database::get_main_table(self::TABLE_ITEM);
2636
        $affectedRows = Database::delete(
2637
            $itemTable,
2638
            ['id = ?' => $itemId]
2639
        );
2640
2641
        if (!$affectedRows) {
2642
            return false;
2643
        }
2644
2645
        return $this->deleteItemBeneficiaries($itemId);
2646
    }
2647
2648
    /**
2649
     * Register a item.
2650
     *
2651
     * @param array $itemData The item data
2652
     *
2653
     * @return int The item ID. Otherwise, return false
2654
     */
2655
    public function registerItem(array $itemData)
2656
    {
2657
        $itemTable = Database::get_main_table(self::TABLE_ITEM);
2658
2659
        return Database::insert($itemTable, $itemData);
2660
    }
2661
2662
    /**
2663
     * Update the item data by product.
2664
     *
2665
     * @param array $itemData    The item data to be updated
2666
     * @param int   $productId   The product ID
2667
     * @param int   $productType The type of product
2668
     *
2669
     * @return int The number of affected rows. Otherwise, return false
2670
     */
2671
    public function updateItem(array $itemData, int $productId, int $productType)
2672
    {
2673
        $itemTable = Database::get_main_table(self::TABLE_ITEM);
2674
2675
        return Database::update(
2676
            $itemTable,
2677
            $itemData,
2678
            [
2679
                'product_id = ? AND ' => $productId,
2680
                'product_type' => $productType,
2681
            ]
2682
        );
2683
    }
2684
2685
    /**
2686
     * Remove all beneficiaries for a item.
2687
     *
2688
     * @param int $itemId The user ID
2689
     *
2690
     * @return int The number of affected rows. Otherwise, return false
2691
     */
2692
    public function deleteItemBeneficiaries(int $itemId)
2693
    {
2694
        $beneficiaryTable = Database::get_main_table(self::TABLE_ITEM_BENEFICIARY);
2695
2696
        return Database::delete(
2697
            $beneficiaryTable,
2698
            ['item_id = ?' => $itemId]
2699
        );
2700
    }
2701
2702
    /**
2703
     * Register the beneficiaries users with the sale of item.
2704
     *
2705
     * @param int   $itemId  The item ID
2706
     * @param array $userIds The beneficiary user ID and Teachers commissions if enabled
2707
     */
2708
    public function registerItemBeneficiaries(int $itemId, array $userIds): void
2709
    {
2710
        $beneficiaryTable = Database::get_main_table(self::TABLE_ITEM_BENEFICIARY);
2711
2712
        $this->deleteItemBeneficiaries($itemId);
2713
2714
        foreach ($userIds as $userId => $commissions) {
2715
            Database::insert(
2716
                $beneficiaryTable,
2717
                [
2718
                    'item_id' => $itemId,
2719
                    'user_id' => (int) $userId,
2720
                    'commissions' => (int) $commissions,
2721
                ]
2722
            );
2723
        }
2724
    }
2725
2726
    /**
2727
     * Check if a course is valid for sale.
2728
     *
2729
     * @param Course $course The course
2730
     *
2731
     * @return bool
2732
     */
2733
    public function isValidCourse(Course $course)
2734
    {
2735
        $courses = $this->getCourses();
2736
2737
        foreach ($courses as $_c) {
2738
            if ($_c->getCode() === $course->getCode()) {
2739
                return true;
2740
            }
2741
        }
2742
2743
        return false;
2744
    }
2745
2746
    /**
2747
     * Gets the beneficiaries with commissions and current paypal accounts by sale.
2748
     *
2749
     * @param int $saleId The sale ID
2750
     *
2751
     * @return array
2752
     */
2753
    public function getBeneficiariesBySale(int $saleId)
2754
    {
2755
        $sale = $this->getSale($saleId);
2756
        $item = $this->getItemByProduct($sale['product_id'], $sale['product_type']);
2757
2758
        return $this->getItemBeneficiaries($item['id']);
2759
    }
2760
2761
    /**
2762
     * gets all payouts.
2763
     *
2764
     * @param int $status   - default 0 - pending
2765
     * @param int $payoutId - for get an individual payout if want all then false
2766
     *
2767
     * @return array
2768
     */
2769
    public function getPayouts(
2770
        int $status = self::PAYOUT_STATUS_PENDING,
2771
        int $payoutId = 0,
2772
        int $userId = 0
2773
    ) {
2774
        $condition = ($payoutId) ? 'AND p.id = '.($payoutId) : '';
2775
        $condition2 = ($userId) ? ' AND p.user_id = '.($userId) : '';
2776
        $typeResult = ($condition) ? 'first' : 'all';
2777
        $payoutsTable = Database::get_main_table(self::TABLE_PAYPAL_PAYOUTS);
2778
        $saleTable = Database::get_main_table(self::TABLE_SALE);
2779
        $currencyTable = Database::get_main_table(self::TABLE_CURRENCY);
2780
        $userTable = Database::get_main_table(TABLE_MAIN_USER);
2781
        $extraFieldTable = Database::get_main_table(TABLE_EXTRA_FIELD);
2782
        $extraFieldValues = Database::get_main_table(TABLE_EXTRA_FIELD_VALUES);
2783
2784
        $paypalExtraField = Database::select(
2785
            '*',
2786
            $extraFieldTable,
2787
            [
2788
                'where' => ['variable = ?' => 'paypal'],
2789
            ],
2790
            'first'
2791
        );
2792
2793
        if (!$paypalExtraField) {
2794
            return false;
2795
        }
2796
2797
        $innerJoins = "
2798
            INNER JOIN $userTable u ON p.user_id = u.id
2799
            INNER JOIN $saleTable s ON s.id = p.sale_id
2800
            INNER JOIN $currencyTable c ON s.currency_id = c.id
2801
            LEFT JOIN  $extraFieldValues efv ON p.user_id = efv.item_id
2802
            AND field_id = ".((int) $paypalExtraField['id']).'
2803
        ';
2804
2805
        return Database::select(
2806
            'p.* , u.firstname, u.lastname, efv.value as paypal_account, s.reference as sale_reference, s.price as item_price, c.iso_code',
2807
            "$payoutsTable p $innerJoins",
2808
            [
2809
                'where' => ['p.status = ? '.$condition.' '.$condition2 => $status],
2810
            ],
2811
            $typeResult
2812
        );
2813
    }
2814
2815
    /**
2816
     * Verify if the beneficiary have a paypal account.
2817
     *
2818
     * @return true if the user have a paypal account, false if not
2819
     */
2820
    public function verifyPaypalAccountByBeneficiary(int $userId)
2821
    {
2822
        $extraFieldTable = Database::get_main_table(TABLE_EXTRA_FIELD);
2823
        $extraFieldValues = Database::get_main_table(TABLE_EXTRA_FIELD_VALUES);
2824
2825
        $paypalExtraField = Database::select(
2826
            '*',
2827
            $extraFieldTable,
2828
            [
2829
                'where' => ['variable = ?' => 'paypal'],
2830
            ],
2831
            'first'
2832
        );
2833
2834
        if (!$paypalExtraField) {
2835
            return false;
2836
        }
2837
2838
        $paypalFieldId = $paypalExtraField['id'];
2839
        $paypalAccount = Database::select(
2840
            'value',
2841
            $extraFieldValues,
2842
            [
2843
                'where' => ['field_id = ? AND item_id = ?' => [(int) $paypalFieldId, $userId]],
2844
            ],
2845
            'first'
2846
        );
2847
2848
        if (!$paypalAccount) {
2849
            return false;
2850
        }
2851
2852
        if ('' === $paypalAccount['value']) {
2853
            return false;
2854
        }
2855
2856
        return true;
2857
    }
2858
2859
    /**
2860
     * Register the users payouts.
2861
     *
2862
     * @return array
2863
     */
2864
    public function storePayouts(int $saleId)
2865
    {
2866
        $payoutsTable = Database::get_main_table(self::TABLE_PAYPAL_PAYOUTS);
2867
        $platformCommission = $this->getPlatformCommission();
2868
2869
        $sale = $this->getSale($saleId);
2870
        $commission = (int) $platformCommission['commission'];
2871
        $teachersCommission = number_format(
2872
            ((float) $sale['price'] * $commission) / 100,
2873
            2
2874
        );
2875
2876
        $beneficiaries = $this->getBeneficiariesBySale($saleId);
2877
        foreach ($beneficiaries as $beneficiary) {
2878
            $beneficiaryCommission = (int) $beneficiary['commissions'];
2879
            Database::insert(
2880
                $payoutsTable,
2881
                [
2882
                    'date' => $sale['date'],
2883
                    'payout_date' => api_get_utc_datetime(),
2884
                    'sale_id' => $saleId,
2885
                    'user_id' => $beneficiary['user_id'],
2886
                    'commission' => number_format(
2887
                        ((float) $teachersCommission * $beneficiaryCommission) / 100,
2888
                        2
2889
                    ),
2890
                    'status' => self::PAYOUT_STATUS_PENDING,
2891
                ]
2892
            );
2893
        }
2894
    }
2895
2896
    /**
2897
     * Register the users payouts.
2898
     *
2899
     * @param int $saleId The subscription sale ID
2900
     *
2901
     * @return array
2902
     */
2903
    public function storeSubscriptionPayouts(int $saleId)
2904
    {
2905
        $payoutsTable = Database::get_main_table(self::TABLE_PAYPAL_PAYOUTS);
2906
        $platformCommission = $this->getPlatformCommission();
2907
2908
        $sale = $this->getSubscriptionSale($saleId);
2909
        $commission = (int) $platformCommission['commission'];
2910
        $teachersCommission = number_format(
2911
            ((float) $sale['price'] * $commission) / 100,
2912
            2
2913
        );
2914
2915
        $beneficiaries = $this->getBeneficiariesBySale($saleId);
2916
        foreach ($beneficiaries as $beneficiary) {
2917
            $beneficiaryCommission = (int) $beneficiary['commissions'];
2918
            Database::insert(
2919
                $payoutsTable,
2920
                [
2921
                    'date' => $sale['date'],
2922
                    'payout_date' => api_get_utc_datetime(),
2923
                    'sale_id' => $saleId,
2924
                    'user_id' => $beneficiary['user_id'],
2925
                    'commission' => number_format(
2926
                        ((float) $teachersCommission * $beneficiaryCommission) / 100,
2927
                        2
2928
                    ),
2929
                    'status' => self::PAYOUT_STATUS_PENDING,
2930
                ]
2931
            );
2932
        }
2933
    }
2934
2935
    /**
2936
     * Register the users payouts.
2937
     *
2938
     * @param int $payoutId The payout ID
2939
     * @param int $status   The status to set (-1 to cancel, 0 to pending, 1 to completed)
2940
     *
2941
     * @return array
2942
     */
2943
    public function setStatusPayouts(int $payoutId, int $status)
2944
    {
2945
        $payoutsTable = Database::get_main_table(self::TABLE_PAYPAL_PAYOUTS);
2946
2947
        Database::update(
2948
            $payoutsTable,
2949
            ['status' => (int) $status],
2950
            ['id = ?' => (int) $payoutId]
2951
        );
2952
    }
2953
2954
    /**
2955
     * Gets the stored platform commission params.
2956
     *
2957
     * @return array
2958
     */
2959
    public function getPlatformCommission()
2960
    {
2961
        return Database::select(
2962
            '*',
2963
            Database::get_main_table(self::TABLE_COMMISSION),
2964
            ['id = ?' => 1],
2965
            'first'
2966
        );
2967
    }
2968
2969
    /**
2970
     * Update the platform commission.
2971
     *
2972
     * @param array $params platform commission
2973
     *
2974
     * @return int The number of affected rows. Otherwise, return false
2975
     */
2976
    public function updateCommission(array $params)
2977
    {
2978
        $commissionTable = Database::get_main_table(self::TABLE_COMMISSION);
2979
2980
        return Database::update(
2981
            $commissionTable,
2982
            ['commission' => (int) $params['commission']]
2983
        );
2984
    }
2985
2986
    /**
2987
     * Register additional service.
2988
     *
2989
     * @param array $service params
2990
     *
2991
     * @return mixed response
2992
     */
2993
    public function storeService(array $service)
2994
    {
2995
        $servicesTable = Database::get_main_table(self::TABLE_SERVICES);
2996
2997
        $return = Database::insert(
2998
            $servicesTable,
2999
            [
3000
                'name' => Security::remove_XSS($service['name']),
3001
                'description' => Security::remove_XSS($service['description']),
3002
                'price' => $service['price'],
3003
                'tax_perc' => '' != $service['tax_perc'] ? (int) $service['tax_perc'] : null,
3004
                'duration_days' => (int) $service['duration_days'],
3005
                'applies_to' => (int) $service['applies_to'],
3006
                'owner_id' => (int) $service['owner_id'],
3007
                'visibility' => (int) $service['visibility'],
3008
                'image' => '',
3009
                'video_url' => $service['video_url'],
3010
                'service_information' => $service['service_information'],
3011
            ]
3012
        );
3013
3014
        if ($return && !empty($service['picture_crop_image_base_64'])
3015
            && !empty($service['picture_crop_result'])
3016
        ) {
3017
            $img = str_replace('data:image/png;base64,', '', $service['picture_crop_image_base_64']);
3018
            $img = str_replace(' ', '+', $img);
3019
            $data = base64_decode($img);
3020
            $file = api_get_path(SYS_PLUGIN_PATH).'buycourses/uploads/services/images/simg-'.$return.'.png';
3021
            file_put_contents($file, $data);
3022
3023
            Database::update(
3024
                $servicesTable,
3025
                ['image' => 'simg-'.$return.'.png'],
3026
                ['id = ?' => $return]
3027
            );
3028
        }
3029
3030
        return $return;
3031
    }
3032
3033
    /**
3034
     * update a service.
3035
     *
3036
     * @return mixed response
3037
     */
3038
    public function updateService(array $service, int $id)
3039
    {
3040
        $servicesTable = Database::get_main_table(self::TABLE_SERVICES);
3041
        if (!empty($service['picture_crop_image_base_64'])) {
3042
            $img = str_replace('data:image/png;base64,', '', $service['picture_crop_image_base_64']);
3043
            $img = str_replace(' ', '+', $img);
3044
            $data = base64_decode($img);
3045
            $file = api_get_path(SYS_PLUGIN_PATH).'buycourses/uploads/services/images/simg-'.$id.'.png';
3046
            file_put_contents($file, $data);
3047
        }
3048
3049
        return Database::update(
3050
            $servicesTable,
3051
            [
3052
                'name' => Security::remove_XSS($service['name']),
3053
                'description' => Security::remove_XSS($service['description']),
3054
                'price' => $service['price'],
3055
                'tax_perc' => '' != $service['tax_perc'] ? (int) $service['tax_perc'] : null,
3056
                'duration_days' => (int) $service['duration_days'],
3057
                'applies_to' => (int) $service['applies_to'],
3058
                'owner_id' => (int) $service['owner_id'],
3059
                'visibility' => (int) $service['visibility'],
3060
                'image' => 'simg-'.$id.'.png',
3061
                'video_url' => $service['video_url'],
3062
                'service_information' => $service['service_information'],
3063
            ],
3064
            ['id = ?' => $id]
3065
        );
3066
    }
3067
3068
    /**
3069
     * Remove a service.
3070
     *
3071
     * @param int $id The transfer account ID
3072
     *
3073
     * @return int Rows affected. Otherwise, return false
3074
     */
3075
    public function deleteService(int $id)
3076
    {
3077
        Database::delete(
3078
            Database::get_main_table(self::TABLE_SERVICES_SALE),
3079
            ['service_id = ?' => $id]
3080
        );
3081
3082
        return Database::delete(
3083
            Database::get_main_table(self::TABLE_SERVICES),
3084
            ['id = ?' => $id]
3085
        );
3086
    }
3087
3088
    /**
3089
     * @param array|null $coupon Array with at least 'discount_type' and 'discount_amount' elements
3090
     */
3091
    public function setPriceSettings(array &$product, int $productType, ?array $coupon = null): bool
3092
    {
3093
        if (empty($product)) {
3094
            return false;
3095
        }
3096
3097
        $taxPerc = null;
3098
        $product['has_coupon'] = null != $coupon ? true : false;
3099
        $couponDiscount = 0;
3100
        if (null != $coupon) {
3101
            if (self::COUPON_DISCOUNT_TYPE_AMOUNT == $coupon['discount_type']) {
3102
                $couponDiscount = $coupon['discount_amount'];
3103
            } elseif (self::COUPON_DISCOUNT_TYPE_PERCENTAGE == $coupon['discount_type']) {
3104
                $couponDiscount = ($product['price'] * $coupon['discount_amount']) / 100;
3105
            }
3106
            $product['price_without_discount'] = $product['price'];
3107
        }
3108
        $product['discount_amount'] = $couponDiscount;
3109
        $product['price'] -= $couponDiscount;
3110
        $priceWithoutTax = $product['price'];
3111
        $product['total_price'] = $product['price'];
3112
        $product['tax_amount'] = 0;
3113
3114
        if ($this->checkTaxEnabledInProduct($productType)) {
3115
            if (null === $product['tax_perc']) {
3116
                $globalParameters = $this->getGlobalParameters();
3117
                $globalTaxPerc = $globalParameters['global_tax_perc'];
3118
                $taxPerc = $globalTaxPerc;
3119
            } else {
3120
                $taxPerc = $product['tax_perc'];
3121
            }
3122
            // $taxPerc = is_null($product['tax_perc']) ? $globalTaxPerc : $product['tax_perc'];
3123
3124
            $taxAmount = round($priceWithoutTax * $taxPerc / 100, 2);
3125
            $product['tax_amount'] = $taxAmount;
3126
            $priceWithTax = $priceWithoutTax + $taxAmount;
3127
            $product['total_price'] = $priceWithTax;
3128
        }
3129
3130
        $product['tax_perc_show'] = $taxPerc;
3131
        $product['price_formatted'] = $this->getPriceWithCurrencyFromIsoCode(
3132
            $product['price'],
3133
            $product['iso_code']
3134
        );
3135
3136
        $product['tax_amount_formatted'] = number_format($product['tax_amount'], 2);
3137
3138
        $product['total_price_formatted'] = $this->getPriceWithCurrencyFromIsoCode(
3139
            $product['total_price'],
3140
            $product['iso_code']
3141
        );
3142
3143
        if (null != $coupon) {
3144
            $product['discount_amount_formatted'] = $this->getPriceWithCurrencyFromIsoCode(
3145
                $product['discount_amount'],
3146
                $product['iso_code']
3147
            );
3148
3149
            $product['price_without_discount_formatted'] = $this->getPriceWithCurrencyFromIsoCode(
3150
                $product['price_without_discount'],
3151
                $product['iso_code']
3152
            );
3153
        }
3154
3155
        return true;
3156
    }
3157
3158
    /**
3159
     * @return array
3160
     */
3161
    public function getService(int $id, ?array $coupon = null)
3162
    {
3163
        if (empty($id)) {
3164
            return [];
3165
        }
3166
3167
        $servicesTable = Database::get_main_table(self::TABLE_SERVICES);
3168
        $userTable = Database::get_main_table(TABLE_MAIN_USER);
3169
        $conditions = ['WHERE' => ['s.id = ?' => $id]];
3170
        $showData = 'first';
3171
        $innerJoins = "INNER JOIN $userTable u ON s.owner_id = u.id";
3172
        $currency = $this->getSelectedCurrency();
3173
        $isoCode = $currency['iso_code'];
3174
        $service = Database::select(
3175
            "s.*, '$isoCode' as currency, u.firstname, u.lastname",
3176
            "$servicesTable s $innerJoins",
3177
            $conditions,
3178
            $showData
3179
        );
3180
3181
        $service['iso_code'] = $isoCode;
3182
        $globalParameters = $this->getGlobalParameters();
3183
3184
        $this->setPriceSettings($service, self::TAX_APPLIES_TO_ONLY_SERVICES, $coupon);
3185
3186
        $service['tax_name'] = $globalParameters['tax_name'];
3187
        $service['tax_enable'] = $this->checkTaxEnabledInProduct(self::TAX_APPLIES_TO_ONLY_SERVICES);
3188
        $service['owner_name'] = api_get_person_name($service['firstname'], $service['lastname']);
3189
        $service['image'] = !empty($service['image']) ? api_get_path(WEB_PLUGIN_PATH).'buycourses/uploads/services/images/'.$service['image'] : null;
3190
3191
        return $service;
3192
    }
3193
3194
    /**
3195
     * List additional services.
3196
     *
3197
     * @return array
3198
     */
3199
    public function getAllServices()
3200
    {
3201
        $servicesTable = Database::get_main_table(self::TABLE_SERVICES);
3202
        $userTable = Database::get_main_table(TABLE_MAIN_USER);
3203
3204
        $innerJoins = "INNER JOIN $userTable u ON s.owner_id = u.id";
3205
        $return = Database::select(
3206
            's.id',
3207
            "$servicesTable s $innerJoins",
3208
            [],
3209
            'all'
3210
        );
3211
3212
        $services = [];
3213
        foreach ($return as $index => $service) {
3214
            $services[$index] = $this->getService($service['id']);
3215
        }
3216
3217
        return $services;
3218
    }
3219
3220
    /**
3221
     * List additional services.
3222
     *
3223
     * @return array|int
3224
     */
3225
    public function getServices(int $start, int $end, string $typeResult = 'all')
3226
    {
3227
        $servicesTable = Database::get_main_table(self::TABLE_SERVICES);
3228
        $userTable = Database::get_main_table(TABLE_MAIN_USER);
3229
3230
        $conditions = ['limit' => "$start, $end"];
3231
        $innerJoins = "INNER JOIN $userTable u ON s.owner_id = u.id";
3232
        $return = Database::select(
3233
            's.id',
3234
            "$servicesTable s $innerJoins",
3235
            $conditions,
3236
            $typeResult
3237
        );
3238
3239
        if ('count' === $typeResult) {
3240
            return $return;
3241
        }
3242
3243
        $services = [];
3244
        foreach ($return as $index => $service) {
3245
            $services[$index] = $this->getService($service['id']);
3246
        }
3247
3248
        return $services;
3249
    }
3250
3251
    /**
3252
     * Get the statuses for sales.
3253
     *
3254
     * @return array
3255
     */
3256
    public function getServiceSaleStatuses()
3257
    {
3258
        return [
3259
            self::SERVICE_STATUS_CANCELLED => $this->get_lang('SaleStatusCancelled'),
3260
            self::SERVICE_STATUS_PENDING => $this->get_lang('SaleStatusPending'),
3261
            self::SERVICE_STATUS_COMPLETED => $this->get_lang('SaleStatusCompleted'),
3262
        ];
3263
    }
3264
3265
    /**
3266
     * List services sales.
3267
     *
3268
     * @param int $buyerId  buyer id
3269
     * @param int $status   status
3270
     * @param int $nodeType The node Type ( User = 1 , Course = 2 , Session = 3 )
3271
     * @param int $nodeId   the nodeId
3272
     *
3273
     * @return array
3274
     */
3275
    public function getServiceSales(
3276
        int $buyerId = 0,
3277
        int $status = 0,
3278
        int $nodeType = 0,
3279
        int $nodeId = 0
3280
    ) {
3281
        $conditions = null;
3282
        $groupBy = '';
3283
3284
        $servicesTable = Database::get_main_table(self::TABLE_SERVICES);
3285
        $servicesSaleTable = Database::get_main_table(self::TABLE_SERVICES_SALE);
3286
3287
        $defaultOrder = 'id ASC';
3288
3289
        if (!empty($buyerId)) {
3290
            $conditions = ['WHERE' => ['ss.buyer_id = ?' => $buyerId], 'ORDER' => $defaultOrder];
3291
        }
3292
3293
        if (is_numeric($status)) {
3294
            $conditions = ['WHERE' => ['ss.status = ?' => $status], 'ORDER' => $defaultOrder];
3295
        }
3296
3297
        if ($buyerId) {
3298
            $conditions = ['WHERE' => ['ss.buyer_id = ?' => [$buyerId]], 'ORDER' => $defaultOrder];
3299
        }
3300
3301
        if ($nodeType && $nodeId) {
3302
            $conditions = [
3303
                'WHERE' => ['ss.node_type = ? AND ss.node_id = ?' => [$nodeType, $nodeId]],
3304
                'ORDER' => $defaultOrder,
3305
            ];
3306
        }
3307
3308
        if ($nodeType && $nodeId && $buyerId && is_numeric($status)) {
3309
            $conditions = [
3310
                'WHERE' => [
3311
                    'ss.node_type = ? AND ss.node_id = ? AND ss.buyer_id = ? AND ss.status = ?' => [
3312
                        $nodeType,
3313
                        $nodeId,
3314
                        $buyerId,
3315
                        $status,
3316
                    ],
3317
                ],
3318
                'ORDER' => $defaultOrder,
3319
            ];
3320
        }
3321
3322
        $innerJoins = "INNER JOIN $servicesTable s ON ss.service_id = s.id $groupBy";
3323
        $return = Database::select(
3324
            'DISTINCT ss.id ',
3325
            "$servicesSaleTable ss $innerJoins",
3326
            $conditions
3327
            // , "all", null, true
3328
        );
3329
3330
        $list = [];
3331
        foreach ($return as $service) {
3332
            $list[] = $this->getServiceSale($service['id']);
3333
        }
3334
3335
        return $list;
3336
    }
3337
3338
    /**
3339
     * @param int $id service sale id
3340
     *
3341
     * @return array
3342
     */
3343
    public function getServiceSale(int $id)
3344
    {
3345
        $servicesTable = Database::get_main_table(self::TABLE_SERVICES);
3346
        $servicesSaleTable = Database::get_main_table(self::TABLE_SERVICES_SALE);
3347
3348
        if (empty($id)) {
3349
            return [];
3350
        }
3351
3352
        $conditions = ['WHERE' => ['ss.id = ?' => $id]];
3353
        $innerJoins = "INNER JOIN $servicesTable s ON ss.service_id = s.id ";
3354
        $currency = $this->getSelectedCurrency();
3355
        $isoCode = $currency['iso_code'];
3356
3357
        $servicesSale = Database::select(
3358
            'ss.*, s.name, s.description, s.price as service_price, s.duration_days, s.applies_to, s.owner_id, s.visibility, s.image',
3359
            "$servicesSaleTable ss $innerJoins",
3360
            $conditions,
3361
            'first'
3362
        );
3363
        $owner = api_get_user_info($servicesSale['owner_id']);
3364
        $buyer = api_get_user_info($servicesSale['buyer_id']);
3365
3366
        $servicesSale['service']['id'] = $servicesSale['service_id'];
3367
        $servicesSale['service']['name'] = $servicesSale['name'];
3368
        $servicesSale['service']['description'] = $servicesSale['description'];
3369
        $servicesSale['service']['price'] = $servicesSale['service_price'];
3370
        $servicesSale['service']['currency'] = $isoCode;
3371
3372
        $servicesSale['service']['total_price'] = $this->getPriceWithCurrencyFromIsoCode(
3373
            $servicesSale['price'],
3374
            $isoCode
3375
        );
3376
3377
        $servicesSale['service']['duration_days'] = $servicesSale['duration_days'];
3378
        $servicesSale['service']['applies_to'] = $servicesSale['applies_to'];
3379
        $servicesSale['service']['owner']['id'] = $servicesSale['owner_id'];
3380
        $servicesSale['service']['owner']['name'] = api_get_person_name($owner['firstname'], $owner['lastname']);
3381
        $servicesSale['service']['visibility'] = $servicesSale['visibility'];
3382
        $servicesSale['service']['image'] = $servicesSale['image'];
3383
        $servicesSale['item'] = $this->getService($servicesSale['service_id']);
3384
        $servicesSale['buyer']['id'] = $buyer['user_id'];
3385
        $servicesSale['buyer']['name'] = api_get_person_name($buyer['firstname'], $buyer['lastname']);
3386
        $servicesSale['buyer']['username'] = $buyer['username'];
3387
3388
        return $servicesSale;
3389
    }
3390
3391
    /**
3392
     * Update service sale status to cancelled.
3393
     *
3394
     * @param int $serviceSaleId The sale ID
3395
     *
3396
     * @return bool
3397
     */
3398
    public function cancelServiceSale(int $serviceSaleId)
3399
    {
3400
        $this->updateServiceSaleStatus(
3401
            $serviceSaleId,
3402
            self::SERVICE_STATUS_CANCELLED
3403
        );
3404
3405
        return true;
3406
    }
3407
3408
    /**
3409
     * Complete service sale process. Update service sale status to completed.
3410
     *
3411
     * @param int $serviceSaleId The service sale ID
3412
     *
3413
     * @return bool
3414
     */
3415
    public function completeServiceSale(int $serviceSaleId)
3416
    {
3417
        $serviceSale = $this->getServiceSale($serviceSaleId);
3418
        if (self::SERVICE_STATUS_COMPLETED == $serviceSale['status']) {
3419
            return true;
3420
        }
3421
3422
        $this->updateServiceSaleStatus(
3423
            $serviceSaleId,
3424
            self::SERVICE_STATUS_COMPLETED
3425
        );
3426
3427
        if ('true' === $this->get('invoicing_enable')) {
3428
            $this->setInvoice($serviceSaleId, 1);
3429
        }
3430
3431
        return true;
3432
    }
3433
3434
    /**
3435
     * Lists current service details.
3436
     *
3437
     * @param mixed $appliesTo
3438
     *
3439
     * @return array|int
3440
     */
3441
    public function getCatalogServiceList(
3442
        int $start,
3443
        int $end,
3444
        ?string $name = null,
3445
        int $min = 0,
3446
        int $max = 0,
3447
        $appliesTo = '',
3448
        string $typeResult = 'all'
3449
    ) {
3450
        $servicesTable = Database::get_main_table(self::TABLE_SERVICES);
3451
        $userTable = Database::get_main_table(TABLE_MAIN_USER);
3452
3453
        $whereConditions = [
3454
            's.visibility <> ? ' => 0,
3455
        ];
3456
3457
        if (!empty($name)) {
3458
            $whereConditions['AND s.name LIKE %?%'] = $name;
3459
        }
3460
3461
        if (!empty($min)) {
3462
            $whereConditions['AND s.price >= ?'] = $min;
3463
        }
3464
3465
        if (!empty($max)) {
3466
            $whereConditions['AND s.price <= ?'] = $max;
3467
        }
3468
3469
        if ('' == !$appliesTo) {
3470
            $whereConditions['AND s.applies_to = ?'] = $appliesTo;
3471
        }
3472
3473
        $innerJoins = "INNER JOIN $userTable u ON s.owner_id = u.id";
3474
        $return = Database::select(
3475
            's.*',
3476
            "$servicesTable s $innerJoins",
3477
            ['WHERE' => $whereConditions, 'limit' => "$start, $end"],
3478
            $typeResult
3479
        );
3480
3481
        if ('count' === $typeResult) {
3482
            return $return;
3483
        }
3484
3485
        $services = [];
3486
        foreach ($return as $index => $service) {
3487
            $services[$index] = $this->getService($service['id']);
3488
        }
3489
3490
        return $services;
3491
    }
3492
3493
    /**
3494
     * Register a Service sale.
3495
     *
3496
     * @param int $serviceId   The service ID
3497
     * @param int $paymentType The payment type
3498
     * @param int $infoSelect  The ID for Service Type
3499
     *
3500
     * @return bool
3501
     */
3502
    public function registerServiceSale(int $serviceId, int $paymentType, int $infoSelect, ?int $couponId = null)
3503
    {
3504
        if (!in_array(
3505
            $paymentType,
3506
            [self::PAYMENT_TYPE_PAYPAL, self::PAYMENT_TYPE_TRANSFER, self::PAYMENT_TYPE_CULQI]
3507
        )
3508
        ) {
3509
            return false;
3510
        }
3511
3512
        $userId = api_get_user_id();
3513
        $service = $this->getService($serviceId);
3514
3515
        if (empty($service)) {
3516
            return false;
3517
        }
3518
3519
        if (null != $couponId) {
3520
            $coupon = $this->getCouponService($couponId, $serviceId);
3521
        }
3522
3523
        $couponDiscount = 0;
3524
        $priceWithoutDiscount = 0;
3525
        if (null != $coupon) {
3526
            if (self::COUPON_DISCOUNT_TYPE_AMOUNT == $coupon['discount_type']) {
3527
                $couponDiscount = $coupon['discount_amount'];
3528
            } elseif (self::COUPON_DISCOUNT_TYPE_PERCENTAGE == $coupon['discount_type']) {
3529
                $couponDiscount = ($service['price'] * $coupon['discount_amount']) / 100;
3530
            }
3531
            $priceWithoutDiscount = $service['price'];
3532
        }
3533
        $service['price'] -= $couponDiscount;
3534
        $currency = $this->getSelectedCurrency();
3535
        $price = $service['price'];
3536
        $priceWithoutTax = null;
3537
        $taxPerc = null;
3538
        $taxEnable = 'true' === $this->get('tax_enable');
3539
        $globalParameters = $this->getGlobalParameters();
3540
        $taxAppliesTo = $globalParameters['tax_applies_to'];
3541
        $taxAmount = 0;
3542
3543
        if ($taxEnable
3544
            && (self::TAX_APPLIES_TO_ALL == $taxAppliesTo || self::TAX_APPLIES_TO_ONLY_SERVICES == $taxAppliesTo)
3545
        ) {
3546
            $priceWithoutTax = $service['price'];
3547
            $globalTaxPerc = $globalParameters['global_tax_perc'];
3548
            $precision = 2;
3549
            $taxPerc = null === $service['tax_perc'] ? $globalTaxPerc : $service['tax_perc'];
3550
            $taxAmount = round($priceWithoutTax * $taxPerc / 100, $precision);
3551
            $price = $priceWithoutTax + $taxAmount;
3552
        }
3553
3554
        $values = [
3555
            'service_id' => $serviceId,
3556
            'reference' => $this->generateReference(
3557
                $userId,
3558
                $service['applies_to'],
3559
                $infoSelect
3560
            ),
3561
            'currency_id' => $currency['id'],
3562
            'price' => $price,
3563
            'price_without_tax' => $priceWithoutTax,
3564
            'tax_perc' => $taxPerc,
3565
            'tax_amount' => $taxAmount,
3566
            'node_type' => $service['applies_to'],
3567
            'node_id' => $infoSelect,
3568
            'buyer_id' => $userId,
3569
            'buy_date' => api_get_utc_datetime(),
3570
            'date_start' => api_get_utc_datetime(),
3571
            'date_end' => date_format(
3572
                date_add(
3573
                    date_create(api_get_utc_datetime()),
3574
                    date_interval_create_from_date_string($service['duration_days'].' days')
3575
                ),
3576
                'Y-m-d H:i:s'
3577
            ),
3578
            'status' => self::SERVICE_STATUS_PENDING,
3579
            'payment_type' => $paymentType,
3580
            'price_without_discount' => $priceWithoutDiscount,
3581
            'discount_amount' => $couponDiscount,
3582
        ];
3583
3584
        return Database::insert(self::TABLE_SERVICES_SALE, $values);
3585
    }
3586
3587
    /**
3588
     * Save Culqi configuration params.
3589
     *
3590
     * @return int Rows affected. Otherwise, return false
3591
     */
3592
    public function saveCulqiParameters(array $params)
3593
    {
3594
        return Database::update(
3595
            Database::get_main_table(self::TABLE_CULQI),
3596
            [
3597
                'commerce_code' => $params['commerce_code'],
3598
                'api_key' => $params['api_key'],
3599
                'integration' => $params['integration'],
3600
            ],
3601
            ['id = ?' => 1]
3602
        );
3603
    }
3604
3605
    /**
3606
     * Gets the stored Culqi params.
3607
     *
3608
     * @return array
3609
     */
3610
    public function getCulqiParams()
3611
    {
3612
        return Database::select(
3613
            '*',
3614
            Database::get_main_table(self::TABLE_CULQI),
3615
            ['id = ?' => 1],
3616
            'first'
3617
        );
3618
    }
3619
3620
    /**
3621
     * Save Cecabank configuration params.
3622
     *
3623
     * @return array
3624
     */
3625
    public function saveCecabankParameters(array $params)
3626
    {
3627
        return Database::update(
3628
            Database::get_main_table(self::TABLE_TPV_CECABANK),
3629
            [
3630
                'crypto_key' => $params['crypto_key'],
3631
                'merchant_id' => $params['merchart_id'],
3632
                'acquirer_bin' => $params['acquirer_bin'],
3633
                'terminal_id' => $params['terminal_id'],
3634
                'cypher' => $params['cypher'],
3635
                'exponent' => $params['exponent'],
3636
                'supported_payment' => $params['supported_payment'],
3637
                'url' => $params['url'],
3638
            ],
3639
            ['id = ?' => 1]
3640
        );
3641
    }
3642
3643
    /**
3644
     * Gets the stored Cecabank params.
3645
     *
3646
     * @return array
3647
     */
3648
    public function getCecabankParams()
3649
    {
3650
        return Database::select(
3651
            '*',
3652
            Database::get_main_table(self::TABLE_TPV_CECABANK),
3653
            ['id = ?' => 1],
3654
            'first'
3655
        );
3656
    }
3657
3658
    /**
3659
     * Save Global Parameters.
3660
     *
3661
     * @return int Rows affected. Otherwise, return false
3662
     */
3663
    public function saveGlobalParameters(array $params)
3664
    {
3665
        $sqlParams = [
3666
            'terms_and_conditions' => $params['terms_and_conditions'],
3667
            'sale_email' => $params['sale_email'],
3668
        ];
3669
3670
        if ('true' === $this->get('tax_enable')) {
3671
            $sqlParams['global_tax_perc'] = $params['global_tax_perc'];
3672
            $sqlParams['tax_applies_to'] = $params['tax_applies_to'];
3673
            $sqlParams['tax_name'] = $params['tax_name'];
3674
        }
3675
3676
        if ('true' === $this->get('invoicing_enable')) {
3677
            $sqlParams['seller_name'] = $params['seller_name'];
3678
            $sqlParams['seller_id'] = $params['seller_id'];
3679
            $sqlParams['seller_address'] = $params['seller_address'];
3680
            $sqlParams['seller_email'] = $params['seller_email'];
3681
            $sqlParams['next_number_invoice'] = $params['next_number_invoice'];
3682
            $sqlParams['invoice_series'] = $params['invoice_series'];
3683
        }
3684
3685
        return Database::update(
3686
            Database::get_main_table(self::TABLE_GLOBAL_CONFIG),
3687
            $sqlParams,
3688
            ['id = ?' => 1]
3689
        );
3690
    }
3691
3692
    /**
3693
     * get Global Parameters.
3694
     *
3695
     * @return array
3696
     */
3697
    public function getGlobalParameters()
3698
    {
3699
        return Database::select(
3700
            '*',
3701
            Database::get_main_table(self::TABLE_GLOBAL_CONFIG),
3702
            ['id = ?' => 1],
3703
            'first'
3704
        );
3705
    }
3706
3707
    /**
3708
     * @return bool
3709
     */
3710
    public function checkTaxEnabledInProduct(int $productType)
3711
    {
3712
        if (empty('true' === $this->get('tax_enable'))) {
3713
            return false;
3714
        }
3715
3716
        $globalParameters = $this->getGlobalParameters();
3717
        $taxAppliesTo = $globalParameters['tax_applies_to'];
3718
        if (self::TAX_APPLIES_TO_ALL == $taxAppliesTo) {
3719
            return true;
3720
        }
3721
3722
        if ($taxAppliesTo == $productType) {
3723
            return true;
3724
        }
3725
3726
        return false;
3727
    }
3728
3729
    /**
3730
     * Get the path.
3731
     */
3732
    public function getPath(string $var): string
3733
    {
3734
        $pluginPath = api_get_path(WEB_PLUGIN_PATH).'buycourses/';
3735
        $paths = [
3736
            'SERVICE_IMAGES' => $pluginPath.'uploads/services/images/',
3737
            'SRC' => $pluginPath.'src/',
3738
            'VIEW' => $pluginPath.'view/',
3739
            'UPLOADS' => $pluginPath.'uploads/',
3740
            'LANGUAGES' => $pluginPath.'lang/',
3741
            'RESOURCES' => $pluginPath.'resources/',
3742
            'RESOURCES_IMG' => $pluginPath.'resources/img/',
3743
            'RESOURCES_CSS' => $pluginPath.'resources/css/',
3744
            'RESOURCES_JS' => $pluginPath.'resources/js/',
3745
        ];
3746
3747
        return $paths[$var];
3748
    }
3749
3750
    public function getBuyCoursePluginPrice(Session $session): array
3751
    {
3752
        // start buycourse validation
3753
        // display the course price and buy button if the buycourses plugin is enabled and this course is configured
3754
        $isThisCourseInSale = $this->buyCoursesForGridCatalogValidator($session->getId(), self::PRODUCT_TYPE_SESSION);
3755
        $return = [];
3756
3757
        if ($isThisCourseInSale) {
3758
            // set the Price label
3759
            $return['html'] = $isThisCourseInSale['html'];
3760
            // set the Buy button instead of register.
3761
            if ($isThisCourseInSale['verificator']) {
3762
                $return['buy_button'] = $this->returnBuyCourseButton($session->getId(), self::PRODUCT_TYPE_SESSION);
3763
            }
3764
        }
3765
3766
        // end buycourse validation
3767
        return $return;
3768
    }
3769
3770
    /**
3771
     * Register a coupon sale.
3772
     *
3773
     * @param int $saleId   The sale ID
3774
     * @param int $couponId The coupon ID
3775
     */
3776
    public function registerCouponSale(int $saleId, int $couponId): bool
3777
    {
3778
        $sale = $this->getSale($saleId);
3779
3780
        if (empty($sale)) {
3781
            return false;
3782
        }
3783
3784
        $values = [
3785
            'coupon_id' => $couponId,
3786
            'sale_id' => $saleId,
3787
        ];
3788
3789
        return Database::insert(self::TABLE_COUPON_SALE, $values) > 0;
3790
    }
3791
3792
    /**
3793
     * Register a coupon service sale.
3794
     *
3795
     * @param int $saleId   The sale ID
3796
     * @param int $couponId The coupon ID
3797
     */
3798
    public function registerCouponServiceSale(int $saleId, int $couponId): bool
3799
    {
3800
        $sale = $this->getSale($saleId);
3801
3802
        if (empty($sale)) {
3803
            return false;
3804
        }
3805
3806
        $values = [
3807
            'coupon_id' => $couponId,
3808
            'service_sale_id' => $saleId,
3809
        ];
3810
3811
        return Database::insert(self::TABLE_COUPON_SERVICE_SALE, $values) > 0;
3812
    }
3813
3814
    /**
3815
     * Register a coupon sale.
3816
     *
3817
     * @param int $saleId   The sale ID
3818
     * @param int $couponId The coupon ID
3819
     */
3820
    public function registerCouponSubscriptionSale(int $saleId, int $couponId): bool
3821
    {
3822
        $sale = $this->getSubscriptionSale($saleId);
3823
3824
        if (empty($sale)) {
3825
            return false;
3826
        }
3827
3828
        $values = [
3829
            'coupon_id' => (int) $couponId,
3830
            'sale_id' => (int) $saleId,
3831
        ];
3832
3833
        return Database::insert(self::TABLE_COUPON_SUBSCRIPTION_SALE, $values) > 0;
3834
    }
3835
3836
    /**
3837
     * Add a new coupon.
3838
     */
3839
    public function addNewCoupon(array $coupon): bool
3840
    {
3841
        $couponId = $this->registerCoupon($coupon);
3842
3843
        if ($couponId) {
3844
            if (isset($coupon['courses'])) {
3845
                foreach ($coupon['courses'] as $course) {
3846
                    $this->registerCouponItem($couponId, self::PRODUCT_TYPE_COURSE, $course);
3847
                }
3848
            }
3849
3850
            if (isset($coupon['sessions'])) {
3851
                foreach ($coupon['sessions'] as $session) {
3852
                    $this->registerCouponItem($couponId, self::PRODUCT_TYPE_SESSION, $session);
3853
                }
3854
            }
3855
3856
            if (isset($coupon['services'])) {
3857
                foreach ($coupon['services'] as $service) {
3858
                    $this->registerCouponService($couponId, $service);
3859
                }
3860
            }
3861
3862
            return true;
3863
        }
3864
3865
        Display::addFlash(
3866
            Display::return_message(
3867
                $this->get_lang('CouponErrorInsert'),
3868
                'error',
3869
                false
3870
            )
3871
        );
3872
3873
        return false;
3874
    }
3875
3876
    /**
3877
     * Add a new coupon.
3878
     *
3879
     * @return bool
3880
     */
3881
    public function updateCouponData(array $coupon)
3882
    {
3883
        $this->updateCoupon($coupon);
3884
        $this->deleteCouponItemsByCoupon(self::PRODUCT_TYPE_COURSE, $coupon['id']);
3885
        $this->deleteCouponItemsByCoupon(self::PRODUCT_TYPE_SESSION, $coupon['id']);
3886
        $this->deleteCouponServicesByCoupon($coupon['id']);
3887
3888
        if (isset($coupon['courses'])) {
3889
            foreach ($coupon['courses'] as $course) {
3890
                $this->registerCouponItem($coupon['id'], self::PRODUCT_TYPE_COURSE, $course);
3891
            }
3892
        }
3893
3894
        if (isset($coupon['sessions'])) {
3895
            foreach ($coupon['sessions'] as $session) {
3896
                $this->registerCouponItem($coupon['id'], self::PRODUCT_TYPE_SESSION, $session);
3897
            }
3898
        }
3899
3900
        if (isset($coupon['services'])) {
3901
            foreach ($coupon['services'] as $service) {
3902
                $this->registerCouponService($coupon['id'], $service);
3903
            }
3904
        }
3905
3906
        return true;
3907
    }
3908
3909
    /**
3910
     * Update coupons delivered.
3911
     *
3912
     * @param int $couponId The coupon ID
3913
     *
3914
     * @return bool
3915
     */
3916
    public function updateCouponDelivered(int $couponId)
3917
    {
3918
        $couponTable = Database::get_main_table(self::TABLE_COUPON);
3919
3920
        $sql = "UPDATE $couponTable
3921
        SET delivered = delivered+1
3922
        WHERE id = $couponId";
3923
3924
        Database::query($sql);
3925
    }
3926
3927
    /**
3928
     * Get coupon info.
3929
     *
3930
     * @param int $couponId The coupon ID
3931
     *
3932
     * @return array The coupon data
3933
     */
3934
    public function getCouponInfo(int $couponId)
3935
    {
3936
        $coupon = $this->getDataCoupon($couponId);
3937
3938
        $couponRelCourses = $this->getItemsCoupons($couponId, self::PRODUCT_TYPE_COURSE);
3939
        $couponRelSessions = $this->getItemsCoupons($couponId, self::PRODUCT_TYPE_SESSION);
3940
        $couponRelServices = $this->getServicesCoupons($couponId);
3941
3942
        $coupon['courses'] = $couponRelCourses;
3943
        $coupon['sessions'] = $couponRelSessions;
3944
        $coupon['services'] = $couponRelServices;
3945
3946
        return $coupon;
3947
    }
3948
3949
    /**
3950
     * Get a list of coupons.
3951
     *
3952
     * @param int $status The coupons activation status
3953
     *
3954
     * @return array Coupons data
3955
     */
3956
    public function getCouponsListByStatus(int $status)
3957
    {
3958
        return $this->getDataCoupons($status);
3959
    }
3960
3961
    /**
3962
     * Get the coupon data.
3963
     *
3964
     * @return array The coupon data
3965
     */
3966
    public function getCoupon(int $couponId, int $productType, int $productId)
3967
    {
3968
        return $this->getDataCoupon($couponId, $productType, $productId);
3969
    }
3970
3971
    /**
3972
     * Get data of the coupon code.
3973
     *
3974
     * @param string $couponCode  The coupon code
3975
     * @param int    $productId   The product ID
3976
     * @param int    $productType The product type
3977
     *
3978
     * @return array The coupon data
3979
     */
3980
    public function getCouponByCode(string $couponCode, ?int $productType = null, ?int $productId = null)
3981
    {
3982
        return $this->getDataCouponByCode($couponCode, $productType, $productId);
3983
    }
3984
3985
    /**
3986
     * Get data of the coupon code for a service.
3987
     *
3988
     * @param int $couponId  The coupon ID
3989
     * @param int $serviceId The product ID
3990
     *
3991
     * @return array The coupon data
3992
     */
3993
    public function getCouponService(int $couponId, int $serviceId)
3994
    {
3995
        return $this->getDataCouponService($couponId, $serviceId);
3996
    }
3997
3998
    /**
3999
     * Get data of the coupon code for a service.
4000
     *
4001
     * @param string $couponCode The coupon code code
4002
     * @param int    $serviceId  The product id
4003
     *
4004
     * @return array The coupon data
4005
     */
4006
    public function getCouponServiceByCode(string $couponCode, int $serviceId)
4007
    {
4008
        return $this->getDataCouponServiceByCode($couponCode, $serviceId);
4009
    }
4010
4011
    /**
4012
     * Get the coupon code of a item sale.
4013
     *
4014
     * @param int $saleId The sale ID
4015
     *
4016
     * @return string The coupon code
4017
     */
4018
    public function getSaleCouponCode(int $saleId)
4019
    {
4020
        $couponTable = Database::get_main_table(self::TABLE_COUPON);
4021
        $couponSaleTable = Database::get_main_table(self::TABLE_COUPON_SALE);
4022
4023
        $couponFrom = "
4024
            $couponTable c
4025
            INNER JOIN $couponSaleTable s
4026
                on c.id = s.coupon_id
4027
        ";
4028
4029
        $couponCode = Database::select(
4030
            ['c.code'],
4031
            $couponFrom,
4032
            [
4033
                'where' => [
4034
                    's.sale_id = ? ' => $saleId,
4035
                ],
4036
            ],
4037
            'first'
4038
        );
4039
4040
        return $couponCode['code'];
4041
    }
4042
4043
    /**
4044
     * Get the coupon code of a service sale.
4045
     *
4046
     * @param int $serviceSaleId The service sale ID
4047
     *
4048
     * @return string The coupon code
4049
     */
4050
    public function getServiceSaleCouponCode(int $serviceSaleId)
4051
    {
4052
        $couponTable = Database::get_main_table(self::TABLE_COUPON);
4053
        $couponServiceSaleTable = Database::get_main_table(self::TABLE_COUPON_SERVICE_SALE);
4054
4055
        $couponFrom = "
4056
            $couponTable c
4057
            INNER JOIN $couponServiceSaleTable s
4058
                on c.id = s.coupon_id
4059
        ";
4060
4061
        $couponCode = Database::select(
4062
            ['c.code'],
4063
            $couponFrom,
4064
            [
4065
                'where' => [
4066
                    's.service_sale_id = ? ' => $serviceSaleId,
4067
                ],
4068
            ],
4069
            'first'
4070
        );
4071
4072
        return $couponCode['code'];
4073
    }
4074
4075
    /**
4076
     * @return array
4077
     */
4078
    public function getCecabankSignature(string $saleReference, float $price)
4079
    {
4080
        $urlOk = api_get_path(WEB_PLUGIN_PATH).'buycourses/src/cecabank_success.php';
4081
        $urlKo = api_get_path(WEB_PLUGIN_PATH).'buycourses/src/cecabank_cancel.php';
4082
4083
        $cecabankParams = $this->getCecabankParams();
4084
        $signature = $cecabankParams['crypto_key']
4085
            .$cecabankParams['merchant_id']
4086
            .$cecabankParams['acquirer_bin']
4087
            .$cecabankParams['terminal_id']
4088
            .$saleReference
4089
            .$price * 100
4090
            .'978'
4091
            .$cecabankParams['exponent']
4092
            .$cecabankParams['cypher']
4093
            .$urlOk
4094
            .$urlKo;
4095
4096
        $sha256 = hash('sha256', $signature);
4097
4098
        return strtolower($sha256);
4099
    }
4100
4101
    /**
4102
     * Register a subscription sale.
4103
     *
4104
     * @param int $productId   The product ID
4105
     * @param int $productType The product type
4106
     * @param int $paymentType The payment type
4107
     * @param int $duration    The subscription duration
4108
     * @param int $couponId    The coupon ID
4109
     *
4110
     * @return int
4111
     */
4112
    public function registerSubscriptionSale(
4113
        int $productId,
4114
        int $productType,
4115
        int $paymentType,
4116
        int $duration,
4117
        ?int $couponId = null
4118
    ) {
4119
        if (!in_array(
4120
            $paymentType,
4121
            [
4122
                self::PAYMENT_TYPE_PAYPAL,
4123
                self::PAYMENT_TYPE_TRANSFER,
4124
                self::PAYMENT_TYPE_CULQI,
4125
                self::PAYMENT_TYPE_TPV_REDSYS,
4126
                self::PAYMENT_TYPE_STRIPE,
4127
                self::PAYMENT_TYPE_TPV_CECABANK,
4128
            ]
4129
        )
4130
        ) {
4131
            return false;
4132
        }
4133
4134
        $entityManager = Database::getManager();
4135
        $item = $this->getSubscriptionItem($productId, $productType);
4136
4137
        if (empty($item)) {
4138
            return false;
4139
        }
4140
4141
        $productName = '';
4142
        if (self::PRODUCT_TYPE_COURSE == $item['product_type']) {
4143
            $course = $entityManager->find(Course::class, $item['product_id']);
4144
4145
            if (empty($course)) {
4146
                return false;
4147
            }
4148
4149
            $productName = $course->getTitle();
4150
        } elseif (self::PRODUCT_TYPE_SESSION == $item['product_type']) {
4151
            $session = $entityManager->find(Session::class, $item['product_id']);
4152
4153
            if (empty($session)) {
4154
                return false;
4155
            }
4156
4157
            $productName = $session->getName();
4158
        }
4159
4160
        if (null != $couponId) {
4161
            $coupon = $this->getCoupon($couponId, $item['product_type'], $item['product_id']);
4162
        }
4163
4164
        $couponDiscount = 0;
4165
        $priceWithoutDiscount = 0;
4166
        if (null != $coupon) {
4167
            if (self::COUPON_DISCOUNT_TYPE_AMOUNT == $coupon['discount_type']) {
4168
                $couponDiscount = $coupon['discount_amount'];
4169
            } elseif (self::COUPON_DISCOUNT_TYPE_PERCENTAGE == $coupon['discount_type']) {
4170
                $couponDiscount = ($item['price'] * $coupon['discount_amount']) / 100;
4171
            }
4172
            $priceWithoutDiscount = $item['price'];
4173
        }
4174
        $item['price'] -= $couponDiscount;
4175
        $price = $item['price'];
4176
        $priceWithoutTax = null;
4177
        $taxPerc = null;
4178
        $taxAmount = 0;
4179
        $taxEnable = 'true' === $this->get('tax_enable');
4180
        $globalParameters = $this->getGlobalParameters();
4181
        $taxAppliesTo = $globalParameters['tax_applies_to'];
4182
4183
        if ($taxEnable
4184
            && (
4185
                self::TAX_APPLIES_TO_ALL == $taxAppliesTo
4186
                || (self::TAX_APPLIES_TO_ONLY_COURSE == $taxAppliesTo && self::PRODUCT_TYPE_COURSE == $item['product_type'])
4187
                || (self::TAX_APPLIES_TO_ONLY_SESSION == $taxAppliesTo && self::PRODUCT_TYPE_SESSION == $item['product_type'])
4188
            )
4189
        ) {
4190
            $priceWithoutTax = $item['price'];
4191
            $globalTaxPerc = $globalParameters['global_tax_perc'];
4192
            $precision = 2;
4193
            $taxPerc = null === $item['tax_perc'] ? $globalTaxPerc : $item['tax_perc'];
4194
            $taxAmount = round($priceWithoutTax * $taxPerc / 100, $precision);
4195
            $price = $priceWithoutTax + $taxAmount;
4196
        }
4197
4198
        $subscriptionEnd = date('y:m:d', strtotime('+'.$duration.' days'));
4199
4200
        $values = [
4201
            'reference' => $this->generateReference(
4202
                api_get_user_id(),
4203
                $item['product_type'],
4204
                $item['product_id']
4205
            ),
4206
            'currency_id' => $item['currency_id'],
4207
            'date' => api_get_utc_datetime(),
4208
            'user_id' => api_get_user_id(),
4209
            'product_type' => $item['product_type'],
4210
            'product_name' => $productName,
4211
            'product_id' => $item['product_id'],
4212
            'price' => $price,
4213
            'price_without_tax' => $priceWithoutTax,
4214
            'tax_perc' => $taxPerc,
4215
            'tax_amount' => $taxAmount,
4216
            'status' => self::SALE_STATUS_PENDING,
4217
            'payment_type' => $paymentType,
4218
            'price_without_discount' => $priceWithoutDiscount,
4219
            'discount_amount' => $couponDiscount,
4220
            'subscription_end' => $subscriptionEnd,
4221
        ];
4222
4223
        return Database::insert(self::TABLE_SUBSCRIPTION_SALE, $values);
4224
    }
4225
4226
    /**
4227
     * Add a new subscription.
4228
     *
4229
     * @return bool
4230
     */
4231
    public function addNewSubscription(array $subscription)
4232
    {
4233
        $result = false;
4234
4235
        if (isset($subscription['frequencies'])) {
4236
            foreach ($subscription['frequencies'] as $frequency) {
4237
                $subscriptionDb = $this->getSubscription($subscription['product_type'], $subscription['product_id'], $frequency['duration']);
4238
4239
                if (!isset($subscriptionDb) || empty($subscription)) {
4240
                    Display::addFlash(
4241
                        Display::return_message(
4242
                            $this->get_lang('SubscriptionAlreadyExists').' ('.$frequency['duration'].')',
4243
                            'error',
4244
                            false
4245
                        )
4246
                    );
4247
4248
                    return false;
4249
                }
4250
                $subscriptionId = $this->registerSubscription($subscription, $frequency);
4251
                if ($subscriptionId) {
4252
                    $result = true;
4253
                } else {
4254
                    Display::addFlash(
4255
                        Display::return_message(
4256
                            $this->get_lang('SubscriptionErrorInsert'),
4257
                            'error',
4258
                            false
4259
                        )
4260
                    );
4261
4262
                    return false;
4263
                }
4264
            }
4265
        } else {
4266
            Display::addFlash(
4267
                Display::return_message(
4268
                    $this->get_lang('FrequenciesNotSetError'),
4269
                    'error',
4270
                    false
4271
                )
4272
            );
4273
4274
            return false;
4275
        }
4276
4277
        return $result;
4278
    }
4279
4280
    /**
4281
     * Delete a subscription.
4282
     *
4283
     * @return int
4284
     */
4285
    public function deleteSubscription(int $productType, int $productId, int $duration)
4286
    {
4287
        return Database::delete(
4288
            Database::get_main_table(self::TABLE_SUBSCRIPTION),
4289
            [
4290
                'product_type = ? AND ' => (int) $productType,
4291
                'product_id = ? AND ' => (int) $productId,
4292
                'duration = ? ' => (int) $duration,
4293
            ]
4294
        );
4295
    }
4296
4297
    /**
4298
     * Get a list of subscriptions by product ID and type.
4299
     *
4300
     * @param string $productId   The product ID
4301
     * @param int    $productType The product type
4302
     *
4303
     * @return array Subscriptions data
4304
     */
4305
    public function getSubscriptions($productType, $productId)
4306
    {
4307
        return $this->getDataSubscriptions($productType, $productId);
4308
    }
4309
4310
    /**
4311
     * Get data of the subscription.
4312
     *
4313
     * @return array The subscription data
4314
     */
4315
    public function getSubscription(int $productType, int $productId, int $duration, ?array $coupon = null)
4316
    {
4317
        $subscription = $this->getDataSubscription($productType, $productId, $duration);
4318
4319
        $currency = $this->getSelectedCurrency();
4320
        $isoCode = $currency['iso_code'];
4321
4322
        $subscription['iso_code'] = $isoCode;
4323
4324
        $this->setPriceSettings($subscription, self::TAX_APPLIES_TO_ONLY_COURSE, $coupon);
4325
4326
        return $subscription;
4327
    }
4328
4329
    /**
4330
     * Get subscription sale data by ID.
4331
     *
4332
     * @param int $saleId The sale ID
4333
     *
4334
     * @return array
4335
     */
4336
    public function getSubscriptionSale(int $saleId)
4337
    {
4338
        return Database::select(
4339
            '*',
4340
            Database::get_main_table(self::TABLE_SUBSCRIPTION_SALE),
4341
            [
4342
                'where' => ['id = ?' => $saleId],
4343
            ],
4344
            'first'
4345
        );
4346
    }
4347
4348
    /**
4349
     * Complete subscription sale process. Update sale status to completed.
4350
     *
4351
     * @param int $saleId The subscription sale ID
4352
     *
4353
     * @return bool
4354
     */
4355
    public function completeSubscriptionSale(int $saleId)
4356
    {
4357
        $sale = $this->getSubscriptionSale($saleId);
4358
4359
        if (self::SALE_STATUS_COMPLETED == $sale['status']) {
4360
            return true;
4361
        }
4362
4363
        $saleIsCompleted = false;
4364
4365
        switch ($sale['product_type']) {
4366
            case self::PRODUCT_TYPE_COURSE:
4367
                $course = api_get_course_info_by_id($sale['product_id']);
4368
                $saleIsCompleted = CourseManager::subscribeUser($sale['user_id'], $course['code']);
4369
4370
                break;
4371
4372
            case self::PRODUCT_TYPE_SESSION:
4373
                SessionManager::subscribeUsersToSession(
4374
                    $sale['product_id'],
4375
                    [$sale['user_id']],
4376
                    api_get_session_visibility($sale['product_id']),
4377
                    false
4378
                );
4379
4380
                $saleIsCompleted = true;
4381
4382
                break;
4383
        }
4384
4385
        if ($saleIsCompleted) {
4386
            $this->updateSubscriptionSaleStatus($sale['id'], self::SALE_STATUS_COMPLETED);
4387
            if ('true' === $this->get('invoicing_enable')) {
4388
                $this->setInvoice($sale['id']);
4389
            }
4390
        }
4391
4392
        return $saleIsCompleted;
4393
    }
4394
4395
    /**
4396
     * Update subscription sale status to canceled.
4397
     *
4398
     * @param int $saleId The subscription sale ID
4399
     */
4400
    public function cancelSubscriptionSale(int $saleId): void
4401
    {
4402
        $this->updateSubscriptionSaleStatus($saleId, self::SALE_STATUS_CANCELED);
4403
    }
4404
4405
    /**
4406
     * Get a list of subscription sales by the status.
4407
     *
4408
     * @param int $status The status to filter
4409
     *
4410
     * @return array The sale list. Otherwise, return false
4411
     */
4412
    public function getSubscriptionSaleListByStatus(int $status = self::SALE_STATUS_PENDING)
4413
    {
4414
        $saleTable = Database::get_main_table(self::TABLE_SUBSCRIPTION_SALE);
4415
        $currencyTable = Database::get_main_table(self::TABLE_CURRENCY);
4416
        $userTable = Database::get_main_table(TABLE_MAIN_USER);
4417
4418
        $innerJoins = "
4419
            INNER JOIN $currencyTable c ON s.currency_id = c.id
4420
            INNER JOIN $userTable u ON s.user_id = u.id
4421
        ";
4422
4423
        return Database::select(
4424
            ['c.iso_code', 'u.firstname', 'u.lastname', 'u.email', 's.*'],
4425
            "$saleTable s $innerJoins",
4426
            [
4427
                'where' => ['s.status = ?' => $status],
4428
                'order' => 'id DESC',
4429
            ]
4430
        );
4431
    }
4432
4433
    /**
4434
     * Get the list statuses for subscriptions sales.
4435
     *
4436
     * @return array
4437
     *
4438
     * @throws Exception
4439
     */
4440
    public function getSubscriptionSaleListReport(?string $dateStart = null, ?string $dateEnd = null)
4441
    {
4442
        $saleTable = Database::get_main_table(self::TABLE_SUBSCRIPTION_SALE);
4443
        $currencyTable = Database::get_main_table(self::TABLE_CURRENCY);
4444
        $userTable = Database::get_main_table(TABLE_MAIN_USER);
4445
        $innerJoins = "
4446
            INNER JOIN $currencyTable c ON s.currency_id = c.id
4447
            INNER JOIN $userTable u ON s.user_id = u.id
4448
        ";
4449
        $list = Database::select(
4450
            ['c.iso_code', 'u.firstname', 'u.lastname', 'u.email', 's.*'],
4451
            "$saleTable s $innerJoins",
4452
            [
4453
                'order' => 'id DESC',
4454
            ]
4455
        );
4456
        $listExportTemp = [];
4457
        $listExport = [];
4458
        $textStatus = null;
4459
        $paymentTypes = $this->getPaymentTypes();
4460
        $productTypes = $this->getProductTypes();
4461
        foreach ($list as $item) {
4462
            $statusSaleOrder = $item['status'];
4463
4464
            switch ($statusSaleOrder) {
4465
                case 0:
4466
                    $textStatus = $this->get_lang('SaleStatusPending');
4467
4468
                    break;
4469
4470
                case 1:
4471
                    $textStatus = $this->get_lang('SaleStatusCompleted');
4472
4473
                    break;
4474
4475
                case -1:
4476
                    $textStatus = $this->get_lang('SaleStatusCanceled');
4477
4478
                    break;
4479
            }
4480
            $dateFilter = new DateTime($item['date']);
4481
            $listExportTemp[] = [
4482
                'id' => $item['id'],
4483
                'reference' => $item['reference'],
4484
                'status' => $textStatus,
4485
                'status_filter' => $item['status'],
4486
                'date' => $dateFilter->format('Y-m-d'),
4487
                'order_time' => $dateFilter->format('H:i:s'),
4488
                'price' => $item['iso_code'].' '.$item['price'],
4489
                'product_type' => $productTypes[$item['product_type']],
4490
                'product_name' => $item['product_name'],
4491
                'payment_type' => $paymentTypes[$item['payment_type']],
4492
                'complete_user_name' => api_get_person_name($item['firstname'], $item['lastname']),
4493
                'email' => $item['email'],
4494
            ];
4495
        }
4496
        $listExport[] = [
4497
            get_lang('Number'),
4498
            $this->get_lang('OrderStatus'),
4499
            $this->get_lang('OrderDate'),
4500
            $this->get_lang('OrderTime'),
4501
            $this->get_lang('PaymentMethod'),
4502
            $this->get_lang('SalePrice'),
4503
            $this->get_lang('ProductType'),
4504
            $this->get_lang('ProductName'),
4505
            $this->get_lang('UserName'),
4506
            get_lang('Email'),
4507
        ];
4508
        // Validation Export
4509
        $dateStart = strtotime($dateStart);
4510
        $dateEnd = strtotime($dateEnd);
4511
        foreach ($listExportTemp as $item) {
4512
            $dateFilter = strtotime($item['date']);
4513
            if (($dateFilter >= $dateStart) && ($dateFilter <= $dateEnd)) {
4514
                $listExport[] = [
4515
                    'id' => $item['id'],
4516
                    'status' => $item['status'],
4517
                    'date' => $item['date'],
4518
                    'order_time' => $item['order_time'],
4519
                    'payment_type' => $item['payment_type'],
4520
                    'price' => $item['price'],
4521
                    'product_type' => $item['product_type'],
4522
                    'product_name' => $item['product_name'],
4523
                    'complete_user_name' => $item['complete_user_name'],
4524
                    'email' => $item['email'],
4525
                ];
4526
            }
4527
        }
4528
4529
        return $listExport;
4530
    }
4531
4532
    /**
4533
     * Get a list of subscription sales by the user.
4534
     *
4535
     * @param string $term The search term
4536
     *
4537
     * @return array The sale list. Otherwise, return false
4538
     */
4539
    public function getSubscriptionSaleListByUser(string $term)
4540
    {
4541
        $term = trim($term);
4542
4543
        if (empty($term)) {
4544
            return [];
4545
        }
4546
4547
        $saleTable = Database::get_main_table(self::TABLE_SUBSCRIPTION_SALE);
4548
        $currencyTable = Database::get_main_table(self::TABLE_CURRENCY);
4549
        $userTable = Database::get_main_table(TABLE_MAIN_USER);
4550
        $innerJoins = "
4551
            INNER JOIN $currencyTable c ON s.currency_id = c.id
4552
            INNER JOIN $userTable u ON s.user_id = u.id
4553
        ";
4554
4555
        return Database::select(
4556
            ['c.iso_code', 'u.firstname', 'u.lastname', 'u.email', 's.*'],
4557
            "$saleTable s $innerJoins",
4558
            [
4559
                'where' => [
4560
                    'u.username LIKE %?% OR ' => $term,
4561
                    'u.lastname LIKE %?% OR ' => $term,
4562
                    'u.firstname LIKE %?%' => $term,
4563
                ],
4564
                'order' => 'id DESC',
4565
            ]
4566
        );
4567
    }
4568
4569
    /**
4570
     * Get a list of subscription sales by the user id.
4571
     *
4572
     * @param int $id The user id
4573
     *
4574
     * @return array The sale list. Otherwise, return false
4575
     */
4576
    public function getSubscriptionSaleListByUserId(int $id)
4577
    {
4578
        if (empty($id)) {
4579
            return [];
4580
        }
4581
4582
        $saleTable = Database::get_main_table(self::TABLE_SUBSCRIPTION_SALE);
4583
        $currencyTable = Database::get_main_table(self::TABLE_CURRENCY);
4584
        $userTable = Database::get_main_table(TABLE_MAIN_USER);
4585
4586
        $innerJoins = "
4587
            INNER JOIN $currencyTable c ON s.currency_id = c.id
4588
            INNER JOIN $userTable u ON s.user_id = u.id
4589
        ";
4590
4591
        return Database::select(
4592
            ['c.iso_code', 'u.firstname', 'u.lastname', 's.*'],
4593
            "$saleTable s $innerJoins",
4594
            [
4595
                'where' => [
4596
                    'u.id = ? AND s.status = ?' => [$id, self::SALE_STATUS_COMPLETED],
4597
                ],
4598
                'order' => 'id DESC',
4599
            ]
4600
        );
4601
    }
4602
4603
    /**
4604
     * Get a list of subscription sales by date range.
4605
     *
4606
     * @return array The sale list. Otherwise, return false
4607
     */
4608
    public function getSubscriptionSaleListByDate(string $dateStart, string $dateEnd)
4609
    {
4610
        $dateStart = trim($dateStart);
4611
        $dateEnd = trim($dateEnd);
4612
        if (empty($dateStart)) {
4613
            return [];
4614
        }
4615
        if (empty($dateEnd)) {
4616
            return [];
4617
        }
4618
        $saleTable = Database::get_main_table(self::TABLE_SUBSCRIPTION_SALE);
4619
        $currencyTable = Database::get_main_table(self::TABLE_CURRENCY);
4620
        $userTable = Database::get_main_table(TABLE_MAIN_USER);
4621
        $innerJoins = "
4622
            INNER JOIN $currencyTable c ON s.currency_id = c.id
4623
            INNER JOIN $userTable u ON s.user_id = u.id
4624
        ";
4625
4626
        return Database::select(
4627
            ['c.iso_code', 'u.firstname', 'u.lastname', 'u.email', 's.*'],
4628
            "$saleTable s $innerJoins",
4629
            [
4630
                'where' => [
4631
                    's.date BETWEEN ? AND ' => $dateStart,
4632
                    ' ? ' => $dateEnd,
4633
                ],
4634
                'order' => 'id DESC',
4635
            ]
4636
        );
4637
    }
4638
4639
    /**
4640
     * Get a list of subscription sales by the user Email.
4641
     *
4642
     * @param string $term The search term
4643
     *
4644
     * @return array The sale list. Otherwise, return false
4645
     */
4646
    public function getSubscriptionSaleListByEmail(string $term)
4647
    {
4648
        $term = trim($term);
4649
        if (empty($term)) {
4650
            return [];
4651
        }
4652
        $saleTable = Database::get_main_table(self::TABLE_SUBSCRIPTION_SALE);
4653
        $currencyTable = Database::get_main_table(self::TABLE_CURRENCY);
4654
        $userTable = Database::get_main_table(TABLE_MAIN_USER);
4655
        $innerJoins = "
4656
            INNER JOIN $currencyTable c ON s.currency_id = c.id
4657
            INNER JOIN $userTable u ON s.user_id = u.id
4658
        ";
4659
4660
        return Database::select(
4661
            ['c.iso_code', 'u.firstname', 'u.lastname', 'u.email', 's.*'],
4662
            "$saleTable s $innerJoins",
4663
            [
4664
                'where' => [
4665
                    'u.email LIKE %?% ' => $term,
4666
                ],
4667
                'order' => 'id DESC',
4668
            ]
4669
        );
4670
    }
4671
4672
    /**
4673
     * Get subscription sale data by ID.
4674
     *
4675
     * @param string $date The date
4676
     *
4677
     * @return array
4678
     */
4679
    public function getSubscriptionsDue(string $date)
4680
    {
4681
        return Database::select(
4682
            'id, user_id, product_id, product_type',
4683
            Database::get_main_table(self::TABLE_SUBSCRIPTION_SALE),
4684
            [
4685
                'where' => ['subscription_end < ? AND status <> ? AND (expired is NULL OR expired <> ?)' => [
4686
                    $date,
4687
                    self::SALE_STATUS_COMPLETED,
4688
                    1,
4689
                ],
4690
                ],
4691
            ],
4692
            'first'
4693
        );
4694
    }
4695
4696
    /**
4697
     * Get subscription sale data by ID.
4698
     *
4699
     * @param int $userId      The user ID
4700
     * @param int $productId   The product ID
4701
     * @param int $productType The product type
4702
     *
4703
     * @return array
4704
     */
4705
    public function checkItemSubscriptionActive(int $userId, int $productId, int $productType)
4706
    {
4707
        return Database::select(
4708
            '*',
4709
            Database::get_main_table(self::TABLE_SUBSCRIPTION_SALE),
4710
            [
4711
                'where' => ['subscription_end >= ? AND userId = ? AND productId = ? AND productType = ? AND status <> ?' => [
4712
                    api_get_utc_datetime(),
4713
                    $userId,
4714
                    $productId,
4715
                    $productType,
4716
                    self::SALE_STATUS_COMPLETED,
4717
                ],
4718
                ],
4719
            ],
4720
            'first'
4721
        );
4722
    }
4723
4724
    /**
4725
     * Get subscription sale data by ID.
4726
     *
4727
     * @return array
4728
     */
4729
    public function updateSubscriptionSaleExpirationStatus(int $id)
4730
    {
4731
        $saleTable = Database::get_main_table(self::TABLE_SUBSCRIPTION_SALE);
4732
4733
        return Database::update(
4734
            $saleTable,
4735
            ['expired' => 1],
4736
            ['id = ?' => $id]
4737
        );
4738
    }
4739
4740
    /**
4741
     * Get the list of frequencies discount types.
4742
     *
4743
     * @return array
4744
     */
4745
    public function getFrequencies()
4746
    {
4747
        $data = Database::select(
4748
            '*',
4749
            Database::get_main_table(self::TABLE_SUBSCRIPTION_PERIOD),
4750
            []
4751
        );
4752
4753
        $frequenciesList = $this->getFrequenciesList();
4754
        $frequencies = [];
4755
4756
        foreach ($data as $key => $items) {
4757
            $frequencies[$items['duration']] = $items['name'];
4758
        }
4759
4760
        return $frequencies;
4761
    }
4762
4763
    /**
4764
     * Get the list of frequencies discount types.
4765
     *
4766
     * @return array
4767
     */
4768
    public function getFrequenciesList()
4769
    {
4770
        return Database::select(
4771
            '*',
4772
            Database::get_main_table(self::TABLE_SUBSCRIPTION_PERIOD),
4773
            []
4774
        );
4775
    }
4776
4777
    /**
4778
     * Get the a frequency.
4779
     *
4780
     * @param int $duration The duration of the frequency value
4781
     *
4782
     * @return array
4783
     */
4784
    public function selectFrequency(int $duration)
4785
    {
4786
        return Database::select(
4787
            '*',
4788
            Database::get_main_table(self::TABLE_SUBSCRIPTION_PERIOD),
4789
            [
4790
                'where' => [
4791
                    'duration = ?' => [
4792
                        (int) $duration,
4793
                    ],
4794
                ],
4795
            ],
4796
            'first'
4797
        );
4798
    }
4799
4800
    /**
4801
     * Add a new subscription frequency.
4802
     *
4803
     * @return array
4804
     */
4805
    public function addFrequency(int $duration, string $name)
4806
    {
4807
        $values = [
4808
            'duration' => $duration,
4809
            'name' => $name,
4810
        ];
4811
4812
        return Database::insert(self::TABLE_SUBSCRIPTION_PERIOD, $values);
4813
    }
4814
4815
    /**
4816
     * Update a subscription frequency.
4817
     *
4818
     * @return array
4819
     */
4820
    public function updateFrequency(int $duration, string $name)
4821
    {
4822
        $periodTable = Database::get_main_table(self::TABLE_SUBSCRIPTION_PERIOD);
4823
4824
        return Database::update(
4825
            $periodTable,
4826
            ['name' => $name],
4827
            ['duration = ?' => $duration]
4828
        );
4829
    }
4830
4831
    /**
4832
     * Delete a subscription frequency.
4833
     *
4834
     * @return array
4835
     */
4836
    public function deleteFrequency(int $duration)
4837
    {
4838
        return Database::delete(
4839
            Database::get_main_table(self::TABLE_SUBSCRIPTION_PERIOD),
4840
            [
4841
                'duration = ?' => $duration,
4842
            ]
4843
        );
4844
    }
4845
4846
    /**
4847
     * @return string
4848
     */
4849
    public function getSubscriptionSuccessMessage(array $saleInfo)
4850
    {
4851
        switch ($saleInfo['product_type']) {
4852
            case self::PRODUCT_TYPE_COURSE:
4853
                $courseInfo = api_get_course_info_by_id($saleInfo['product_id']);
4854
                $url = api_get_course_url($courseInfo['code']);
4855
4856
                break;
4857
4858
            case self::PRODUCT_TYPE_SESSION:
4859
                $sessionId = (int) $saleInfo['product_id'];
4860
                $url = api_get_path(WEB_CODE_PATH).'session/index.php?session_id='.$sessionId;
4861
4862
                break;
4863
4864
            default:
4865
                $url = '#';
4866
        }
4867
4868
        return Display::return_message(
4869
            sprintf(
4870
                $this->get_lang('SubscriptionToCourseXSuccessful'),
4871
                $url,
4872
                $saleInfo['product_name']
4873
            ),
4874
            'success',
4875
            false
4876
        );
4877
    }
4878
4879
    /**
4880
     * @return string
4881
     */
4882
    public static function returnPagination(
4883
        string $baseUrl,
4884
        int $currentPage,
4885
        int $pagesCount,
4886
        int $totalItems,
4887
        array $extraQueryParams = []
4888
    ) {
4889
        $queryParams = HttpRequest::createFromGlobals()->query->all();
4890
4891
        unset($queryParams['page']);
4892
4893
        $url = $baseUrl.'?'.http_build_query(
4894
            array_merge($queryParams, $extraQueryParams)
4895
        );
4896
4897
        return Display::getPagination($url, $currentPage, $pagesCount, $totalItems);
4898
    }
4899
4900
    /**
4901
     * Returns the javascript to set the sales report table for courses.
4902
     */
4903
    public static function getSalesReportScript(array $sales = [], bool $invoicingEnable = false)
4904
    {
4905
        $cols = "
4906
    '".preg_replace("/'/", "\\'", get_plugin_lang('OrderReference', 'BuyCoursesPlugin'))."',
4907
    '".preg_replace("/'/", "\\'", get_plugin_lang('OrderStatus', 'BuyCoursesPlugin'))."',
4908
    '".preg_replace("/'/", "\\'", get_plugin_lang('OrderDate', 'BuyCoursesPlugin'))."',
4909
    '".preg_replace("/'/", "\\'", get_plugin_lang('PaymentMethod', 'BuyCoursesPlugin'))."',
4910
    '".preg_replace("/'/", "\\'", get_plugin_lang('Price', 'BuyCoursesPlugin'))."',
4911
    '".preg_replace("/'/", "\\'", get_plugin_lang('CouponDiscount', 'BuyCoursesPlugin'))."',
4912
    '".preg_replace("/'/", "\\'", get_plugin_lang('Coupon', 'BuyCoursesPlugin'))."',
4913
    '".preg_replace("/'/", "\\'", get_plugin_lang('ProductType', 'BuyCoursesPlugin'))."',
4914
    '".preg_replace("/'/", "\\'", get_plugin_lang('Name', 'BuyCoursesPlugin'))."',
4915
    '".preg_replace("/'/", "\\'", get_lang('UserName'))."',
4916
    '".preg_replace("/'/", "\\'", get_lang('Email'))."',";
4917
        $model = "
4918
        {name:'reference', index:'reference', height:'auto', width:70, sorttype:'string', align:'center'},
4919
        {name:'status', index:'status', height:'auto', width:70, sorttype:'string', align:'center'},
4920
        {name:'date', index:'date', height:'auto', width:70, sorttype:'date', align:'center'},
4921
        {name:'payment_type', index:'payment_type', height:'auto', width:70, sorttype:'string', align:'center'},
4922
        {name:'total_price', index:'total_price', height:'auto', width:70, sorttype:'string', align:'center'},
4923
        {name:'coupon_discount', index:'coupon_discount', height:'auto', width:40, sorttype:'string', align: 'center'},
4924
        {name:'coupon', index:'coupon', height:'auto', width:60, sorttype:'string', align:'center'},
4925
        {name:'product_type', index:'product_type', height:'auto', width:40, sorttype:'string'},
4926
        {name:'product_name', index:'product_name', height:'auto', /*width:60,*/ sorttype:'string'},
4927
        {name:'complete_user_name', index:'complete_user_name', height:'auto', width:70, sorttype:'string'},
4928
        {name:'email', index:'email', height:'auto', /*width:60,*/ sorttype:'string'}, ";
4929
        if ($invoicingEnable) {
4930
            $model .= "{name:'invoice', index:'invoice', height:'auto', width:70, sorttype:'string'},";
4931
            $cols .= "'".get_plugin_lang('Invoice', 'BuyCoursesPlugin')."',";
4932
        }
4933
        $cols .= "'".get_lang('Options')."',";
4934
        $model .= "
4935
        {name:'options', index:'options', height:'auto', width:60, sortable:false},";
4936
        $data = '';
4937
        foreach ($sales as $item) {
4938
            $option = '';
4939
            if (!isset($item['complete_user_name'])) {
4940
                $item['complete_user_name'] = api_get_person_name($item['firstname'], $item['lastname']);
4941
            }
4942
            if (1 == $item['invoice']) {
4943
                if ($invoicingEnable) {
4944
                    $item['invoice'] = "<a href='".api_get_path(WEB_PLUGIN_PATH).'buycourses/src/invoice.php?invoice='.$item['id'].'&is_service=0'
4945
                        ."' title='".get_plugin_lang('InvoiceView', 'BuyCoursesPlugin')."'>".
4946
                        Display::return_icon('default.png', get_plugin_lang('InvoiceView', 'BuyCoursesPlugin'), '', ICON_SIZE_MEDIUM).
4947
                        '<br/>'.$item['num_invoice'].
4948
                        '</a>';
4949
                }
4950
            } else {
4951
                $item['invoice'] = null;
4952
            }
4953
            if (self::SALE_STATUS_CANCELED == $item['status']) {
4954
                $item['status'] = get_plugin_lang('SaleStatusCanceled', 'BuyCoursesPlugin');
4955
            } elseif (self::SALE_STATUS_PENDING == $item['status']) {
4956
                $item['status'] = get_plugin_lang('SaleStatusPending', 'BuyCoursesPlugin');
4957
                $option = "<div class='btn-group btn-group-xs' role='group'>".
4958
                    "<a title='".get_plugin_lang('SubscribeUser', 'BuyCoursesPlugin')."'".
4959
                    " href='".api_get_self().'?order='.$item['id']."&action=confirm'".
4960
                    " class='btn btn-default'>".
4961
                    Display::return_icon('user_subscribe_session.png', get_plugin_lang('SubscribeUser', 'BuyCoursesPlugin'), '', ICON_SIZE_SMALL)
4962
                    .'</a>'.
4963
                    "<a title='".get_plugin_lang('DeleteOrder', 'BuyCoursesPlugin')."'".
4964
                    " href='".api_get_self().'?order='.$item['id']."&action=cancel'".
4965
                    " class='btn btn-default'>".
4966
                    Display::return_icon('delete.png', get_plugin_lang('DeleteOrder', 'BuyCoursesPlugin'), '', ICON_SIZE_SMALL)
4967
                    .'</a>'.
4968
                    '</div>';
4969
            } elseif (self::SALE_STATUS_COMPLETED == $item['status']) {
4970
                $item['status'] = get_plugin_lang('SaleStatusCompleted', 'BuyCoursesPlugin');
4971
            }
4972
            $item['options'] = $option;
4973
            $item['date'] = api_get_local_time($item['date']);
4974
            $data .= json_encode($item).',';
4975
        }
4976
4977
        return "
4978
<script>
4979
    $(window).load( function () {
4980
        $('#table_report').jqGrid({
4981
            height: '100%',
4982
            autowidth: true,
4983
            LoadOnce: true,
4984
            rowNum:10,
4985
            rowList: [10, 25, 50, 100],
4986
            pager: 'tblGridPager',
4987
            datatype: 'local',
4988
            viewrecords: true,
4989
            gridview: true,
4990
            colNames:[ $cols ],
4991
            colModel:[ $model ],
4992
            caption: '".get_plugin_lang('SalesReport', 'BuyCoursesPlugin')."'
4993
        });
4994
        var mydata = [ $data ];
4995
        for(var i=0;i<=mydata.length;i++){
4996
            $('#table_report').jqGrid('addRowData',i+1,mydata[i]);
4997
            if(i==mydata.length){
4998
                $('#table_report').trigger('reloadGrid',[{page:1}])
4999
            }
5000
        }
5001
    });
5002
</script>";
5003
    }
5004
5005
    /**
5006
     * Filter the registered courses for show in the plugin catalog.
5007
     */
5008
    public function getCourses(int $first, int $maxResults): QueryBuilder
5009
    {
5010
        $em = Database::getManager();
5011
        $urlId = api_get_current_access_url_id();
5012
5013
        $qb = $em->createQueryBuilder();
5014
        $qb2 = $em->createQueryBuilder();
5015
        $qb3 = $em->createQueryBuilder();
5016
5017
        return $qb
5018
            ->select('c')
5019
            ->from(Course::class, 'c')
5020
            ->where(
5021
                $qb->expr()->notIn(
5022
                    'c',
5023
                    $qb2
5024
                        ->select('course2')
5025
                        ->from(SessionRelCourse::class, 'sc')
5026
                        ->innerJoin('sc.course', 'course2')
5027
                        ->innerJoin(
5028
                            AccessUrlRelSession::class,
5029
                            'us',
5030
                            Join::WITH,
5031
                            'us.session = sc.session'
5032
                        )->where(
5033
                            $qb->expr()->eq('us.url ', $urlId)
5034
                        )
5035
                        ->getDQL()
5036
                )
5037
            )->andWhere(
5038
                $qb->expr()->in(
5039
                    'c',
5040
                    $qb3
5041
                        ->select('course3')
5042
                        ->from(AccessUrlRelCourse::class, 'uc')
5043
                        ->innerJoin('uc.course', 'course3')
5044
                        ->where(
5045
                            $qb3->expr()->eq('uc.url ', $urlId)
5046
                        )
5047
                        ->getDQL()
5048
                )
5049
            )
5050
            ->setFirstResult($first)
5051
            ->setMaxResults($maxResults)
5052
        ;
5053
    }
5054
5055
    /**
5056
     * Get the user status for the session.
5057
     *
5058
     * @param int     $userId  The user ID
5059
     * @param Session $session The session
5060
     *
5061
     * @return string
5062
     */
5063
    private function getUserStatusForSession(int $userId, Session $session)
5064
    {
5065
        if (empty($userId)) {
5066
            return 'NO';
5067
        }
5068
5069
        $entityManager = Database::getManager();
5070
        $scuRepo = $entityManager->getRepository(SessionRelCourseRelUser::class);
5071
5072
        $buySaleTable = Database::get_main_table(self::TABLE_SALE);
5073
5074
        // Check if user bought the course
5075
        $sale = Database::select(
5076
            'COUNT(1) as qty',
5077
            $buySaleTable,
5078
            [
5079
                'where' => [
5080
                    'user_id = ? AND product_type = ? AND product_id = ? AND status = ?' => [
5081
                        $userId,
5082
                        self::PRODUCT_TYPE_SESSION,
5083
                        $session->getId(),
5084
                        self::SALE_STATUS_PENDING,
5085
                    ],
5086
                ],
5087
            ],
5088
            'first'
5089
        );
5090
5091
        if ($sale['qty'] > 0) {
5092
            return 'TMP';
5093
        }
5094
5095
        // Check if user is already subscribe to session
5096
        $userSubscription = $scuRepo->findBy([
5097
            'session' => $session,
5098
            'user' => $userId,
5099
        ]);
5100
5101
        if (!empty($userSubscription)) {
5102
            return 'YES';
5103
        }
5104
5105
        return 'NO';
5106
    }
5107
5108
    /**
5109
     * Get the user status for the course.
5110
     *
5111
     * @param int    $userId The user Id
5112
     * @param Course $course The course
5113
     *
5114
     * @return string
5115
     */
5116
    private function getUserStatusForCourse(int $userId, Course $course)
5117
    {
5118
        if (empty($userId)) {
5119
            return 'NO';
5120
        }
5121
5122
        $entityManager = Database::getManager();
5123
        $cuRepo = $entityManager->getRepository(CourseRelUser::class);
5124
        $buySaleTable = Database::get_main_table(self::TABLE_SALE);
5125
5126
        // Check if user bought the course
5127
        $sale = Database::select(
5128
            'COUNT(1) as qty',
5129
            $buySaleTable,
5130
            [
5131
                'where' => [
5132
                    'user_id = ? AND product_type = ? AND product_id = ? AND status = ?' => [
5133
                        $userId,
5134
                        self::PRODUCT_TYPE_COURSE,
5135
                        $course->getId(),
5136
                        self::SALE_STATUS_PENDING,
5137
                    ],
5138
                ],
5139
            ],
5140
            'first'
5141
        );
5142
5143
        if ($sale['qty'] > 0) {
5144
            return 'TMP';
5145
        }
5146
5147
        // Check if user is already subscribe to course
5148
        $userSubscription = $cuRepo->findBy([
5149
            'course' => $course,
5150
            'user' => $userId,
5151
        ]);
5152
5153
        if (!empty($userSubscription)) {
5154
            return 'YES';
5155
        }
5156
5157
        return 'NO';
5158
    }
5159
5160
    /**
5161
     * Update the sale status.
5162
     *
5163
     * @param int $saleId    The sale ID
5164
     * @param int $newStatus The new status
5165
     *
5166
     * @return bool
5167
     */
5168
    private function updateSaleStatus(int $saleId, int $newStatus = self::SALE_STATUS_PENDING)
5169
    {
5170
        $saleTable = Database::get_main_table(self::TABLE_SALE);
5171
5172
        return Database::update(
5173
            $saleTable,
5174
            ['status' => (int) $newStatus],
5175
            ['id = ?' => (int) $saleId]
5176
        );
5177
    }
5178
5179
    /**
5180
     * Search filtered sessions by name, and range of price.
5181
     *
5182
     * @param string $name            Optional. The name filter
5183
     * @param int    $min             Optional. The minimum price filter
5184
     * @param int    $max             Optional. The maximum price filter
5185
     * @param string $typeResult      Optional. 'all' and 'count'
5186
     * @param int    $sessionCategory Optional. Session category id
5187
     *
5188
     * @return array<int, Session>|int
5189
     */
5190
    private function filterSessionList(
5191
        int $start,
5192
        int $end,
5193
        ?string $name = null,
5194
        int $min = 0,
5195
        int $max = 0,
5196
        string $typeResult = 'all',
5197
        int $sessionCategory = 0
5198
    ): array|int {
5199
        $itemTable = Database::get_main_table(self::TABLE_ITEM);
5200
        $sessionTable = Database::get_main_table(TABLE_MAIN_SESSION);
5201
5202
        $innerJoin = "$itemTable i ON s.id = i.product_id";
5203
        $whereConditions = [
5204
            'i.product_type = ? ' => self::PRODUCT_TYPE_SESSION,
5205
        ];
5206
5207
        if (!empty($name)) {
5208
            $whereConditions['AND s.title LIKE %?%'] = $name;
5209
        }
5210
5211
        if (!empty($min)) {
5212
            $whereConditions['AND i.price >= ?'] = $min;
5213
        }
5214
5215
        if (!empty($max)) {
5216
            $whereConditions['AND i.price <= ?'] = $max;
5217
        }
5218
5219
        if (0 != $sessionCategory) {
5220
            $whereConditions['AND s.session_category_id = ?'] = $sessionCategory;
5221
        }
5222
5223
        $sessionIds = Database::select(
5224
            's.id',
5225
            "$sessionTable s INNER JOIN $innerJoin",
5226
            ['where' => $whereConditions, 'limit' => "$start, $end"],
5227
            $typeResult
5228
        );
5229
5230
        if ('count' === $typeResult) {
5231
            return $sessionIds;
5232
        }
5233
5234
        if (!$sessionIds) {
5235
            return [];
5236
        }
5237
5238
        $sessions = [];
5239
5240
        foreach ($sessionIds as $sessionId) {
5241
            $sessions[] = Database::getManager()->find(
5242
                Session::class,
5243
                $sessionId
5244
            );
5245
        }
5246
5247
        return $sessions;
5248
    }
5249
5250
    /**
5251
     * Search filtered courses by name, and range of price.
5252
     *
5253
     * @param string $name Optional. The name filter
5254
     * @param int    $min  Optional. The minimun price filter
5255
     * @param int    $max  Optional. The maximum price filter
5256
     *
5257
     * @return array<int, Course>|int
5258
     */
5259
    private function filterCourseList(
5260
        int $start,
5261
        int $end,
5262
        ?string $name = null,
5263
        int $min = 0,
5264
        int $max = 0,
5265
        string $typeResult = 'all'
5266
    ): array|int {
5267
        $itemTable = Database::get_main_table(self::TABLE_ITEM);
5268
        $courseTable = Database::get_main_table(TABLE_MAIN_COURSE);
5269
        $urlTable = Database::get_main_table(TABLE_MAIN_ACCESS_URL_REL_COURSE);
5270
5271
        $urlId = api_get_current_access_url_id();
5272
5273
        $min = (float) $min;
5274
        $max = (float) $max;
5275
5276
        $whereConditions = [
5277
            'i.product_type = ? ' => self::PRODUCT_TYPE_COURSE,
5278
        ];
5279
5280
        if (!empty($name)) {
5281
            $whereConditions['AND c.title LIKE %?%'] = $name;
5282
        }
5283
5284
        if (!empty($min)) {
5285
            $whereConditions['AND i.price >= ?'] = $min;
5286
        }
5287
5288
        if (!empty($max)) {
5289
            $whereConditions['AND i.price <= ?'] = $max;
5290
        }
5291
5292
        $whereConditions['AND url.access_url_id = ?'] = $urlId;
5293
5294
        $courseIds = Database::select(
5295
            'c.id',
5296
            "$courseTable c
5297
            INNER JOIN $itemTable i
5298
            ON c.id = i.product_id
5299
            INNER JOIN $urlTable url
5300
            ON c.id = url.c_id
5301
            ",
5302
            ['where' => $whereConditions, 'limit' => "$start, $end"],
5303
            $typeResult
5304
        );
5305
5306
        if ('count' === $typeResult) {
5307
            return $courseIds;
5308
        }
5309
5310
        if (!$courseIds) {
5311
            return [];
5312
        }
5313
5314
        $courses = [];
5315
        foreach ($courseIds as $courseId) {
5316
            $courses[] = Database::getManager()->find(
5317
                Course::class,
5318
                $courseId['id']
5319
            );
5320
        }
5321
5322
        return $courses;
5323
    }
5324
5325
    /**
5326
     * Search filtered sessions by name, and range of price.
5327
     *
5328
     * @param string $name            Optional. The name filter
5329
     * @param int    $sessionCategory Optional. Session category id
5330
     *
5331
     * @return array<int, Session>|int
5332
     */
5333
    private function filterSubscriptionSessionList(
5334
        int $start,
5335
        int $end,
5336
        ?string $name = null,
5337
        string $typeResult = 'all',
5338
        int $sessionCategory = 0
5339
    ): array|int {
5340
        $subscriptionTable = Database::get_main_table(self::TABLE_SUBSCRIPTION);
5341
        $sessionTable = Database::get_main_table(TABLE_MAIN_SESSION);
5342
5343
        $innerJoin = "$subscriptionTable st ON s.id = st.product_id";
5344
        $whereConditions = [
5345
            'st.product_type = ? ' => self::PRODUCT_TYPE_SESSION,
5346
        ];
5347
5348
        if (!empty($name)) {
5349
            $whereConditions['AND s.name LIKE %?%'] = $name;
5350
        }
5351
5352
        if (0 != $sessionCategory) {
5353
            $whereConditions['AND s.session_category_id = ?'] = $sessionCategory;
5354
        }
5355
5356
        $sessionIds = Database::select(
5357
            'DISTINCT s.id',
5358
            "$sessionTable s INNER JOIN $innerJoin",
5359
            ['where' => $whereConditions, 'limit' => "$start, $end"],
5360
            $typeResult
5361
        );
5362
5363
        if ('count' === $typeResult) {
5364
            return $sessionIds;
5365
        }
5366
5367
        if (!$sessionIds) {
5368
            return [];
5369
        }
5370
5371
        $sessions = [];
5372
5373
        foreach ($sessionIds as $sessionId) {
5374
            $sessions[] = Database::getManager()->find(
5375
                Session::class,
5376
                $sessionId
5377
            );
5378
        }
5379
5380
        return $sessions;
5381
    }
5382
5383
    /**
5384
     * Search filtered subscription courses by name, and range of price.
5385
     *
5386
     * @param string $name Optional. The name filter
5387
     *
5388
     * @return array<int, Course>|int
5389
     */
5390
    private function filterSubscriptionCourseList(
5391
        int $start,
5392
        int $end,
5393
        string $name = '',
5394
        string $typeResult = 'all'
5395
    ): array|int {
5396
        $subscriptionTable = Database::get_main_table(self::TABLE_SUBSCRIPTION);
5397
        $courseTable = Database::get_main_table(TABLE_MAIN_COURSE);
5398
        $urlTable = Database::get_main_table(TABLE_MAIN_ACCESS_URL_REL_COURSE);
5399
5400
        $urlId = api_get_current_access_url_id();
5401
5402
        $whereConditions = [
5403
            'st.product_type = ? ' => self::PRODUCT_TYPE_COURSE,
5404
        ];
5405
5406
        if (!empty($name)) {
5407
            $whereConditions['AND c.title LIKE %?%'] = $name;
5408
        }
5409
5410
        $whereConditions['AND url.access_url_id = ?'] = $urlId;
5411
5412
        $courseIds = Database::select(
5413
            'DISTINCT c.id',
5414
            "$courseTable c
5415
            INNER JOIN $subscriptionTable st
5416
            ON c.id = st.product_id
5417
            INNER JOIN $urlTable url
5418
            ON c.id = url.c_id
5419
            ",
5420
            ['where' => $whereConditions, 'limit' => "$start, $end"],
5421
            $typeResult
5422
        );
5423
5424
        if ('count' === $typeResult) {
5425
            return $courseIds;
5426
        }
5427
5428
        if (!$courseIds) {
5429
            return [];
5430
        }
5431
5432
        $courses = [];
5433
        foreach ($courseIds as $courseId) {
5434
            $courses[] = Database::getManager()->find(
5435
                Course::class,
5436
                $courseId
5437
            );
5438
        }
5439
5440
        return $courses;
5441
    }
5442
5443
    /**
5444
     * Update the service sale status.
5445
     *
5446
     * @param int $serviceSaleId The service sale ID
5447
     * @param int $newStatus     The new status
5448
     */
5449
    private function updateServiceSaleStatus(
5450
        int $serviceSaleId,
5451
        int $newStatus = self::SERVICE_STATUS_PENDING
5452
    ): void {
5453
        $serviceSaleTable = Database::get_main_table(self::TABLE_SERVICES_SALE);
5454
5455
        Database::update(
5456
            $serviceSaleTable,
5457
            ['status' => $newStatus],
5458
            ['id = ?' => $serviceSaleId]
5459
        );
5460
    }
5461
5462
    /**
5463
     * Get the items (courses or sessions) of a coupon.
5464
     *
5465
     * @return array The item data
5466
     */
5467
    private function getItemsCoupons(int $couponId, int $productType): array
5468
    {
5469
        $couponItemTable = Database::get_main_table(self::TABLE_COUPON_ITEM);
5470
5471
        if (self::PRODUCT_TYPE_COURSE == $productType) {
5472
            $itemTable = Database::get_main_table(TABLE_MAIN_COURSE);
5473
            $select = ['ci.product_id as id', 'it.title'];
5474
        } elseif (self::PRODUCT_TYPE_SESSION == $productType) {
5475
            $itemTable = Database::get_main_table(TABLE_MAIN_SESSION);
5476
            $select = ['ci.product_id as id', 'it.name'];
5477
        }
5478
5479
        $couponFrom = "
5480
            $couponItemTable ci
5481
            INNER JOIN $itemTable it
5482
                on it.id = ci.product_id and ci.product_type = $productType
5483
        ";
5484
5485
        return Database::select(
5486
            $select,
5487
            $couponFrom,
5488
            [
5489
                'where' => [
5490
                    'ci.coupon_id = ? ' => $couponId,
5491
                ],
5492
            ]
5493
        );
5494
    }
5495
5496
    /**
5497
     * Get the services of a coupon.
5498
     *
5499
     * @param int $couponId The coupon ID
5500
     *
5501
     * @return array The service data
5502
     */
5503
    private function getServicesCoupons(int $couponId): array
5504
    {
5505
        $couponServiceTable = Database::get_main_table(self::TABLE_COUPON_SERVICE);
5506
        $serviceTable = Database::get_main_table(self::TABLE_SERVICES);
5507
5508
        $couponFrom = "
5509
            $couponServiceTable cs
5510
            INNER JOIN $serviceTable s
5511
                on s.id = cs.service_id
5512
        ";
5513
5514
        return Database::select(
5515
            ['cs.service_id as id', 's.name'],
5516
            $couponFrom,
5517
            [
5518
                'where' => [
5519
                    'cs.coupon_id = ? ' => $couponId,
5520
                ],
5521
            ]
5522
        );
5523
    }
5524
5525
    /**
5526
     * Get an array of coupons filtered by their status.
5527
     *
5528
     * @param int $status The coupon activation status
5529
     *
5530
     * @return array Coupons data
5531
     */
5532
    private function getDataCoupons(?int $status = null): array
5533
    {
5534
        $couponTable = Database::get_main_table(self::TABLE_COUPON);
5535
5536
        if (null != $status) {
5537
            return Database::select(
5538
                ['*'],
5539
                $couponTable,
5540
                [
5541
                    'where' => [
5542
                        ' active = ? ' => (int) $status,
5543
                    ],
5544
                    'order' => 'id DESC',
5545
                ]
5546
            );
5547
        }
5548
5549
        return Database::select(
5550
            ['*'],
5551
            $couponTable,
5552
            [
5553
                'order' => 'id DESC',
5554
            ]
5555
        );
5556
    }
5557
5558
    /**
5559
     * Get data of a coupon for a product (course or service) by the coupon ID.
5560
     *
5561
     * @param int $couponId    The coupon code code
5562
     * @param int $productType The product type
5563
     * @param int $productId   The product ID
5564
     *
5565
     * @return array The coupon data
5566
     */
5567
    private function getDataCoupon(int $couponId, ?int $productType = null, ?int $productId = null): array
5568
    {
5569
        $couponTable = Database::get_main_table(self::TABLE_COUPON);
5570
5571
        if (null == $productType || null == $productId) {
5572
            return Database::select(
5573
                ['*'],
5574
                $couponTable,
5575
                [
5576
                    'where' => [
5577
                        'id = ? ' => $couponId,
5578
                    ],
5579
                ],
5580
                'first'
5581
            );
5582
        }
5583
5584
        $couponItemTable = Database::get_main_table(self::TABLE_COUPON_ITEM);
5585
        $dtmNow = api_get_utc_datetime();
5586
5587
        $couponFrom = "
5588
            $couponTable c
5589
            INNER JOIN $couponItemTable ci
5590
                on ci.coupon_id = c.id
5591
        ";
5592
5593
        return Database::select(
5594
            ['c.*'],
5595
            $couponFrom,
5596
            [
5597
                'where' => [
5598
                    'c.id = ? AND ' => $couponId,
5599
                    'c.valid_start <= ? AND ' => $dtmNow,
5600
                    'c.valid_end >= ? AND ' => $dtmNow,
5601
                    'ci.product_type = ? AND ' => $productType,
5602
                    'ci.product_id = ?' => $productId,
5603
                ],
5604
            ],
5605
            'first'
5606
        );
5607
    }
5608
5609
    /**
5610
     * Get data of a coupon for a product (course or service) by the coupon code.
5611
     *
5612
     * @param string $couponCode  The coupon code code
5613
     * @param int    $productType The product type
5614
     * @param int    $productId   The product ID
5615
     *
5616
     * @return array The coupon data
5617
     */
5618
    private function getDataCouponByCode(string $couponCode, ?int $productType = null, ?int $productId = null)
5619
    {
5620
        $couponTable = Database::get_main_table(self::TABLE_COUPON);
5621
        $couponItemTable = Database::get_main_table(self::TABLE_COUPON_ITEM);
5622
        $dtmNow = api_get_utc_datetime();
5623
5624
        if (null == $productType || null == $productId) {
5625
            return Database::select(
5626
                ['*'],
5627
                $couponTable,
5628
                [
5629
                    'where' => [
5630
                        'code = ? ' => $couponCode,
5631
                    ],
5632
                ],
5633
                'first'
5634
            );
5635
        }
5636
5637
        $couponFrom = "
5638
            $couponTable c
5639
            INNER JOIN $couponItemTable ci
5640
                on ci.coupon_id = c.id
5641
        ";
5642
5643
        return Database::select(
5644
            ['c.*'],
5645
            $couponFrom,
5646
            [
5647
                'where' => [
5648
                    'c.code = ? AND ' => $couponCode,
5649
                    'c.valid_start <= ? AND ' => $dtmNow,
5650
                    'c.valid_end >= ? AND ' => $dtmNow,
5651
                    'ci.product_type = ? AND ' => $productType,
5652
                    'ci.product_id = ?' => $productId,
5653
                ],
5654
            ],
5655
            'first'
5656
        );
5657
    }
5658
5659
    /**
5660
     * Get data of a coupon for a service by the coupon ID.
5661
     *
5662
     * @param int $couponId  The coupon ID
5663
     * @param int $serviceId The service ID
5664
     *
5665
     * @return array The coupon data
5666
     */
5667
    private function getDataCouponService(int $couponId, int $serviceId): array
5668
    {
5669
        $couponTable = Database::get_main_table(self::TABLE_COUPON);
5670
        $couponServiceTable = Database::get_main_table(self::TABLE_COUPON_SERVICE);
5671
        $dtmNow = api_get_utc_datetime();
5672
5673
        $couponFrom = "
5674
            $couponTable c
5675
            INNER JOIN $couponServiceTable cs
5676
                on cs.coupon_id = c.id
5677
        ";
5678
5679
        return Database::select(
5680
            ['c.*'],
5681
            $couponFrom,
5682
            [
5683
                'where' => [
5684
                    'c.id = ? AND ' => $couponId,
5685
                    'c.valid_start <= ? AND ' => $dtmNow,
5686
                    'c.valid_end >= ? AND ' => $dtmNow,
5687
                    'cs.service_id = ?' => $serviceId,
5688
                ],
5689
            ],
5690
            'first'
5691
        );
5692
    }
5693
5694
    /**
5695
     * Get data of coupon for a service by the coupon code.
5696
     *
5697
     * @param string $couponCode The coupon code
5698
     * @param int    $serviceId  The service ID
5699
     *
5700
     * @return array The coupon data
5701
     */
5702
    private function getDataCouponServiceByCode(string $couponCode, int $serviceId): array
5703
    {
5704
        $couponTable = Database::get_main_table(self::TABLE_COUPON);
5705
        $couponServiceTable = Database::get_main_table(self::TABLE_COUPON_SERVICE);
5706
        $dtmNow = api_get_utc_datetime();
5707
5708
        $couponFrom = "
5709
            $couponTable c
5710
            INNER JOIN $couponServiceTable cs
5711
                on cs.coupon_id = c.id
5712
        ";
5713
5714
        return Database::select(
5715
            ['c.*'],
5716
            $couponFrom,
5717
            [
5718
                'where' => [
5719
                    'c.code = ? AND ' => $couponCode,
5720
                    'c.valid_start <= ? AND ' => $dtmNow,
5721
                    'c.valid_end >= ? AND ' => $dtmNow,
5722
                    'cs.service_id = ?' => $serviceId,
5723
                ],
5724
            ],
5725
            'first'
5726
        );
5727
    }
5728
5729
    /**
5730
     * Update a coupon.
5731
     */
5732
    private function updateCoupon(array $coupon): void
5733
    {
5734
        $couponExist = $this->getCouponByCode($coupon['code']);
5735
        if (!$couponExist) {
5736
            Display::addFlash(
5737
                Display::return_message(
5738
                    $this->get_lang('CouponNoExists'),
5739
                    'error',
5740
                    false
5741
                )
5742
            );
5743
5744
            return;
5745
        }
5746
5747
        $values = [
5748
            'valid_start' => $coupon['valid_start'],
5749
            'valid_end' => $coupon['valid_end'],
5750
            'active' => $coupon['active'],
5751
        ];
5752
5753
        Database::update(
5754
            self::TABLE_COUPON,
5755
            $values,
5756
            ['id = ?' => $coupon['id']]
5757
        );
5758
    }
5759
5760
    /**
5761
     * Register a coupon.
5762
     */
5763
    private function registerCoupon(array $coupon): int
5764
    {
5765
        $couponExist = $this->getCouponByCode($coupon['code']);
5766
        if ($couponExist) {
5767
            Display::addFlash(
5768
                Display::return_message(
5769
                    $this->get_lang('CouponCodeUsed'),
5770
                    'error',
5771
                    false
5772
                )
5773
            );
5774
5775
            return 0;
5776
        }
5777
5778
        $values = [
5779
            'code' => (string) $coupon['code'],
5780
            'discount_type' => (int) $coupon['discount_type'],
5781
            'discount_amount' => $coupon['discount_amount'],
5782
            'valid_start' => $coupon['valid_start'],
5783
            'valid_end' => $coupon['valid_end'],
5784
            'delivered' => 0,
5785
            'active' => $coupon['active'],
5786
        ];
5787
5788
        return Database::insert(self::TABLE_COUPON, $values);
5789
    }
5790
5791
    /**
5792
     * Register a coupon item.
5793
     *
5794
     * @param int $couponId    The coupon ID
5795
     * @param int $productType The product type
5796
     * @param int $productId   The product ID
5797
     */
5798
    private function registerCouponItem(int $couponId, int $productType, int $productId): void
5799
    {
5800
        $coupon = $this->getDataCoupon($couponId);
5801
        if (empty($coupon)) {
5802
            Display::addFlash(
5803
                Display::return_message(
5804
                    $this->get_lang('CouponNoExists'),
5805
                    'error',
5806
                    false
5807
                )
5808
            );
5809
5810
            return;
5811
        }
5812
5813
        $values = [
5814
            'coupon_id' => $couponId,
5815
            'product_type' => $productType,
5816
            'product_id' => $productId,
5817
        ];
5818
5819
        Database::insert(self::TABLE_COUPON_ITEM, $values);
5820
    }
5821
5822
    /**
5823
     * Remove all coupon items for a product type and coupon ID.
5824
     *
5825
     * @param int $productType The product type
5826
     * @param int $couponId    The coupon ID
5827
     */
5828
    private function deleteCouponItemsByCoupon(int $productType, int $couponId): void
5829
    {
5830
        Database::delete(
5831
            Database::get_main_table(self::TABLE_COUPON_ITEM),
5832
            [
5833
                'product_type = ? AND ' => $productType,
5834
                'coupon_id = ?' => $couponId,
5835
            ]
5836
        );
5837
    }
5838
5839
    /**
5840
     * Register a coupon service.
5841
     *
5842
     * @param int $couponId  The coupon ID
5843
     * @param int $serviceId The service ID
5844
     */
5845
    private function registerCouponService(int $couponId, int $serviceId): void
5846
    {
5847
        $coupon = $this->getDataCoupon($couponId);
5848
        if (empty($coupon)) {
5849
            Display::addFlash(
5850
                Display::return_message(
5851
                    $this->get_lang('CouponNoExists'),
5852
                    'error',
5853
                    false
5854
                )
5855
            );
5856
5857
            return;
5858
        }
5859
5860
        $values = [
5861
            'coupon_id' => $couponId,
5862
            'service_id' => $serviceId,
5863
        ];
5864
5865
        Database::insert(self::TABLE_COUPON_SERVICE, $values);
5866
    }
5867
5868
    /**
5869
     * Remove all coupon services for a product type and coupon ID.
5870
     */
5871
    private function deleteCouponServicesByCoupon(int $couponId): void
5872
    {
5873
        Database::delete(
5874
            Database::get_main_table(self::TABLE_COUPON_SERVICE),
5875
            [
5876
                'coupon_id = ?' => (int) $couponId,
5877
            ]
5878
        );
5879
    }
5880
5881
    /**
5882
     * Get an array of subscriptions.
5883
     *
5884
     * @return array Subscriptions data
5885
     */
5886
    private function getDataSubscriptions(int $productType, int $productId): array
5887
    {
5888
        $subscriptionTable = Database::get_main_table(self::TABLE_SUBSCRIPTION);
5889
5890
        return Database::select(
5891
            ['*'],
5892
            $subscriptionTable,
5893
            [
5894
                'where' => [
5895
                    'product_type = ? AND ' => (int) $productType,
5896
                    'product_id = ?  ' => (int) $productId,
5897
                ],
5898
                'order' => 'duration ASC',
5899
            ]
5900
        );
5901
    }
5902
5903
    /**
5904
     * Get data of a subscription for a product (course or service) by the subscription ID.
5905
     *
5906
     * @param int $productType The product type
5907
     * @param int $productId   The product ID
5908
     * @param int $duration    The duration (in seconds)
5909
     *
5910
     * @return array The subscription data
5911
     */
5912
    private function getDataSubscription(int $productType, int $productId, int $duration): array
5913
    {
5914
        $subscriptionTable = Database::get_main_table(self::TABLE_SUBSCRIPTION);
5915
5916
        return Database::select(
5917
            ['*'],
5918
            $subscriptionTable,
5919
            [
5920
                'where' => [
5921
                    'product_type = ? AND ' => $productType,
5922
                    'product_id = ? AND ' => $productId,
5923
                    'duration = ? ' => $duration,
5924
                ],
5925
            ],
5926
            'first'
5927
        );
5928
    }
5929
5930
    /**
5931
     * Update a subscription.
5932
     */
5933
    public function updateSubscription(int $productType, int $productId, int $taxPerc): bool
5934
    {
5935
        $values = [
5936
            'tax_perc' => $taxPerc,
5937
        ];
5938
5939
        return Database::update(
5940
            self::TABLE_SUBSCRIPTION,
5941
            $values,
5942
            [
5943
                'product_type = ? AND ' => $productType,
5944
                'product_id = ?' => $productId,
5945
            ]
5946
        ) > 0;
5947
    }
5948
5949
    /**
5950
     * Register a subscription.
5951
     */
5952
    private function registerSubscription(array $subscription, array $frequency): bool
5953
    {
5954
        $values = [
5955
            'product_type' => (int) $subscription['product_type'],
5956
            'product_id' => (int) $subscription['product_id'],
5957
            'duration' => (int) $frequency['duration'],
5958
            'currency_id' => (int) $subscription['currency_id'],
5959
            'tax_perc' => (int) $subscription['tax_perc'],
5960
            'price' => (float) $frequency['price'],
5961
        ];
5962
5963
        return Database::insert(self::TABLE_SUBSCRIPTION, $values) > 0;
5964
    }
5965
5966
    /**
5967
     * Update the subscription sale status.
5968
     *
5969
     * @param int $saleId    The sale ID
5970
     * @param int $newStatus The new status
5971
     */
5972
    private function updateSubscriptionSaleStatus(int $saleId, int $newStatus = self::SALE_STATUS_PENDING): void
5973
    {
5974
        $saleTable = Database::get_main_table(self::TABLE_SUBSCRIPTION_SALE);
5975
5976
        Database::update(
5977
            $saleTable,
5978
            ['status' => $newStatus],
5979
            ['id = ?' => $saleId]
5980
        );
5981
    }
5982
5983
    /**
5984
     * Get the user status for the subscription session.
5985
     *
5986
     * @param int     $userId  The user ID
5987
     * @param Session $session The session
5988
     */
5989
    private function getUserStatusForSubscriptionSession(int $userId, Session $session): string
5990
    {
5991
        if (empty($userId)) {
5992
            return 'NO';
5993
        }
5994
5995
        $scuRepo = Database::getManager()->getRepository(SessionRelCourseRelUser::class);
5996
5997
        $buySaleTable = Database::get_main_table(self::TABLE_SUBSCRIPTION_SALE);
5998
5999
        // Check if user bought the course
6000
        $sale = Database::select(
6001
            'COUNT(1) as qty',
6002
            $buySaleTable,
6003
            [
6004
                'where' => [
6005
                    'user_id = ? AND product_type = ? AND product_id = ? AND status = ? AND (expired is NULL OR expired <> ?)' => [
6006
                        $userId,
6007
                        self::PRODUCT_TYPE_SESSION,
6008
                        $session->getId(),
6009
                        self::SALE_STATUS_PENDING,
6010
                        1,
6011
                    ],
6012
                ],
6013
            ],
6014
            'first'
6015
        );
6016
6017
        if ($sale['qty'] > 0) {
6018
            return 'TMP';
6019
        }
6020
6021
        // Check if user is already subscribed to session
6022
        $userSubscription = $scuRepo->findBy([
6023
            'session' => $session,
6024
            'user' => $userId,
6025
        ]);
6026
6027
        if (!empty($userSubscription)) {
6028
            return 'YES';
6029
        }
6030
6031
        return 'NO';
6032
    }
6033
6034
    /**
6035
     * Get the user status for the subscription course.
6036
     *
6037
     * @param int    $userId The user Id
6038
     * @param Course $course The course
6039
     */
6040
    private function getUserStatusForSubscriptionCourse(int $userId, Course $course): string
6041
    {
6042
        if (empty($userId)) {
6043
            return 'NO';
6044
        }
6045
6046
        $cuRepo = Database::getManager()->getRepository(CourseRelUser::class);
6047
        $buySaleTable = Database::get_main_table(self::TABLE_SUBSCRIPTION_SALE);
6048
6049
        // Check if user bought the course
6050
        $sale = Database::select(
6051
            'COUNT(1) as qty',
6052
            $buySaleTable,
6053
            [
6054
                'where' => [
6055
                    'user_id = ? AND product_type = ? AND product_id = ? AND status = ? AND (expired is NULL OR expired <> ?)' => [
6056
                        $userId,
6057
                        self::PRODUCT_TYPE_COURSE,
6058
                        $course->getId(),
6059
                        self::SALE_STATUS_PENDING,
6060
                        1,
6061
                    ],
6062
                ],
6063
            ],
6064
            'first'
6065
        );
6066
6067
        if ($sale['qty'] > 0) {
6068
            return 'TMP';
6069
        }
6070
6071
        // Check if a user is already subscribed to course
6072
        $userSubscription = $cuRepo->findBy([
6073
            'course' => $course,
6074
            'user' => $userId,
6075
        ]);
6076
6077
        if (!empty($userSubscription)) {
6078
            return 'YES';
6079
        }
6080
6081
        return 'NO';
6082
    }
6083
}
6084