GitHub Access Token became invalid

It seems like the GitHub access token used for retrieving details about this repository from GitHub became invalid. This might prevent certain types of inspections from being run (in particular, everything related to pull requests).
Please ask an admin of your repository to re-new the access token on this website.

ipn.php ➔ availability_paypal_ipn_exception_handler()   A
last analyzed

Complexity

Conditions 2
Paths 2

Size

Total Lines 10

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 2
nc 2
nop 1
dl 0
loc 10
rs 9.9332
c 0
b 0
f 0
1
<?php
2
// This file is part of Moodle - http://moodle.org/
3
//
4
// Moodle is free software: you can redistribute it and/or modify
5
// it under the terms of the GNU General Public License as published by
6
// the Free Software Foundation, either version 3 of the License, or
7
// (at your option) any later version.
8
//
9
// Moodle is distributed in the hope that it will be useful,
10
// but WITHOUT ANY WARRANTY; without even the implied warranty of
11
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12
// GNU General Public License for more details.
13
//
14
// You should have received a copy of the GNU General Public License
15
// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
16
17
/**
18
 * Listens for Instant Payment Notification from PayPal
19
 *
20
 * This script waits for Payment notification from PayPal,
21
 * then double checks that data by sending it back to PayPal.
22
 * If PayPal verifies this then sets the activity as completed.
23
 *
24
 * @package    availability_paypal
25
 * @copyright  2010 Eugene Venter
26
 * @copyright  2015 Daniel Neis
27
 * @author     Eugene Venter - based on code by others
28
 * @author     Daniel Neis - based on code by others
29
 * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
30
 */
31
32
// This file do not require login because paypal service will use to confirm transactions.
33
// @codingStandardsIgnoreLine
34
require("../../../config.php");
35
36
require_once($CFG->libdir . '/filelib.php');
37
38
// PayPal does not like when we return error messages here,
39
// the custom handler just logs exceptions and stops.
40
set_exception_handler('availability_paypal_ipn_exception_handler');
41
42
// Keep out casual intruders.
43
if (empty($_POST) or !empty($_GET)) {
44
    print_error("Sorry, you can not use the script that way.");
45
}
46
47
// Read all the data from PayPal and get it ready for later;
48
// we expect only valid UTF-8 encoding, it is the responsibility
49
// of user to set it up properly in PayPal business account,
50
// it is documented in docs wiki.
51
$req = 'cmd=_notify-validate';
52
53
foreach ($_POST as $key => $value) {
54
        $req .= "&$key=".urlencode($value);
55
}
56
57
$data = new stdclass();
58
$data->business             = optional_param('business', '', PARAM_TEXT);
59
$data->receiver_email       = optional_param('receiver_email', '', PARAM_TEXT);
60
$data->receiver_id          = optional_param('receiver_id', '', PARAM_TEXT);
61
$data->item_name            = optional_param('item_name', '', PARAM_TEXT);
62
$data->memo                 = optional_param('memo', '', PARAM_TEXT);
63
$data->tax                  = optional_param('tax', '', PARAM_TEXT);
64
$data->option_name1         = optional_param('option_name1', '', PARAM_TEXT);
65
$data->option_selection1_x  = optional_param('option_selection1_x', '', PARAM_TEXT);
66
$data->option_name2         = optional_param('option_name2', '', PARAM_TEXT);
67
$data->option_selection2_x  = optional_param('option_selection2_x', '', PARAM_TEXT);
68
$data->payment_status       = optional_param('payment_status', '', PARAM_TEXT);
69
$data->pending_reason       = optional_param('pending_reason', '', PARAM_TEXT);
70
$data->reason_code          = optional_param('reason_code', '', PARAM_TEXT);
71
$data->txn_id               = optional_param('txn_id', '', PARAM_TEXT);
72
$data->parent_txn_id        = optional_param('parent_txn_id', '', PARAM_TEXT);
73
$data->payment_type         = optional_param('payment_type', '', PARAM_TEXT);
74
$data->payment_gross        = optional_param('mc_gross', '', PARAM_TEXT);
75
$data->payment_currency     = optional_param('mc_currency', '', PARAM_TEXT);
76
$custom = optional_param('custom', '', PARAM_TEXT);
77
$custom = explode('-', $custom);
78
$data->userid           = (int)$custom[0];
79
$data->contextid       = (int)$custom[1];
80
$data->timeupdated      = time();
81
82 View Code Duplication
if (! $user = $DB->get_record("user", array("id" => $data->userid))) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
83
    availability_paypal_message_error_to_admin("Not a valid user id", $data);
