setName('rescue') ->setDescription("Rescues wiki changes from the activity stream in an Elasticsearch index\nExample usage: php doc/devtools/rescue_wiki_changes_from_elastic.php rescue -u http://elastic.example.com:9200/ -i mytiki_main") ->addOption( 'elasticuri', 'u', InputOption::VALUE_OPTIONAL, 'Elasticsearch URI', 'http://localhost:9200/' ) ->addOption( 'indexname', 'i', InputOption::VALUE_OPTIONAL, 'Index Name', 'tiki_main' ) ->addOption( 'startdate', 's', InputOption::VALUE_OPTIONAL, 'Start Date', '2016-11-04' ) ->addOption( 'enddate', 'e', InputOption::VALUE_OPTIONAL, 'End Date', '2017-04-05' ) ->addOption( 'confirm', 'c', InputOption::VALUE_NONE, 'Confirm (add -c or --confirm to perform the actual changes)' ); } protected function execute(InputInterface $input, OutputInterface $output) { $this->elasticUri = $input->getOption('elasticuri'); $this->elasticUri = strrpos($this->elasticUri, '/') !== (strlen($this->elasticUri) - 1) ? $this->elasticUri . '/' : $this->elasticUri; $this->indexName = $input->getOption('indexname'); $startDate = $input->getOption('startdate'); $endDate = $input->getOption('enddate'); $results = $this->getWikiPageEditEvents($startDate, $endDate); $output->writeln(tr('Found %0 wiki edits', count($results))); $confirm = $input->getOption('confirm'); if (! $confirm) { $output->writeln(tr('Dry run mode: add --confirm to run for real', count($results))); } $this->makeThePageUpdates($results, $output, $confirm); } /** * @param array $edits * @param OutputInterface $output */ private function makeThePageUpdates(array $edits, OutputInterface $output, $confirm = false) { $tikilib = \TikiLib::lib('tiki'); $histlib = \TikiLib::lib('hist'); $now = date('c'); $progress = new ProgressBar($output, count($edits)); if ($confirm) { $progress->start(); } $transaction = $tikilib->begin(); foreach ($edits as $edit) { $page = $edit['page']; if (! $tikilib->page_exists($page)) { $info = $this->getWikiPage($page); if ($confirm) { $tikilib->create_page( $page, 0, $edit['data'], strtotime($info['creation_date']), 'Page created by rescue script ' . $now, $edit['user'], '0.0.0.0', $info['description'], $info['language'], null, null, null, '', 0, strtotime($info['creation_date']) ); } else { $output->writeln(tr('Page: "%0" (version %1) CREATED', $page, $edit['version'])); } if ($edit['version'] > 1 && $confirm) { // missing history $tiki_history = $tikilib->table('tiki_history'); $old_version = ! empty($edit['old_version']) ? $edit['old_version'] : $edit['version'] - 1; $tiki_history->update([ 'version' => $old_version, ], [ 'pageName' => $page, ]); $tiki_history->insert([ 'pageName' => $page, 'version' => $edit['version'], 'version_minor' => 0, 'lastModif' => strtotime($edit['modification_date']), 'user' => $edit['user'], 'ip' => '0.0.0.0', 'comment' => 'Version created by rescue script ' . $now, 'data' => $edit['old_data'], 'description' => $info['description'], 'is_html' => 0, // a guess ]); } } else { $info = $tikilib->get_page_info($page); if (! $histlib->get_version($page, $edit['version'])) { if ($confirm) { $tikilib->update_page( $page, $edit['data'], 'Edit restored by rescue script ' . $now, $edit['user'], '0.0.0.0', null, 0, '', null, null, strtotime($edit['modification_date']) ); } else { $output->writeln(tr('Page: "%0" (version %1) UPDATED', $page, $edit['version'])); } } else { // version alrteady exists, do what? } } if ($confirm) { $progress->advance(); } } if ($confirm) { $progress->finish(); } $output->writeln(''); $output->writeln(tr('Committing transaction')); $transaction->commit(); $output->writeln(''); $output->writeln(tr('Done')); } /** * Get all activity stream events for wiki pages * * @param $startDate * @param $endDate * @return array|bool * @internal param $unifiedsearchlib * @internal param $this */ private function getWikiPageEditEvents($startDate, $endDate) { $unifiedsearchlib = \TikiLib::lib('unifiedsearch'); $connection = new \Search_Elastic_Connection($this->elasticUri); $esIndex = new \Search_Elastic_Index($connection, $this->indexName); $query = new \Search_Query(); $unifiedsearchlib->initQueryBase($query); //$query = $unifiedsearchlib->buildQuery($filter, $query); // annoying, can't use type in this as that converts it to object_type, meh $query->filterIdentifier('wiki page', 'type'); $query->filterType('activity'); if ($startDate && $endDate) { $query->filterRange($startDate, $endDate); } $query->setOrder('modification_date_nasc'); $query->setRange(0, 1000); // build query for es $builder = new \Search_Elastic_OrderBuilder(); $orderPart = $builder->build($query->getSortOrder()); $builder = new \Search_Elastic_QueryBuilder($esIndex); $queryPart = $builder->build($query->getExpr()); $fullQuery = array_merge( $queryPart, $orderPart, [ 'from' => 0, 'size' => 1000, ] ); $data = json_encode($fullQuery); $full = "{$this->elasticUri}{$this->indexName}/_search"; //$full = "{$elasticUri}{$indexName}/_validate/query?explain=true"; $client = \TikiLib::lib('tiki')->get_http_client($full); $client->setRawBody($data); $client->setMethod(\Laminas\Http\Request::METHOD_GET); $client->setHeaders(['Content-Type: application/json']); try { $response = $client->send(); $body = $response->getBody(); $decoded = json_decode($body); $hits = $decoded->hits; $results = []; foreach ($hits->hits as $hit) { $results[] = [ 'modification_date' => $hit->_source->modification_date, 'page' => $hit->_source->object, 'user' => $hit->_source->user, 'version' => $hit->_source->version, 'old_version' => $hit->_source->old_version, 'data' => $hit->_source->data, 'old_data' => $hit->_source->old_data, ]; } return $results; } catch (\Exception $e) { echo $e->getMessage(); return null; } } /** * Gets a single wiki page definitioopn from the index (for missing pages) * * @param string $page page name * @return array|bool * @internal param $unifiedsearchlib * @internal param $this */ private function getWikiPage($page) { $unifiedsearchlib = \TikiLib::lib('unifiedsearch'); $connection = new \Search_Elastic_Connection($this->elasticUri); $esIndex = new \Search_Elastic_Index($connection, $this->indexName); $query = new \Search_Query(); $unifiedsearchlib->initQueryBase($query); //$query = $unifiedsearchlib->buildQuery($filter, $query); // annoying, can't use type in this as that converts it to object_type, meh $query->filterIdentifier($page, 'object_id'); $query->filterType('wiki page'); // build query for es $builder = new \Search_Elastic_QueryBuilder($esIndex); $queryPart = $builder->build($query->getExpr()); $data = json_encode($queryPart); $full = "{$this->elasticUri}{$this->indexName}/_search"; //$full = "{$elasticUri}{$indexName}/_validate/query?explain=true"; $client = \TikiLib::lib('tiki')->get_http_client($full); $client->setRawBody($data); $client->setMethod(\Laminas\Http\Request::METHOD_GET); $client->setHeaders(['Content-Type: application/json']); $response = $client->send(); $body = $response->getBody(); $decoded = json_decode($body, true); if ($decoded && ! empty($decoded['hits']['hits'][0]['_source'])) { return $decoded['hits']['hits'][0]['_source']; } else { return false; } } } // create the application and new console $console = new Application(); $console->add(new ESRescueCommand()); $console->setDefaultCommand('rescue'); // run the command $console->run();