1<?php 2/** 3 * Copyright (c) 2021. ComboStrap, Inc. and its affiliates. All Rights Reserved. 4 * 5 * This source code is licensed under the GPL license found in the 6 * COPYING file in the root directory of this source tree. 7 * 8 * @license GPL 3 (https://www.gnu.org/licenses/gpl-3.0.en.html) 9 * @author ComboStrap <support@combostrap.com> 10 * 11 */ 12if (!defined('DOKU_INC')) die(); 13 14use ComboStrap\Analytics; 15use ComboStrap\FsWikiUtility; 16use ComboStrap\Page; 17use ComboStrap\Sqlite; 18use splitbrain\phpcli\Options; 19 20/** 21 * All dependency are loaded in plugin utility 22 */ 23require_once(__DIR__ . '/ComboStrap/PluginUtility.php'); 24 25/** 26 * The memory of the server 128 is not enough 27 */ 28ini_set('memory_limit', '256M'); 29 30/** 31 * Class cli_plugin_combo 32 * 33 * This is a cli: 34 * https://www.dokuwiki.org/devel:cli_plugins#example 35 * 36 * Usage: 37 * 38 * ``` 39 * docker exec -ti $(CONTAINER) /bin/bash 40 * ./bin/plugin.php combo -c 41 * ``` 42 * or via the IDE 43 * 44 * 45 * Example: 46 * https://www.dokuwiki.org/tips:grapher 47 * 48 */ 49class cli_plugin_combo extends DokuWiki_CLI_Plugin 50{ 51 const ANALYTICS = "analytics"; 52 const SYNC = "sync"; 53 54 /** 55 * register options and arguments 56 * @param Options $options 57 */ 58 protected function setup(Options $options) 59 { 60 $options->setHelp( 61 "Manage the analytics database\n\n" . 62 "analytics\n" . 63 "sync" 64 ); 65 $options->registerOption('version', 'print version', 'v'); 66 $options->registerCommand(self::ANALYTICS, "Update the analytics data"); 67 $options->registerOption( 68 'namespaces', 69 "If no namespace is given, the root namespace is assumed.", 70 'n', 71 true 72 ); 73 $options->registerOption( 74 'output', 75 "Optional, where to store the analytical data as csv eg. a filename.", 76 'o', 'file'); 77 $options->registerOption( 78 'cache', 79 "Optional, returns from the cache if set", 80 'c', false); 81 $options->registerOption( 82 'dry', 83 "Optional, dry-run", 84 'd', false); 85 $options->registerCommand(self::SYNC, "Sync the database"); 86 87 } 88 89 /** 90 * The main entry 91 * @param Options $options 92 */ 93 protected function main(Options $options) 94 { 95 96 $namespaces = array_map('cleanID', $options->getArgs()); 97 if (!count($namespaces)) $namespaces = array(''); //import from top 98 99 $cache = $options->getOpt('cache', false); 100 $depth = $options->getOpt('depth', 0); 101 $cmd = $options->getCmd(); 102 if ($cmd == "") { 103 $cmd = self::ANALYTICS; 104 } 105 switch ($cmd) { 106 case self::ANALYTICS: 107 $output = $options->getOpt('output', ''); 108 //if ($output == '-') $output = 'php://stdout'; 109 $this->updateAnalyticsData($namespaces, $output, $cache, $depth); 110 break; 111 case self::SYNC: 112 $this->syncPages(); 113 break; 114 default: 115 throw new \RuntimeException("Combo: Command unknown (" . $cmd . ")"); 116 } 117 118 119 } 120 121 /** 122 * @param array $namespaces 123 * @param $output 124 * @param bool $cache 125 * @param int $depth recursion depth. 0 for unlimited 126 */ 127 private function updateAnalyticsData($namespaces = array(), $output = null, $cache = false, $depth = 0) 128 { 129 130 $fileHandle = null; 131 if (!empty($output)) { 132 $fileHandle = @fopen($output, 'w'); 133 if (!$fileHandle) $this->fatal("Failed to open $output"); 134 } 135 136 /** 137 * Run as admin to overcome the fact that 138 * anonymous user cannot see all links and backlinks 139 */ 140 global $USERINFO; 141 $USERINFO['grps'] = array('admin'); 142 global $INPUT; 143 $INPUT->server->set('REMOTE_USER', "cli"); 144 145 $pages = FsWikiUtility::getPages($namespaces, $depth); 146 147 148 if (!empty($fileHandle)) { 149 $header = array( 150 'id', 151 'backlinks', 152 'broken_links', 153 'changes', 154 'chars', 155 'external_links', 156 'external_medias', 157 'h1', 158 'h2', 159 'h3', 160 'h4', 161 'h5', 162 'internal_links', 163 'internal_medias', 164 'words', 165 'score' 166 ); 167 fwrite($fileHandle, implode(",", $header) . PHP_EOL); 168 } 169 $pageCounter = 0; 170 $totalNumberOfPages = sizeof($pages); 171 while ($page = array_shift($pages)) { 172 $id = $page['id']; 173 174 $pageCounter++; 175 echo "Processing the page {$id} ($pageCounter / $totalNumberOfPages)\n"; 176 177 $data = Analytics::processAndGetDataAsArray($id, $cache); 178 if (!empty($fileHandle)) { 179 $statistics = $data[Analytics::STATISTICS]; 180 $row = array( 181 'id' => $id, 182 'backlinks' => $statistics[Analytics::INTERNAL_BACKLINK_COUNT], 183 'broken_links' => $statistics[Analytics::INTERNAL_LINK_BROKEN_COUNT], 184 'changes' => $statistics[Analytics::EDITS_COUNT], 185 'chars' => $statistics[Analytics::CHAR_COUNT], 186 'external_links' => $statistics[Analytics::EXTERNAL_LINK_COUNT], 187 'external_medias' => $statistics[Analytics::EXTERNAL_MEDIA_COUNT], 188 Analytics::H1 => $statistics[Analytics::HEADING_COUNT][Analytics::H1], 189 'h2' => $statistics[Analytics::HEADING_COUNT]['h2'], 190 'h3' => $statistics[Analytics::HEADING_COUNT]['h3'], 191 'h4' => $statistics[Analytics::HEADING_COUNT]['h4'], 192 'h5' => $statistics[Analytics::HEADING_COUNT]['h5'], 193 'internal_links' => $statistics[Analytics::INTERNAL_LINK_COUNT], 194 'internal_medias' => $statistics[Analytics::INTERNAL_MEDIA_COUNT], 195 'words' => $statistics[Analytics::WORD_COUNT], 196 'low' => $data[Analytics::QUALITY]['low'] 197 ); 198 fwrite($fileHandle, implode(",", $row) . PHP_EOL); 199 } 200 } 201 if (!empty($fileHandle)) { 202 fclose($fileHandle); 203 } 204 205 } 206 207 208 209 private function syncPages() 210 { 211 $sqlite = Sqlite::getSqlite(); 212 $res = $sqlite->query("select ID from pages"); 213 if (!$res) { 214 throw new \RuntimeException("An exception has occurred with the alias selection query"); 215 } 216 $res2arr = $sqlite->res2arr($res); 217 $sqlite->res_close($res); 218 foreach ($res2arr as $row) { 219 $id = $row['ID']; 220 if (!page_exists($id)) { 221 echo 'Page does not exist on the file system. Deleted from the database (' . $id . ")\n"; 222 Page::createPageFromId($id)->deleteInDb(); 223 } 224 } 225 226 227 } 228} 229