Passed
Pull Request — master (#6999)
by Angel Fernando Quiroz
10:22
created

BuyCoursesPlugin::getSaleListByEmail()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 22
Code Lines 15

Duplication

Lines 0
Ratio 0 %

Importance

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