BuyCoursesPlugin::getTaxAppliesTo()   A
last analyzed

Complexity

Conditions 1
Paths 1

Size

Total Lines 7
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Importance

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