84
    die;
85
}
86
87
if (! $context = context::instance_by_id($data->contextid, IGNORE_MISSING)) {
88
    availability_paypal_message_error_to_admin("Not a valid context id", $data);
89
    die;
90
}
91
92
$instanceid = $context->instanceid;
93 View Code Duplication
if ($context instanceof context_module) {
0 ignored issues
show
Bug introduced by
The class context_module does not exist. Did you forget a USE statement, or did you not list all dependencies?

This error could be the result of:

1. Missing dependencies

PHP Analyzer uses your composer.json file (if available) to determine the dependencies of your project and to determine all the available classes and functions. It expects the composer.json to be in the root folder of your repository.

Are you sure this class is defined by one of your dependencies, or did you maybe not list a dependency in either the require or require-dev section?

2. Missing use statement

PHP does not complain about undefined classes in ìnstanceof checks. For example, the following PHP code will work perfectly fine:

if ($x instanceof DoesNotExist) {
    // Do something.
}

If you have not tested against this specific condition, such errors might go unnoticed.

Loading history...
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
94
    $availability = $DB->get_field('course_modules', 'availability', array('id' => $instanceid), MUST_EXIST);
95
    $availability = json_decode($availability);
96
    foreach ($availability->c as $condition) {
97
        if ($condition->type == 'paypal') {
98
            // TODO: handle more than one paypal for this context.
99
            $paypal = $condition;
100
            break;
101
        } else {
102
            availability_paypal_message_error_to_admin("Not a valid context id", $data);
103
        }
104
    }
105
} else {
106
    // TODO: handle sections.
107
    print_error('support to sections not yet implemented.');
108
}
109
110
// Open a connection back to PayPal to validate the data.
111
$paypaladdr = empty($CFG->usepaypalsandbox) ? 'www.paypal.com' : 'www.sandbox.paypal.com';
112
$c = new curl();
113
$options = array(
114
    'returntransfer' => true,
115
    'httpheader' => array('application/x-www-form-urlencoded', "Host: $paypaladdr"),
116
    'timeout' => 30,
117
    'CURLOPT_HTTP_VERSION' => CURL_HTTP_VERSION_1_1,
118
);
119
$location = "https://{$paypaladdr}/cgi-bin/webscr";
120
$result = $c->post($location, $req, $options);
121
122
if (!$result) {  // Could not connect to PayPal - FAIL.
123
    echo "<p>Error: could not access paypal.com</p>";
124
    availability_paypal_message_error_to_admin("Could not access paypal.com to verify payment", $data);
125
    die;
126
}
127
128
// Connection is OK, so now we post the data to validate it.
129
130
// Now read the response and check if everything is OK.
131
132
if (strlen($result) > 0) {
133
    if (strcmp($result, "VERIFIED") == 0) {          // VALID PAYMENT!
134
135
        $DB->insert_record("availability_paypal_tnx", $data);
136
137
        // Check the payment_status and payment_reason.
138
139
        // If status is not completed, just tell admin, transaction will be saved later.
140
        if ($data->payment_status != "Completed" and $data->payment_status != "Pending") {
141
            availability_paypal_message_error_to_admin("Status not completed or pending. User payment status updated", $data);
142
        }
143
144
        // If currency is incorrectly set then someone maybe trying to cheat the system.
145
        if ($data->payment_currency != $paypal->currency) {
146
            $str = "Currency does not match course settings, received: " . $data->payment_currency;
147
            availability_paypal_message_error_to_admin($str, $data);
148
            die;
149
        }
150
151
        // If status is pending and reason is other than echeck,
152
        // then we are on hold until further notice.
153
        // Email user to let them know. Email admin.
154
        if ($data->payment_status == "Pending" and $data->pending_reason != "echeck") {
155
156
            $eventdata = new \core\message\message();
157
            $eventdata->component         = 'availability_paypal';
158
            $eventdata->name              = 'payment_pending';
159
            $eventdata->userfrom          = get_admin();
160
            $eventdata->userto            = $user;
161
            $eventdata->subject           = get_string("paypalpaymentpendingsubject", 'availability_paypal');
162
            $eventdata->fullmessage       = get_string('paypalpaymentpendingmessage', 'availability_paypal');
163
            $eventdata->fullmessageformat = FORMAT_PLAIN;
164
            $eventdata->fullmessagehtml   = '';
165
            $eventdata->smallmessage      = '';
166
            message_send($eventdata);
167
        }
168
169
        // If our status is not completed or not pending on an echeck clearance then ignore and die.
170
        // This check is redundant at present but may be useful if paypal extend the return codes in the future.
171
        if (! ( $data->payment_status == "Completed" or
172
               ($data->payment_status == "Pending" and $data->pending_reason == "echeck") ) ) {
173
            die;
174
        }
175
176
        // At this point we only proceed with a status of completed or pending with a reason of echeck.
177
178
        // Make sure this transaction doesn't exist already.
179
        if ($existing = $DB->get_record("availability_paypal_tnx", array("txn_id" => $data->txn_id))) {
180
            availability_paypal_message_error_to_admin("Transaction $data->txn_id is being repeated!", $data);
181
            die;
182
        }
183
184
        // Check that the email is the one we want it to be.
185
        if (core_text::strtolower($data->business) !== core_text::strtolower($paypal->businessemail)) {
186
            availability_paypal_message_error_to_admin("Business email is {$data->business} (not ".
187
                                            $paypal->businessemail.")", $data);
188
            die;
189
        }
190
191
        // Check that user exists.
192 View Code Duplication
        if (!$user = $DB->get_record('user', array('id' => $data->userid))) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
193
            availability_paypal_message_error_to_admin("User {$data->userid} doesn't exist", $data);
194
            die;
195
        }
196
197
        // Check that course exists.
198
        if (!$course = $DB->get_record('course', array('id' => $data->courseid))) {
199
            availability_paypal_message_error_to_admin("Course {$data->courseid} doesn't exist", $data);
200
            die;
201
        }
202
203
        $coursecontext = context_course::instance($course->id, IGNORE_MISSING);
204
205
        // Check that amount paid is the correct amount.
206
        if ( (float) $paypal->cost < 0 ) {
207
            $cost = (float) 0;
208
        } else {
209
            $cost = (float) $paypal->cost;
210
        }
211
212
        // Use the same rounding of floats as on the plugin form.
213
        $cost = format_float($cost, 2, false);
214
215
        if ($data->payment_gross < $cost) {
216
            availability_paypal_message_error_to_admin("Amount paid is not enough ({$data->payment_gross} < {$cost}))", $data);
217
            die;
218
        }
219
220
        // All clear!
221
222
        // Pass $view=true to filter hidden caps if the user cannot see them.
223
        if ($users = get_users_by_capability($context, 'moodle/course:update', 'u.*', 'u.id ASC',
224
                                             '', '', '', '', false, true)) {
225
            $users = sort_by_roleassignment_authority($users, $context);
226
            $teacher = array_shift($users);
227
        } else {
228
            $teacher = false;
229
        }
230
231
    } else if (strcmp ($result, "INVALID") == 0) { // ERROR.
232
        $DB->insert_record("availability_paypal_tnx", $data, false);
233
        availability_paypal_message_error_to_admin("Received an invalid payment notification!! (Fake payment?)", $data);
234
    }
