-num-new"> 191
+                , new_vehicle.id, centroid[0], centroid[1])
192
+
193
+        # Count any uncounted vehicles that are past the divider
194
+        for vehicle in self.vehicles:
195
+            if not vehicle.counted and (((vehicle.last_position[1] > self.divider) and (vehicle.vehicle_dir == 1)) or
196
+                                          ((vehicle.last_position[1] < self.divider) and (vehicle.vehicle_dir == -1))) and (vehicle.frames_seen > 6):
197
+
198
+                vehicle.counted = True
199
+                # update appropriate counter
200
+                if ((vehicle.last_position[1] > self.divider) and (vehicle.vehicle_dir == 1) and (vehicle.last_position[0] >= (int(frame_w/2)-10))):
201
+                    self.vehicle_RHS += 1
202
+                    self.vehicle_count += 1
203
+                elif ((vehicle.last_position[1] < self.divider) and (vehicle.vehicle_dir == -1) and (vehicle.last_position[0] <= (int(frame_w/2)+10))):
204
+                    self.vehicle_LHS += 1
205
+                    self.vehicle_count += 1
206
+
207
+                self.log.debug("Counted vehicle #%d (total count=%d)."
208
+                    , vehicle.id, self.vehicle_count)
209
+
210
+        # Optionally draw the vehicles on an image
211
+        if output_image is not None:
212
+            for vehicle in self.vehicles:
213
+                vehicle.draw(output_image)
214
+
215
+            # LHS
216
+            cv2.putText(output_image, ("LH Lane: %02d" % self.vehicle_LHS), (12, 56)
217
+                , cv2.FONT_HERSHEY_PLAIN, 1.2, (127,255, 255), 2)
218
+            # RHS
219
+            cv2.putText(output_image, ("RH Lane: %02d" % self.vehicle_RHS), (216, 56)
220
+                , cv2.FONT_HERSHEY_PLAIN, 1.2, (127, 255, 255), 2)
221
+
222
+        # Remove vehicles that have not been seen long enough
223
+        removed = [ v.id for v in self.vehicles
224
+            if v.frames_since_seen >= self.max_unseen_frames ]
225
+        self.vehicles[:] = [ v for v in self.vehicles
226
+            if not v.frames_since_seen >= self.max_unseen_frames ]
227
+        for id in removed:
228
+            self.log.debug("Removed vehicle #%d.", id)
229
+
230
+        self.log.debug("Count updated, tracking %d vehicles.", len(self.vehicles))
231
+
232
+# ============================================================================
233
+
234
+def process_video():
235
+    global frame_no
236
+    global frame_w
237
+    camera = re.match(r".*/(\d+)_.*", inputFile)
238
+    camera = camera.group(1)
239
+
240
+# import video file
241
+    cap = cv2.VideoCapture(inputFile)
242
+
243
+# get list of background files
244
+    f = []
245
+    for (_, _, filenames) in walk(loc+"/backgrounds/"):
246
+        f.extend(filenames)
247
+        break
248
+
249
+# if background exists for camera: import, else avg will be built on fly
250
+    if camera+"_bg.jpg" in f:
251
+        bg = loc+"/backgrounds/"+camera+"_bg.jpg"
252
+        default_bg = cv2.imread(bg)
253
+        default_bg = cv2.cvtColor(default_bg, cv2.COLOR_BGR2HSV)
254
+        (_,avgSat,default_bg) = cv2.split(default_bg)
255
+        avg = default_bg.copy().astype("float")
256
+    else:
257
+        avg = None
258
+
259
+# get frame size
260
+    frame_w = int(cap.get(cv2.CAP_PROP_FRAME_WIDTH))
261
+    frame_h = int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT))
262
+
263
+# create a mask (manual for each camera)
264
+    mask = np.zeros((frame_h,frame_w), np.uint8)
265
+    mask[:,:] = 255
266
+    mask[:100, :] = 0
267
+    mask[230:, 160:190] = 0
268
+    mask[170:230,170:190] = 0
269
+    mask[140:170,176:190] = 0
270
+    mask[100:140,176:182] = 0
271
+
272
+# The cutoff for threshold. A lower number means smaller changes between
273
+# the average and current scene are more readily detected.
274
+    THRESHOLD_SENSITIVITY = 40
275
+    t_retval.append(THRESHOLD_SENSITIVITY)
276
+# Blob size limit before we consider it for tracking.
277
+    CONTOUR_WIDTH = 21
278
+    CONTOUR_HEIGHT = 16#21
279
+# The weighting to apply to "this" frame when averaging. A higher number
280
+# here means that the average scene will pick up changes more readily,
281
+# thus making the difference between average and current scenes smaller.
282
+    DEFAULT_AVERAGE_WEIGHT = 0.01
283
+    INITIAL_AVERAGE_WEIGHT = DEFAULT_AVERAGE_WEIGHT / 50
284
+# Blob smoothing function, to join 'gaps' in cars
285
+    SMOOTH = max(2,int(round((CONTOUR_WIDTH**0.5)/2,0)))
286
+# Constants for drawing on the frame.
287
+    LINE_THICKNESS = 1
288
+
289
+    fourcc = cv2.VideoWriter_fourcc(*'mp4v')
290
+    out = loc+'/outputs/'+camera+'_output.mp4'
291
+#print(out)
292
+#exit()
293
+    out = cv2.VideoWriter(out, fourcc, 20, (frame_w, frame_h))
294
+
295
+    outblob = loc+'/outputs/'+camera+'_outblob.mp4'
296
+    diffop = loc+'/outputs/'+camera+'_outdiff.mp4'
297
+    outblob = cv2.VideoWriter(outblob, fourcc, 20, (frame_w, frame_h))
298
+    diffop = cv2.VideoWriter(diffop, fourcc, 20, (frame_w, frame_h))
299
+
300
+# A list of "tracked blobs".
301
+    blobs = []
302
+    car_counter = None  # will be created later
303
+    frame_no = 0
304
+
305
+    total_frames = cap.get(cv2.CAP_PROP_FRAME_COUNT)
306
+    total_cars = 0
307
+
308
+    start_time = time.time()
309
+    ret, frame = cap.read()
310
+
311
+    while ret:
312
+        ret, frame = cap.read()
313
+        frame_no = frame_no + 1
314
+
315
+        if ret and frame_no < total_frames:
316
+
317
+            print("Processing frame ",frame_no)
318
+
319
+            # get returned time
320
+            frame_time = time.time()
321
+
322
+            # convert BGR to HSV
323
+            frame = cv2.cvtColor(frame, cv2.COLOR_BGR2HSV)
324
+
325
+            # only use the Value channel of the frame
326
+            (_,_,grayFrame) = cv2.split(frame)
327
+            grayFrame = cv2.bilateralFilter(grayFrame, 11, 21, 21)
328
+
329
+            if avg is None:
330
+                # Set up the average if this is the first time through.
331
+                avg = grayFrame.copy().astype("float")
332
+                continue
333
+
334
+            # Build the average scene image by accumulating this frame
335
+            # with the existing average.
336
+            if frame_no < 10:
337
+                def_wt = INITIAL_AVERAGE_WEIGHT
338
+            else:
339
+                def_wt = DEFAULT_AVERAGE_WEIGHT
340
+
341
+            cv2.accumulateWeighted(grayFrame, avg, def_wt)
342
+
343
+            # export averaged background for use in next video feed run
344
+            #if frame_no > int(total_frames * 0.975):
345
+            if frame_no > int(200):
346
+                grayOp = cv2.cvtColor(cv2.convertScaleAbs(avg), cv2.COLOR_GRAY2BGR)
347
+                backOut = loc+"/backgrounds/"+camera+"_bg.jpg"
348
+                cv2.imwrite(backOut, grayOp)
349
+
350
+            # Compute the grayscale difference between the current grayscale frame and
351
+            # the average of the scene.
352
+            differenceFrame = cv2.absdiff(grayFrame, cv2.convertScaleAbs(avg))
353
+            # blur the difference image
354
+            differenceFrame = cv2.GaussianBlur(differenceFrame, (5, 5), 0)
355
+#        cv2.imshow("difference", differenceFrame)
356
+            diffout = cv2.cvtColor(differenceFrame, cv2.COLOR_GRAY2BGR)
357
+            diffop.write(diffout)
358
+
359
+            # get estimated otsu threshold level
360
+            retval, _ = cv2.threshold(differenceFrame, 0, 255,
361
+                                      cv2.THRESH_BINARY+cv2.THRESH_OTSU)
362
+            # add to list of threshold levels
363
+            t_retval.append(retval)
364
+
365
+            # apply threshold based on average threshold value
366
+            if frame_no < 10:
367
+                ret2, thresholdImage = cv2.threshold(differenceFrame,
368
+                                                     int(np.mean(t_retval)*0.9),
369
+                                                     255, cv2.THRESH_BINARY)
370
+            else:
371
+                ret2, thresholdImage = cv2.threshold(differenceFrame,
372
+                                                 int(np.mean(t_retval[-10:-1])*0.9),
373
+                                                 255, cv2.THRESH_BINARY)
374
+
375
+            # We'll need to fill in the gaps to make a complete vehicle as windows
376
+            # and other features can split them!
377
+            kernel = cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (SMOOTH, SMOOTH))
378
+            # Fill any small holes
379
+            thresholdImage = cv2.morphologyEx(thresholdImage, cv2.MORPH_CLOSE, kernel)
380
+
381
+            # Remove noise
382
+            thresholdImage = cv2.morphologyEx(thresholdImage, cv2.MORPH_OPEN, kernel)
383
+
384
+            # Dilate to merge adjacent blobs
385
+            thresholdImage = cv2.dilate(thresholdImage, kernel, iterations = 2)
386
+
387
+            # apply mask
388
+            thresholdImage = cv2.bitwise_and(thresholdImage, thresholdImage, mask = mask)
389
+#        cv2.imshow("threshold", thresholdImage)
390
+            threshout = cv2.cvtColor(thresholdImage, cv2.COLOR_GRAY2BGR)
391
+            outblob.write(threshout)
392
+
393
+            # Find contours aka blobs in the threshold image.
394
+            contours, hierarchy = cv2.findContours(thresholdImage,
395
+                                                      cv2.RETR_EXTERNAL,
396
+                                                      cv2.CHAIN_APPROX_SIMPLE)
397
+
398
+            print("Found ",len(contours)," vehicle contours.")
399
+            # process contours if they exist!
400
+            if contours:
401
+                for (i, contour) in enumerate(contours):
402
+                    # Find the bounding rectangle and center for each blob
403
+                    (x, y, w, h) = cv2.boundingRect(contour)
404
+                    contour_valid = (w > CONTOUR_WIDTH) and (h > CONTOUR_HEIGHT)
405
+
406
+                    print("Contour #",i,": pos=(x=",x,", y=",y,") size=(w=",w,
407
+                          ", h=",h,") valid=",contour_valid)
408
+
409
+                    if not contour_valid:
410
+                        continue
411
+
412
+                    center = (int(x + w/2), int(y + h/2))
413
+                    blobs.append(((x, y, w, h), center))
414
+
415
+            for (i, match) in enumerate(blobs):
416
+                contour, centroid = match
417
+                x, y, w, h = contour
418
+
419
+                # store the contour data
420
+                c = dict(
421
+                            frame_no = frame_no,
422
+                            centre_x = x,
423
+                            centre_y = y,
424
+                            width = w,
425
+                            height = h
426
+                            )
427
+                tracked_conts.append(c)
428
+
429
+                cv2.rectangle(frame, (x, y), (x + w - 1, y + h - 1), (0, 0, 255), LINE_THICKNESS)
430
+                cv2.circle(frame, centroid, 2, (0, 0, 255), -1)
431
+
432
+            if car_counter is None:
433
+                print("Creating vehicle counter...")
434
+                car_counter = VehicleCounter(frame.shape[:2], 2*frame.shape[0] / 3)
435
+
436
+            # get latest count
437
+            car_counter.update_count(blobs, frame)
438
+            current_count = car_counter.vehicle_RHS + car_counter.vehicle_LHS
439
+
440
+            # print elapsed time to console
441
+            elapsed_time = time.time()-start_time
442
+            print("-- %s seconds --" % round(elapsed_time,2))
443
+
444
+            # output video
445
+            frame = cv2.cvtColor(frame, cv2.COLOR_HSV2BGR)
446
+
447
+            # draw dividing line
448
+            # flash green when new car counted
449
+            if current_count > total_cars:
450
+                cv2.line(frame, (0, int(2*frame_h/3)),(frame_w, int(2*frame_h/3)),
451
+                     (0,255,0), 2*LINE_THICKNESS)
452
+            else:
453
+                cv2.line(frame, (0, int(2*frame_h/3)),(frame_w, int(2*frame_h/3)),
454
+                 (0,0,255), LINE_THICKNESS)
455
+
456
+             # update with latest count
457
+            total_cars = current_count
458
+
459
+            # draw upper limit
460
+            cv2.line(frame, (0, 100),(frame_w, 100), (0,0,0), LINE_THICKNESS)
461
+
462
+            ret, buffer = cv2.imencode('.jpg', frame)
463
+            frame2 = buffer.tobytes()
464
+            yield (b'--frame\r\n'
465
+                b'Content-Type: image/jpeg\r\n\r\n' + frame2 + b'\r\n')  # concat frame one by one and show result
466
+
467
+            #cv2.imshow("preview", frame)
468
+            #cv2.imwrite("../flask-hls-demo/static/frame.jpg", frame)
469
+            out.write(frame)
470
+
471
+            if cv2.waitKey(27) and 0xFF == ord('q'):
472
+                break
473
+        else:
474
+            break
475
+
476
+    #cv2.line()
477
+    #cv2.destroyAllWindows()
478
+    #cap.release()
479
+    #out.release()
480
+
481
+from flask import Flask, render_template, Response
482
+import cv2
483
+
484
+app = Flask(__name__)
485
+
486
+
487
+def find_camera(id):
488
+    '''
489
+    cameras = ['rtsp://username:password@ip_address:554/user=username_password='password'_channel=channel_number_stream=0.sdp',
490
+    'rtsp://username:password@ip_address:554/user=username_password='password'_channel=channel_number_stream=0.sdp']
491
+    '''
492
+    cameras = ['rtsp://admin:@Unv123456@192.168.10.252:554/unicast/c1/s1/live']
493
+    return cameras[int(id)]
494
+#  for cctv camera use rtsp://username:password@ip_address:554/user=username_password='password'_channel=channel_number_stream=0.sdp' instead of camera
495
+#  for webcam use zero(0)
496
+
497
+
498
+def gen_frames(camera_id):
499
+
500
+    cam = find_camera(camera_id)
501
+    cap=  cv2.VideoCapture(cam)
502
+
503
+    while True:
504
+        # for cap in caps:
505
+        # # Capture frame-by-frame
506
+        success, frame = cap.read()  # read the camera frame
507
+        if not success:
508
+            break
509
+        else:
510
+            ret, buffer = cv2.imencode('.jpg', frame)
511
+            frame = buffer.tobytes()
512
+            yield (b'--frame\r\n'
513
+                b'Content-Type: image/jpeg\r\n\r\n' + frame + b'\r\n')  # concat frame one by one and show result
514
+@app.route('/video_feed/<string:id>/', methods=["GET"])
515
+def video_feed(id):
516
+
517
+    """Video streaming route. Put this in the src attribute of an img tag."""
518
+    '''
519
+    return Response(gen_frames(id),
520
+                    mimetype='multipart/x-mixed-replace; boundary=frame')
521
+    '''
522
+    return Response(process_video(),
523
+                    mimetype='multipart/x-mixed-replace; boundary=frame')
524
+
525
+@app.route('/', methods=["GET"])
526
+def index():
527
+    return render_template('index.html')
528
+
529
+
530
+if __name__ == '__main__':
531
+    app.run(debug=True, port=9099)

