<?php
// PHP 5.4-compatible, fixed target. Secrets stay in the server's existing config.
// prepare: creates protected backups/staged files; apply: optimistic one-row update.
// verify: reads only. Never changes content body, service config or publication root.
error_reporting(E_ALL);
ini_set('display_errors', '0');
umask(0077);
$mode = isset($argv[1]) ? $argv[1] : 'prepare';
$base = '/data/www/cmstop';
$backup = '/root/cpt-oem-maintenance-20260917/phone';
$new = '所在地区：北京市昌平区科技园东区利祥路5号  OEM业务联系电话：010-60730270';
$paths = array(
    'public/www/guanwang/guanwang/about/index.shtml',
    'public/www/guanwang/guanwang/about/kjkbt/index.shtml',
    'public/www/guanwang/guanwang/about/kjkbt/oem/1.shtml',
    'public/www/guanwang/guanwang/about/kjkbt/oem/index.shtml',
    'public/www/guanwang/about/index.shtml',
    'public/www/guanwang/about/kjkbt/index.shtml',
    'public/www/guanwang/about/kjkbt/oem/1.shtml',
    'public/www/guanwang/about/kjkbt/oem/index.shtml',
    'public/www/guanwang/2016/0317/1620.shtml',
    'public/www/2016/0317/1620.shtml'
);
$template = 'templates/chinacpt/guanwang/about_oem.html';
function must($ok, $message) { if (!$ok) throw new Exception($message); }
function put_private($path, $data) {
    must(file_put_contents($path, $data) === strlen($data), 'Protected write failed');
    must(chmod($path, 0600), 'Protected file chmod failed');
}
function atom($path, $data, $meta) {
    $tmp = $path.'.cpt-oem-20260917.tmp';
    must(!file_exists($tmp), 'Temporary file already exists');
    put_private($tmp, $data);
    must(chown($tmp, $meta['uid']) && chgrp($tmp, $meta['gid']), 'Owner preservation failed');
    must(chmod($tmp, $meta['mode'] & 0777), 'Mode preservation failed');
    must(rename($tmp, $path), 'Atomic replacement failed');
}
try {
    must(in_array($mode, array('prepare','apply','verify'), true), 'Unknown mode');
    must(gethostname() === 'iZ25rr8uynoZ', 'Wrong host');
    $c = include $base.'/config/db.php';
    must($c['dbname'] === 'cmstop' && $c['prefix'] === 'cmstop_', 'Wrong database');
    $db = new PDO('mysql:host='.$c['host'].';port='.$c['port'].';dbname='.$c['dbname'].';charset=utf8', $c['username'], $c['password'], array(PDO::ATTR_ERRMODE=>PDO::ERRMODE_EXCEPTION));
    $db->exec('SET SESSION innodb_lock_wait_timeout=5');
    $row = $db->query('SELECT * FROM cmstop_article WHERE contentid=1620')->fetch(PDO::FETCH_ASSOC);
    $identity = $db->query('SELECT contentid,title,catid,modelid,status,weight FROM cmstop_content WHERE contentid=1620')->fetch(PDO::FETCH_ASSOC);
    must($row && $identity && $identity['title'] === '所在地区' && (int)$identity['catid'] === 131 && (int)$identity['status'] === 6, 'Content identity mismatch');
    if ($mode === 'prepare') {
        must(!file_exists($backup), 'Backup already exists; do not overwrite');
        $old = $row['description'];
        must(substr_count($old, '010-60730274') === 1 && strpos($old, '邢彦斌') !== false && strpos($old, '宣文舰') !== false, 'Unexpected old description');
        $parts = explode('@@', $old);
        must(count($parts) === 2, 'Unexpected contact structure');
        must(mkdir($backup, 0700), 'Cannot create backup');
        put_private($backup.'/article-before.json', json_encode($row));
        put_private($backup.'/identity-before.json', json_encode($identity));
        $files = array();
        foreach (array_merge($paths, array($template)) as $i=>$rel) {
            $p = $base.'/'.$rel;
            must(is_file($p) && !is_link($p), 'Expected regular source file');
            $before = file_get_contents($p);
            if ($rel === $template) {
                $needle = '<p style="color: #FF7500;text-align: right;">{$description[1]}</p>';
                must(substr_count($before, $needle) === 1, 'Template anchor not unique');
                $after = str_replace($needle, '<!--{if !empty($description[1])}-->'.$needle.'<!--{/if}-->', $before);
            } else {
                $count = substr_count($before, $old);
                if ($count) {
                    $after = str_replace($old, $new, $before);
                } elseif (substr_count($before, preg_replace('/\s+/', ' ', $old))) {
                    // CMS category summaries collapse whitespace; match that exact variant.
                    $after = str_replace(preg_replace('/\s+/', ' ', $old), $new, $before);
                } else {
                    must(substr_count($before, $parts[0]) === 1 && substr_count($before, $parts[1]) === 1, 'Static anchors not unique');
                    $after = str_replace($parts[0], $new, $before);
                    $after = preg_replace('~[\t ]*<p\b[^>]*>'.preg_quote($parts[1], '~').'</p>\r?\n?~', '', $after, 1, $removed);
                    must($removed === 1, 'Second contact paragraph not found');
                }
                must(strpos($after, '010-60730274') === false && strpos($after, '邢彦斌') === false && strpos($after, '宣文舰') === false && strpos($after, '@@') === false, 'Old contact remains');
                must(substr_count($after, '010-60730270') === substr_count($before, '010-60730274'), 'Unexpected replacement count');
            }
            put_private($backup.'/'.$i.'.before', $before);
            put_private($backup.'/'.$i.'.after', $after);
            $files[] = array('path'=>$rel, 'index'=>$i, 'before'=>hash('sha256',$before), 'after'=>hash('sha256',$after), 'stat'=>stat($p));
        }
        put_private($backup.'/manifest.json', json_encode(array('contentid'=>1620,'old_description_sha256'=>hash('sha256',$old),'new_description'=>$new,'body_sha256'=>hash('sha256',$row['content']),'files'=>$files)));
        echo json_encode(array('status'=>'PREPARED','static_pages'=>count($paths),'templates'=>1,'db_rows_expected'=>1,'backup'=>$backup))."\n";
        exit(0);
    }
    must(is_file($backup.'/manifest.json'), 'No prepared backup');
    $m = json_decode(file_get_contents($backup.'/manifest.json'), true);
    $oldrow = json_decode(file_get_contents($backup.'/article-before.json'), true);
    $beforeIdentity = json_decode(file_get_contents($backup.'/identity-before.json'), true);
    must($identity === $beforeIdentity, 'Content metadata changed');
    must(hash('sha256', $row['content']) === $m['body_sha256'], 'Article body changed');
    if ($mode === 'apply') {
        must($row === $oldrow, 'Article changed since prepare');
        foreach ($m['files'] as $f) {
            must(hash_file('sha256',$base.'/'.$f['path']) === $f['before'], 'Source file changed');
            must(hash_file('sha256',$backup.'/'.$f['index'].'.after') === $f['after'], 'Staged checksum changed');
        }
        $changed = array();
        $db->beginTransaction();
        try {
            $locked = $db->query('SELECT * FROM cmstop_article WHERE contentid=1620 FOR UPDATE')->fetch(PDO::FETCH_ASSOC);
            must($locked === $oldrow, 'Concurrent article update');
            $stmt = $db->prepare('UPDATE cmstop_article SET description=? WHERE contentid=1620 AND BINARY description=BINARY ?');
            $stmt->execute(array($new,$oldrow['description']));
            must($stmt->rowCount() === 1, 'Expected exactly one changed row');
            foreach ($m['files'] as $f) {
                must(hash_file('sha256',$base.'/'.$f['path']) === $f['before'], 'Concurrent file update');
                atom($base.'/'.$f['path'], file_get_contents($backup.'/'.$f['index'].'.after'), $f['stat']);
                $changed[]=$f;
            }
            $db->commit();
        } catch (Exception $e) {
            if ($db->inTransaction()) {
                $db->rollBack();
                foreach (array_reverse($changed) as $f) {
                    if (hash_file('sha256',$base.'/'.$f['path']) === $f['after']) atom($base.'/'.$f['path'],file_get_contents($backup.'/'.$f['index'].'.before'),$f['stat']);
                }
            }
            throw $e;
        }
        put_private($backup.'/applied.json', json_encode(array('at'=>date('c'),'rows'=>1,'files'=>count($changed))));
        $row = $db->query('SELECT * FROM cmstop_article WHERE contentid=1620')->fetch(PDO::FETCH_ASSOC);
    }
    $expected = $oldrow; $expected['description']=$new;
    must($row === $expected, 'Database readback/invariants failed');
    foreach ($m['files'] as $f) must(hash_file('sha256',$base.'/'.$f['path']) === $f['after'], 'File readback failed');
    echo json_encode(array('status'=>'VERIFIED','changed_column'=>'cmstop_article.description','contentid'=>1620,'static_pages'=>10,'template_files'=>1,'body_unchanged'=>true,'other_article_fields_unchanged'=>true,'content_metadata_unchanged'=>true,'new_description'=>$new))."\n";
} catch (Exception $e) {
    // Do not print PDO messages or configuration data, which may expose credentials.
    fwrite(STDERR, 'STOP: '.(get_class($e)==='PDOException' ? 'Database operation failed' : $e->getMessage())."\n");
    exit(1);
}