235
}
236
237
/**
238
 * Sends message to admin about error
239
 *
240
 * @param string $subject
241
 * @param stdClass $data
242
 */
243
function availability_paypal_message_error_to_admin($subject, $data) {
244
    $admin = get_admin();
245
    $site = get_site();
246
247
    $message = "$site->fullname:  Transaction failed:{$subject}";
248
249
    foreach ($data as $key => $value) {
0 ignored issues
show
Bug introduced by
The expression $data of type object<stdClass> is not traversable.
Loading history...
250
        $message .= "{$key} => {$value};";
251
    }
252
253
    $eventdata = new stdClass();
254
    $eventdata->component         = 'availability_paypal';
255
    $eventdata->name              = 'payment_error';
256
    $eventdata->userfrom          = $admin;
257
    $eventdata->userto            = $admin;
258
    $eventdata->subject           = "PayPal ERROR: ".$subject;
259
    $eventdata->fullmessage       = $message;
260
    $eventdata->fullmessageformat = FORMAT_PLAIN;
261
    $eventdata->fullmessagehtml   = '';
262
    $eventdata->smallmessage      = '';
263
    message_send($eventdata);
264
}
265
266
/**
267
 * Silent exception handler.
268
 *
269
 * @param Exception $ex
270
 * @return void - does not return. Terminates execution!
271
 */
272
function availability_paypal_ipn_exception_handler($ex) {
273
    $info = get_exception_info($ex);
274
275
    $logerrmsg = "availability_paypal IPN exception handler: ".$info->message;
276
    if (debugging('', DEBUG_NORMAL)) {
277
        $logerrmsg .= ' Debug: '.$info->debuginfo."\n".format_backtrace($info->backtrace, true);
278
    }
279
    mtrace($logerrmsg);
280
    exit(0);
281
}
282