BIN
cap/frame0.jpg


BIN
cap/frame1.jpg


BIN
cap/frame10.jpg


BIN
cap/frame100.jpg


BIN
cap/frame101.jpg


BIN
cap/frame102.jpg


BIN
cap/frame103.jpg


BIN
cap/frame104.jpg


BIN
cap/frame105.jpg


BIN
cap/frame106.jpg


BIN
cap/frame107.jpg


BIN
cap/frame108.jpg


BIN
cap/frame109.jpg


BIN
cap/frame11.jpg


BIN
cap/frame110.jpg


BIN
cap/frame111.jpg


BIN
cap/frame112.jpg


BIN
cap/frame113.jpg


BIN
cap/frame114.jpg


BIN
cap/frame115.jpg


BIN
cap/frame116.jpg


BIN
cap/frame117.jpg


BIN
cap/frame118.jpg


BIN
cap/frame119.jpg


BIN
cap/frame12.jpg


BIN
cap/frame120.jpg


BIN
cap/frame121.jpg


BIN
cap/frame122.jpg


BIN
cap/frame123.jpg


BIN
cap/frame124.jpg


BIN
cap/frame125.jpg


BIN
cap/frame126.jpg


BIN
cap/frame127.jpg


BIN
cap/frame128.jpg


BIN
cap/frame129.jpg


