Completed
Push — master ( 27e209...a08afa )
by Julito
186:04 queued 150:53
created

BuyCoursesPlugin   F

Complexity

Total Complexity 196

Size/Duplication

Total Lines 2357
Duplicated Lines 0 %

Importance

Changes 0
Metric Value
dl 0
loc 2357
rs 0.6314
c 0
b 0
f 0
wmc 196

77 Methods

Rating   Name   Duplication   Size   Complexity  
A getItemByProduct() 0 23 1
A getCoursesForConfiguration() 0 19 3
B getCatalogSessionList() 0 55 6
A isEnabled() 0 3 3
B getUserStatusForCourse() 0 42 4
A getCurrencies() 0 5 1
A uninstall() 0 23 2
B buyCoursesForGridCatalogValidator() 0 28 5
B getUserStatusForSession() 0 43 4
A returnBuyCourseButton() 0 7 1
A savePaypalParams() 0 11 1
A deleteTransferAccount() 0 5 1
B getCatalogCourseList() 0 48 6
B getSessionInfo() 0 71 6
A getTransferAccounts() 0 5 1
B install() 0 26 2
A getSelectedCurrency() 0 9 1
B getCourseInfo() 0 45 5
A create() 0 5 2
A getCourses() 0 47 1
A selectCurrency() 0 14 1
A getPaypalParams() 0 7 1
A getItem() 0 9 1
A getSessionsForConfiguration() 0 11 2
A saveTransferAccount() 0 8 1
A updateServiceSaleStatus() 0 10 1
A getPath() 0 16 1
A deleteService() 0 10 1
A registerItem() 0 4 1
A setStatusPayouts() 0 8 1
A getPayoutStatuses() 0 6 1
A saveCulqiParameters() 0 10 1
B storeService() 0 37 4
A getProductTypes() 0 5 1
A getSaleListByPaymentType() 0 22 1
A getItemBeneficiaries() 0 10 1
A getBeneficiariesBySale() 0 7 1
B storePayouts() 0 25 2
A getCulqiParams() 0 7 1
B filterCourseList() 0 57 9
A deleteItemBeneficiaries() 0 7 1
B getPayouts() 0 46 5
B verifyPaypalAccountByBeneficiary() 0 37 4
A getSaleStatuses() 0 6 1
A updateItem() 0 10 1
B getCatalogServiceList() 0 55 7
A generateReference() 0 5 1
A getPaymentTypes() 0 6 1
A getServiceSaleStatuses() 0 6 1
A updateSaleStatus() 0 8 1
A registerItemBeneficiaries() 0 13 2
A getSaleListByStatus() 0 17 1
A registerServiceSale() 0 47 3
A deleteItem() 0 13 2
C filterSessionList() 0 51 9
B getSessionForConfiguration() 0 59 5
B completeSale() 0 31 5
A getSaleListByUserId() 0 23 2
B getServices() 0 60 4
A updateCommission() 0 7 1
F getServiceSale() 0 145 15
A getGlobalParameters() 0 7 1
B updateService() 0 26 2
B registerSale() 0 53 7
B getSaleListByUser() 0 26 2
A getCurrency() 0 9 1
B getCourseForConfiguration() 0 25 3
A getServiceTypes() 0 7 1
A completeServiceSale() 0 13 2
A saveGlobalParameters() 0 8 1
B __construct() 0 24 1
A isValidCourse() 0 11 3
C randomText() 0 24 7
A getSale() 0 9 1
A cancelServiceSale() 0 8 1
A getPlatformCommission() 0 7 1
A cancelSale() 0 3 1

How to fix   Complexity   

Complex Class

Complex classes like BuyCoursesPlugin often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use BuyCoursesPlugin, and based on these observations, apply Extract Interface, too.

1
<?php
2
/* For license terms, see /license.txt */
3
4
use Chamilo\CoreBundle\Entity\Session;
5
use Chamilo\CoreBundle\Entity\Course;
0 ignored issues
show
Bug introduced by
This use statement conflicts with another class in this namespace, Course. Consider defining an alias.

Let?s assume that you have a directory layout like this:

