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

BuyCoursesPlugin::getSubscriptionSaleListByUser()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 26
Code Lines 17

Duplication

Lines 0
Ratio 0 %

Importance

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