BIN
cap/frame13.jpg


BIN
cap/frame130.jpg


BIN
cap/frame131.jpg


BIN
cap/frame132.jpg


BIN
cap/frame133.jpg


BIN
cap/frame134.jpg


BIN
cap/frame135.jpg


BIN
cap/frame136.jpg


BIN
cap/frame137.jpg


BIN
cap/frame138.jpg


BIN
cap/frame139.jpg


BIN
cap/frame14.jpg


BIN
cap/frame140.jpg


BIN
cap/frame141.jpg


BIN
cap/frame142.jpg


BIN
cap/frame143.jpg


BIN
cap/frame144.jpg


BIN
cap/frame145.jpg


BIN
cap/frame146.jpg


BIN
cap/frame147.jpg


BIN
cap/frame148.jpg


BIN
cap/frame149.jpg


BIN
cap/frame15.jpg


BIN
cap/frame150.jpg


BIN
cap/frame151.jpg


BIN
cap/frame152.jpg


BIN
cap/frame153.jpg


BIN
cap/frame154.jpg


BIN
cap/frame155.jpg


BIN
cap/frame156.jpg


BIN
cap/frame157.jpg


BIN
cap/frame158.jpg


BIN
cap/frame159.jpg


BIN
cap/frame16.jpg


BIN
cap/frame160.jpg


BIN
cap/frame161.jpg


BIN
cap/frame162.jpg


BIN
cap/frame163.jpg


BIN
cap/frame164.jpg


BIN
cap/frame165.jpg


