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

BuyCoursesPlugin::getDataCouponService()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 24
Code Lines 15

Duplication

Lines 0
Ratio 0 %

Importance

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