[ Index ] |
PHP Cross Reference of WordPress |
[Summary view] [Print] [Text view]
1 /******/ (function() { // webpackBootstrap 2 /******/ "use strict"; 3 /******/ // The require scope 4 /******/ var __webpack_require__ = {}; 5 /******/ 6 /************************************************************************/ 7 /******/ /* webpack/runtime/define property getters */ 8 /******/ !function() { 9 /******/ // define getter functions for harmony exports 10 /******/ __webpack_require__.d = function(exports, definition) { 11 /******/ for(var key in definition) { 12 /******/ if(__webpack_require__.o(definition, key) && !__webpack_require__.o(exports, key)) { 13 /******/ Object.defineProperty(exports, key, { enumerable: true, get: definition[key] }); 14 /******/ } 15 /******/ } 16 /******/ }; 17 /******/ }(); 18 /******/ 19 /******/ /* webpack/runtime/hasOwnProperty shorthand */ 20 /******/ !function() { 21 /******/ __webpack_require__.o = function(obj, prop) { return Object.prototype.hasOwnProperty.call(obj, prop); } 22 /******/ }(); 23 /******/ 24 /************************************************************************/ 25 var __webpack_exports__ = {}; 26 27 // EXPORTS 28 __webpack_require__.d(__webpack_exports__, { 29 "default": function() { return /* binding */ build_module; } 30 }); 31 32 ;// CONCATENATED MODULE: external ["wp","i18n"] 33 var external_wp_i18n_namespaceObject = window["wp"]["i18n"]; 34 ;// CONCATENATED MODULE: ./node_modules/@wordpress/api-fetch/build-module/middlewares/nonce.js 35 /** 36 * @param {string} nonce 37 * @return {import('../types').APIFetchMiddleware & { nonce: string }} A middleware to enhance a request with a nonce. 38 */ 39 function createNonceMiddleware(nonce) { 40 /** 41 * @type {import('../types').APIFetchMiddleware & { nonce: string }} 42 */ 43 const middleware = (options, next) => { 44 const { 45 headers = {} 46 } = options; // If an 'X-WP-Nonce' header (or any case-insensitive variation 47 // thereof) was specified, no need to add a nonce header. 48 49 for (const headerName in headers) { 50 if (headerName.toLowerCase() === 'x-wp-nonce' && headers[headerName] === middleware.nonce) { 51 return next(options); 52 } 53 } 54 55 return next({ ...options, 56 headers: { ...headers, 57 'X-WP-Nonce': middleware.nonce 58 } 59 }); 60 }; 61 62 middleware.nonce = nonce; 63 return middleware; 64 } 65 66 /* harmony default export */ var nonce = (createNonceMiddleware); 67 68 ;// CONCATENATED MODULE: ./node_modules/@wordpress/api-fetch/build-module/middlewares/namespace-endpoint.js 69 /** 70 * @type {import('../types').APIFetchMiddleware} 71 */ 72 const namespaceAndEndpointMiddleware = (options, next) => { 73 let path = options.path; 74 let namespaceTrimmed, endpointTrimmed; 75 76 if (typeof options.namespace === 'string' && typeof options.endpoint === 'string') { 77 namespaceTrimmed = options.namespace.replace(/^\/|\/$/g, ''); 78 endpointTrimmed = options.endpoint.replace(/^\//, ''); 79 80 if (endpointTrimmed) { 81 path = namespaceTrimmed + '/' + endpointTrimmed; 82 } else { 83 path = namespaceTrimmed; 84 } 85 } 86 87 delete options.namespace; 88 delete options.endpoint; 89 return next({ ...options, 90 path 91 }); 92 }; 93 94 /* harmony default export */ var namespace_endpoint = (namespaceAndEndpointMiddleware); 95 96 ;// CONCATENATED MODULE: ./node_modules/@wordpress/api-fetch/build-module/middlewares/root-url.js 97 /** 98 * Internal dependencies 99 */ 100 101 /** 102 * @param {string} rootURL 103 * @return {import('../types').APIFetchMiddleware} Root URL middleware. 104 */ 105 106 const createRootURLMiddleware = rootURL => (options, next) => { 107 return namespace_endpoint(options, optionsWithPath => { 108 let url = optionsWithPath.url; 109 let path = optionsWithPath.path; 110 let apiRoot; 111 112 if (typeof path === 'string') { 113 apiRoot = rootURL; 114 115 if (-1 !== rootURL.indexOf('?')) { 116 path = path.replace('?', '&'); 117 } 118 119 path = path.replace(/^\//, ''); // API root may already include query parameter prefix if site is 120 // configured to use plain permalinks. 121 122 if ('string' === typeof apiRoot && -1 !== apiRoot.indexOf('?')) { 123 path = path.replace('?', '&'); 124 } 125 126 url = apiRoot + path; 127 } 128 129 return next({ ...optionsWithPath, 130 url 131 }); 132 }); 133 }; 134 135 /* harmony default export */ var root_url = (createRootURLMiddleware); 136 137 ;// CONCATENATED MODULE: external ["wp","url"] 138 var external_wp_url_namespaceObject = window["wp"]["url"]; 139 ;// CONCATENATED MODULE: ./node_modules/@wordpress/api-fetch/build-module/middlewares/preloading.js 140 /** 141 * WordPress dependencies 142 */ 143 144 /** 145 * @param {Record<string, any>} preloadedData 146 * @return {import('../types').APIFetchMiddleware} Preloading middleware. 147 */ 148 149 function createPreloadingMiddleware(preloadedData) { 150 const cache = Object.fromEntries(Object.entries(preloadedData).map(_ref => { 151 let [path, data] = _ref; 152 return [(0,external_wp_url_namespaceObject.normalizePath)(path), data]; 153 })); 154 return (options, next) => { 155 const { 156 parse = true 157 } = options; 158 /** @type {string | void} */ 159 160 let rawPath = options.path; 161 162 if (!rawPath && options.url) { 163 const { 164 rest_route: pathFromQuery, 165 ...queryArgs 166 } = (0,external_wp_url_namespaceObject.getQueryArgs)(options.url); 167 168 if (typeof pathFromQuery === 'string') { 169 rawPath = (0,external_wp_url_namespaceObject.addQueryArgs)(pathFromQuery, queryArgs); 170 } 171 } 172 173 if (typeof rawPath !== 'string') { 174 return next(options); 175 } 176 177 const method = options.method || 'GET'; 178 const path = (0,external_wp_url_namespaceObject.normalizePath)(rawPath); 179 180 if ('GET' === method && cache[path]) { 181 const cacheData = cache[path]; // Unsetting the cache key ensures that the data is only used a single time. 182 183 delete cache[path]; 184 return prepareResponse(cacheData, !!parse); 185 } else if ('OPTIONS' === method && cache[method] && cache[method][path]) { 186 const cacheData = cache[method][path]; // Unsetting the cache key ensures that the data is only used a single time. 187 188 delete cache[method][path]; 189 return prepareResponse(cacheData, !!parse); 190 } 191 192 return next(options); 193 }; 194 } 195 /** 196 * This is a helper function that sends a success response. 197 * 198 * @param {Record<string, any>} responseData 199 * @param {boolean} parse 200 * @return {Promise<any>} Promise with the response. 201 */ 202 203 204 function prepareResponse(responseData, parse) { 205 return Promise.resolve(parse ? responseData.body : new window.Response(JSON.stringify(responseData.body), { 206 status: 200, 207 statusText: 'OK', 208 headers: responseData.headers 209 })); 210 } 211 212 /* harmony default export */ var preloading = (createPreloadingMiddleware); 213 214 ;// CONCATENATED MODULE: ./node_modules/@wordpress/api-fetch/build-module/middlewares/fetch-all-middleware.js 215 /** 216 * WordPress dependencies 217 */ 218 219 /** 220 * Internal dependencies 221 */ 222 223 224 /** 225 * Apply query arguments to both URL and Path, whichever is present. 226 * 227 * @param {import('../types').APIFetchOptions} props 228 * @param {Record<string, string | number>} queryArgs 229 * @return {import('../types').APIFetchOptions} The request with the modified query args 230 */ 231 232 const modifyQuery = (_ref, queryArgs) => { 233 let { 234 path, 235 url, 236 ...options 237 } = _ref; 238 return { ...options, 239 url: url && (0,external_wp_url_namespaceObject.addQueryArgs)(url, queryArgs), 240 path: path && (0,external_wp_url_namespaceObject.addQueryArgs)(path, queryArgs) 241 }; 242 }; 243 /** 244 * Duplicates parsing functionality from apiFetch. 245 * 246 * @param {Response} response 247 * @return {Promise<any>} Parsed response json. 248 */ 249 250 251 const parseResponse = response => response.json ? response.json() : Promise.reject(response); 252 /** 253 * @param {string | null} linkHeader 254 * @return {{ next?: string }} The parsed link header. 255 */ 256 257 258 const parseLinkHeader = linkHeader => { 259 if (!linkHeader) { 260 return {}; 261 } 262 263 const match = linkHeader.match(/<([^>]+)>; rel="next"/); 264 return match ? { 265 next: match[1] 266 } : {}; 267 }; 268 /** 269 * @param {Response} response 270 * @return {string | undefined} The next page URL. 271 */ 272 273 274 const getNextPageUrl = response => { 275 const { 276 next 277 } = parseLinkHeader(response.headers.get('link')); 278 return next; 279 }; 280 /** 281 * @param {import('../types').APIFetchOptions} options 282 * @return {boolean} True if the request contains an unbounded query. 283 */ 284 285 286 const requestContainsUnboundedQuery = options => { 287 const pathIsUnbounded = !!options.path && options.path.indexOf('per_page=-1') !== -1; 288 const urlIsUnbounded = !!options.url && options.url.indexOf('per_page=-1') !== -1; 289 return pathIsUnbounded || urlIsUnbounded; 290 }; 291 /** 292 * The REST API enforces an upper limit on the per_page option. To handle large 293 * collections, apiFetch consumers can pass `per_page=-1`; this middleware will 294 * then recursively assemble a full response array from all available pages. 295 * 296 * @type {import('../types').APIFetchMiddleware} 297 */ 298 299 300 const fetchAllMiddleware = async (options, next) => { 301 if (options.parse === false) { 302 // If a consumer has opted out of parsing, do not apply middleware. 303 return next(options); 304 } 305 306 if (!requestContainsUnboundedQuery(options)) { 307 // If neither url nor path is requesting all items, do not apply middleware. 308 return next(options); 309 } // Retrieve requested page of results. 310 311 312 const response = await build_module({ ...modifyQuery(options, { 313 per_page: 100 314 }), 315 // Ensure headers are returned for page 1. 316 parse: false 317 }); 318 const results = await parseResponse(response); 319 320 if (!Array.isArray(results)) { 321 // We have no reliable way of merging non-array results. 322 return results; 323 } 324 325 let nextPage = getNextPageUrl(response); 326 327 if (!nextPage) { 328 // There are no further pages to request. 329 return results; 330 } // Iteratively fetch all remaining pages until no "next" header is found. 331 332 333 let mergedResults = 334 /** @type {any[]} */ 335 [].concat(results); 336 337 while (nextPage) { 338 const nextResponse = await build_module({ ...options, 339 // Ensure the URL for the next page is used instead of any provided path. 340 path: undefined, 341 url: nextPage, 342 // Ensure we still get headers so we can identify the next page. 343 parse: false 344 }); 345 const nextResults = await parseResponse(nextResponse); 346 mergedResults = mergedResults.concat(nextResults); 347 nextPage = getNextPageUrl(nextResponse); 348 } 349 350 return mergedResults; 351 }; 352 353 /* harmony default export */ var fetch_all_middleware = (fetchAllMiddleware); 354 355 ;// CONCATENATED MODULE: ./node_modules/@wordpress/api-fetch/build-module/middlewares/http-v1.js 356 /** 357 * Set of HTTP methods which are eligible to be overridden. 358 * 359 * @type {Set<string>} 360 */ 361 const OVERRIDE_METHODS = new Set(['PATCH', 'PUT', 'DELETE']); 362 /** 363 * Default request method. 364 * 365 * "A request has an associated method (a method). Unless stated otherwise it 366 * is `GET`." 367 * 368 * @see https://fetch.spec.whatwg.org/#requests 369 * 370 * @type {string} 371 */ 372 373 const DEFAULT_METHOD = 'GET'; 374 /** 375 * API Fetch middleware which overrides the request method for HTTP v1 376 * compatibility leveraging the REST API X-HTTP-Method-Override header. 377 * 378 * @type {import('../types').APIFetchMiddleware} 379 */ 380 381 const httpV1Middleware = (options, next) => { 382 const { 383 method = DEFAULT_METHOD 384 } = options; 385 386 if (OVERRIDE_METHODS.has(method.toUpperCase())) { 387 options = { ...options, 388 headers: { ...options.headers, 389 'X-HTTP-Method-Override': method, 390 'Content-Type': 'application/json' 391 }, 392 method: 'POST' 393 }; 394 } 395 396 return next(options); 397 }; 398 399 /* harmony default export */ var http_v1 = (httpV1Middleware); 400 401 ;// CONCATENATED MODULE: ./node_modules/@wordpress/api-fetch/build-module/middlewares/user-locale.js 402 /** 403 * WordPress dependencies 404 */ 405 406 /** 407 * @type {import('../types').APIFetchMiddleware} 408 */ 409 410 const userLocaleMiddleware = (options, next) => { 411 if (typeof options.url === 'string' && !(0,external_wp_url_namespaceObject.hasQueryArg)(options.url, '_locale')) { 412 options.url = (0,external_wp_url_namespaceObject.addQueryArgs)(options.url, { 413 _locale: 'user' 414 }); 415 } 416 417 if (typeof options.path === 'string' && !(0,external_wp_url_namespaceObject.hasQueryArg)(options.path, '_locale')) { 418 options.path = (0,external_wp_url_namespaceObject.addQueryArgs)(options.path, { 419 _locale: 'user' 420 }); 421 } 422 423 return next(options); 424 }; 425 426 /* harmony default export */ var user_locale = (userLocaleMiddleware); 427 428 ;// CONCATENATED MODULE: ./node_modules/@wordpress/api-fetch/build-module/utils/response.js 429 /** 430 * WordPress dependencies 431 */ 432 433 /** 434 * Parses the apiFetch response. 435 * 436 * @param {Response} response 437 * @param {boolean} shouldParseResponse 438 * 439 * @return {Promise<any> | null | Response} Parsed response. 440 */ 441 442 const response_parseResponse = function (response) { 443 let shouldParseResponse = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : true; 444 445 if (shouldParseResponse) { 446 if (response.status === 204) { 447 return null; 448 } 449 450 return response.json ? response.json() : Promise.reject(response); 451 } 452 453 return response; 454 }; 455 /** 456 * Calls the `json` function on the Response, throwing an error if the response 457 * doesn't have a json function or if parsing the json itself fails. 458 * 459 * @param {Response} response 460 * @return {Promise<any>} Parsed response. 461 */ 462 463 464 const parseJsonAndNormalizeError = response => { 465 const invalidJsonError = { 466 code: 'invalid_json', 467 message: (0,external_wp_i18n_namespaceObject.__)('The response is not a valid JSON response.') 468 }; 469 470 if (!response || !response.json) { 471 throw invalidJsonError; 472 } 473 474 return response.json().catch(() => { 475 throw invalidJsonError; 476 }); 477 }; 478 /** 479 * Parses the apiFetch response properly and normalize response errors. 480 * 481 * @param {Response} response 482 * @param {boolean} shouldParseResponse 483 * 484 * @return {Promise<any>} Parsed response. 485 */ 486 487 488 const parseResponseAndNormalizeError = function (response) { 489 let shouldParseResponse = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : true; 490 return Promise.resolve(response_parseResponse(response, shouldParseResponse)).catch(res => parseAndThrowError(res, shouldParseResponse)); 491 }; 492 /** 493 * Parses a response, throwing an error if parsing the response fails. 494 * 495 * @param {Response} response 496 * @param {boolean} shouldParseResponse 497 * @return {Promise<any>} Parsed response. 498 */ 499 500 function parseAndThrowError(response) { 501 let shouldParseResponse = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : true; 502 503 if (!shouldParseResponse) { 504 throw response; 505 } 506 507 return parseJsonAndNormalizeError(response).then(error => { 508 const unknownError = { 509 code: 'unknown_error', 510 message: (0,external_wp_i18n_namespaceObject.__)('An unknown error occurred.') 511 }; 512 throw error || unknownError; 513 }); 514 } 515 516 ;// CONCATENATED MODULE: ./node_modules/@wordpress/api-fetch/build-module/middlewares/media-upload.js 517 /** 518 * WordPress dependencies 519 */ 520 521 /** 522 * Internal dependencies 523 */ 524 525 526 /** 527 * @param {import('../types').APIFetchOptions} options 528 * @return {boolean} True if the request is for media upload. 529 */ 530 531 function isMediaUploadRequest(options) { 532 const isCreateMethod = !!options.method && options.method === 'POST'; 533 const isMediaEndpoint = !!options.path && options.path.indexOf('/wp/v2/media') !== -1 || !!options.url && options.url.indexOf('/wp/v2/media') !== -1; 534 return isMediaEndpoint && isCreateMethod; 535 } 536 /** 537 * Middleware handling media upload failures and retries. 538 * 539 * @type {import('../types').APIFetchMiddleware} 540 */ 541 542 543 const mediaUploadMiddleware = (options, next) => { 544 if (!isMediaUploadRequest(options)) { 545 return next(options); 546 } 547 548 let retries = 0; 549 const maxRetries = 5; 550 /** 551 * @param {string} attachmentId 552 * @return {Promise<any>} Processed post response. 553 */ 554 555 const postProcess = attachmentId => { 556 retries++; 557 return next({ 558 path: `/wp/v2/media/$attachmentId}/post-process`, 559 method: 'POST', 560 data: { 561 action: 'create-image-subsizes' 562 }, 563 parse: false 564 }).catch(() => { 565 if (retries < maxRetries) { 566 return postProcess(attachmentId); 567 } 568 569 next({ 570 path: `/wp/v2/media/$attachmentId}?force=true`, 571 method: 'DELETE' 572 }); 573 return Promise.reject(); 574 }); 575 }; 576 577 return next({ ...options, 578 parse: false 579 }).catch(response => { 580 const attachmentId = response.headers.get('x-wp-upload-attachment-id'); 581 582 if (response.status >= 500 && response.status < 600 && attachmentId) { 583 return postProcess(attachmentId).catch(() => { 584 if (options.parse !== false) { 585 return Promise.reject({ 586 code: 'post_process', 587 message: (0,external_wp_i18n_namespaceObject.__)('Media upload failed. If this is a photo or a large image, please scale it down and try again.') 588 }); 589 } 590 591 return Promise.reject(response); 592 }); 593 } 594 595 return parseAndThrowError(response, options.parse); 596 }).then(response => parseResponseAndNormalizeError(response, options.parse)); 597 }; 598 599 /* harmony default export */ var media_upload = (mediaUploadMiddleware); 600 601 ;// CONCATENATED MODULE: ./node_modules/@wordpress/api-fetch/build-module/index.js 602 /** 603 * WordPress dependencies 604 */ 605 606 /** 607 * Internal dependencies 608 */ 609 610 611 612 613 614 615 616 617 618 619 /** 620 * Default set of header values which should be sent with every request unless 621 * explicitly provided through apiFetch options. 622 * 623 * @type {Record<string, string>} 624 */ 625 626 const DEFAULT_HEADERS = { 627 // The backend uses the Accept header as a condition for considering an 628 // incoming request as a REST request. 629 // 630 // See: https://core.trac.wordpress.org/ticket/44534 631 Accept: 'application/json, */*;q=0.1' 632 }; 633 /** 634 * Default set of fetch option values which should be sent with every request 635 * unless explicitly provided through apiFetch options. 636 * 637 * @type {Object} 638 */ 639 640 const DEFAULT_OPTIONS = { 641 credentials: 'include' 642 }; 643 /** @typedef {import('./types').APIFetchMiddleware} APIFetchMiddleware */ 644 645 /** @typedef {import('./types').APIFetchOptions} APIFetchOptions */ 646 647 /** 648 * @type {import('./types').APIFetchMiddleware[]} 649 */ 650 651 const middlewares = [user_locale, namespace_endpoint, http_v1, fetch_all_middleware]; 652 /** 653 * Register a middleware 654 * 655 * @param {import('./types').APIFetchMiddleware} middleware 656 */ 657 658 function registerMiddleware(middleware) { 659 middlewares.unshift(middleware); 660 } 661 /** 662 * Checks the status of a response, throwing the Response as an error if 663 * it is outside the 200 range. 664 * 665 * @param {Response} response 666 * @return {Response} The response if the status is in the 200 range. 667 */ 668 669 670 const checkStatus = response => { 671 if (response.status >= 200 && response.status < 300) { 672 return response; 673 } 674 675 throw response; 676 }; 677 /** @typedef {(options: import('./types').APIFetchOptions) => Promise<any>} FetchHandler*/ 678 679 /** 680 * @type {FetchHandler} 681 */ 682 683 684 const defaultFetchHandler = nextOptions => { 685 const { 686 url, 687 path, 688 data, 689 parse = true, 690 ...remainingOptions 691 } = nextOptions; 692 let { 693 body, 694 headers 695 } = nextOptions; // Merge explicitly-provided headers with default values. 696 697 headers = { ...DEFAULT_HEADERS, 698 ...headers 699 }; // The `data` property is a shorthand for sending a JSON body. 700 701 if (data) { 702 body = JSON.stringify(data); 703 headers['Content-Type'] = 'application/json'; 704 } 705 706 const responsePromise = window.fetch( // Fall back to explicitly passing `window.location` which is the behavior if `undefined` is passed. 707 url || path || window.location.href, { ...DEFAULT_OPTIONS, 708 ...remainingOptions, 709 body, 710 headers 711 }); 712 return responsePromise.then(value => Promise.resolve(value).then(checkStatus).catch(response => parseAndThrowError(response, parse)).then(response => parseResponseAndNormalizeError(response, parse)), err => { 713 // Re-throw AbortError for the users to handle it themselves. 714 if (err && err.name === 'AbortError') { 715 throw err; 716 } // Otherwise, there is most likely no network connection. 717 // Unfortunately the message might depend on the browser. 718 719 720 throw { 721 code: 'fetch_error', 722 message: (0,external_wp_i18n_namespaceObject.__)('You are probably offline.') 723 }; 724 }); 725 }; 726 /** @type {FetchHandler} */ 727 728 729 let fetchHandler = defaultFetchHandler; 730 /** 731 * Defines a custom fetch handler for making the requests that will override 732 * the default one using window.fetch 733 * 734 * @param {FetchHandler} newFetchHandler The new fetch handler 735 */ 736 737 function setFetchHandler(newFetchHandler) { 738 fetchHandler = newFetchHandler; 739 } 740 /** 741 * @template T 742 * @param {import('./types').APIFetchOptions} options 743 * @return {Promise<T>} A promise representing the request processed via the registered middlewares. 744 */ 745 746 747 function apiFetch(options) { 748 // creates a nested function chain that calls all middlewares and finally the `fetchHandler`, 749 // converting `middlewares = [ m1, m2, m3 ]` into: 750 // ``` 751 // opts1 => m1( opts1, opts2 => m2( opts2, opts3 => m3( opts3, fetchHandler ) ) ); 752 // ``` 753 const enhancedHandler = middlewares.reduceRight(( 754 /** @type {FetchHandler} */ 755 next, middleware) => { 756 return workingOptions => middleware(workingOptions, next); 757 }, fetchHandler); 758 return enhancedHandler(options).catch(error => { 759 if (error.code !== 'rest_cookie_invalid_nonce') { 760 return Promise.reject(error); 761 } // If the nonce is invalid, refresh it and try again. 762 763 764 return window // @ts-ignore 765 .fetch(apiFetch.nonceEndpoint).then(checkStatus).then(data => data.text()).then(text => { 766 // @ts-ignore 767 apiFetch.nonceMiddleware.nonce = text; 768 return apiFetch(options); 769 }); 770 }); 771 } 772 773 apiFetch.use = registerMiddleware; 774 apiFetch.setFetchHandler = setFetchHandler; 775 apiFetch.createNonceMiddleware = nonce; 776 apiFetch.createPreloadingMiddleware = preloading; 777 apiFetch.createRootURLMiddleware = root_url; 778 apiFetch.fetchAllMiddleware = fetch_all_middleware; 779 apiFetch.mediaUploadMiddleware = media_upload; 780 /* harmony default export */ var build_module = (apiFetch); 781 782 (window.wp = window.wp || {}).apiFetch = __webpack_exports__["default"]; 783 /******/ })() 784 ;
title
Description
Body
title
Description
Body
title
Description
Body
title
Body
Generated: Thu Nov 21 01:00:03 2024 | Cross-referenced by PHPXref 0.7.1 |