BIN
cap/frame166.jpg


BIN
cap/frame167.jpg


BIN
cap/frame168.jpg


BIN
cap/frame169.jpg


BIN
cap/frame17.jpg


BIN
cap/frame170.jpg


BIN
cap/frame171.jpg


BIN
cap/frame172.jpg


BIN
cap/frame173.jpg


BIN
cap/frame174.jpg


BIN
cap/frame175.jpg


BIN
cap/frame176.jpg


BIN
cap/frame177.jpg


BIN
cap/frame178.jpg


BIN
cap/frame179.jpg


BIN
cap/frame18.jpg


BIN
cap/frame180.jpg


BIN
cap/frame181.jpg


+ 0 - 0
cap/frame182.jpg


Bu fark içinde çok fazla dosya değişikliği olduğu için bazı dosyalar gösterilmiyor

tum/whitesports - Gogs: Simplico Git Service

Нема описа

class.json-api.php 24KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872
  1. <?php
  2. defined( 'WPCOM_JSON_API__DEBUG' ) or define( 'WPCOM_JSON_API__DEBUG', false );
  3. require_once dirname( __FILE__ ) . '/sal/class.json-api-platform.php';
  4. class WPCOM_JSON_API {
  5. static $self = null;
  6. public $endpoints = array();
  7. public $token_details = array();
  8. public $method = '';
  9. public $url = '';
  10. public $path = '';
  11. public $version = null;
  12. public $query = array();
  13. public $post_body = null;
  14. public $files = null;
  15. public $content_type = null;
  16. public $accept = '';
  17. public $_server_https;
  18. public $exit = true;
  19. public $public_api_scheme = 'https';
  20. public $output_status_code = 200;
  21. public $trapped_error = null;
  22. public $did_output = false;
  23. public $extra_headers = array();
  24. public $amp_source_origin = null;
  25. /**
  26. * @return WPCOM_JSON_API instance
  27. */
  28. static function init( $method = null, $url = null, $post_body = null ) {
  29. if ( ! self::$self ) {
  30. $class = function_exists( 'get_called_class' ) ? get_called_class() : __CLASS__; // phpcs:ignore PHPCompatibility.PHP.NewFunctions.get_called_classFound
  31. self::$self = new $class( $method, $url, $post_body );
  32. }
  33. return self::$self;
  34. }
  35. function add( WPCOM_JSON_API_Endpoint $endpoint ) {
  36. $path_versions = serialize(
  37. array(
  38. $endpoint->path,
  39. $endpoint->min_version,
  40. $endpoint->max_version,
  41. )
  42. );
  43. if ( ! isset( $this->endpoints[ $path_versions ] ) ) {
  44. $this->endpoints[ $path_versions ] = array();
  45. }
  46. $this->endpoints[ $path_versions ][ $endpoint->method ] = $endpoint;
  47. }
  48. static function is_truthy( $value ) {
  49. switch ( strtolower( (string) $value ) ) {
  50. case '1':
  51. case 't':
  52. case 'true':
  53. return true;
  54. }
  55. return false;
  56. }
  57. static function is_falsy( $value ) {
  58. switch ( strtolower( (string) $value ) ) {
  59. case '0':
  60. case 'f':
  61. case 'false':
  62. return true;
  63. }
  64. return false;
  65. }
  66. function __construct( ...$args ) {
  67. call_user_func_array( array( $this, 'setup_inputs' ), $args );
  68. }
  69. function setup_inputs( $method = null, $url = null, $post_body = null ) {
  70. if ( is_null( $method ) ) {
  71. $this->method = strtoupper( $_SERVER['REQUEST_METHOD'] );
  72. } else {
  73. $this->method = strtoupper( $method );
  74. }
  75. if ( is_null( $url ) ) {
  76. $this->url = set_url_scheme( 'http://' . $_SERVER['HTTP_HOST'] . $_SERVER['REQUEST_URI'] );
  77. } else {
  78. $this->url = $url;
  79. }
  80. $parsed = wp_parse_url( $this->url );
  81. if ( ! empty( $parsed['path'] ) ) {
  82. $this->path = $parsed['path'];
  83. }
  84. if ( ! empty( $parsed['query'] ) ) {
  85. wp_parse_str( $parsed['query'], $this->query );
  86. }
  87. if ( isset( $_SERVER['HTTP_ACCEPT'] ) && $_SERVER['HTTP_ACCEPT'] ) {
  88. $this->accept = $_SERVER['HTTP_ACCEPT'];
  89. }
  90. if ( 'POST' === $this->method ) {
  91. if ( is_null( $post_body ) ) {
  92. $this->post_body = file_get_contents( 'php://input' );
  93. if ( isset( $_SERVER['HTTP_CONTENT_TYPE'] ) && $_SERVER['HTTP_CONTENT_TYPE'] ) {
  94. $this->content_type = $_SERVER['HTTP_CONTENT_TYPE'];
  95. } elseif ( isset( $_SERVER['CONTENT_TYPE'] ) && $_SERVER['CONTENT_TYPE'] ) {
  96. $this->content_type = $_SERVER['CONTENT_TYPE'];
  97. } elseif ( '{' === $this->post_body[0] ) {
  98. $this->content_type = 'application/json';
  99. } else {
  100. $this->content_type = 'application/x-www-form-urlencoded';
  101. }
  102. if ( 0 === strpos( strtolower( $this->content_type ), 'multipart/' ) ) {
  103. $this->post_body = http_build_query( stripslashes_deep( $_POST ) );
  104. $this->files = $_FILES;
  105. $this->content_type = 'multipart/form-data';
  106. }
  107. } else {
  108. $this->post_body = $post_body;
  109. $this->content_type = '{' === isset( $this->post_body[0] ) && $this->post_body[0] ? 'application/json' : 'application/x-www-form-urlencoded';
  110. }
  111. } else {
  112. $this->post_body = null;
  113. $this->content_type = null;
  114. }
  115. $this->_server_https = array_key_exists( 'HTTPS', $_SERVER ) ? $_SERVER['HTTPS'] : '--UNset--';
  116. }
  117. function initialize() {
  118. $this->token_details['blog_id'] = Jetpack_Options::get_option( 'id' );
  119. }
  120. /**
  121. * Checks if the current request is authorized with a blog token.
  122. * This method is overridden by a child class in WPCOM.
  123. *
  124. * @since 9.1.0
  125. *
  126. * @param boolean|int $site_id The site id.
  127. * @return boolean
  128. */
  129. public function is_jetpack_authorized_for_site( $site_id = false ) {
  130. if ( ! $this->token_details ) {
  131. return false;
  132. }
  133. $token_details = (object) $this->token_details;
  134. $site_in_token = (int) $token_details->blog_id;
  135. if ( $site_in_token < 1 ) {
  136. return false;
  137. }
  138. if ( $site_id && $site_in_token !== (int) $site_id ) {
  139. return false;
  140. }
  141. if ( (int) get_current_user_id() !== 0 ) {
  142. // If Jetpack blog token is used, no logged-in user should exist.
  143. return false;
  144. }
  145. return true;
  146. }
  147. function serve( $exit = true ) {
  148. ini_set( 'display_errors', false );
  149. $this->exit = (bool) $exit;
  150. // This was causing problems with Jetpack, but is necessary for wpcom
  151. // @see https://github.com/Automattic/jetpack/pull/2603
  152. // @see r124548-wpcom
  153. if ( defined( 'IS_WPCOM' ) && IS_WPCOM ) {
  154. add_filter( 'home_url', array( $this, 'ensure_http_scheme_of_home_url' ), 10, 3 );
  155. }
  156. add_filter( 'user_can_richedit', '__return_true' );
  157. add_filter( 'comment_edit_pre', array( $this, 'comment_edit_pre' ) );
  158. $initialization = $this->initialize();
  159. if ( 'OPTIONS' == $this->method ) {
  160. /**
  161. * Fires before the page output.
  162. * Can be used to specify custom header options.
  163. *
  164. * @module json-api
  165. *
  166. * @since 3.1.0
  167. */
  168. do_action( 'wpcom_json_api_options' );
  169. return $this->output( 200, '', 'text/plain' );
  170. }
  171. if ( is_wp_error( $initialization ) ) {
  172. $this->output_error( $initialization );
  173. return;
  174. }
  175. // Normalize path and extract API version
  176. $this->path = untrailingslashit( $this->path );
  177. preg_match( '#^/rest/v(\d+(\.\d+)*)#', $this->path, $matches );
  178. $this->path = substr( $this->path, strlen( $matches[0] ) );
  179. $this->version = $matches[1];
  180. $allowed_methods = array( 'GET', 'POST' );
  181. $four_oh_five = false;
  182. $is_help = preg_match( '#/help/?$#i', $this->path );
  183. $matching_endpoints = array();
  184. if ( $is_help ) {
  185. $origin = get_http_origin();
  186. if ( ! empty( $origin ) && 'GET' == $this->method ) {
  187. header( 'Access-Control-Allow-Origin: ' . esc_url_raw( $origin ) );
  188. }
  189. $this->path = substr( rtrim( $this->path, '/' ), 0, -5 );
  190. // Show help for all matching endpoints regardless of method
  191. $methods = $allowed_methods;
  192. $find_all_matching_endpoints = true;
  193. // How deep to truncate each endpoint's path to see if it matches this help request
  194. $depth = substr_count( $this->path, '/' ) + 1;
  195. if ( false !== stripos( $this->accept, 'javascript' ) || false !== stripos( $this->accept, 'json' ) ) {
  196. $help_content_type = 'json';
  197. } else {
  198. $help_content_type = 'html';
  199. }
  200. } else {
  201. if ( in_array( $this->method, $allowed_methods ) ) {
  202. // Only serve requested method
  203. $methods = array( $this->method );
  204. $find_all_matching_endpoints = false;
  205. } else {
  206. // We don't allow this requested method - find matching endpoints and send 405
  207. $methods = $allowed_methods;
  208. $find_all_matching_endpoints = true;
  209. $four_oh_five = true;
  210. }
  211. }
  212. // Find which endpoint to serve
  213. $found = false;
  214. foreach ( $this->endpoints as $endpoint_path_versions => $endpoints_by_method ) {
  215. $endpoint_path_versions = unserialize( $endpoint_path_versions );
  216. $endpoint_path = $endpoint_path_versions[0];
  217. $endpoint_min_version = $endpoint_path_versions[1];
  218. $endpoint_max_version = $endpoint_path_versions[2];
  219. // Make sure max_version is not less than min_version
  220. if ( version_compare( $endpoint_max_version, $endpoint_min_version, '<' ) ) {
  221. $endpoint_max_version = $endpoint_min_version;
  222. }
  223. foreach ( $methods as $method ) {
  224. if ( ! isset( $endpoints_by_method[ $method ] ) ) {
  225. continue;
  226. }
  227. // Normalize
  228. $endpoint_path = untrailingslashit( $endpoint_path );
  229. if ( $is_help ) {
  230. // Truncate path at help depth
  231. $endpoint_path = join( '/', array_slice( explode( '/', $endpoint_path ), 0, $depth ) );
  232. }
  233. // Generate regular expression from sprintf()
  234. $endpoint_path_regex = str_replace( array( '%s', '%d' ), array( '([^/?&]+)', '(\d+)' ), $endpoint_path );
  235. if ( ! preg_match( "#^$endpoint_path_regex\$#", $this->path, $path_pieces ) ) {
  236. // This endpoint does not match the requested path.
  237. continue;
  238. }
  239. if ( version_compare( $this->version, $endpoint_min_version, '<' ) || version_compare( $this->version, $endpoint_max_version, '>' ) ) {
  240. // This endpoint does not match the requested version.
  241. continue;
  242. }
  243. $found = true;
  244. if ( $find_all_matching_endpoints ) {
  245. $matching_endpoints[] = array( $endpoints_by_method[ $method ], $path_pieces );
  246. } else {
  247. // The method parameters are now in $path_pieces
  248. $endpoint = $endpoints_by_method[ $method ];
  249. break 2;
  250. }
  251. }
  252. }
  253. if ( ! $found ) {
  254. return $this->output( 404, '', 'text/plain' );
  255. }
  256. if ( $four_oh_five ) {
  257. $allowed_methods = array();
  258. foreach ( $matching_endpoints as $matching_endpoint ) {
  259. $allowed_methods[] = $matching_endpoint[0]->method;
  260. }
  261. header( 'Allow: ' . strtoupper( join( ',', array_unique( $allowed_methods ) ) ) );
  262. return $this->output(
  263. 405,
  264. array(
  265. 'error' => 'not_allowed',
  266. 'error_message' => 'Method not allowed',
  267. )
  268. );
  269. }
  270. if ( $is_help ) {
  271. /**
  272. * Fires before the API output.
  273. *
  274. * @since 1.9.0
  275. *
  276. * @param string help.
  277. */
  278. do_action( 'wpcom_json_api_output', 'help' );
  279. $proxied = function_exists( 'wpcom_is_proxied_request' ) ? wpcom_is_proxied_request() : false;
  280. if ( 'json' === $help_content_type ) {
  281. $docs = array();
  282. foreach ( $matching_endpoints as $matching_endpoint ) {
  283. if ( $matching_endpoint[0]->is_publicly_documentable() || $proxied || WPCOM_JSON_API__DEBUG ) {
  284. $docs[] = call_user_func( array( $matching_endpoint[0], 'generate_documentation' ) );
  285. }
  286. }
  287. return $this->output( 200, $docs );
  288. } else {
  289. status_header( 200 );
  290. foreach ( $matching_endpoints as $matching_endpoint ) {
  291. if ( $matching_endpoint[0]->is_publicly_documentable() || $proxied || WPCOM_JSON_API__DEBUG ) {
  292. call_user_func( array( $matching_endpoint[0], 'document' ) );
  293. }
  294. }
  295. }
  296. exit;
  297. }
  298. if ( $endpoint->in_testing && ! WPCOM_JSON_API__DEBUG ) {
  299. return $this->output( 404, '', 'text/plain' );
  300. }
  301. /** This action is documented in class.json-api.php */
  302. do_action( 'wpcom_json_api_output', $endpoint->stat );
  303. $response = $this->process_request( $endpoint, $path_pieces );
  304. if ( ! $response && ! is_array( $response ) ) {
  305. return $this->output( 500, '', 'text/plain' );
  306. } elseif ( is_wp_error( $response ) ) {
  307. return $this->output_error( $response );
  308. }
  309. $output_status_code = $this->output_status_code;
  310. $this->set_output_status_code();
  311. return $this->output( $output_status_code, $response, 'application/json', $this->extra_headers );
  312. }
  313. function process_request( WPCOM_JSON_API_Endpoint $endpoint, $path_pieces ) {
  314. $this->endpoint = $endpoint;
  315. return call_user_func_array( array( $endpoint, 'callback' ), $path_pieces );
  316. }
  317. function output_early( $status_code, $response = null, $content_type = 'application/json' ) {
  318. $exit = $this->exit;
  319. $this->exit = false;
  320. if ( is_wp_error( $response ) ) {
  321. $this->output_error( $response );
  322. } else {
  323. $this->output( $status_code, $response, $content_type );
  324. }
  325. $this->exit = $exit;
  326. if ( ! defined( 'XMLRPC_REQUEST' ) || ! XMLRPC_REQUEST ) {
  327. $this->finish_request();
  328. }
  329. }
  330. function set_output_status_code( $code = 200 ) {
  331. $this->output_status_code = $code;
  332. }
  333. function output( $status_code, $response = null, $content_type = 'application/json', $extra = array() ) {
  334. // In case output() was called before the callback returned
  335. if ( $this->did_output ) {
  336. if ( $this->exit ) {
  337. exit;
  338. }
  339. return $content_type;
  340. }
  341. $this->did_output = true;
  342. // 400s and 404s are allowed for all origins
  343. if ( 404 == $status_code || 400 == $status_code ) {
  344. header( 'Access-Control-Allow-Origin: *' );
  345. }
  346. /* Add headers for form submission from <amp-form/> */
  347. if ( $this->amp_source_origin ) {
  348. header( 'Access-Control-Allow-Origin: ' . wp_unslash( $this->amp_source_origin ) );
  349. header( 'Access-Control-Allow-Credentials: true' );
  350. }
  351. if ( is_null( $response ) ) {
  352. $response = new stdClass();
  353. }
  354. if ( 'text/plain' === $content_type ||
  355. 'text/html' === $content_type ) {
  356. status_header( (int) $status_code );
  357. header( 'Content-Type: ' . $content_type );
  358. foreach ( $extra as $key => $value ) {
  359. header( "$key: $value" );
  360. }
  361. echo $response;
  362. if ( $this->exit ) {
  363. exit;
  364. }
  365. return $content_type;
  366. }
  367. $response = $this->filter_fields( $response );
  368. if ( isset( $this->query['http_envelope'] ) && self::is_truthy( $this->query['http_envelope'] ) ) {
  369. $headers = array(
  370. array(
  371. 'name' => 'Content-Type',
  372. 'value' => $content_type,
  373. ),
  374. );
  375. foreach ( $extra as $key => $value ) {
  376. $headers[] = array(
  377. 'name' => $key,
  378. 'value' => $value,
  379. );
  380. }
  381. $response = array(
  382. 'code' => (int) $status_code,
  383. 'headers' => $headers,
  384. 'body' => $response,
  385. );
  386. $status_code = 200;
  387. $content_type = 'application/json';
  388. }
  389. status_header( (int) $status_code );
  390. header( "Content-Type: $content_type" );
  391. if ( isset( $this->query['callback'] ) && is_string( $this->query['callback'] ) ) {
  392. $callback = preg_replace( '/[^a-z0-9_.]/i', '', $this->query['callback'] );
  393. } else {
  394. $callback = false;
  395. }
  396. if ( $callback ) {
  397. // Mitigate Rosetta Flash [1] by setting the Content-Type-Options: nosniff header
  398. // and by prepending the JSONP response with a JS comment.
  399. // [1] https://blog.miki.it/2014/7/8/abusing-jsonp-with-rosetta-flash/index.html
  400. echo "/**/$callback(";
  401. }
  402. echo $this->json_encode( $response );
  403. if ( $callback ) {
  404. echo ');';
  405. }
  406. if ( $this->exit ) {
  407. exit;
  408. }
  409. return $content_type;
  410. }
  411. public static function serializable_error( $error ) {
  412. $status_code = $error->get_error_data();
  413. if ( is_array( $status_code ) ) {
  414. $status_code = $status_code['status_code'];
  415. }
  416. if ( ! $status_code ) {
  417. $status_code = 400;
  418. }
  419. $response = array(
  420. 'error' => $error->get_error_code(),
  421. 'message' => $error->get_error_message(),
  422. );
  423. if ( $additional_data = $error->get_error_data( 'additional_data' ) ) {
  424. $response['data'] = $additional_data;
  425. }
  426. return array(
  427. 'status_code' => $status_code,
  428. 'errors' => $response,
  429. );
  430. }
  431. function output_error( $error ) {
  432. $error_response = $this->serializable_error( $error );
  433. return $this->output( $error_response['status_code'], $error_response['errors'] );
  434. }
  435. function filter_fields( $response ) {
  436. if ( empty( $this->query['fields'] ) || ( is_array( $response ) && ! empty( $response['error'] ) ) || ! empty( $this->endpoint->custom_fields_filtering ) ) {
  437. return $response;
  438. }
  439. $fields = array_map( 'trim', explode( ',', $this->query['fields'] ) );
  440. if ( is_object( $response ) ) {
  441. $response = (array) $response;
  442. }
  443. $has_filtered = false;
  444. if ( is_array( $response ) && empty( $response['ID'] ) ) {
  445. $keys_to_filter = array(
  446. 'categories',
  447. 'comments',
  448. 'connections',
  449. 'domains',
  450. 'groups',
  451. 'likes',
  452. 'media',
  453. 'notes',
  454. 'posts',
  455. 'services',
  456. 'sites',
  457. 'suggestions',
  458. 'tags',
  459. 'themes',
  460. 'topics',
  461. 'users',
  462. );
  463. foreach ( $keys_to_filter as $key_to_filter ) {
  464. if ( ! isset( $response[ $key_to_filter ] ) || $has_filtered ) {
  465. continue;
  466. }
  467. foreach ( $response[ $key_to_filter ] as $key => $values ) {
  468. if ( is_object( $values ) ) {
  469. if ( is_object( $response[ $key_to_filter ] ) ) {
  470. $response[ $key_to_filter ]->$key = (object) array_intersect_key( ( (array) $values ), array_flip( $fields ) );
  471. } elseif ( is_array( $response[ $key_to_filter ] ) ) {
  472. $response[ $key_to_filter ][ $key ] = (object) array_intersect_key( ( (array) $values ), array_flip( $fields ) );
  473. }
  474. } elseif ( is_array( $values ) ) {
  475. $response[ $key_to_filter ][ $key ] = array_intersect_key( $values, array_flip( $fields ) );
  476. }
  477. }
  478. $has_filtered = true;
  479. }
  480. }
  481. if ( ! $has_filtered ) {
  482. if ( is_object( $response ) ) {
  483. $response = (object) array_intersect_key( (array) $response, array_flip( $fields ) );
  484. } elseif ( is_array( $response ) ) {
  485. $response = array_intersect_key( $response, array_flip( $fields ) );
  486. }
  487. }
  488. return $response;
  489. }
  490. function ensure_http_scheme_of_home_url( $url, $path, $original_scheme ) {
  491. if ( $original_scheme ) {
  492. return $url;
  493. }
  494. return preg_replace( '#^https:#', 'http:', $url );
  495. }
  496. function comment_edit_pre( $comment_content ) {
  497. return htmlspecialchars_decode( $comment_content, ENT_QUOTES );
  498. }
  499. function json_encode( $data ) {
  500. return wp_json_encode( $data );
  501. }
  502. function ends_with( $haystack, $needle ) {
  503. return $needle === substr( $haystack, -strlen( $needle ) );
  504. }
  505. // Returns the site's blog_id in the WP.com ecosystem
  506. function get_blog_id_for_output() {
  507. return $this->token_details['blog_id'];
  508. }
  509. // Returns the site's local blog_id
  510. function get_blog_id( $blog_id ) {
  511. return $GLOBALS['blog_id'];
  512. }
  513. function switch_to_blog_and_validate_user( $blog_id = 0, $verify_token_for_blog = true ) {
  514. if ( $this->is_restricted_blog( $blog_id ) ) {
  515. return new WP_Error( 'unauthorized', 'User cannot access this restricted blog', 403 );
  516. }
  517. /**
  518. * If this is a private site we check for 2 things:
  519. * 1. In case of user based authentication, we need to check if the logged-in user has the 'read' capability.
  520. * 2. In case of site based authentication, make sure the endpoint accepts it.
  521. */
  522. if ( -1 === (int) get_option( 'blog_public' ) &&
  523. ! current_user_can( 'read' ) &&
  524. ! $this->endpoint->accepts_site_based_authentication()
  525. ) {
  526. return new WP_Error( 'unauthorized', 'User cannot access this private blog.', 403 );
  527. }
  528. return $blog_id;
  529. }
  530. // Returns true if the specified blog ID is a restricted blog
  531. function is_restricted_blog( $blog_id ) {
  532. /**
  533. * Filters all REST API access and return a 403 unauthorized response for all Restricted blog IDs.
  534. *
  535. * @module json-api
  536. *
  537. * @since 3.4.0
  538. *
  539. * @param array $array Array of Blog IDs.
  540. */
  541. $restricted_blog_ids = apply_filters( 'wpcom_json_api_restricted_blog_ids', array() );
  542. return true === in_array( $blog_id, $restricted_blog_ids );
  543. }
  544. function post_like_count( $blog_id, $post_id ) {
  545. return 0;
  546. }
  547. function is_liked( $blog_id, $post_id ) {
  548. return false;
  549. }
  550. function is_reblogged( $blog_id, $post_id ) {
  551. return false;
  552. }
  553. function is_following( $blog_id ) {
  554. return false;
  555. }
  556. function add_global_ID( $blog_id, $post_id ) {
  557. return '';
  558. }
  559. function get_avatar_url( $email, $avatar_size = null ) {
  560. if ( function_exists( 'wpcom_get_avatar_url' ) ) {
  561. return null === $avatar_size
  562. ? wpcom_get_avatar_url( $email )
  563. : wpcom_get_avatar_url( $email, $avatar_size );
  564. } else {
  565. return null === $avatar_size
  566. ? get_avatar_url( $email )
  567. : get_avatar_url( $email, $avatar_size );
  568. }
  569. }
  570. /**
  571. * Counts the number of comments on a site, excluding certain comment types.
  572. *
  573. * @param $post_id int Post ID.
  574. * @return array Array of counts, matching the output of https://developer.wordpress.org/reference/functions/get_comment_count/.
  575. */
  576. public function wp_count_comments( $post_id ) {
  577. global $wpdb;
  578. if ( 0 !== $post_id ) {
  579. return wp_count_comments( $post_id );
  580. }
  581. $counts = array(
  582. 'total_comments' => 0,
  583. 'all' => 0,
  584. );
  585. /**
  586. * Exclude certain comment types from comment counts in the REST API.
  587. *
  588. * @since 6.9.0
  589. * @module json-api
  590. *
  591. * @param array Array of comment types to exclude (default: 'order_note', 'webhook_delivery', 'review', 'action_log')
  592. */
  593. $exclude = apply_filters(
  594. 'jetpack_api_exclude_comment_types_count',
  595. array( 'order_note', 'webhook_delivery', 'review', 'action_log' )
  596. );
  597. if ( empty( $exclude ) ) {
  598. return wp_count_comments( $post_id );
  599. }
  600. array_walk( $exclude, 'esc_sql' );
  601. $where = sprintf(
  602. "WHERE comment_type NOT IN ( '%s' )",
  603. implode( "','", $exclude )
  604. );
  605. $count = $wpdb->get_results(
  606. "SELECT comment_approved, COUNT(*) AS num_comments
  607. FROM $wpdb->comments
  608. {$where}
  609. GROUP BY comment_approved
  610. "
  611. );
  612. $approved = array(
  613. '0' => 'moderated',
  614. '1' => 'approved',
  615. 'spam' => 'spam',
  616. 'trash' => 'trash',
  617. 'post-trashed' => 'post-trashed',
  618. );
  619. // https://developer.wordpress.org/reference/functions/get_comment_count/#source
  620. foreach ( $count as $row ) {
  621. if ( ! in_array( $row->comment_approved, array( 'post-trashed', 'trash', 'spam' ), true ) ) {
  622. $counts['all'] += $row->num_comments;
  623. $counts['total_comments'] += $row->num_comments;
  624. } elseif ( ! in_array( $row->comment_approved, array( 'post-trashed', 'trash' ), true ) ) {
  625. $counts['total_comments'] += $row->num_comments;
  626. }
  627. if ( isset( $approved[ $row->comment_approved ] ) ) {
  628. $counts[ $approved[ $row->comment_approved ] ] = $row->num_comments;
  629. }
  630. }
  631. foreach ( $approved as $key ) {
  632. if ( empty( $counts[ $key ] ) ) {
  633. $counts[ $key ] = 0;
  634. }
  635. }
  636. $counts = (object) $counts;
  637. return $counts;
  638. }
  639. /**
  640. * traps `wp_die()` calls and outputs a JSON response instead.
  641. * The result is always output, never returned.
  642. *
  643. * @param string|null $error_code Call with string to start the trapping. Call with null to stop.
  644. * @param int $http_status HTTP status code, 400 by default.
  645. */
  646. function trap_wp_die( $error_code = null, $http_status = 400 ) {
  647. // Determine the filter name; based on the conditionals inside the wp_die function.
  648. if ( wp_is_json_request() ) {
  649. $die_handler = 'wp_die_json_handler';
  650. } elseif ( wp_is_jsonp_request() ) {
  651. $die_handler = 'wp_die_jsonp_handler';
  652. } elseif ( wp_is_xml_request() ) {
  653. $die_handler = 'wp_die_xml_handler';
  654. } else {
  655. $die_handler = 'wp_die_handler';
  656. }
  657. if ( is_null( $error_code ) ) {
  658. $this->trapped_error = null;
  659. // Stop trapping
  660. remove_filter( $die_handler, array( $this, 'wp_die_handler_callback' ) );
  661. return;
  662. }
  663. // If API called via PHP, bail: don't do our custom wp_die(). Do the normal wp_die().
  664. if ( defined( 'IS_WPCOM' ) && IS_WPCOM ) {
  665. if ( ! defined( 'REST_API_REQUEST' ) || ! REST_API_REQUEST ) {
  666. return;
  667. }
  668. } else {
  669. if ( ! defined( 'XMLRPC_REQUEST' ) || ! XMLRPC_REQUEST ) {
  670. return;
  671. }
  672. }
  673. $this->trapped_error = array(
  674. 'status' => $http_status,
  675. 'code' => $error_code,
  676. 'message' => '',
  677. );
  678. // Start trapping
  679. add_filter( $die_handler, array( $this, 'wp_die_handler_callback' ) );
  680. }
  681. function wp_die_handler_callback() {
  682. return array( $this, 'wp_die_handler' );
  683. }
  684. function wp_die_handler( $message, $title = '', $args = array() ) {
  685. // Allow wp_die calls to override HTTP status code...
  686. $args = wp_parse_args(
  687. $args,
  688. array(
  689. 'response' => $this->trapped_error['status'],
  690. )
  691. );
  692. // ... unless it's 500
  693. if ( (int) $args['response'] !== 500 ) {
  694. $this->trapped_error['status'] = $args['response'];
  695. }
  696. if ( $title ) {
  697. $message = "$title: $message";
  698. }
  699. $this->trapped_error['message'] = wp_kses( $message, array() );
  700. switch ( $this->trapped_error['code'] ) {
  701. case 'comment_failure':
  702. if ( did_action( 'comment_duplicate_trigger' ) ) {
  703. $this->trapped_error['code'] = 'comment_duplicate';
  704. } elseif ( did_action( 'comment_flood_trigger' ) ) {
  705. $this->trapped_error['code'] = 'comment_flood';
  706. }
  707. break;
  708. }
  709. // We still want to exit so that code execution stops where it should.
  710. // Attach the JSON output to the WordPress shutdown handler
  711. add_action( 'shutdown', array( $this, 'output_trapped_error' ), 0 );
  712. exit;
  713. }
  714. function output_trapped_error() {
  715. $this->exit = false; // We're already exiting once. Don't do it twice.
  716. $this->output(
  717. $this->trapped_error['status'],
  718. (object) array(
  719. 'error' => $this->trapped_error['code'],
  720. 'message' => $this->trapped_error['message'],
  721. )
  722. );
  723. }
  724. function finish_request() {
  725. if ( function_exists( 'fastcgi_finish_request' ) ) {
  726. return fastcgi_finish_request();
  727. }
  728. }
  729. }