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\Page; 16use ComboStrap\Sqlite; 17use splitbrain\phpcli\Options; 18 19require_once(__DIR__ . '/class/Analytics.php'); 20 21/** 22 * The memory of the server 128 is not enough 23 */ 24ini_set('memory_limit', '256M'); 25 26/** 27 * Class cli_plugin_combo 28 * 29 * This is a cli: 30 * https://www.dokuwiki.org/devel:cli_plugins#example 31 * 32 * Usage: 33 * 34 * ``` 35 * docker exec -ti $(CONTAINER) /bin/bash 36 * ./bin/plugin.php combo -c 37 * ``` 38 * or via the IDE 39 * 40 * 41 * Example: 42 * https://www.dokuwiki.org/tips:grapher 43 * 44 */ 45class cli_plugin_combo extends DokuWiki_CLI_Plugin 46{ 47 const ANALYTICS = "analytics"; 48 const SYNC = "sync"; 49 50 /** 51 * register options and arguments 52 * @param Options $options 53 */ 54 protected function setup(Options $options) 55 { 56 $options->setHelp( 57 "Manage the analytics database\n\n" . 58 "analytics\n" . 59 "sync" 60 ); 61 $options->registerOption('version', 'print version', 'v'); 62 $options->registerCommand(self::ANALYTICS, "Update the analytics data"); 63 $options->registerOption( 64 'namespaces', 65 "If no namespace is given, the root namespace is assumed.", 66 'n', 67 true 68 ); 69 $options->registerOption( 70 'output', 71 "Optional, where to store the analytical data as csv eg. a filename.", 72 'o', 'file'); 73 $options->registerOption( 74 'cache', 75 "Optional, returns from the cache if set", 76 'c', false); 77 $options->registerOption( 78 'dry', 79 "Optional, dry-run", 80 'd', false); 81 $options->registerCommand(self::SYNC, "Sync the database"); 82 83 } 84 85 /** 86 * The main entry 87 * @param Options $options 88 */ 89 protected function main(Options $options) 90 { 91 92 $namespaces = array_map('cleanID', $options->getArgs()); 93 if (!count($namespaces)) $namespaces = array(''); //import from top 94 95 $cache = $options->getOpt('cache', false); 96 $depth = $options->getOpt('depth', 0); 97 switch ($options->getCmd()) { 98 case self::ANALYTICS: 99 $output = $options->getOpt('output', ''); 100 //if ($output == '-') $output = 'php://stdout'; 101 $this->updateAnalyticsData($namespaces, $output, $cache, $depth); 102 break; 103 case self::SYNC: 104 $this->syncPages(); 105 break; 106 default: 107 throw new \RuntimeException("Command unknown (" . $options->getCmd() . ")"); 108 } 109 110 111 } 112 113 /** 114 * @param array $namespaces 115 * @param $output 116 * @param bool $cache 117 * @param int $depth recursion depth. 0 for unlimited 118 */ 119 private function updateAnalyticsData($namespaces = array(), $output = null, $cache = false, $depth = 0) 120 { 121 122 $fileHandle = null; 123 if (!empty($output)) { 124 $fileHandle = @fopen($output, 'w'); 125 if (!$fileHandle) $this->fatal("Failed to open $output"); 126 } 127 128 $pages = $this->findPages($namespaces, $depth); 129 130 131 if (!empty($fileHandle)) { 132 $header = array( 133 'id', 134 'backlinks', 135 'broken_links', 136 'changes', 137 'chars', 138 'external_links', 139 'external_medias', 140 'h1', 141 'h2', 142 'h3', 143 'h4', 144 'h5', 145 'internal_links', 146 'internal_medias', 147 'words', 148 'score' 149 ); 150 fwrite($fileHandle, implode(",", $header) . PHP_EOL); 151 } 152 while ($page = array_shift($pages)) { 153 $id = $page['id']; 154 155 // Run as admin to overcome the fact that 156 // anonymous user cannot set all links and backlinnks 157 global $USERINFO; 158 $USERINFO['grps'] = array('admin'); 159 160 161 echo 'Processing the page ' . $id . "\n"; 162 163 $data = Analytics::processAndGetDataAsArray($id, $cache); 164 if (!empty($fileHandle)) { 165 $statistics = $data[Analytics::STATISTICS]; 166 $row = array( 167 'id' => $id, 168 'backlinks' => $statistics[Analytics::INTERNAL_BACKLINKS_COUNT], 169 'broken_links' => $statistics[Analytics::INTERNAL_LINKS_BROKEN_COUNT], 170 'changes' => $statistics[Analytics::EDITS_COUNT], 171 'chars' => $statistics[Analytics::CHARS_COUNT], 172 'external_links' => $statistics[Analytics::EXTERNAL_LINKS_COUNT], 173 'external_medias' => $statistics[Analytics::EXTERNAL_MEDIAS], 174 'h1' => $statistics[Analytics::HEADERS_COUNT]['h1'], 175 'h2' => $statistics[Analytics::HEADERS_COUNT]['h2'], 176 'h3' => $statistics[Analytics::HEADERS_COUNT]['h3'], 177 'h4' => $statistics[Analytics::HEADERS_COUNT]['h4'], 178 'h5' => $statistics[Analytics::HEADERS_COUNT]['h5'], 179 'internal_links' => $statistics[Analytics::INTERNAL_LINKS_COUNT], 180 'internal_medias' => $statistics[Analytics::INTERNAL_MEDIAS_COUNT], 181 'words' => $statistics[Analytics::WORDS_COUNT], 182 'low' => $data[Analytics::QUALITY]['low'] 183 ); 184 fwrite($fileHandle, implode(",", $row) . PHP_EOL); 185 } 186 } 187 if (!empty($fileHandle)) { 188 fclose($fileHandle); 189 } 190 191 } 192 193 /** 194 * Find the pages in the tree 195 * @param $namespaces 196 * @param $depth 197 * @return array 198 */ 199 private function findPages($namespaces = array(), $depth = 0) 200 { 201 global $conf; 202 $datadir = $conf['datadir']; 203 204 $pages = array(); 205 foreach ($namespaces as $ns) { 206 207 search( 208 $pages, 209 $datadir, 210 'search_universal', 211 array( 212 'depth' => $depth, 213 'listfiles' => true, 214 'listdirs' => false, 215 'pagesonly' => true, 216 'skipacl' => true, 217 'firsthead' => false, 218 'meta' => false, 219 ), 220 str_replace(':', '/', $ns) 221 ); 222 223 // add the ns start page 224 if ($ns && page_exists($ns)) { 225 $pages[] = array( 226 'id' => $ns, 227 'ns' => getNS($ns), 228 'title' => p_get_first_heading($ns, false), 229 'size' => filesize(wikiFN($ns)), 230 'mtime' => filemtime(wikiFN($ns)), 231 'perm' => 16, 232 'type' => 'f', 233 'level' => 0, 234 'open' => 1, 235 ); 236 } 237 238 } 239 return $pages; 240 } 241 242 private function syncPages() 243 { 244 $sqlite = Sqlite::getSqlite(); 245 $res = $sqlite->query("select ID from pages"); 246 if (!$res) { 247 throw new \RuntimeException("An exception has occurred with the alias selection query"); 248 } 249 $res2arr = $sqlite->res2arr($res); 250 $sqlite->res_close($res); 251 foreach ($res2arr as $row) { 252 $id = $row['ID']; 253 if (!page_exists($id)){ 254 echo 'Page does not exist on the file system. Deleted from the database (' . $id . ")\n"; 255 Page::createFromId($id)->deleteInDb(); 256 } 257 } 258 259 260 } 261} 262