Variable SAVE_NODE_PHPConst
SAVE_NODE_PHP: "\nuse Drupal\\Core\\DrupalKernel;\nuse Drupal\\Core\\Site\\Settings;\nuse Drupal\\node\\Entity\\Node;\nuse Drupal\\node\\Entity\\NodeType;\nuse Drupal\\user\\Entity\\User;\nuse Symfony\\Component\\HttpFoundation\\Request;\n\n// __CFW_FIBER_SHIM__\n// __CFW_HOST_HELPERS__\n// __CFW_PW_SERVE_INLINE__\nchdir('/drupal');\n\n$opt = json_decode(__CFW_PAYLOAD__, true);\n$out = ['ok' => false];\n$clock = function () {\n\treturn microtime(true) * 1000;\n};\n$statements = function () {\n\treturn json_decode(cfw_host('cfwStats')(), true)['queryCount'] ?? 0;\n};\n\n$_SERVER['HTTP_HOST'] = 'localhost';\n$_SERVER['SERVER_NAME'] = 'localhost';\n$_SERVER['SERVER_PORT'] = '80';\n$_SERVER['REQUEST_URI'] = '/';\n$_SERVER['REQUEST_METHOD'] = 'GET';\n$_SERVER['SCRIPT_NAME'] = '/index.php';\n$_SERVER['SCRIPT_FILENAME'] = '/drupal/index.php';\n$_SERVER['PHP_SELF'] = '/index.php';\n$_SERVER['DOCUMENT_ROOT'] = '/drupal';\n$_SERVER['REMOTE_ADDR'] = '127.0.0.1';\n$_SERVER['SERVER_SOFTWARE'] = 'workerd';\n$_SERVER['SERVER_PROTOCOL'] = 'HTTP/1.1';\n\ntry {\n\tif (!isset($GLOBALS['__pw_autoloader']) || !is_object($GLOBALS['__pw_autoloader'])) {\n\t\t$GLOBALS['__pw_autoloader'] = require '/drupal/autoload.php';\n\t}\n\t$autoloader = $GLOBALS['__pw_autoloader'];\n\n\tif (!isset($GLOBALS['__pw_kernel'])) {\n\t\t$request = Request::create('/', 'GET');\n\t\t$kernel = new DrupalKernel('prod', $autoloader);\n\t\tDrupalKernel::bootEnvironment();\n\t\t$sitePath = DrupalKernel::findSitePath($request);\n\t\t$kernel->setSitePath($sitePath);\n\t\tSettings::initialize('/drupal', $sitePath, $autoloader);\n\t\t$kernel->boot();\n\t\t$GLOBALS['__pw_kernel'] = $kernel;\n\t\t$out['bootedKernel'] = 1;\n\t}\n\n\t// SAVED_NEW / SAVED_UPDATED live in core/includes/common.inc, which boot() does\n\t// not include and no render needs; EntityStorageBase::doSave() returns one\n\tif (!defined('SAVED_NEW')) {\n\t\trequire_once '/drupal/core/includes/common.inc';\n\t\t$out['loadedCommonInc'] = true;\n\t}\n\n\t// __CFW_SCHEMA_REPAIR__\n\n\t// Whoever owns the content has to be the acting user for the save, or node access\n\t// denies the save's own reads.\n\t//\n\t// and it must be put back. The interpreter persists between requests, so a\n\t// current-user switch that is never undone leaks into every later render in the\n\t// process -- measured: the front page went from 12,296 bytes to 90,038 because the\n\t// alarm chain rendered it as uid 1, and that ADMIN HTML was then stored in the\n\t// anonymous page cache and served to visitors. A cache-poisoning bug from one\n\t// unrestored global.\n\t$previousAccount = Drupal::currentUser()->getAccount();\n\t$admin = User::load(1);\n\tif ($admin !== null) {\n\t\tDrupal::currentUser()->setAccount($admin);\n\t\t$out['actingUid'] = (int) $admin->id();\n\t}\n\n\t$types = array_keys(NodeType::loadMultiple());\n\t$out['availableTypes'] = $types;\n\t$type = $opt['type'] ?? null;\n\tif ($type === null || !in_array($type, $types, true)) {\n\t\t$type = in_array('article', $types, true) ? 'article' : $types[0] ?? null;\n\t}\n\tif ($type === null) {\n\t\tthrow new RuntimeException('no node type exists in this site, so nothing can be saved');\n\t}\n\t$out['type'] = $type;\n\n\t$title = $opt['title'] ?? 'Measured save ' . date('H:i:s');\n\t$values = [\n\t\t'type' => $type,\n\t\t'title' => $title,\n\t\t'uid' => 1,\n\t\t'status' => 1,\n\t\t// promoted, so the FRONT PAGE changes and this measures a refresh\n\t\t'promote' => 1,\n\t];\n\t$node = Node::create($values);\n\t$definitions = Drupal::service('entity_field.manager')->getFieldDefinitions('node', $type);\n\tif (isset($definitions['body'])) {\n\t\t$node->set('body', [\n\t\t\t'value' => $opt['body'] ?? 'Written from inside a Durable Object.',\n\t\t\t'format' => 'basic_html',\n\t\t]);\n\t\t$out['bodySet'] = true;\n\t}\n\n\t$before = $statements();\n\t$t0 = $clock();\n\t$result = $node->save();\n\t$out['saveMs'] = round($clock() - $t0, 2);\n\t$out['saveStatements'] = $statements() - $before;\n\t$out['saveResult'] = (int) $result;\n\t$out['savedIsNew'] = defined('SAVED_NEW') && $result === SAVED_NEW;\n\t$out['nid'] = (int) $node->id();\n\t$out['vid'] = (int) $node->getRevisionId();\n\n\t// read it back through a FRESH storage handler, so this is the database answering\n\t// rather than the entity object that was just held in memory\n\tDrupal::entityTypeManager()\n\t\t->getStorage('node')\n\t\t->resetCache([$node->id()]);\n\t$reloaded = Node::load($node->id());\n\t$out['reloadedTitle'] = $reloaded === null ? null : $reloaded->getTitle();\n\t$out['persisted'] = $reloaded !== null && $reloaded->getTitle() === $title;\n\n\t// back to whoever was acting before, BEFORE anything renders, so these figures are\n\t// the anonymous page a visitor gets and comparable to every other render here\n\tDrupal::currentUser()->setAccount($previousAccount);\n\t$out['restoredUid'] = (int) Drupal::currentUser()->id();\n\n\t// and the refresh half: the node's own page, then the front page\n\tforeach (['/node/' . $node->id() => 'nodePage', '/' => 'frontPage'] as $path => $key) {\n\t\tforeach (['page', 'dynamic_page_cache'] as $bin) {\n\t\t\ttry {\n\t\t\t\tDrupal::cache($bin)->deleteAll();\n\t\t\t} catch (Throwable $e) {\n\t\t\t}\n\t\t}\n\t\ttry {\n\t\t\t$middleware = Drupal::service('http_middleware.page_cache');\n\t\t\t$rp = new ReflectionProperty($middleware, 'cid');\n\t\t\t$rp->setValue($middleware, null);\n\t\t} catch (Throwable $e) {\n\t\t}\n\t\t$b = $statements();\n\t\t$a = $clock();\n\t\t$response = cfw_serve($path);\n\t\t$body = (string) $response->getContent();\n\t\t$out[$key] = [\n\t\t\t'ms' => round($clock() - $a, 2),\n\t\t\t'status' => $response->getStatusCode(),\n\t\t\t'bytes' => strlen($body),\n\t\t\t'statements' => $statements() - $b,\n\t\t\t'pageCache' => $response->headers->get('x-drupal-cache'),\n\t\t\t'dynamicCache' => $response->headers->get('x-drupal-dynamic-cache'),\n\t\t\t'showsTitle' => str_contains($body, $title) ? 1 : 0,\n\t\t];\n\t}\n\n\t$out['ok'] = $out['persisted'] === true;\n} catch (Throwable $e) {\n\t$out['error'] = get_class($e) . ': ' . $e->getMessage();\n\t$out['trace'] = substr($e->getTraceAsString(), 0, 1200);\n} finally {\n\t// a throw between the switch and the restore would poison every later render in\n\t// this interpreter, so the restore cannot live only on the happy path\n\tif (isset($previousAccount)) {\n\t\ttry {\n\t\t\tDrupal::currentUser()->setAccount($previousAccount);\n\t\t} catch (Throwable $e2) {\n\t\t}\n\t}\n}\n\necho json_encode($out);\n"