.
|-- OtherDir
|   |-- Bar.php
|   `-- Foo.php
`-- SomeDir
    `-- Foo.php

and let?s assume the following content of Bar.php:

// Bar.php
namespace OtherDir;

use SomeDir\Foo; // This now conflicts the class OtherDir\Foo

If both files OtherDir/Foo.php and SomeDir/Foo.php are loaded in the same runtime, you will see a PHP error such as the following:

PHP Fatal error:  Cannot use SomeDir\Foo as Foo because the name is already in use in OtherDir/Foo.php

However, as OtherDir/Foo.php does not necessarily have to be loaded and the error is only triggered if it is loaded before OtherDir/Bar.php, this problem might go unnoticed for a while. In order to prevent this error from surfacing, you must import the namespace with a different alias:

// Bar.php
namespace OtherDir;

use SomeDir\Foo as SomeDirFoo; // There is no conflict anymore.
Loading history...
6
use Doctrine\ORM\Query\Expr\Join;
7
8
/**
9
 * Plugin class for the BuyCourses plugin
10
 * @package chamilo.plugin.buycourses
11
 * @author Jose Angel Ruiz <[email protected]>
12
 * @author Imanol Losada <[email protected]>
13
 * @author Alex Aragón <[email protected]>
14
 * @author Angel Fernando Quiroz Campos <[email protected]>
15
 * @author José Loguercio Silva  <[email protected]>
16
 * @author Julio Montoya
17
 */
18
class BuyCoursesPlugin extends Plugin
19
{
20
    const TABLE_PAYPAL = 'plugin_buycourses_paypal_account';
21
    const TABLE_CURRENCY = 'plugin_buycourses_currency';
22
    const TABLE_ITEM = 'plugin_buycourses_item';
23
    const TABLE_ITEM_BENEFICIARY = 'plugin_buycourses_item_rel_beneficiary';
24
    const TABLE_SALE = 'plugin_buycourses_sale';
25
    const TABLE_TRANSFER = 'plugin_buycourses_transfer';
26
    const TABLE_COMMISSION = 'plugin_buycourses_commission';
27
    const TABLE_PAYPAL_PAYOUTS = 'plugin_buycourses_paypal_payouts';
28
    const TABLE_SERVICES = 'plugin_buycourses_services';
29
    const TABLE_SERVICES_SALE = 'plugin_buycourses_service_sale';
30
    const TABLE_CULQI = 'plugin_buycourses_culqi';
31
    const TABLE_GLOBAL_CONFIG = 'plugin_buycourses_global_config';
32
    const PRODUCT_TYPE_COURSE = 1;
33
    const PRODUCT_TYPE_SESSION = 2;
34
    const PAYMENT_TYPE_PAYPAL = 1;
35
    const PAYMENT_TYPE_TRANSFER = 2;
36
    const PAYMENT_TYPE_CULQI = 3;
37
    const PAYOUT_STATUS_CANCELED = 2;
38
    const PAYOUT_STATUS_PENDING = 0;
39
    const PAYOUT_STATUS_COMPLETED = 1;
40
    const SALE_STATUS_CANCELED = -1;
41
    const SALE_STATUS_PENDING = 0;
42
    const SALE_STATUS_COMPLETED = 1;
43
    const SERVICE_STATUS_PENDING = 0;
44
    const SERVICE_STATUS_COMPLETED = 1;
45
    const SERVICE_STATUS_CANCELLED = -1;
46
    const SERVICE_TYPE_USER = 1;
47
    const SERVICE_TYPE_COURSE = 2;
48
    const SERVICE_TYPE_SESSION = 3;
49
    const SERVICE_TYPE_LP_FINAL_ITEM = 4;
50
    const CULQI_INTEGRATION_TYPE = 'INTEG';
51
    const CULQI_PRODUCTION_TYPE = 'PRODUC';
52
53
    /**
54
     * @return BuyCoursesPlugin
55
     */
56
    public static function create()
57
    {
58
        static $result = null;
59
60
        return $result ? $result : $result = new self();
61
    }
62
63
    /**
64
     * BuyCoursesPlugin constructor.
65
     */
66
    public function __construct()
67
    {
68
        parent::__construct(
69
            '1.0',
70
            "
71
                Jose Angel Ruiz - NoSoloRed (original author) <br/>
72
                Francis Gonzales and Yannick Warnier - BeezNest (integration) <br/>
73
                Alex Aragón - BeezNest (Design icons and css styles) <br/>
74
                Imanol Losada - BeezNest (introduction of sessions purchase) <br/>
75
                Angel Fernando Quiroz Campos - BeezNest (cleanup and new reports) <br/>
76
                José Loguercio Silva - BeezNest (Payouts and buy Services) <br/>
77
                Julio Montoya
78
            ",
79
            [
80
                'show_main_menu_tab' => 'boolean',
81
                'public_main_menu_tab' => 'boolean',
82
                'include_sessions' => 'boolean',
83
                'include_services' => 'boolean',
84
                'paypal_enable' => 'boolean',
85
                'transfer_enable' => 'boolean',
86
                'culqi_enable' => 'boolean',
87
                'commissions_enable' => 'boolean',
88
                'unregistered_users_enable' => 'boolean',
89
                'hide_free_text' => 'boolean',
90
            ]
91
        );
92
    }
93
94
    /**
95
     * Check if plugin is enabled
96
     * @return bool
97
     */
98
    public function isEnabled()
99
    {
100
        return $this->get('paypal_enable') || $this->get('transfer_enable') || $this->get('culqi_enable');
101
    }
102
103
    /**
104
     * This method creates the tables required to this plugin
105
     */
106
    public function install()
107
    {
108
        $tablesToBeCompared = [
109
            self::TABLE_PAYPAL,
110
            self::TABLE_TRANSFER,
111
            self::TABLE_CULQI,
112
            self::TABLE_ITEM_BENEFICIARY,
113
            self::TABLE_ITEM,
114
            self::TABLE_SALE,
115
            self::TABLE_CURRENCY,
116
            self::TABLE_COMMISSION,
117
            self::TABLE_PAYPAL_PAYOUTS,
118
            self::TABLE_SERVICES,
119
            self::TABLE_SERVICES_SALE,
120
            self::TABLE_GLOBAL_CONFIG,
121
        ];
122
        $em = Database::getManager();
123
        $cn = $em->getConnection();
124
        $sm = $cn->getSchemaManager();
125
        $tables = $sm->tablesExist($tablesToBeCompared);
126
127
        if ($tables) {
128
            return false;
129
        }
130
131
        require_once api_get_path(SYS_PLUGIN_PATH).'buycourses/database.php';
132
    }
133
134
    /**
135
     * This method drops the plugin tables
136
     */
137
    public function uninstall()
138
    {
139
        $tablesToBeDeleted = [
140
            self::TABLE_PAYPAL,
141
            self::TABLE_TRANSFER,
142
            self::TABLE_CULQI,
143
            self::TABLE_ITEM_BENEFICIARY,
144
            self::TABLE_ITEM,
145
            self::TABLE_SALE,
146
            self::TABLE_CURRENCY,
147
            self::TABLE_COMMISSION,
148
            self::TABLE_PAYPAL_PAYOUTS,
149
            self::TABLE_SERVICES_SALE,
150
            self::TABLE_SERVICES,
151
            self::TABLE_GLOBAL_CONFIG,
152
        ];
153
154
        foreach ($tablesToBeDeleted as $tableToBeDeleted) {
155
            $table = Database::get_main_table($tableToBeDeleted);
156
            $sql = "DROP TABLE IF EXISTS $table";
157
            Database::query($sql);
158
        }
159
        $this->manageTab(false);
160
    }
161
162
    /**
163
     * This function verify if the plugin is enable and return the price info for a course or session in the new grid
164
     * 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
165
     * course catalog so the old buycourses plugin catalog can be deprecated.
166
     * @param int $productId course or session id
167
     * @param int $productType course or session type
168
     * @return mixed bool|string html
169
     */
170
    public function buyCoursesForGridCatalogValidator($productId, $productType)
171
    {
172
        $return = [];
173
        $paypal = $this->get('paypal_enable') === 'true';
174
        $transfer = $this->get('transfer_enable') === 'true';
175
        $hideFree = $this->get('hide_free_text') === 'true';
176
177
        if ($paypal || $transfer) {
178
            $item = $this->getItemByProduct($productId, $productType);
179
            $html = '<div class="buycourses-price">';
180
            if ($item) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $item of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
181
                $html .= '<span class="label label-primary"><strong>'.$item['iso_code'].' '.$item['price'].'</strong></span>';
182
                $return['verificator'] = true;
183
            } else {
184
                if ($hideFree == false) {
0 ignored issues
show
Coding Style Best Practice introduced by
It seems like you are loosely comparing two booleans. Considering using the strict comparison === instead.

When comparing two booleans, it is generally considered safer to use the strict comparison operator.

Loading history...
185
                    $html .= '<span class="label label-primary"><strong>'.$this->get_lang('Free').'</strong></span>';
186
                }
187
                $return['verificator'] = false;
188
            }
189
            $html .= '</div>';
190
            $return['html'] = $html;
191
        } else {
192
            return false;
193
        }
194
195
196
197
        return $return;
198
    }
199
200
    /**
201
     * Return the buyCourses plugin button to buy the course
202
     * @param int $productId
203
     * @param int $productType
204
     * @return string $html
205
     */
206
    public function returnBuyCourseButton($productId, $productType)
207
    {
208
        $url = api_get_path(WEB_PLUGIN_PATH).'buycourses/src/process.php?i='.intval($productId).'&t='.$productType;
209
        $html = '<a class="btn btn-success btn-sm" title="'.$this->get_lang('Buy').'" href="'.$url.'">'.
210
            Display::returnFontAwesomeIcon('fa fa-shopping-cart').'</a>';
211
212
        return $html;
213
    }
214
215
    /**
216
     * Get the currency for sales
217
     * @return array The selected currency. Otherwise return false
218
     */
219
    public function getSelectedCurrency()
220
    {
221
        return Database::select(
222
            '*',
223
            Database::get_main_table(self::TABLE_CURRENCY),
224
            [
225
                'where' => ['status = ?' => true],
226
            ],
227
            'first'
228
        );
229
    }
230
231
    /**
232
     * Get a list of currencies
233
     * @return array The currencies. Otherwise return false
234
     */
235
    public function getCurrencies()
236
    {
237
        return Database::select(
238
            '*',
239
            Database::get_main_table(self::TABLE_CURRENCY)
240
        );
241
    }
242
243
    /**
244
     * Save the selected currency
245
     * @param int $selectedId The currency Id
246
     */
247
    public function selectCurrency($selectedId)
248
    {
249
        $currencyTable = Database::get_main_table(
250
            self::TABLE_CURRENCY
251
        );
252
253
        Database::update(
254
            $currencyTable,
255
            ['status' => 0]
256
        );
257
        Database::update(
258
            $currencyTable,
259
            ['status' => 1],
260
            ['id = ?' => intval($selectedId)]
261
        );
262
    }
263
264
    /**
265
     * Save the PayPal configuration params
266
     * @param array $params
267
     * @return int Rows affected. Otherwise return false
268
     */
269
    public function savePaypalParams($params)
270
    {
271
        return Database::update(
0 ignored issues
show
Bug Best Practice introduced by
The expression return Database::update(..., array('id = ?' => 1)) returns the type false which is incompatible with the documented return type integer.
Loading history...
272
            Database::get_main_table(self::TABLE_PAYPAL),
273
            [
274
                'username' => $params['username'],
275
                'password' => $params['password'],
276
                'signature' => $params['signature'],
277
                'sandbox' => isset($params['sandbox']),
278
            ],
279
            ['id = ?' => 1]
280
        );
281
    }
282
283
    /**
284
     * Gets the stored PayPal params
285
     * @return array
286
     */
287
    public function getPaypalParams()
288
    {
289
        return Database::select(
290
            '*',
291
            Database::get_main_table(self::TABLE_PAYPAL),
292
            ['id = ?' => 1],
293
            'first'
294
        );
295
    }
296
297
    /**
298
     * Save a transfer account information
299
     * @param array $params The transfer account
300
     * @return int Rows affected. Otherwise return false
301
     */
302
    public function saveTransferAccount($params)
303
    {
304
        return Database::insert(
0 ignored issues
show
Bug Best Practice introduced by
The expression return Database::insert(... => $params['tswift'])) could also return false which is incompatible with the documented return type integer. Did you maybe forget to handle an error condition?

If the returned type also contains false, it is an indicator that maybe an error condition leading to the specific return statement remains unhandled.

Loading history...
305
            Database::get_main_table(self::TABLE_TRANSFER),
306
            [
307
                'name' => $params['tname'],
308
                'account' => $params['taccount'],
309
                'swift' => $params['tswift'],
310
            ]
311
        );
312
    }
313
314
    /**
315
     * Get a list of transfer accounts
316
     * @return array
317
     */
318
    public function getTransferAccounts()
319
    {
320
        return Database::select(
321
            '*',
322
            Database::get_main_table(self::TABLE_TRANSFER)
323
        );
324
    }
325
326
    /**
327
     * Remove a transfer account
328
     * @param int $id The transfer account ID
329
     * @return int Rows affected. Otherwise return false
330
     */
331
    public function deleteTransferAccount($id)
332
    {
333
        return Database::delete(
334
            Database::get_main_table(self::TABLE_TRANSFER),
335
            ['id = ?' => intval($id)]
336
        );
337
    }
338
339
    /**
340
     * Filter the registered courses for show in plugin catalog
341
     * @return array
342
     */
343
    private function getCourses()
344
    {
345
        $em = Database::getManager();
346
        $urlId = api_get_current_access_url_id();
347
348
        $qb = $em->createQueryBuilder();
349
        $qb2 = $em->createQueryBuilder();
350
        $qb3 = $em->createQueryBuilder();
351
352
        $qb = $qb
353
            ->select('c')
354
            ->from('ChamiloCoreBundle:Course', 'c')
355
            ->where(
356
                $qb->expr()->notIn(
357
                    'c',
358
                    $qb2
359
                        ->select('course2')
360
                        ->from('ChamiloCoreBundle:SessionRelCourse', 'sc')
361
                        ->join('sc.course', 'course2')
362
                        ->innerJoin(
363
                            'ChamiloCoreBundle:AccessUrlRelSession',
364
                            'us',
365
                            Join::WITH,
366
                            'us.sessionId = sc.session'
367
                        )->where(
368
                            $qb->expr()->eq('us.accessUrlId ', $urlId)
369
                        )
370
                        ->getDQL()
371
                )
372
            )->andWhere(
373
                $qb->expr()->in(
374
                    'c',
375
                    $qb3
376
                        ->select('course3')
377
                        ->from('ChamiloCoreBundle:AccessUrlRelCourse', 'uc')
378
                        ->join('uc.course', 'course3')
379
                        ->where(
380
                            $qb3->expr()->eq('uc.url ', $urlId)
381
                        )
382
                        ->getDQL()
383
                )
384
            )
385
            ->getQuery();
386
387
        $courses = $qb->getResult();
388
389
        return $courses;
390
    }
391
392
    /**
393
     * Get the item data
394
     * @param int $productId The item ID
395
     * @param int $itemType The item type
396
     * @return array
397
     */
398
    public function getItemByProduct($productId, $itemType)
399
    {
400
        $buyItemTable = Database::get_main_table(self::TABLE_ITEM);
401
        $buyCurrencyTable = Database::get_main_table(self::TABLE_CURRENCY);
402
403
        $fakeItemFrom = "
404
            $buyItemTable i
405
            INNER JOIN $buyCurrencyTable c
406
                ON i.currency_id = c.id
407
        ";
408
409
        return Database::select(
410
            ['i.*', 'c.iso_code'],
411
            $fakeItemFrom,
412
            [
413
                'where' => [
414
                    'i.product_id = ? AND i.product_type = ?' => [
415
                        intval($productId),
416
                        intval($itemType),
417
                    ],
418
                ],
419
            ],
420
            'first'
421
        );
422
    }
423
424
    /**
425
     * List courses details from the configuration page
426
     * @return array
427
     */
428
    public function getCoursesForConfiguration()
429
    {
430
        $courses = $this->getCourses();
431
432
        if (empty($courses)) {
433
            return [];
434
        }
435
436
        $configurationCourses = [];
437
        $currency = $this->getSelectedCurrency();
438
439
        foreach ($courses as $course) {
440
            $configurationCourses[] = $this->getCourseForConfiguration(
441
                $course,
442
                $currency
443
            );
444
        }
445
446
        return $configurationCourses;
447
    }
448
449
    /**
450
     * List sessions details from the buy-session table and the session table
451
     * @return array The sessions. Otherwise return false
452
     */
453
    public function getSessionsForConfiguration()
454
    {
455
        $auth = new Auth();
456
        $sessions = $auth->browseSessions();
457
        $currency = $this->getSelectedCurrency();
458
        $items = [];
459
        foreach ($sessions as $session) {
460
            $items[] = $this->getSessionForConfiguration($session, $currency);
461
        }
462
463
        return $items;
464
    }
465
466
    /**
467
     * Get the user status for the session
468
     * @param int $userId The user ID
469
     * @param Session $session The session
470
     * @return string
471
     */
472
    private function getUserStatusForSession($userId, Session $session)
473
    {
474
        if (empty($userId)) {
475
            return 'NO';
476
        }
477
478
        $entityManager = Database::getManager();
479
        $scuRepo = $entityManager->getRepository('ChamiloCoreBundle:SessionRelCourseRelUser');
480
481
        $buySaleTable = Database::get_main_table(self::TABLE_SALE);
482
483
        // Check if user bought the course
484
        $sale = Database::select(
485
            'COUNT(1) as qty',
486
            $buySaleTable,
487
            [
488
                'where' => [
489
                    'user_id = ? AND product_type = ? AND product_id = ? AND status = ?' => [
490
                        $userId,
491
                        self::PRODUCT_TYPE_SESSION,
492
                        $session->getId(),
493
                        self::SALE_STATUS_PENDING,
494
                    ],
495
                ],
496
            ],
497
            'first'
498
        );
499
500
        if ($sale['qty'] > 0) {
501
            return "TMP";
502
        }
503
504
        // Check if user is already subscribe to session
505
        $userSubscription = $scuRepo->findBy([
506
            'session' => $session,
507
            'user' => $userId,
508
        ]);
509
510
        if (!empty($userSubscription)) {
511
            return 'YES';
512
        }
513
514
        return 'NO';
515
    }
516
517
    /**
518
     * Lists current user session details, including each session course details
519
     * @param string $name Optional. The name filter
520
     * @param int $min Optional. The minimum price filter
521
     * @param int $max Optional. The maximum price filter
522
     * @return array
523
     */
524
    public function getCatalogSessionList($name = null, $min = 0, $max = 0)
525
    {
526
        $sessions = $this->filterSessionList($name, $min, $max);
527
528
        $sessionCatalog = [];
529
        // loop through all sessions
530
        foreach ($sessions as $session) {
531
            $sessionCourses = $session->getCourses();
532
533
            if (empty($sessionCourses)) {
534
                continue;
535
            }
536
537
            $item = $this->getItemByProduct(
538
                $session->getId(),
539
                self::PRODUCT_TYPE_SESSION
540
            );
541
542
            if (empty($item)) {
543
                continue;
544
            }
545
546
            $sessionData = $this->getSessionInfo($session->getId());
547
            $sessionData['coach'] = $session->getGeneralCoach()->getCompleteName();
548
            $sessionData['enrolled'] = $this->getUserStatusForSession(
549
                api_get_user_id(),
550
                $session
551
            );
552
            $sessionData['courses'] = [];
553
554
            foreach ($sessionCourses as $sessionCourse) {
555
                $course = $sessionCourse->getCourse();
556
557
                $sessionCourseData = [
558
                    'title' => $course->getTitle(),
559
                    'coaches' => [],
560
                ];
561
562
                $userCourseSubscriptions = $session->getUserCourseSubscriptionsByStatus(
563
                    $course,
564
                    Chamilo\CoreBundle\Entity\Session::COACH
565
                );
566
567
                foreach ($userCourseSubscriptions as $userCourseSubscription) {
568
                    $user = $userCourseSubscription->getUser();
569
                    $sessionCourseData['coaches'][] = $user->getCompleteName();
570
                }
571
572
                $sessionData['courses'][] = $sessionCourseData;
573
            }
574
575
            $sessionCatalog[] = $sessionData;
576
        }
577
578
        return $sessionCatalog;
579
    }
580
581
    /**
582
     * Get the user status for the course
583
     * @param int $userId The user Id
584
     * @param Course $course The course
585
     *
586
     * @return string
587
     */
588
    private function getUserStatusForCourse($userId, Course $course)
589
    {
590
        if (empty($userId)) {
591
            return 'NO';
592
        }
593
594
        $entityManager = Database::getManager();
595
        $cuRepo = $entityManager->getRepository('ChamiloCoreBundle:CourseRelUser');
596
        $buySaleTable = Database::get_main_table(self::TABLE_SALE);
597
598
        // Check if user bought the course
599
        $sale = Database::select(
600
            'COUNT(1) as qty',
601
            $buySaleTable,
602
            [
603
                'where' => [
604
                    'user_id = ? AND product_type = ? AND product_id = ? AND status = ?' => [
605
                        $userId,
606
                        self::PRODUCT_TYPE_COURSE,
607
                        $course->getId(),
608
                        self::SALE_STATUS_PENDING,
609
                    ],
610
                ],
611
            ],
612
            'first'
613
        );
614
615
        if ($sale['qty'] > 0) {
616
            return "TMP";
617
        }
618
619
        // Check if user is already subscribe to course
620
        $userSubscription = $cuRepo->findBy([
621
            'course' => $course,
622
            'user' => $userId,
623
        ]);
624
625
        if (!empty($userSubscription)) {
626
            return 'YES';
627
        }
628
629
        return 'NO';
630
    }
631
632
    /**
633
     * Lists current user course details
634
     * @param string $name Optional. The name filter
635
     * @param int $min Optional. The minimum price filter
636
     * @param int $max Optional. The maximum price filter
637
     * @return array
638
     */
639
    public function getCatalogCourseList($name = null, $min = 0, $max = 0)
640
    {
641
        $courses = $this->filterCourseList($name, $min, $max);
642
643
        if (empty($courses)) {
644
            return [];
645
        }
646
647
        $courseCatalog = [];
648
        foreach ($courses as $course) {
649
            $item = $this->getItemByProduct(
650
                $course->getId(),
651
                self::PRODUCT_TYPE_COURSE
652
            );
653
654
            if (empty($item)) {
655
                continue;
656
            }
657
658
            $courseItem = [
659
                'id' => $course->getId(),
660
                'title' => $course->getTitle(),
661
                'code' => $course->getCode(),
662
                'course_img' => null,
663
                'price' => $item['price'],
664
                'currency' => $item['iso_code'],
665
                'teachers' => [],
666
                'enrolled' => $this->getUserStatusForCourse(api_get_user_id(), $course),
667
            ];
668
669
            foreach ($course->getTeachers() as $courseUser) {
670
                $teacher = $courseUser->getUser();
671
                $courseItem['teachers'][] = $teacher->getCompleteName();
672
            }
673
674
            //check images
675
            $possiblePath = api_get_path(SYS_COURSE_PATH);
676
            $possiblePath .= $course->getDirectory();
677
            $possiblePath .= '/course-pic.png';
678
679
            if (file_exists($possiblePath)) {
680
                $courseItem['course_img'] = api_get_path(WEB_COURSE_PATH).$course->getDirectory().'/course-pic.png';
681
            }
682
683
            $courseCatalog[] = $courseItem;
684
        }
685
686
        return $courseCatalog;
687
    }
688
689
    /**
690
     * Get course info
691
     * @param int $courseId The course ID
692
     * @return array
693
     */
694
    public function getCourseInfo($courseId)
695
    {
696
        $entityManager = Database::getManager();
697
        $course = $entityManager->find('ChamiloCoreBundle:Course', $courseId);
698
699
        if (empty($course)) {
700
            return [];
701
        }
702
703
        $item = $this->getItemByProduct(
704
            $course->getId(),
705
            self::PRODUCT_TYPE_COURSE
706
        );
707
708
        if (empty($item)) {
709
            return [];
710
        }
711
712
        $courseInfo = [
713
            'id' => $course->getId(),
714
            'title' => $course->getTitle(),
715
            'description' => $course->getDescription(),
716
            'code' => $course->getCode(),
717
            'visual_code' => $course->getVisualCode(),
718
            'teachers' => [],
719
            'price' => $item['price'],
720
            'currency' => $item['iso_code'],
721
            'course_img' => null,
722
        ];
723
724
        $courseTeachers = $course->getTeachers();
725
726
        foreach ($courseTeachers as $teacher) {
727
            $courseInfo['teachers'][] = $teacher->getUser()->getCompleteName();
728
        }
729
730
        $possiblePath = api_get_path(SYS_COURSE_PATH);
731
        $possiblePath .= $course->getDirectory();
732
        $possiblePath .= '/course-pic.png';
733
734
        if (file_exists($possiblePath)) {
735
            $courseInfo['course_img'] = api_get_path(WEB_COURSE_PATH).$course->getDirectory().'/course-pic.png';
736
        }
737
738
        return $courseInfo;
739
    }
740
741
    /**
742
     * Get session info
743
     * @param array $sessionId The session ID
744
     * @return array
745
     */
746
    public function getSessionInfo($sessionId)
747
    {
748
        $entityManager = Database::getManager();
749
        $session = $entityManager->find('ChamiloCoreBundle:Session', $sessionId);
750
751
        if (empty($session)) {
752
            return [];
753
        }
754
755
        $item = $this->getItemByProduct(
756
            $session->getId(),
757
            self::PRODUCT_TYPE_SESSION
758
        );
759
760
        if (empty($item)) {
761
            return [];
762
        }
763
764
        $sessionDates = SessionManager::parseSessionDates([
765
            'display_start_date' => $session->getDisplayStartDate(),
766
            'display_end_date' => $session->getDisplayEndDate(),
767
            'access_start_date' => $session->getAccessStartDate(),
768
            'access_end_date' => $session->getAccessEndDate(),
769
            'coach_access_start_date' => $session->getCoachAccessStartDate(),
770
            'coach_access_end_date' => $session->getCoachAccessEndDate(),
771
        ]);
772
773
        $sessionInfo = [
774
            'id' => $session->getId(),
775
            'name' => $session->getName(),
776
            'dates' => $sessionDates,
777
            'courses' => [],
778
            'price' => $item['price'],
779
            'currency' => $item['iso_code'],
780
            'image' => null,
781
        ];
782
783
        $fieldValue = new ExtraFieldValue('session');
784
        $sessionImage = $fieldValue->get_values_by_handler_and_field_variable(
785
            $session->getId(),
786
            'image'
787
        );
788
789
        if (!empty($sessionImage)) {
790
            $sessionInfo['image'] = api_get_path(WEB_UPLOAD_PATH).$sessionImage['value'];
791
        }
792
793
        $sessionCourses = $session->getCourses();
794
795
        foreach ($sessionCourses as $sessionCourse) {
796
            $course = $sessionCourse->getCourse();
797
798
            $sessionCourseData = [
799
                'title' => $course->getTitle(),
800
                'coaches' => [],
801
            ];
802
803
            $userCourseSubscriptions = $session->getUserCourseSubscriptionsByStatus(
804
                $course,
805
                Chamilo\CoreBundle\Entity\Session::COACH
806
            );
807
808
            foreach ($userCourseSubscriptions as $userCourseSubscription) {
809
                $user = $userCourseSubscription->getUser();
810
                $sessionCourseData['coaches'][] = $user->getCompleteName();
811
            }
812
813
            $sessionInfo['courses'][] = $sessionCourseData;
814
        }
815
816
        return $sessionInfo;
817
    }
818
819
    /**
820
     * Get registered item data
821
     * @param int $itemId The item ID
822
     * @return array
823
     */
824
    public function getItem($itemId)
825
    {
826
        return Database::select(
827
            '*',
828
            Database::get_main_table(self::TABLE_ITEM),
829
            [
830
                'where' => ['id = ?' => intval($itemId)],
831
            ],
832
            'first'
833
        );
834
    }
835
836
    /**
837
     * Register a sale
838
     * @param int $itemId The product ID
839
     * @param int $paymentType The payment type
840
     * @return boolean
841
     */
842
    public function registerSale($itemId, $paymentType)
843
    {
844
        if (!in_array(
845
            $paymentType,
846
            [self::PAYMENT_TYPE_PAYPAL, self::PAYMENT_TYPE_TRANSFER, self::PAYMENT_TYPE_CULQI]
847
        )
848
        ) {
849
            return false;
850
        }
851
852
        $entityManager = Database::getManager();
853
        $item = $this->getItem($itemId);
854
855
        if (empty($item)) {
856
            return false;
857
        }
858
859
        if ($item['product_type'] == self::PRODUCT_TYPE_COURSE) {
860
            $course = $entityManager->find('ChamiloCoreBundle:Course', $item['product_id']);
861
862
            if (empty($course)) {
863
                return false;
864
            }
865
866
            $productName = $course->getTitle();
867
        } elseif ($item['product_type'] == self::PRODUCT_TYPE_SESSION) {
868
            $session = $entityManager->find('ChamiloCoreBundle:Session', $item['product_id']);
869
870
            if (empty($session)) {
871
                return false;
872
            }
873
874
            $productName = $session->getName();
875
        }
876
877
        $values = [
878
            'reference' => $this->generateReference(
879
                api_get_user_id(),
880
                $item['product_type'],
881
                $item['product_id']
882
            ),
883
            'currency_id' => $item['currency_id'],
884
            'date' => api_get_utc_datetime(),
885
            'user_id' => api_get_user_id(),
886
            'product_type' => $item['product_type'],
887
            'product_name' => $productName,
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $productName does not seem to be defined for all execution paths leading up to this point.
Loading history...
888
            'product_id' => $item['product_id'],
889
            'price' => $item['price'],
890
            'status' => self::SALE_STATUS_PENDING,
891
            'payment_type' => intval($paymentType),
892
        ];
893
894
        return Database::insert(self::TABLE_SALE, $values);
0 ignored issues
show
Bug Best Practice introduced by
The expression return Database::insert(self::TABLE_SALE, $values) also could return the type integer which is incompatible with the documented return type boolean.
Loading history...
895
    }
896
897
    /**
898
     * Get sale data by ID
899
     * @param int $saleId The sale ID
900
     * @return array
901
     */
902
    public function getSale($saleId)
903
    {
904
        return Database::select(
905
            '*',
906
            Database::get_main_table(self::TABLE_SALE),
907
            [
908
                'where' => ['id = ?' => intval($saleId)],
909
            ],
910
            'first'
911
        );
912
    }
913
914
    /**
915
     * Get a list of sales by the payment type
916
     * @param int $paymentType The payment type to filter (default : Paypal)
917
     * @return array The sale list. Otherwise return false
918
     */
919
    public function getSaleListByPaymentType($paymentType = self::PAYMENT_TYPE_PAYPAL)
920
    {
921
        $saleTable = Database::get_main_table(self::TABLE_SALE);
922
        $currencyTable = Database::get_main_table(self::TABLE_CURRENCY);
923
        $userTable = Database::get_main_table(TABLE_MAIN_USER);
924
925
        $innerJoins = "
926
            INNER JOIN $currencyTable c ON s.currency_id = c.id
927
            INNER JOIN $userTable u ON s.user_id = u.id
928
        ";
929
930
        return Database::select(
931
            ['c.iso_code', 'u.firstname', 'u.lastname', 's.*'],
932
            "$saleTable s $innerJoins",
933
            [
934
                'where' => [
935
                    's.payment_type = ? AND s.status = ?' => [
936
                        intval($paymentType),
937
                        self::SALE_STATUS_COMPLETED
938
                    ]
939
                ],
940
                'order' => 'id DESC',
941
            ]
942
        );
943
    }
944
945
    /**
946
     * Get currency data by ID
947
     * @param int $currencyId The currency ID
948
     * @return array
949
     */
950
    public function getCurrency($currencyId)
951
    {
952
        return Database::select(
953
            '*',
954
            Database::get_main_table(self::TABLE_CURRENCY),
955
            [
956
                'where' => ['id = ?' => intval($currencyId)],
957
            ],
958
            'first'
959
        );
960
    }
961
962
    /**
963
     * Update the sale status
964
     * @param int $saleId The sale ID
965
     * @param int $newStatus The new status
966
     * @return boolean
967
     */
968
    private function updateSaleStatus($saleId, $newStatus = self::SALE_STATUS_PENDING)
969
    {
970
        $saleTable = Database::get_main_table(self::TABLE_SALE);
971
972
        return Database::update(
973
            $saleTable,
974
            ['status' => intval($newStatus)],
975
            ['id = ?' => intval($saleId)]
976
        );
977
    }
978
979
    /**
980
     * Complete sale process. Update sale status to completed
981
     * @param int $saleId The sale ID
982
     * @return boolean
983
     */
984
    public function completeSale($saleId)
985
    {
986
        $sale = $this->getSale($saleId);
987
988
        if ($sale['status'] == self::SALE_STATUS_COMPLETED) {
989
            return true;
990
        }
991
992
        $saleIsCompleted = false;
993
        switch ($sale['product_type']) {
994
            case self::PRODUCT_TYPE_COURSE:
995
                $course = api_get_course_info_by_id($sale['product_id']);
996
                $saleIsCompleted = CourseManager::subscribe_user($sale['user_id'], $course['code']);
997
                break;
998
            case self::PRODUCT_TYPE_SESSION:
999
                SessionManager::subscribe_users_to_session(
1000
                    $sale['product_id'],
1001
                    [$sale['user_id']],
1002
                    api_get_session_visibility($sale['product_id']),
1003
                    false
1004
                );
1005
1006
                $saleIsCompleted = true;
1007
                break;
1008
        }
1009
1010
        if ($saleIsCompleted) {
1011
            $this->updateSaleStatus($sale['id'], self::SALE_STATUS_COMPLETED);
1012
        }
1013
1014
        return $saleIsCompleted;
1015
    }
1016
1017
    /**
1018
     * Update sale status to canceled
1019
     * @param int $saleId The sale ID
1020
     */
1021
    public function cancelSale($saleId)
1022
    {
1023
        $this->updateSaleStatus($saleId, self::SALE_STATUS_CANCELED);
1024
    }
1025
1026
    /**
1027
     * Get payment types
1028
     * @return array
1029
     */
1030
    public function getPaymentTypes()
1031
    {
1032
        return [
1033
            self::PAYMENT_TYPE_PAYPAL => 'PayPal',
1034
            self::PAYMENT_TYPE_TRANSFER => $this->get_lang('BankTransfer'),
1035
            self::PAYMENT_TYPE_CULQI => 'Culqi',
1036
        ];
1037
    }
1038
1039
    /**
1040
     * Get a list of sales by the status
1041
     * @param int $status The status to filter
1042
     * @return array The sale list. Otherwise return false
1043
     */
1044
    public function getSaleListByStatus($status = self::SALE_STATUS_PENDING)
1045
    {
1046
        $saleTable = Database::get_main_table(self::TABLE_SALE);
1047
        $currencyTable = Database::get_main_table(self::TABLE_CURRENCY);
1048
        $userTable = Database::get_main_table(TABLE_MAIN_USER);
1049
1050
        $innerJoins = "
1051
            INNER JOIN $currencyTable c ON s.currency_id = c.id
1052
            INNER JOIN $userTable u ON s.user_id = u.id
1053
        ";
1054
1055
        return Database::select(
1056
            ['c.iso_code', 'u.firstname', 'u.lastname', 's.*'],
1057
            "$saleTable s $innerJoins",
1058
            [
1059
                'where' => ['s.status = ?' => intval($status)],
1060
                'order' => 'id DESC',
1061
            ]
1062
        );
1063
    }
1064
1065
    /**
1066
     * Get the statuses for sales
1067
     * @return array
1068
     */
1069
    public function getSaleStatuses()
1070
    {
1071
        return [
1072
            self::SALE_STATUS_CANCELED => $this->get_lang('SaleStatusCanceled'),
1073
            self::SALE_STATUS_PENDING => $this->get_lang('SaleStatusPending'),
1074
            self::SALE_STATUS_COMPLETED => $this->get_lang('SaleStatusCompleted'),
1075
        ];
1076
    }
1077
1078
    /**
1079
     * Get the statuses for Payouts
1080
     * @return array
1081
     */
1082
    public function getPayoutStatuses()
1083
    {
1084
        return [
1085
            self::PAYOUT_STATUS_CANCELED => $this->get_lang('PayoutStatusCanceled'),
1086
            self::PAYOUT_STATUS_PENDING => $this->get_lang('PayoutStatusPending'),
1087
            self::PAYOUT_STATUS_COMPLETED => $this->get_lang('PayoutStatusCompleted'),
1088
        ];
1089
    }
1090
1091
    /**
1092
     * Get the list of product types
1093
     * @return array
1094
     */
1095
    public function getProductTypes()
1096
    {
1097
        return [
1098
            self::PRODUCT_TYPE_COURSE => get_lang('Course'),
1099
            self::PRODUCT_TYPE_SESSION => get_lang('Session'),
1100
        ];
1101
    }
1102
1103
    /**
1104
     * Get the list of service types
1105
     * @return array
1106
     */
1107
    public function getServiceTypes()
1108
    {
1109
        return [
1110
            self::SERVICE_TYPE_USER => get_lang('User'),
1111
            self::SERVICE_TYPE_COURSE => get_lang('Course'),
1112
            self::SERVICE_TYPE_SESSION => get_lang('Session'),
1113
            self::SERVICE_TYPE_LP_FINAL_ITEM => get_lang('TemplateTitleCertificate'),
1114
        ];
1115
    }
1116
1117
    /**
1118
     * Search filtered sessions by name, and range of price
1119
     * @param string $name Optional. The name filter
1120
     * @param int $min Optional. The minimun price filter
1121
     * @param int $max Optional. The maximum price filter
1122
     * @return array
1123
     */
1124
    private function filterSessionList($name = null, $min = 0, $max = 0)
1125
    {
1126
        if (empty($name) && empty($min) && empty($max)) {
1127
            $auth = new Auth();
1128
1129
            return $auth->browseSessions();
1130
        }
1131
1132
        $itemTable = Database::get_main_table(self::TABLE_ITEM);
1133
        $sessionTable = Database::get_main_table(TABLE_MAIN_SESSION);
1134
1135
        $min = floatval($min);
1136
        $max = floatval($max);
1137
1138
        $innerJoin = "$itemTable i ON s.id = i.product_id";
1139
        $whereConditions = [
1140
            'i.product_type = ? ' => self::PRODUCT_TYPE_SESSION,
1141
        ];
1142
1143
        if (!empty($name)) {
1144
            $whereConditions['AND s.name LIKE %?%'] = $name;
1145
        }
1146
1147
        if (!empty($min)) {
1148
            $whereConditions['AND i.price >= ?'] = $min;
1149
        }
1150
1151
        if (!empty($max)) {
1152
            $whereConditions['AND i.price <= ?'] = $max;
1153
        }
1154
1155
        $sessionIds = Database::select(
1156
            's.id',
1157
            "$sessionTable s INNER JOIN $innerJoin",
1158
            ['where' => $whereConditions]
1159
        );
1160
1161
        if (!$sessionIds) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $sessionIds of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
1162
            return [];
1163
        }
1164
1165
        $sessions = [];
1166
1167
        foreach ($sessionIds as $sessionId) {
1168
            $sessions[] = Database::getManager()->find(
1169
                'ChamiloCoreBundle:Session',
1170
                $sessionId
1171
            );
1172
        }
1173
1174
        return $sessions;
1175
    }
1176
1177
    /**
1178
     * Search filtered courses by name, and range of price
1179
     * @param string $name Optional. The name filter
1180
     * @param int $min Optional. The minimun price filter
1181
     * @param int $max Optional. The maximum price filter
1182
     * @return array
1183
     */
1184
    private function filterCourseList($name = null, $min = 0, $max = 0)
1185
    {
1186
        if (empty($name) && empty($min) && empty($max)) {
1187
            return $this->getCourses();
1188
        }
1189
1190
        $itemTable = Database::get_main_table(self::TABLE_ITEM);
1191
        $courseTable = Database::get_main_table(TABLE_MAIN_COURSE);
1192
        $urlTable = Database::get_main_table(TABLE_MAIN_ACCESS_URL_REL_COURSE);
1193
1194
        $urlId = api_get_current_access_url_id();
1195
1196
        $min = floatval($min);
1197
        $max = floatval($max);
1198
1199
        $whereConditions = [
1200
            'i.product_type = ? ' => self::PRODUCT_TYPE_COURSE,
1201
        ];
1202
1203
        if (!empty($name)) {
1204
            $whereConditions['AND c.title LIKE %?%'] = $name;
1205
        }
1206
1207
        if (!empty($min)) {
1208
            $whereConditions['AND i.price >= ?'] = $min;
1209
        }
1210
1211
        if (!empty($max)) {
1212
            $whereConditions['AND i.price <= ?'] = $max;
1213
        }
1214
1215
        $whereConditions['AND url.access_url_id = ?'] = $urlId;
1216
1217
        $courseIds = Database::select(
1218
            'c.id',
1219
            "$courseTable c 
1220
            INNER JOIN $itemTable i 
1221
            ON c.id = i.product_id 
1222
            INNER JOIN $urlTable url 
1223
            ON c.id = url.c_id
1224
            ",
1225
            ['where' => $whereConditions]
1226
        );
1227
1228
        if (!$courseIds) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $courseIds of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
1229
            return [];
1230
        }
1231
1232
        $courses = [];
1233
        foreach ($courseIds as $courseId) {
1234
            $courses[] = Database::getManager()->find(
1235
                'ChamiloCoreBundle:Course',
1236
                $courseId
1237
            );
1238
        }
1239
1240
        return $courses;
1241
    }
1242
1243
    /**
1244
     * Generates a random text (used for order references)
1245
     * @param int $length Optional. Length of characters
1246
     * @param boolean $lowercase Optional. Include lowercase characters
1247
     * @param boolean $uppercase Optional. Include uppercase characters
1248
     * @param boolean $numbers Optional. Include numbers
1249
     * @return string
1250
     */
1251
    public static function randomText(
1252
        $length = 6,
1253
        $lowercase = true,
1254
        $uppercase = true,
1255
        $numbers = true
1256
    ) {
1257
        $salt = $lowercase ? 'abchefghknpqrstuvwxyz' : '';
1258
        $salt .= $uppercase ? 'ACDEFHKNPRSTUVWXYZ' : '';
1259
        $salt .= $numbers ? (strlen($salt) ? '2345679' : '0123456789') : '';
1260
1261
        if (strlen($salt) == 0) {
1262
            return '';
1263
        }
1264
1265
        $str = '';
1266
1267
        srand((double) microtime() * 1000000);
1268
1269
        for ($i = 0; $i < $length; $i++) {
1270
            $numbers = rand(0, strlen($salt) - 1);
1271
            $str .= substr($salt, $numbers, 1);
1272
        }
1273
1274
        return $str;
1275
    }
1276
1277
    /**
1278
     * Generates an order reference
1279
     * @param int $userId The user ID
1280
     * @param int $productType The course/session type
1281
     * @param int $productId The course/session ID
1282
     * @return string
1283
     */
1284
    public function generateReference($userId, $productType, $productId)
1285
    {
1286
        return vsprintf(
1287
            "%d-%d-%d-%s",
1288
            [$userId, $productType, $productId, self::randomText()]
1289
        );
1290
    }
1291
1292
    /**
1293
     * Get a list of sales by the user
1294
     * @param string $term The search term
1295
     * @return array The sale list. Otherwise return false
1296
     */
1297
    public function getSaleListByUser($term)
1298
    {
1299
        $term = trim($term);
1300
1301
        if (empty($term)) {
1302
            return [];
1303
        }
1304
1305
        $saleTable = Database::get_main_table(self::TABLE_SALE);
1306
        $currencyTable = Database::get_main_table(self::TABLE_CURRENCY);
1307
        $userTable = Database::get_main_table(TABLE_MAIN_USER);
1308
        $innerJoins = "
1309
            INNER JOIN $currencyTable c ON s.currency_id = c.id
1310
            INNER JOIN $userTable u ON s.user_id = u.id
1311
        ";
1312
1313
        return Database::select(
1314
            ['c.iso_code', 'u.firstname', 'u.lastname', 's.*'],
1315
            "$saleTable s $innerJoins",
1316
            [
1317
                'where' => [
1318
                    'u.username LIKE %?% OR ' => $term,
1319
                    'u.lastname LIKE %?% OR ' => $term,
1320
                    'u.firstname LIKE %?%' => $term,
1321
                ],
1322
                'order' => 'id DESC',
1323
            ]
1324
        );
1325
    }
1326
1327
    /**
1328
     * Get a list of sales by the user id
1329
     * @param int $id The user id
1330
     * @return array The sale list. Otherwise return false
1331
     */
1332
    public function getSaleListByUserId($id)
1333
    {
1334
        if (empty($id)) {
1335
            return [];
1336
        }
1337
1338
        $saleTable = Database::get_main_table(self::TABLE_SALE);
1339
        $currencyTable = Database::get_main_table(self::TABLE_CURRENCY);
1340
        $userTable = Database::get_main_table(TABLE_MAIN_USER);
1341
1342
        $innerJoins = "
1343
            INNER JOIN $currencyTable c ON s.currency_id = c.id
1344
            INNER JOIN $userTable u ON s.user_id = u.id
1345
        ";
1346
1347
        return Database::select(
1348
            ['c.iso_code', 'u.firstname', 'u.lastname', 's.*'],
1349
            "$saleTable s $innerJoins",
1350
            [
1351
                'where' => [
1352
                    'u.id = ? AND s.status = ?' => [intval($id), self::SALE_STATUS_COMPLETED],
1353
                ],
1354
                'order' => 'id DESC',
1355
            ]
1356
        );
1357
    }
1358
1359
    /**
1360
     * Convert the course info to array with necessary course data for save item
1361
     * @param Course $course
1362
     * @param array $defaultCurrency Optional. Currency data
1363
     * @return array
1364
     */
1365
    public function getCourseForConfiguration(Course $course, $defaultCurrency = null)
1366
    {
1367
        $courseItem = [
1368
            'item_id' => null,
1369
            'course_id' => $course->getId(),
1370
            'course_visual_code' => $course->getVisualCode(),
1371
            'course_code' => $course->getCode(),
1372
            'course_title' => $course->getTitle(),
1373
            'course_directory' => $course->getDirectory(),
1374
            'course_visibility' => $course->getVisibility(),
1375
            'visible' => false,
1376
            'currency' => empty($defaultCurrency) ? null : $defaultCurrency['iso_code'],
1377
            'price' => 0.00,
1378
        ];
1379
1380
        $item = $this->getItemByProduct($course->getId(), self::PRODUCT_TYPE_COURSE);
1381
1382
        if ($item !== false) {
1383
            $courseItem['item_id'] = $item['id'];
1384
            $courseItem['visible'] = true;
1385
            $courseItem['currency'] = $item['iso_code'];
1386
            $courseItem['price'] = $item['price'];
1387
        }
1388
1389
        return $courseItem;
1390
    }
1391
1392
    /**
1393
     * Convert the session info to array with necessary session data for save item
1394
     * @param Session $session The session data
1395
     * @param array $defaultCurrency Optional. Currency data
1396
     * @return array
1397
     */
1398
    public function getSessionForConfiguration(Session $session, $defaultCurrency = null)
1399
    {
1400
        $buyItemTable = Database::get_main_table(self::TABLE_ITEM);
1401
        $buyCurrencyTable = Database::get_main_table(self::TABLE_CURRENCY);
1402
1403
        $fakeItemFrom = "
1404
            $buyItemTable i
1405
            INNER JOIN $buyCurrencyTable c ON i.currency_id = c.id
1406
        ";
1407
1408
        $sessionItem = [
1409
            'item_id' => null,
1410
            'session_id' => $session->getId(),
1411
            'session_name' => $session->getName(),
1412
            'session_visibility' => $session->getVisibility(),
1413
            'session_display_start_date' => null,
1414
            'session_display_end_date' => null,
1415
            'visible' => false,
1416
            'currency' => empty($defaultCurrency) ? null : $defaultCurrency['iso_code'],
1417
            'price' => 0.00,
1418
        ];
1419
1420
        $displayStartDate = $session->getDisplayStartDate();
1421
1422
        if (!empty($displayStartDate)) {
1423
            $sessionItem['session_display_start_date'] = api_format_date(
1424
                $session->getDisplayStartDate()->format('Y-m-d h:i:s')
1425
            );
1426
        }
1427
1428
        $displayEndDate = $session->getDisplayEndDate();
1429
1430
        if (!empty($displayEndDate)) {
1431
            $sessionItem['session_display_end_date'] = api_format_date(
1432
                $session->getDisplayEndDate()->format('Y-m-d h:i:s'),
1433
                DATE_TIME_FORMAT_LONG_24H
1434
            );
1435
        }
1436
1437
        $item = Database::select(
1438
            ['i.*', 'c.iso_code'],
1439
            $fakeItemFrom,
1440
            [
1441
                'where' => [
1442
                    'i.product_id = ? AND ' => $session->getId(),
1443
                    'i.product_type = ?' => self::PRODUCT_TYPE_SESSION,
1444
                ],
1445
            ],
1446
            'first'
1447
        );
1448
1449
        if ($item !== false) {
1450
            $sessionItem['item_id'] = $item['id'];
1451
            $sessionItem['visible'] = true;
1452
            $sessionItem['currency'] = $item['iso_code'];
1453
            $sessionItem['price'] = $item['price'];
1454
        }
1455
1456
        return $sessionItem;
1457
    }
1458
1459
    /**
1460
     * Get all beneficiaries for a item
1461
     * @param int $itemId The item ID
1462
     * @return array The beneficiaries. Otherwise return false
1463
     */
1464
    public function getItemBeneficiaries($itemId)
1465
    {
1466
        $beneficiaryTable = Database::get_main_table(self::TABLE_ITEM_BENEFICIARY);
1467
1468
        return Database::select(
1469
            '*',
1470
            $beneficiaryTable,
1471
            [
1472
                'where' => [
1473
                    'item_id = ?' => intval($itemId),
1474
                ]
1475
            ]
1476
        );
1477
    }
1478
1479
    /**
1480
     * Delete a item with its beneficiaries
1481
     * @param int $itemId The item ID
1482
     * @return int The number of affected rows. Otherwise return false
1483
     */
1484
    public function deleteItem($itemId)
1485
    {
1486
        $itemTable = Database::get_main_table(self::TABLE_ITEM);
1487
        $affectedRows = Database::delete(
1488
            $itemTable,
1489
            ['id = ?' => intval($itemId)]
1490
        );
1491
1492
        if (!$affectedRows) {
1493
            return false;
0 ignored issues
show
Bug Best Practice introduced by
The expression return false returns the type false which is incompatible with the documented return type integer.
Loading history...
1494
        }
1495
1496
        return $this->deleteItemBeneficiaries($itemId);
1497
    }
1498
1499
    /**
1500
     * Register a item
1501
     * @param array $itemData The item data
1502
     * @return int The item ID. Otherwise return false
1503
     */
1504
    public function registerItem(array $itemData)
1505
    {
1506
        $itemTable = Database::get_main_table(self::TABLE_ITEM);
1507
        return Database::insert($itemTable, $itemData);
0 ignored issues
show
Bug Best Practice introduced by
The expression return Database::insert($itemTable, $itemData) could also return false which is incompatible with the documented return type integer. Did you maybe forget to handle an error condition?

If the returned type also contains false, it is an indicator that maybe an error condition leading to the specific return statement remains unhandled.

Loading history...
1508
    }
1509
1510
    /**
1511
     * Update the item data by product
1512
     * @param array $itemData The item data to be updated
1513
     * @param int $productId The product ID
1514
     * @param int $productType The type of product
1515
     * @return int The number of affected rows. Otherwise return false
1516
     */
1517
    public function updateItem(array $itemData, $productId, $productType)
1518
    {
1519
        $itemTable = Database::get_main_table(self::TABLE_ITEM);
1520
1521
        return Database::update(
0 ignored issues
show
Bug Best Practice introduced by
The expression return Database::update(...type' => $productType)) returns the type false which is incompatible with the documented return type integer.
Loading history...
1522
            $itemTable,
1523
            $itemData,
1524
            [
1525
                'product_id = ? AND ' => intval($productId),
1526
                'product_type' => $productType,
1527
            ]
1528
        );
1529
    }
1530
1531
    /**
1532
     * Remove all beneficiaries for a item
1533
     * @param int $itemId The user ID
1534
     * @return int The number of affected rows. Otherwise return false
1535
     */
1536
    public function deleteItemBeneficiaries($itemId)
1537
    {
1538
        $beneficiaryTable = Database::get_main_table(self::TABLE_ITEM_BENEFICIARY);
1539
1540
        return Database::delete(
1541
            $beneficiaryTable,
1542
            ['item_id = ?' => intval($itemId)]
1543
        );
1544
    }
1545
1546
    /**
1547
     * Register the beneficiaries users with the sale of item
1548
     * @param int $itemId The item ID
1549
     * @param array $userIds The beneficiary user ID and Teachers commissions if enabled
1550
     */
1551
    public function registerItemBeneficiaries($itemId, array $userIds)
1552
    {
1553
        $beneficiaryTable = Database::get_main_table(self::TABLE_ITEM_BENEFICIARY);
1554
1555
        $this->deleteItemBeneficiaries($itemId);
1556
1557
        foreach ($userIds as $userId => $commissions) {
1558
            Database::insert(
1559
                $beneficiaryTable,
1560
                [
1561
                    'item_id' => intval($itemId),
1562
                    'user_id' => intval($userId),
1563
                    'commissions' => intval($commissions),
1564
                ]
1565
            );
1566
        }
1567
    }
1568
1569
    /**
1570
     * Check if a course is valid for sale
1571
     * @param Course $course The course
1572
     * @return boolean
1573
     */
1574
    public function isValidCourse(Course $course)
1575
    {
1576
        $courses = $this->getCourses();
1577
1578
        foreach ($courses as $_c) {
1579
            if ($_c->getCode() === $course->getCode()) {
1580
                return true;
1581
            }
1582
        }
1583
1584
        return false;
1585
    }
1586
1587
    /**
1588
     * Gets the beneficiaries with commissions and current paypal accounts by sale
1589
     * @param int $saleId The sale ID
1590
     * @return array
1591
     */
1592
    public function getBeneficiariesBySale($saleId)
1593
    {
1594
        $sale = $this->getSale($saleId);
1595
        $item = $this->getItemByProduct($sale['product_id'], $sale['product_type']);
1596
        $itemBeneficiaries = $this->getItemBeneficiaries($item['id']);
1597
1598
        return $itemBeneficiaries;
1599
    }
1600
1601
    /**
1602
     * gets all payouts
1603
     * @param int $status - default 0 - pending
1604
     * @param int $payoutId - for get an individual payout if want all then false
1605
     * @param int $userId
1606
     * @return array
1607
     */
1608
    public function getPayouts(
1609
        $status = self::PAYOUT_STATUS_PENDING,
1610
        $payoutId = false,
1611
        $userId = false
1612
    ) {
1613
        $condition = ($payoutId) ? 'AND p.id = '.intval($payoutId) : '';
1614
        $condition2 = ($userId) ? ' AND p.user_id = '.intval($userId) : '';
1615
        $typeResult = ($condition) ? 'first' : 'all';
1616
        $payoutsTable = Database::get_main_table(self::TABLE_PAYPAL_PAYOUTS);
1617
        $saleTable = Database::get_main_table(self::TABLE_SALE);
1618
        $currencyTable = Database::get_main_table(self::TABLE_CURRENCY);
1619
        $userTable = Database::get_main_table(TABLE_MAIN_USER);
1620
        $extraFieldTable = Database::get_main_table(TABLE_EXTRA_FIELD);
1621
        $extraFieldValues = Database::get_main_table(TABLE_EXTRA_FIELD_VALUES);
1622
1623
        $paypalExtraField = Database::select(
1624
            "*",
1625
            $extraFieldTable,
1626
            [
1627
                'where' => ['variable = ?' => 'paypal'],
1628
            ],
1629
            'first'
1630
        );
1631
1632
        if (!$paypalExtraField) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $paypalExtraField of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
1633
            return false;
0 ignored issues
show
Bug Best Practice introduced by
The expression return false returns the type false which is incompatible with the documented return type array.
Loading history...
1634
        }
1635
1636
        $innerJoins = "
1637
            INNER JOIN $userTable u ON p.user_id = u.id
1638
            INNER JOIN $saleTable s ON s.id = p.sale_id
1639
            INNER JOIN $currencyTable c ON s.currency_id = c.id
1640
            LEFT JOIN  $extraFieldValues efv ON p.user_id = efv.item_id 
1641
            AND field_id = ".intval($paypalExtraField['id'])."
1642
        ";
1643
1644
        $payouts = Database::select(
1645
            "p.* , u.firstname, u.lastname, efv.value as paypal_account, s.reference as sale_reference, s.price as item_price, c.iso_code",
1646
            "$payoutsTable p $innerJoins",
1647
            [
1648
                'where' => ['p.status = ? '.$condition.' '.$condition2 => $status],
1649
            ],
1650
            $typeResult
1651
        );
1652
1653
        return $payouts;
1654
    }
1655
1656
    /**
1657
     * Verify if the beneficiary have a paypal account
1658
     * @param int $userId
1659
     * @return true if the user have a paypal account, false if not
1660
     */
1661
    public function verifyPaypalAccountByBeneficiary($userId)
1662
    {
1663
        $extraFieldTable = Database::get_main_table(TABLE_EXTRA_FIELD);
1664
        $extraFieldValues = Database::get_main_table(TABLE_EXTRA_FIELD_VALUES);
1665
1666
        $paypalExtraField = Database::select(
1667
            "*",
1668
            $extraFieldTable,
1669
            [
1670
                'where' => ['variable = ?' => 'paypal'],
1671
            ],
1672
            'first'
1673
        );
1674
1675
        if (!$paypalExtraField) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $paypalExtraField of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
1676
            return false;
0 ignored issues
show
Bug Best Practice introduced by
The expression return false returns the type false which is incompatible with the documented return type true.
Loading history...
1677
        }
1678
1679
        $paypalFieldId = $paypalExtraField['id'];
1680
        $paypalAccount = Database::select(
1681
            "value",
1682
            $extraFieldValues,
1683
            [
1684
                'where' => ['field_id = ? AND item_id = ?' => [intval($paypalFieldId), intval($userId)]],
1685
            ],
1686
            'first'
1687
        );
1688
1689
        if (!$paypalAccount) {
1690
            return false;
1691
        }
1692
1693
        if ($paypalAccount['value'] === '') {
1694
            return false;
1695
        }
1696
1697
        return true;
1698
    }
1699
1700
    /**
1701
     * Register the users payouts
1702
     * @param int $saleId The sale ID
1703
     * @return array
1704
     */
1705
    public function storePayouts($saleId)
1706
    {
1707
        $payoutsTable = Database::get_main_table(self::TABLE_PAYPAL_PAYOUTS);
1708
        $platformCommission = $this->getPlatformCommission();
1709
1710
        $sale = $this->getSale($saleId);
1711
        $teachersCommission = number_format(
1712
            (floatval($sale['price']) * intval($platformCommission['commission'])) / 100,
1713
            2
1714
        );
1715
1716
        $beneficiaries = $this->getBeneficiariesBySale($saleId);
1717
        foreach ($beneficiaries as $beneficiary) {
1718
            Database::insert(
1719
                $payoutsTable,
1720
                [
1721
                    'date' => $sale['date'],
1722
                    'payout_date' => getdate(),
1723
                    'sale_id' => intval($saleId),
1724
                    'user_id' => $beneficiary['user_id'],
1725
                    'commission' => number_format(
1726
                        (floatval($teachersCommission) * intval($beneficiary['commissions'])) / 100,
1727
                        2
1728
                    ),
1729
                    'status' => self::PAYOUT_STATUS_PENDING,
1730
                ]
1731
            );
1732
        }
1733
    }
1734
1735
    /**
1736
     * Register the users payouts
1737
     * @param int $payoutId The payout ID
1738
     * @param int $status The status to set (-1 to cancel, 0 to pending, 1 to completed)
1739
     * @return array
1740
     */
1741
    public function setStatusPayouts($payoutId, $status)
1742
    {
1743
        $payoutsTable = Database::get_main_table(self::TABLE_PAYPAL_PAYOUTS);
1744
1745
        Database::update(
1746
            $payoutsTable,
1747
            ['status' => intval($status)],
1748
            ['id = ?' => intval($payoutId)]
1749
        );
1750
    }
1751
1752
    /**
1753
     * Gets the stored platform commission params
1754
     * @return array
1755
     */
1756
    public function getPlatformCommission()
1757
    {
1758
        return Database::select(
1759
            '*',
1760
            Database::get_main_table(self::TABLE_COMMISSION),
1761
            ['id = ?' => 1],
1762
            'first'
1763
        );
1764
    }
1765
1766
    /**
1767
     * Update the platform commission
1768
     * @param int $params platform commission
1769
     * @return int The number of affected rows. Otherwise return false
1770
     */
1771
    public function updateCommission($params)
1772
    {
1773
        $commissionTable = Database::get_main_table(self::TABLE_COMMISSION);
1774
1775
        return Database::update(
0 ignored issues
show
Bug Best Practice introduced by
The expression return Database::update(...params['commission']))) returns the type false which is incompatible with the documented return type integer.
Loading history...
1776
            $commissionTable,
1777
            ['commission' => intval($params['commission'])]
1778
        );
1779
    }
1780
1781
    /**
1782
     * Register additional service
1783
     * @param array $service params
1784
     *
1785
     * @return mixed response
1786
     */
1787
    public function storeService($service)
1788
    {
1789
        $servicesTable = Database::get_main_table(self::TABLE_SERVICES);
1790
1791
        $return = Database::insert(
1792
            $servicesTable,
1793
            [
1794
                'name' => Security::remove_XSS($service['name']),
1795
                'description' => Security::remove_XSS($service['description']),
1796
                'price' => $service['price'],
1797
                'duration_days' => intval($service['duration_days']),
1798
                'applies_to' => intval($service['applies_to']),
1799
                'owner_id' => intval($service['owner_id']),
1800
                'visibility' => intval($service['visibility']),
1801
                'image' => '',
1802
                'video_url' => $service['video_url'],
1803
                'service_information' => $service['service_information'],
1804
            ]
1805
        );
1806
1807
        if ($return && !empty($service['picture_crop_image_base_64']) &&
0 ignored issues
show
Bug Best Practice introduced by
The expression $return of type integer|false is loosely compared to true; this is ambiguous if the integer can be 0. You might want to explicitly use !== false instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For integer values, zero is a special case, in particular the following results might be unexpected:

0   == false // true
0   == null  // true
123 == false // false
123 == null  // false

// It is often better to use strict comparison
0 === false // false
0 === null  // false
Loading history...
1808
            !empty($service['picture_crop_result'])
1809
        ) {
1810
            $img = str_replace('data:image/png;base64,', '', $service['picture_crop_image_base_64']);
1811
            $img = str_replace(' ', '+', $img);
1812
            $data = base64_decode($img);
1813
            $file = api_get_path(SYS_PLUGIN_PATH).'buycourses/uploads/services/images/simg-'.$return.'.png';
1814
            file_put_contents($file, $data);
1815
1816
            Database::update(
1817
                $servicesTable,
1818
                ['image' => 'simg-'.$return.'.png'],
1819
                ['id = ?' => intval($return)]
1820
            );
1821
        }
1822
1823
        return $return;
1824
    }
1825
1826
    /**
1827
     * update a service
1828
     * @param array $service
1829
     * @param integer $id
1830
     * @return mixed response
1831
     */
1832
    public function updateService($service, $id)
1833
    {
1834
        $servicesTable = Database::get_main_table(self::TABLE_SERVICES);
1835
        if (!empty($service['picture_crop_image_base_64'])) {
1836
            $img = str_replace('data:image/png;base64,', '', $service['picture_crop_image_base_64']);
1837
            $img = str_replace(' ', '+', $img);
1838
            $data = base64_decode($img);
1839
            $file = api_get_path(SYS_PLUGIN_PATH).'buycourses/uploads/services/images/simg-'.$id.'.png';
1840
            file_put_contents($file, $data);
1841
        }
1842
1843
        return Database::update(
1844
            $servicesTable,
1845
            [
1846
                'name' => Security::remove_XSS($service['name']),
1847
                'description' => Security::remove_XSS($service['description']),
1848
                'price' => $service['price'],
1849
                'duration_days' => intval($service['duration_days']),
1850
                'applies_to' => intval($service['applies_to']),
1851
                'owner_id' => intval($service['owner_id']),
1852
                'visibility' => intval($service['visibility']),
1853
                'image' => 'simg-'.$id.'.png',
1854
                'video_url' => $service['video_url'],
1855
                'service_information' => $service['service_information'],
1856
            ],
1857
            ['id = ?' => intval($id)]
1858
        );
1859
    }
1860
1861
    /**
1862
     * Remove a service
1863
     * @param int $id The transfer account ID
1864
     * @return int Rows affected. Otherwise return false
1865
     */
1866
    public function deleteService($id)
1867
    {
1868
        Database::delete(
1869
            Database::get_main_table(self::TABLE_SERVICES_SALE),
1870
            ['service_id = ?' => intval($id)]
1871
        );
1872
1873
        return Database::delete(
1874
            Database::get_main_table(self::TABLE_SERVICES),
1875
            ['id = ?' => intval($id)]
1876
        );
1877
    }
1878
1879
    /**
1880
     * List additional services
1881
     * @param integer $id service id
1882
     * @return array
1883
     */
1884
    public function getServices($id = null)
1885
    {
1886
        $servicesTable = Database::get_main_table(self::TABLE_SERVICES);
1887
        $userTable = Database::get_main_table(TABLE_MAIN_USER);
1888
1889
        $conditions = null;
1890
        $showData = "all";
1891
1892
        if ($id) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $id of type null|integer is loosely compared to true; this is ambiguous if the integer can be 0. You might want to explicitly use !== null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For integer values, zero is a special case, in particular the following results might be unexpected:

0   == false // true
0   == null  // true
123 == false // false
123 == null  // false

// It is often better to use strict comparison
0 === false // false
0 === null  // false
Loading history...
1893
            $conditions = ['WHERE' => ['s.id = ?' => $id]];
1894
            $showData = "first";
1895
        }
1896
1897
        $innerJoins = "INNER JOIN $userTable u ON s.owner_id = u.id";
1898
        $currency = $this->getSelectedCurrency();
1899
        $isoCode = $currency['iso_code'];
1900
        $return = Database::select(
1901
            "s.*, '$isoCode' as currency, u.firstname, u.lastname",
1902
            "$servicesTable s $innerJoins",
1903
            $conditions,
1904
            $showData
1905
        );
1906
1907
        $services = [];
1908
1909
        if ($id) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $id of type null|integer is loosely compared to true; this is ambiguous if the integer can be 0. You might want to explicitly use !== null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For integer values, zero is a special case, in particular the following results might be unexpected:

0   == false // true
0   == null  // true
123 == false // false
123 == null  // false

// It is often better to use strict comparison
0 === false // false
0 === null  // false
Loading history...
1910
            $services['id'] = $return['id'];
1911
            $services['name'] = $return['name'];
1912
            $services['description'] = $return['description'];
1913
            $services['price'] = $return['price'];
1914
            $services['currency'] = $return['currency'];
1915
            $services['duration_days'] = $return['duration_days'];
1916
            $services['applies_to'] = $return['applies_to'];
1917
            $services['owner_id'] = $return['owner_id'];
1918
            $services['owner_name'] = api_get_person_name($return['firstname'], $return['lastname']);
1919
            $services['visibility'] = $return['visibility'];
1920
            $services['image'] = $return['image'];
1921
            $services['video_url'] = $return['video_url'];
1922
            $services['service_information'] = $return['service_information'];
1923
1924
            return $services;
1925
        }
1926
1927
        foreach ($return as $index => $service) {
1928
            $services[$index]['id'] = $service['id'];
1929
            $services[$index]['name'] = $service['name'];
1930
            $services[$index]['description'] = $service['description'];
1931
            $services[$index]['price'] = $service['price'];
1932
            $services[$index]['currency'] = $service['currency'];
1933
            $services[$index]['duration_days'] = $service['duration_days'];
1934
            $services[$index]['applies_to'] = $service['applies_to'];
1935
            $services[$index]['owner_id'] = $service['owner_id'];
1936
            $services[$index]['owner_name'] = api_get_person_name($service['firstname'], $service['lastname']);
1937
            $services[$index]['visibility'] = $service['visibility'];
1938
            $services[$index]['image'] = $service['image'];
1939
            $services[$index]['video_url'] = $service['video_url'];
1940
            $services[$index]['service_information'] = $service['service_information'];
1941
        }
1942
1943
        return $services;
1944
    }
1945
1946
    /**
1947
     * Get the statuses for sales
1948
     * @return array
1949
     */
1950
    public function getServiceSaleStatuses()
1951
    {
1952
        return [
1953
            self::SERVICE_STATUS_CANCELLED => $this->get_lang('SaleStatusCancelled'),
1954
            self::SERVICE_STATUS_PENDING => $this->get_lang('SaleStatusPending'),
1955
            self::SERVICE_STATUS_COMPLETED => $this->get_lang('SaleStatusCompleted'),
1956
        ];
1957
    }
1958
1959
    /**
1960
     * List services sales
1961
     * @param integer $id service id
1962
     * @param integer $buyerId buyer id
1963
     * @param integer $status status
1964
     * @param integer $nodeType The node Type ( User = 1 , Course = 2 , Session = 3 )
1965
     * @param integer $nodeId the nodeId
1966
     * @param boolean $hot enable hot services
1967
     * @return array
1968
     */
1969
    public function getServiceSale(
1970
        $id = 0,
1971
        $buyerId = 0,
1972
        $status = 0,
1973
        $nodeType = 0,
1974
        $nodeId = 0,
1975
        $hot = false
1976
    ) {
1977
        $servicesTable = Database::get_main_table(self::TABLE_SERVICES);
1978
        $servicesSaleTable = Database::get_main_table(self::TABLE_SERVICES_SALE);
1979
1980
        $conditions = null;
1981
        $showData = 'all';
1982
        $groupBy = '';
1983
        $id = (int) $id;
1984
        $buyerId = (int) $buyerId;
1985
        $status = (int) $status;
1986
        $nodeType = (int) $nodeType;
1987
        $nodeId = (int) $nodeId;
1988
1989
        if (!empty($id)) {
1990
            $conditions = ['WHERE' => ['ss.id = ?' => $id]];
1991
            $showData = "first";
1992
        }
1993
1994
        if (!empty($buyerId)) {
1995
            $conditions = ['WHERE' => ['ss.buyer_id = ?' => $buyerId], 'ORDER' => 'id ASC'];
1996
        }
1997
1998
        if (is_numeric($status)) {
1999
            $conditions = ['WHERE' => ['ss.status = ?' => $status], 'ORDER' => 'id ASC'];
2000
        }
2001
2002
        if ($id && $buyerId) {
2003
            $conditions = ['WHERE' => ['ss.id = ? AND ss.buyer_id = ?' => [$id, $buyerId]], 'ORDER' => 'id ASC'];
2004
        }
2005
2006
        if ($nodeType && $nodeId) {
2007
            $conditions = [
2008
                'WHERE' => ['ss.node_type = ? AND ss.node_id = ?' => [$nodeType, $nodeId]], 'ORDER' => 'id ASC'
2009
            ];
2010
        }
2011
2012
        if ($nodeType && $nodeId && $buyerId && is_numeric($status)) {
2013
            $conditions = [
2014
                'WHERE' => [
2015
                    'ss.node_type = ? AND ss.node_id = ? AND ss.buyer_id = ? AND ss.status = ?' => [
2016
                        $nodeType,
2017
                        $nodeId,
2018
                        $buyerId,
2019
                        $status
2020
                    ]
2021
                ],
2022
                'ORDER' => 'id ASC'
2023
            ];
2024
        }
2025
2026
        if ($hot) {
2027
            $hot = "count(ss.service_id) as hot, ";
2028
            $conditions = ['ORDER' => 'hot DESC', 'LIMIT' => '6'];
2029
            $groupBy = "GROUP BY ss.service_id";
2030
            "clean_teacher_files.php";
2031
        }
2032
2033
        $innerJoins = "INNER JOIN $servicesTable s ON ss.service_id = s.id $groupBy";
2034
        $currency = $this->getSelectedCurrency();
2035
        $isoCode = $currency['iso_code'];
2036
        $return = Database::select(
2037
            "ss.*, s.name, s.description, s.price as service_price, s.duration_days, s.applies_to, s.owner_id, s.visibility, s.image, $hot '$isoCode' as currency",
2038
            "$servicesSaleTable ss $innerJoins",
2039
            $conditions,
2040
            $showData
2041
        );
2042
2043
        $servicesSale = [];
2044
2045
        if ($id) {
2046
            $owner = api_get_user_info($return['owner_id']);
2047
            $buyer = api_get_user_info($return['buyer_id']);
2048
2049
            $servicesSale['id'] = $return['id'];
2050
            $servicesSale['service']['id'] = $return['service_id'];
2051
            $servicesSale['service']['name'] = $return['name'];
2052
            $servicesSale['service']['description'] = $return['description'];
2053
            $servicesSale['service']['price'] = $return['service_price'];
2054
            $servicesSale['service']['currency'] = $return['currency'];
2055
            $servicesSale['service']['duration_days'] = $return['duration_days'];
2056
            $servicesSale['service']['applies_to'] = $return['applies_to'];
2057
            $servicesSale['service']['owner']['id'] = $return['owner_id'];
2058
            $servicesSale['service']['owner']['name'] = api_get_person_name($owner['firstname'], $owner['lastname']);
2059
            $servicesSale['service']['visibility'] = $return['visibility'];
2060
            $servicesSale['service']['image'] = $return['image'];
2061
            $servicesSale['reference'] = $return['reference'];
2062
            $servicesSale['currency_id'] = $return['currency_id'];
2063
            $servicesSale['currency'] = $return['currency'];
2064
            $servicesSale['price'] = $return['price'];
2065
            $servicesSale['node_type'] = $return['node_type'];
2066
            $servicesSale['node_id'] = $return['node_id'];
2067
            $servicesSale['buyer']['id'] = $buyer['user_id'];
2068
            $servicesSale['buyer']['name'] = api_get_person_name($buyer['firstname'], $buyer['lastname']);
2069
            $servicesSale['buyer']['username'] = $buyer['username'];
2070
            $servicesSale['buy_date'] = $return['buy_date'];
2071
            $servicesSale['date_start'] = $return['date_start'];
2072
            $servicesSale['date_end'] = $return['date_end'];
2073
            $servicesSale['status'] = $return['status'];
2074
            $servicesSale['payment_type'] = $return['payment_type'];
2075
2076
            return $servicesSale;
2077
        }
2078
2079
        foreach ($return as $index => $service) {
2080
            $owner = api_get_user_info($service['owner_id']);
2081
            $buyer = api_get_user_info($service['buyer_id']);
2082
2083
            $servicesSale[$index]['id'] = $service['id'];
2084
            $servicesSale[$index]['service']['id'] = $service['service_id'];
2085
            $servicesSale[$index]['service']['name'] = $service['name'];
2086
            $servicesSale[$index]['service']['description'] = $service['description'];
2087
            $servicesSale[$index]['service']['price'] = $service['service_price'];
2088
            $servicesSale[$index]['service']['duration_days'] = $service['duration_days'];
2089
            $servicesSale[$index]['service']['applies_to'] = $service['applies_to'];
2090
            $servicesSale[$index]['service']['owner']['id'] = $service['owner_id'];
2091
            $servicesSale[$index]['service']['owner']['name'] = api_get_person_name(
2092
                $owner['firstname'],
2093
                $owner['lastname']
2094
            );
2095
            $servicesSale[$index]['service']['visibility'] = $service['visibility'];
2096
            $servicesSale[$index]['service']['image'] = $service['image'];
2097
            $servicesSale[$index]['reference'] = $service['reference'];
2098
            $servicesSale[$index]['currency_id'] = $service['currency_id'];
2099
            $servicesSale[$index]['currency'] = $service['currency'];
2100
            $servicesSale[$index]['price'] = $service['price'];
2101
            $servicesSale[$index]['node_type'] = $service['node_type'];
2102
            $servicesSale[$index]['node_id'] = $service['node_id'];
2103
            $servicesSale[$index]['buyer']['id'] = $service['buyer_id'];
2104
            $servicesSale[$index]['buyer']['name'] = api_get_person_name($buyer['firstname'], $buyer['lastname']);
2105
            $servicesSale[$index]['buyer']['username'] = $buyer['username'];
2106
            $servicesSale[$index]['buy_date'] = $service['buy_date'];
2107
            $servicesSale[$index]['date_start'] = $service['date_start'];
2108
            $servicesSale[$index]['date_end'] = $service['date_end'];
2109
            $servicesSale[$index]['status'] = $service['status'];
2110
            $servicesSale[$index]['payment_type'] = $service['payment_type'];
2111
        }
2112
2113
        return $servicesSale;
2114
    }
2115
2116
    /**
2117
     * Update service sale status to cancelled
2118
     * @param int $serviceSaleId The sale ID
2119
     * @return boolean
2120
     */
2121
    public function cancelServiceSale($serviceSaleId)
2122
    {
2123
        $this->updateServiceSaleStatus(
2124
            $serviceSaleId,
2125
            self::SERVICE_STATUS_CANCELLED
2126
        );
2127
2128
        return true;
2129
    }
2130
2131
    /**
2132
     * Complete service sale process. Update service sale status to completed
2133
     * @param int $serviceSaleId The service sale ID
2134
     * @return boolean
2135
     */
2136
    public function completeServiceSale($serviceSaleId)
2137
    {
2138
        $serviceSale = $this->getServiceSale($serviceSaleId);
2139
        if ($serviceSale['status'] == self::SERVICE_STATUS_COMPLETED) {
2140
            return true;
2141
        }
2142
2143
        $this->updateServiceSaleStatus(
2144
            $serviceSaleId,
2145
            self::SERVICE_STATUS_COMPLETED
2146
        );
2147
2148
        return true;
2149
    }
2150
2151
    /**
2152
     * Lists current service details
2153
     * @param string $name Optional. The name filter
2154
     * @param int $min Optional. The minimum price filter
2155
     * @param int $max Optional. The maximum price filter
2156
     * @param mixed $appliesTo Optional.
2157
     * @return array
2158
     */
2159
    public function getCatalogServiceList($name = null, $min = 0, $max = 0, $appliesTo = '')
2160
    {
2161
        $servicesTable = Database::get_main_table(self::TABLE_SERVICES);
2162
        $userTable = Database::get_main_table(TABLE_MAIN_USER);
2163
2164
        $whereConditions = [
2165
            's.visibility <> ? ' => 0,
2166
        ];
2167
2168
        if (!empty($name)) {
2169
            $whereConditions['AND s.name LIKE %?%'] = $name;
2170
        }
2171
2172
        if (!empty($min)) {
2173
            $whereConditions['AND s.price >= ?'] = $min;
2174
        }
2175
2176
        if (!empty($max)) {
2177
            $whereConditions['AND s.price <= ?'] = $max;
2178
        }
2179
2180
        if (!$appliesTo == '') {
2181
            $whereConditions['AND s.applies_to = ?'] = $appliesTo;
2182
        }
2183
2184
        $innerJoins = "INNER JOIN $userTable u ON s.owner_id = u.id";
2185
        $currency = $this->getSelectedCurrency();
2186
        $isoCode = $currency['iso_code'];
2187
        $return = Database::select(
2188
            "s.*, '$isoCode' as currency, u.firstname, u.lastname",
2189
            "$servicesTable s $innerJoins",
2190
            ['WHERE' => $whereConditions]
2191
        );
2192
2193
        $services = [];
2194
2195
        foreach ($return as $index => $service) {
2196
            $services[$index]['id'] = $service['id'];
2197
            $services[$index]['name'] = $service['name'];
2198
            $services[$index]['description'] = $service['description'];
2199
            $services[$index]['price'] = $service['price'];
2200
            $services[$index]['currency'] = $service['currency'];
2201
            $services[$index]['duration_days'] = $service['duration_days'];
2202
            $services[$index]['applies_to'] = $service['applies_to'];
2203
            $services[$index]['owner_id'] = $service['owner_id'];
2204
            $services[$index]['owner_name'] = api_get_person_name($service['firstname'], $service['lastname']);
2205
            $services[$index]['visibility'] = $service['visibility'];
2206
            $services[$index]['image'] = !empty($service['image'])
2207
                ? api_get_path(WEB_PLUGIN_PATH).'buycourses/uploads/services/images/'.$service['image']
2208
                : null;
2209
            $services[$index]['video_url'] = $service['video_url'];
2210
            $services[$index]['service_information'] = $service['service_information'];
2211
        }
2212
2213
        return $services;
2214
    }
2215
2216
    /**
2217
     * Update the service sale status
2218
     * @param int $serviceSaleId The service sale ID
2219
     * @param int $newStatus The new status
2220
     * @return boolean
2221
     */
2222
    private function updateServiceSaleStatus(
2223
        $serviceSaleId,
2224
        $newStatus = self::SERVICE_STATUS_PENDING
2225
    ) {
2226
        $serviceSaleTable = Database::get_main_table(self::TABLE_SERVICES_SALE);
2227
2228
        return Database::update(
2229
            $serviceSaleTable,
2230
            ['status' => intval($newStatus)],
2231
            ['id = ?' => intval($serviceSaleId)]
2232
        );
2233
    }
2234
2235
    /**
2236
     * Register a Service sale
2237
     * @param int $serviceId The service ID
2238
     * @param int $paymentType The payment type
2239
     * @param int $infoSelect The ID for Service Type
2240
     * @param int $trial trial mode
2241
     * @return boolean
2242
     */
2243
    public function registerServiceSale($serviceId, $paymentType, $infoSelect, $trial = null)
0 ignored issues
show
Unused Code introduced by
The parameter $trial is not used and could be removed. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-unused  annotation

2243
    public function registerServiceSale($serviceId, $paymentType, $infoSelect, /** @scrutinizer ignore-unused */ $trial = null)

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
2244
    {
2245
        if (!in_array(
2246
            $paymentType,
2247
            [self::PAYMENT_TYPE_PAYPAL, self::PAYMENT_TYPE_TRANSFER, self::PAYMENT_TYPE_CULQI]
2248
        )
2249
        ) {
2250
            return false;
2251
        }
2252
2253
        $userId = api_get_user_id();
2254
        $service = $this->getServices($serviceId);
2255
2256
        if (empty($service)) {
2257
            return false;
2258
        }
2259
2260
        $currency = $this->getSelectedCurrency();
2261
2262
        $values = [
2263
            'service_id' => $serviceId,
2264
            'reference' => $this->generateReference(
2265
                $userId,
2266
                $service['applies_to'],
2267
                $infoSelect
2268
            ),
2269
            'currency_id' => $currency['id'],
2270
            'price' => $service['price'],
2271
            'node_type' => $service['applies_to'],
2272
            'node_id' => intval($infoSelect),
2273
            'buyer_id' => $userId,
2274
            'buy_date' => api_get_utc_datetime(),
2275
            'date_start' => api_get_utc_datetime(),
2276
            'date_end' => date_format(
2277
                date_add(
2278
                    date_create(api_get_utc_datetime()),
0 ignored issues
show
Bug introduced by
It seems like date_create(api_get_utc_datetime()) can also be of type false; however, parameter $object of date_add() does only seem to accept DateTime, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

2278
                    /** @scrutinizer ignore-type */ date_create(api_get_utc_datetime()),
Loading history...
2279
                    date_interval_create_from_date_string($service['duration_days'].' days')
2280
                ),
2281
                'Y-m-d H:i:s'
2282
            ),
2283
            'status' => self::SERVICE_STATUS_PENDING,
2284
            'payment_type' => intval($paymentType),
2285
        ];
2286
2287
        $returnedServiceSaleId = Database::insert(self::TABLE_SERVICES_SALE, $values);
2288
2289
        return $returnedServiceSaleId;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $returnedServiceSaleId also could return the type integer which is incompatible with the documented return type boolean.
Loading history...
2290
    }
2291
2292
    /**
2293
     * Save Culqi configuration params
2294
     * @param array $params
2295
     * @return int Rows affected. Otherwise return false
2296
     */
2297
    public function saveCulqiParameters($params)
2298
    {
2299
        return Database::update(
0 ignored issues
show
Bug Best Practice introduced by
The expression return Database::update(..., array('id = ?' => 1)) returns the type false which is incompatible with the documented return type integer.
Loading history...
2300
            Database::get_main_table(self::TABLE_CULQI),
2301
            [
2302
                'commerce_code' => $params['commerce_code'],
2303
                'api_key' => $params['api_key'],
2304
                'integration' => $params['integration'],
2305
            ],
2306
            ['id = ?' => 1]
2307
        );
2308
    }
2309
2310
    /**
2311
     * Gets the stored Culqi params
2312
     * @return array
2313
     */
2314
    public function getCulqiParams()
2315
    {
2316
        return Database::select(
2317
            '*',
2318
            Database::get_main_table(self::TABLE_CULQI),
2319
            ['id = ?' => 1],
2320
            'first'
2321
        );
2322
    }
2323
2324
    /**
2325
     * Save Global Parameters
2326
     * @param array $params
2327
     * @return int Rows affected. Otherwise return false
2328
     */
2329
    public function saveGlobalParameters($params)
2330
    {
2331
        return Database::update(
0 ignored issues
show
Bug Best Practice introduced by
The expression return Database::update(..., array('id = ?' => 1)) returns the type false which is incompatible with the documented return type integer.
Loading history...
2332
            Database::get_main_table(self::TABLE_GLOBAL_CONFIG),
2333
            [
2334
                'terms_and_conditions' => $params['terms_and_conditions'],
2335
            ],
2336
            ['id = ?' => 1]
2337
        );
2338
    }
2339
2340
    /**
2341
     * get Global Parameters
2342
     * @return array
2343
     */
2344
    public function getGlobalParameters()
2345
    {
2346
        return Database::select(
2347
            '*',
2348
            Database::get_main_table(self::TABLE_GLOBAL_CONFIG),
2349
            ['id = ?' => 1],
2350
            'first'
2351
        );
2352
    }
2353
2354
    /**
2355
     * Get the path
2356
     * @param string $var path variable
2357
     * @return string path
2358
     */
2359
    public function getPath($var)
2360
    {
2361
        $pluginPath = api_get_path(WEB_PLUGIN_PATH).'buycourses/';
2362
        $paths = [
2363
            'SERVICE_IMAGES' => $pluginPath.'uploads/services/images/',
2364
            'SRC' => $pluginPath.'src/',
2365
            'VIEW' => $pluginPath.'view/',
2366
            'UPLOADS' => $pluginPath.'uploads/',
2367
            'LANGUAGES' => $pluginPath.'lang/',
2368
            'RESOURCES' => $pluginPath.'resources/',
2369
            'RESOURCES_IMG' => $pluginPath.'resources/img/',
2370
            'RESOURCES_CSS' => $pluginPath.'resources/css/',
2371
            'RESOURCES_JS' => $pluginPath.'resources/js/',
2372
        ];
2373
2374
        return $paths[$var];
2375
    }
2376
}
2377