├── .github └── FUNDING.yml ├── .gitignore ├── LICENSE ├── README.md ├── cache ├── .gitignore └── README.md ├── composer.json ├── composer.lock ├── public ├── .htaccess ├── favicon.ico ├── icons │ ├── android-chrome-192x192.png │ ├── android-chrome-512x512.png │ ├── apple-touch-icon.png │ ├── favicon-16x16.png │ ├── favicon-32x32.png │ └── site.webmanifest ├── index.php ├── robots.txt └── style.css ├── src ├── Base.php ├── JustRefs.php ├── Mediawiki.php ├── Refresh.php ├── Search.php ├── Template.php └── Topic.php └── templates ├── about.php ├── custom ├── .gitignore └── README.md ├── footer.php ├── header.php ├── home.php ├── html_head.php └── topic.php /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: attogram 4 | patreon: # Replace with a single Patreon username 5 | open_collective: # Replace with a single Open Collective username 6 | ko_fi: # Replace with a single Ko-fi username 7 | tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel 8 | community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry 9 | liberapay: # Replace with a single Liberapay username 10 | issuehunt: # Replace with a single IssueHunt username 11 | otechie: # Replace with a single Otechie username 12 | custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2'] 13 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | vendor/ 2 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Attogram Project 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Just Refs 2 | 3 | Welcome to **Just Refs** - _The Naked Wikipedia_ 4 | 5 | Live Demo: 6 | 7 | Github Repository: 8 | 9 | [![Maintainability](https://api.codeclimate.com/v1/badges/c38dead93fac4b544a9e/maintainability)](https://codeclimate.com/github/attogram/justrefs/maintainability) 10 | [![Latest Stable Version](https://poser.pugx.org/attogram/justrefs/v/stable)](https://packagist.org/packages/attogram/justrefs) 11 | [![Total Downloads](https://poser.pugx.org/attogram/justrefs/downloads)](https://packagist.org/packages/attogram/justrefs) 12 | [![License](https://poser.pugx.org/attogram/justrefs/license)](https://packagist.org/packages/attogram/justrefs) 13 | 14 | ## Goal 15 | 16 | The goal of the **Just Refs** project is to help students and researchers 17 | by extracting lists of **references** and **related topics** from any 18 | page on the English Wikipedia. This removes the distraction of 19 | the prose written by others, and allows concentrating 20 | exclusively on judging the quality of the references. 21 | 22 | ## Disclaimer 23 | 24 | **Just Refs** is not affiliated with the Wikimedia Foundation, or any Wikipedia website. 25 | -------------------------------------------------------------------------------- /cache/.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | !.gitignore 3 | !README.md 4 | -------------------------------------------------------------------------------- /cache/README.md: -------------------------------------------------------------------------------- 1 | # Just Refs - Cache Directory 2 | 3 | Cached files directory 4 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "attogram/justrefs", 3 | "description": "Just Refs - extract lists of references and related topics from any page on the English Wikipedia.", 4 | "type": "project", 5 | "license": "MIT", 6 | "require": { 7 | "php": "^7", 8 | "lib-curl": "*", 9 | "attogram/router": "^4.1.3", 10 | "attogram/file-cache-cleaner": "^2.5", 11 | "illuminate/cache": "^7.2", 12 | "illuminate/filesystem": "^7.2" 13 | }, 14 | "autoload": { 15 | "psr-4": { 16 | "Attogram\\Justrefs\\": "src/" 17 | } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /composer.lock: -------------------------------------------------------------------------------- 1 | { 2 | "_readme": [ 3 | "This file locks the dependencies of your project to a known state", 4 | "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", 5 | "This file is @generated automatically" 6 | ], 7 | "content-hash": "fb59f0fe21190c723d6996ec61d9a212", 8 | "packages": [ 9 | { 10 | "name": "attogram/file-cache-cleaner", 11 | "version": "v2.5.0", 12 | "source": { 13 | "type": "git", 14 | "url": "https://github.com/attogram/file-cache-cleaner.git", 15 | "reference": "877b1cb97b2ccb478f1472e4cb42598c1030a99f" 16 | }, 17 | "dist": { 18 | "type": "zip", 19 | "url": "https://api.github.com/repos/attogram/file-cache-cleaner/zipball/877b1cb97b2ccb478f1472e4cb42598c1030a99f", 20 | "reference": "877b1cb97b2ccb478f1472e4cb42598c1030a99f", 21 | "shasum": "" 22 | }, 23 | "require": { 24 | "php": "^7" 25 | }, 26 | "bin": [ 27 | "bin/file-cache-cleaner" 28 | ], 29 | "type": "library", 30 | "autoload": { 31 | "psr-4": { 32 | "Attogram\\Cache\\": "src/" 33 | } 34 | }, 35 | "notification-url": "https://packagist.org/downloads/", 36 | "license": [ 37 | "MIT" 38 | ], 39 | "description": "Delete expired Laravel-style `Illuminate\\Cache` cache files and empty directories", 40 | "time": "2020-05-18T12:47:24+00:00" 41 | }, 42 | { 43 | "name": "attogram/router", 44 | "version": "v4.1.3", 45 | "source": { 46 | "type": "git", 47 | "url": "https://github.com/attogram/router.git", 48 | "reference": "4018f7842abfe2f91fb649c7eed7588238687a72" 49 | }, 50 | "dist": { 51 | "type": "zip", 52 | "url": "https://api.github.com/repos/attogram/router/zipball/4018f7842abfe2f91fb649c7eed7588238687a72", 53 | "reference": "4018f7842abfe2f91fb649c7eed7588238687a72", 54 | "shasum": "" 55 | }, 56 | "require": { 57 | "php": "^7.0" 58 | }, 59 | "type": "library", 60 | "autoload": { 61 | "psr-4": { 62 | "Attogram\\Router\\": "src/" 63 | } 64 | }, 65 | "notification-url": "https://packagist.org/downloads/", 66 | "license": [ 67 | "MIT" 68 | ], 69 | "authors": [ 70 | { 71 | "name": "Attogram Project", 72 | "homepage": "https://github.com/attogram", 73 | "role": "Developer" 74 | } 75 | ], 76 | "description": "Attogram Router for PHP 7 - small, flexible, and surprisingly powerful", 77 | "homepage": "https://github.com/attogram/router", 78 | "keywords": [ 79 | "One class", 80 | "php 7", 81 | "router" 82 | ], 83 | "time": "2020-02-15T17:00:15+00:00" 84 | }, 85 | { 86 | "name": "doctrine/inflector", 87 | "version": "2.0.1", 88 | "source": { 89 | "type": "git", 90 | "url": "https://github.com/doctrine/inflector.git", 91 | "reference": "18b995743e7ec8b15fd6efc594f0fa3de4bfe6d7" 92 | }, 93 | "dist": { 94 | "type": "zip", 95 | "url": "https://api.github.com/repos/doctrine/inflector/zipball/18b995743e7ec8b15fd6efc594f0fa3de4bfe6d7", 96 | "reference": "18b995743e7ec8b15fd6efc594f0fa3de4bfe6d7", 97 | "shasum": "" 98 | }, 99 | "require": { 100 | "php": "^7.2" 101 | }, 102 | "require-dev": { 103 | "doctrine/coding-standard": "^7.0", 104 | "phpstan/phpstan": "^0.11", 105 | "phpstan/phpstan-phpunit": "^0.11", 106 | "phpstan/phpstan-strict-rules": "^0.11", 107 | "phpunit/phpunit": "^7.0 || ^8.0 || ^9.0" 108 | }, 109 | "type": "library", 110 | "extra": { 111 | "branch-alias": { 112 | "dev-master": "2.0.x-dev" 113 | } 114 | }, 115 | "autoload": { 116 | "psr-4": { 117 | "Doctrine\\Inflector\\": "lib/Doctrine/Inflector" 118 | } 119 | }, 120 | "notification-url": "https://packagist.org/downloads/", 121 | "license": [ 122 | "MIT" 123 | ], 124 | "authors": [ 125 | { 126 | "name": "Guilherme Blanco", 127 | "email": "guilhermeblanco@gmail.com" 128 | }, 129 | { 130 | "name": "Roman Borschel", 131 | "email": "roman@code-factory.org" 132 | }, 133 | { 134 | "name": "Benjamin Eberlei", 135 | "email": "kontakt@beberlei.de" 136 | }, 137 | { 138 | "name": "Jonathan Wage", 139 | "email": "jonwage@gmail.com" 140 | }, 141 | { 142 | "name": "Johannes Schmitt", 143 | "email": "schmittjoh@gmail.com" 144 | } 145 | ], 146 | "description": "PHP Doctrine Inflector is a small library that can perform string manipulations with regard to upper/lowercase and singular/plural forms of words.", 147 | "homepage": "https://www.doctrine-project.org/projects/inflector.html", 148 | "keywords": [ 149 | "inflection", 150 | "inflector", 151 | "lowercase", 152 | "manipulation", 153 | "php", 154 | "plural", 155 | "singular", 156 | "strings", 157 | "uppercase", 158 | "words" 159 | ], 160 | "time": "2020-05-11T11:25:59+00:00" 161 | }, 162 | { 163 | "name": "illuminate/cache", 164 | "version": "v7.11.0", 165 | "source": { 166 | "type": "git", 167 | "url": "https://github.com/illuminate/cache.git", 168 | "reference": "88ba9ccf6c9373ecf4cf39f43b4e6f3f313d8bab" 169 | }, 170 | "dist": { 171 | "type": "zip", 172 | "url": "https://api.github.com/repos/illuminate/cache/zipball/88ba9ccf6c9373ecf4cf39f43b4e6f3f313d8bab", 173 | "reference": "88ba9ccf6c9373ecf4cf39f43b4e6f3f313d8bab", 174 | "shasum": "" 175 | }, 176 | "require": { 177 | "illuminate/contracts": "^7.0", 178 | "illuminate/support": "^7.0", 179 | "php": "^7.2.5" 180 | }, 181 | "suggest": { 182 | "ext-memcached": "Required to use the memcache cache driver.", 183 | "illuminate/database": "Required to use the database cache driver (^7.0).", 184 | "illuminate/filesystem": "Required to use the file cache driver (^7.0).", 185 | "illuminate/redis": "Required to use the redis cache driver (^7.0).", 186 | "symfony/cache": "Required to PSR-6 cache bridge (^5.0)." 187 | }, 188 | "type": "library", 189 | "extra": { 190 | "branch-alias": { 191 | "dev-master": "7.x-dev" 192 | } 193 | }, 194 | "autoload": { 195 | "psr-4": { 196 | "Illuminate\\Cache\\": "" 197 | } 198 | }, 199 | "notification-url": "https://packagist.org/downloads/", 200 | "license": [ 201 | "MIT" 202 | ], 203 | "authors": [ 204 | { 205 | "name": "Taylor Otwell", 206 | "email": "taylor@laravel.com" 207 | } 208 | ], 209 | "description": "The Illuminate Cache package.", 210 | "homepage": "https://laravel.com", 211 | "time": "2020-05-08T20:55:59+00:00" 212 | }, 213 | { 214 | "name": "illuminate/contracts", 215 | "version": "v7.11.0", 216 | "source": { 217 | "type": "git", 218 | "url": "https://github.com/illuminate/contracts.git", 219 | "reference": "5681c90368ffafaaa2e12c42112e344281466f23" 220 | }, 221 | "dist": { 222 | "type": "zip", 223 | "url": "https://api.github.com/repos/illuminate/contracts/zipball/5681c90368ffafaaa2e12c42112e344281466f23", 224 | "reference": "5681c90368ffafaaa2e12c42112e344281466f23", 225 | "shasum": "" 226 | }, 227 | "require": { 228 | "php": "^7.2.5", 229 | "psr/container": "^1.0", 230 | "psr/simple-cache": "^1.0" 231 | }, 232 | "type": "library", 233 | "extra": { 234 | "branch-alias": { 235 | "dev-master": "7.x-dev" 236 | } 237 | }, 238 | "autoload": { 239 | "psr-4": { 240 | "Illuminate\\Contracts\\": "" 241 | } 242 | }, 243 | "notification-url": "https://packagist.org/downloads/", 244 | "license": [ 245 | "MIT" 246 | ], 247 | "authors": [ 248 | { 249 | "name": "Taylor Otwell", 250 | "email": "taylor@laravel.com" 251 | } 252 | ], 253 | "description": "The Illuminate Contracts package.", 254 | "homepage": "https://laravel.com", 255 | "time": "2020-05-06T13:37:50+00:00" 256 | }, 257 | { 258 | "name": "illuminate/filesystem", 259 | "version": "v7.11.0", 260 | "source": { 261 | "type": "git", 262 | "url": "https://github.com/illuminate/filesystem.git", 263 | "reference": "43d0aafb620151b9c88331e13e1660564fc56244" 264 | }, 265 | "dist": { 266 | "type": "zip", 267 | "url": "https://api.github.com/repos/illuminate/filesystem/zipball/43d0aafb620151b9c88331e13e1660564fc56244", 268 | "reference": "43d0aafb620151b9c88331e13e1660564fc56244", 269 | "shasum": "" 270 | }, 271 | "require": { 272 | "illuminate/contracts": "^7.0", 273 | "illuminate/support": "^7.0", 274 | "php": "^7.2.5", 275 | "symfony/finder": "^5.0" 276 | }, 277 | "suggest": { 278 | "illuminate/http": "Required for handling uploaded files (^7.0)", 279 | "league/flysystem": "Required to use the Flysystem local and FTP drivers (^1.0).", 280 | "league/flysystem-aws-s3-v3": "Required to use the Flysystem S3 driver (^1.0).", 281 | "league/flysystem-cached-adapter": "Required to use the Flysystem cache (^1.0).", 282 | "league/flysystem-sftp": "Required to use the Flysystem SFTP driver (^1.0).", 283 | "psr/http-message": "Required to allow Storage::put to accept a StreamInterface (^1.0)" 284 | }, 285 | "type": "library", 286 | "extra": { 287 | "branch-alias": { 288 | "dev-master": "7.x-dev" 289 | } 290 | }, 291 | "autoload": { 292 | "psr-4": { 293 | "Illuminate\\Filesystem\\": "" 294 | } 295 | }, 296 | "notification-url": "https://packagist.org/downloads/", 297 | "license": [ 298 | "MIT" 299 | ], 300 | "authors": [ 301 | { 302 | "name": "Taylor Otwell", 303 | "email": "taylor@laravel.com" 304 | } 305 | ], 306 | "description": "The Illuminate Filesystem package.", 307 | "homepage": "https://laravel.com", 308 | "time": "2020-03-26T19:53:03+00:00" 309 | }, 310 | { 311 | "name": "illuminate/support", 312 | "version": "v7.11.0", 313 | "source": { 314 | "type": "git", 315 | "url": "https://github.com/illuminate/support.git", 316 | "reference": "5458d0d0048f185b3d4a911e5d21d2bb2fb336ca" 317 | }, 318 | "dist": { 319 | "type": "zip", 320 | "url": "https://api.github.com/repos/illuminate/support/zipball/5458d0d0048f185b3d4a911e5d21d2bb2fb336ca", 321 | "reference": "5458d0d0048f185b3d4a911e5d21d2bb2fb336ca", 322 | "shasum": "" 323 | }, 324 | "require": { 325 | "doctrine/inflector": "^1.4|^2.0", 326 | "ext-json": "*", 327 | "ext-mbstring": "*", 328 | "illuminate/contracts": "^7.0", 329 | "nesbot/carbon": "^2.17", 330 | "php": "^7.2.5", 331 | "voku/portable-ascii": "^1.4.8" 332 | }, 333 | "conflict": { 334 | "tightenco/collect": "<5.5.33" 335 | }, 336 | "suggest": { 337 | "illuminate/filesystem": "Required to use the composer class (^7.0).", 338 | "moontoast/math": "Required to use ordered UUIDs (^1.1).", 339 | "ramsey/uuid": "Required to use Str::uuid() (^3.7|^4.0).", 340 | "symfony/process": "Required to use the composer class (^5.0).", 341 | "symfony/var-dumper": "Required to use the dd function (^5.0).", 342 | "vlucas/phpdotenv": "Required to use the Env class and env helper (^4.0)." 343 | }, 344 | "type": "library", 345 | "extra": { 346 | "branch-alias": { 347 | "dev-master": "7.x-dev" 348 | } 349 | }, 350 | "autoload": { 351 | "psr-4": { 352 | "Illuminate\\Support\\": "" 353 | }, 354 | "files": [ 355 | "helpers.php" 356 | ] 357 | }, 358 | "notification-url": "https://packagist.org/downloads/", 359 | "license": [ 360 | "MIT" 361 | ], 362 | "authors": [ 363 | { 364 | "name": "Taylor Otwell", 365 | "email": "taylor@laravel.com" 366 | } 367 | ], 368 | "description": "The Illuminate Support package.", 369 | "homepage": "https://laravel.com", 370 | "time": "2020-05-12T14:31:35+00:00" 371 | }, 372 | { 373 | "name": "nesbot/carbon", 374 | "version": "2.34.0", 375 | "source": { 376 | "type": "git", 377 | "url": "https://github.com/briannesbitt/Carbon.git", 378 | "reference": "52ea68aebbad8a3b27b5d24e4c66ebe1933f8399" 379 | }, 380 | "dist": { 381 | "type": "zip", 382 | "url": "https://api.github.com/repos/briannesbitt/Carbon/zipball/52ea68aebbad8a3b27b5d24e4c66ebe1933f8399", 383 | "reference": "52ea68aebbad8a3b27b5d24e4c66ebe1933f8399", 384 | "shasum": "" 385 | }, 386 | "require": { 387 | "ext-json": "*", 388 | "php": "^7.1.8 || ^8.0", 389 | "symfony/polyfill-mbstring": "^1.0", 390 | "symfony/translation": "^3.4 || ^4.0 || ^5.0" 391 | }, 392 | "require-dev": { 393 | "doctrine/orm": "^2.7", 394 | "friendsofphp/php-cs-fixer": "^2.14 || ^3.0", 395 | "kylekatarnls/multi-tester": "^1.1", 396 | "phpmd/phpmd": "^2.8", 397 | "phpstan/phpstan": "^0.11", 398 | "phpunit/phpunit": "^7.5 || ^8.0", 399 | "squizlabs/php_codesniffer": "^3.4" 400 | }, 401 | "bin": [ 402 | "bin/carbon" 403 | ], 404 | "type": "library", 405 | "extra": { 406 | "branch-alias": { 407 | "dev-master": "2.x-dev", 408 | "dev-3.x": "3.x-dev" 409 | }, 410 | "laravel": { 411 | "providers": [ 412 | "Carbon\\Laravel\\ServiceProvider" 413 | ] 414 | } 415 | }, 416 | "autoload": { 417 | "psr-4": { 418 | "Carbon\\": "src/Carbon/" 419 | } 420 | }, 421 | "notification-url": "https://packagist.org/downloads/", 422 | "license": [ 423 | "MIT" 424 | ], 425 | "authors": [ 426 | { 427 | "name": "Brian Nesbitt", 428 | "email": "brian@nesbot.com", 429 | "homepage": "http://nesbot.com" 430 | }, 431 | { 432 | "name": "kylekatarnls", 433 | "homepage": "http://github.com/kylekatarnls" 434 | } 435 | ], 436 | "description": "An API extension for DateTime that supports 281 different languages.", 437 | "homepage": "http://carbon.nesbot.com", 438 | "keywords": [ 439 | "date", 440 | "datetime", 441 | "time" 442 | ], 443 | "time": "2020-05-12T19:53:34+00:00" 444 | }, 445 | { 446 | "name": "psr/container", 447 | "version": "1.0.0", 448 | "source": { 449 | "type": "git", 450 | "url": "https://github.com/php-fig/container.git", 451 | "reference": "b7ce3b176482dbbc1245ebf52b181af44c2cf55f" 452 | }, 453 | "dist": { 454 | "type": "zip", 455 | "url": "https://api.github.com/repos/php-fig/container/zipball/b7ce3b176482dbbc1245ebf52b181af44c2cf55f", 456 | "reference": "b7ce3b176482dbbc1245ebf52b181af44c2cf55f", 457 | "shasum": "" 458 | }, 459 | "require": { 460 | "php": ">=5.3.0" 461 | }, 462 | "type": "library", 463 | "extra": { 464 | "branch-alias": { 465 | "dev-master": "1.0.x-dev" 466 | } 467 | }, 468 | "autoload": { 469 | "psr-4": { 470 | "Psr\\Container\\": "src/" 471 | } 472 | }, 473 | "notification-url": "https://packagist.org/downloads/", 474 | "license": [ 475 | "MIT" 476 | ], 477 | "authors": [ 478 | { 479 | "name": "PHP-FIG", 480 | "homepage": "http://www.php-fig.org/" 481 | } 482 | ], 483 | "description": "Common Container Interface (PHP FIG PSR-11)", 484 | "homepage": "https://github.com/php-fig/container", 485 | "keywords": [ 486 | "PSR-11", 487 | "container", 488 | "container-interface", 489 | "container-interop", 490 | "psr" 491 | ], 492 | "time": "2017-02-14T16:28:37+00:00" 493 | }, 494 | { 495 | "name": "psr/simple-cache", 496 | "version": "1.0.1", 497 | "source": { 498 | "type": "git", 499 | "url": "https://github.com/php-fig/simple-cache.git", 500 | "reference": "408d5eafb83c57f6365a3ca330ff23aa4a5fa39b" 501 | }, 502 | "dist": { 503 | "type": "zip", 504 | "url": "https://api.github.com/repos/php-fig/simple-cache/zipball/408d5eafb83c57f6365a3ca330ff23aa4a5fa39b", 505 | "reference": "408d5eafb83c57f6365a3ca330ff23aa4a5fa39b", 506 | "shasum": "" 507 | }, 508 | "require": { 509 | "php": ">=5.3.0" 510 | }, 511 | "type": "library", 512 | "extra": { 513 | "branch-alias": { 514 | "dev-master": "1.0.x-dev" 515 | } 516 | }, 517 | "autoload": { 518 | "psr-4": { 519 | "Psr\\SimpleCache\\": "src/" 520 | } 521 | }, 522 | "notification-url": "https://packagist.org/downloads/", 523 | "license": [ 524 | "MIT" 525 | ], 526 | "authors": [ 527 | { 528 | "name": "PHP-FIG", 529 | "homepage": "http://www.php-fig.org/" 530 | } 531 | ], 532 | "description": "Common interfaces for simple caching", 533 | "keywords": [ 534 | "cache", 535 | "caching", 536 | "psr", 537 | "psr-16", 538 | "simple-cache" 539 | ], 540 | "time": "2017-10-23T01:57:42+00:00" 541 | }, 542 | { 543 | "name": "symfony/finder", 544 | "version": "v5.0.8", 545 | "source": { 546 | "type": "git", 547 | "url": "https://github.com/symfony/finder.git", 548 | "reference": "600a52c29afc0d1caa74acbec8d3095ca7e9910d" 549 | }, 550 | "dist": { 551 | "type": "zip", 552 | "url": "https://api.github.com/repos/symfony/finder/zipball/600a52c29afc0d1caa74acbec8d3095ca7e9910d", 553 | "reference": "600a52c29afc0d1caa74acbec8d3095ca7e9910d", 554 | "shasum": "" 555 | }, 556 | "require": { 557 | "php": "^7.2.5" 558 | }, 559 | "type": "library", 560 | "extra": { 561 | "branch-alias": { 562 | "dev-master": "5.0-dev" 563 | } 564 | }, 565 | "autoload": { 566 | "psr-4": { 567 | "Symfony\\Component\\Finder\\": "" 568 | }, 569 | "exclude-from-classmap": [ 570 | "/Tests/" 571 | ] 572 | }, 573 | "notification-url": "https://packagist.org/downloads/", 574 | "license": [ 575 | "MIT" 576 | ], 577 | "authors": [ 578 | { 579 | "name": "Fabien Potencier", 580 | "email": "fabien@symfony.com" 581 | }, 582 | { 583 | "name": "Symfony Community", 584 | "homepage": "https://symfony.com/contributors" 585 | } 586 | ], 587 | "description": "Symfony Finder Component", 588 | "homepage": "https://symfony.com", 589 | "time": "2020-03-27T16:56:45+00:00" 590 | }, 591 | { 592 | "name": "symfony/polyfill-mbstring", 593 | "version": "v1.17.0", 594 | "source": { 595 | "type": "git", 596 | "url": "https://github.com/symfony/polyfill-mbstring.git", 597 | "reference": "fa79b11539418b02fc5e1897267673ba2c19419c" 598 | }, 599 | "dist": { 600 | "type": "zip", 601 | "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/fa79b11539418b02fc5e1897267673ba2c19419c", 602 | "reference": "fa79b11539418b02fc5e1897267673ba2c19419c", 603 | "shasum": "" 604 | }, 605 | "require": { 606 | "php": ">=5.3.3" 607 | }, 608 | "suggest": { 609 | "ext-mbstring": "For best performance" 610 | }, 611 | "type": "library", 612 | "extra": { 613 | "branch-alias": { 614 | "dev-master": "1.17-dev" 615 | } 616 | }, 617 | "autoload": { 618 | "psr-4": { 619 | "Symfony\\Polyfill\\Mbstring\\": "" 620 | }, 621 | "files": [ 622 | "bootstrap.php" 623 | ] 624 | }, 625 | "notification-url": "https://packagist.org/downloads/", 626 | "license": [ 627 | "MIT" 628 | ], 629 | "authors": [ 630 | { 631 | "name": "Nicolas Grekas", 632 | "email": "p@tchwork.com" 633 | }, 634 | { 635 | "name": "Symfony Community", 636 | "homepage": "https://symfony.com/contributors" 637 | } 638 | ], 639 | "description": "Symfony polyfill for the Mbstring extension", 640 | "homepage": "https://symfony.com", 641 | "keywords": [ 642 | "compatibility", 643 | "mbstring", 644 | "polyfill", 645 | "portable", 646 | "shim" 647 | ], 648 | "time": "2020-05-12T16:47:27+00:00" 649 | }, 650 | { 651 | "name": "symfony/translation", 652 | "version": "v5.0.8", 653 | "source": { 654 | "type": "git", 655 | "url": "https://github.com/symfony/translation.git", 656 | "reference": "c3879db7a68fe3e12b41263b05879412c87b27fd" 657 | }, 658 | "dist": { 659 | "type": "zip", 660 | "url": "https://api.github.com/repos/symfony/translation/zipball/c3879db7a68fe3e12b41263b05879412c87b27fd", 661 | "reference": "c3879db7a68fe3e12b41263b05879412c87b27fd", 662 | "shasum": "" 663 | }, 664 | "require": { 665 | "php": "^7.2.5", 666 | "symfony/polyfill-mbstring": "~1.0", 667 | "symfony/translation-contracts": "^2" 668 | }, 669 | "conflict": { 670 | "symfony/config": "<4.4", 671 | "symfony/dependency-injection": "<5.0", 672 | "symfony/http-kernel": "<5.0", 673 | "symfony/twig-bundle": "<5.0", 674 | "symfony/yaml": "<4.4" 675 | }, 676 | "provide": { 677 | "symfony/translation-implementation": "2.0" 678 | }, 679 | "require-dev": { 680 | "psr/log": "~1.0", 681 | "symfony/config": "^4.4|^5.0", 682 | "symfony/console": "^4.4|^5.0", 683 | "symfony/dependency-injection": "^5.0", 684 | "symfony/finder": "^4.4|^5.0", 685 | "symfony/http-kernel": "^5.0", 686 | "symfony/intl": "^4.4|^5.0", 687 | "symfony/service-contracts": "^1.1.2|^2", 688 | "symfony/yaml": "^4.4|^5.0" 689 | }, 690 | "suggest": { 691 | "psr/log-implementation": "To use logging capability in translator", 692 | "symfony/config": "", 693 | "symfony/yaml": "" 694 | }, 695 | "type": "library", 696 | "extra": { 697 | "branch-alias": { 698 | "dev-master": "5.0-dev" 699 | } 700 | }, 701 | "autoload": { 702 | "psr-4": { 703 | "Symfony\\Component\\Translation\\": "" 704 | }, 705 | "exclude-from-classmap": [ 706 | "/Tests/" 707 | ] 708 | }, 709 | "notification-url": "https://packagist.org/downloads/", 710 | "license": [ 711 | "MIT" 712 | ], 713 | "authors": [ 714 | { 715 | "name": "Fabien Potencier", 716 | "email": "fabien@symfony.com" 717 | }, 718 | { 719 | "name": "Symfony Community", 720 | "homepage": "https://symfony.com/contributors" 721 | } 722 | ], 723 | "description": "Symfony Translation Component", 724 | "homepage": "https://symfony.com", 725 | "time": "2020-04-12T16:45:47+00:00" 726 | }, 727 | { 728 | "name": "symfony/translation-contracts", 729 | "version": "v2.0.1", 730 | "source": { 731 | "type": "git", 732 | "url": "https://github.com/symfony/translation-contracts.git", 733 | "reference": "8cc682ac458d75557203b2f2f14b0b92e1c744ed" 734 | }, 735 | "dist": { 736 | "type": "zip", 737 | "url": "https://api.github.com/repos/symfony/translation-contracts/zipball/8cc682ac458d75557203b2f2f14b0b92e1c744ed", 738 | "reference": "8cc682ac458d75557203b2f2f14b0b92e1c744ed", 739 | "shasum": "" 740 | }, 741 | "require": { 742 | "php": "^7.2.5" 743 | }, 744 | "suggest": { 745 | "symfony/translation-implementation": "" 746 | }, 747 | "type": "library", 748 | "extra": { 749 | "branch-alias": { 750 | "dev-master": "2.0-dev" 751 | } 752 | }, 753 | "autoload": { 754 | "psr-4": { 755 | "Symfony\\Contracts\\Translation\\": "" 756 | } 757 | }, 758 | "notification-url": "https://packagist.org/downloads/", 759 | "license": [ 760 | "MIT" 761 | ], 762 | "authors": [ 763 | { 764 | "name": "Nicolas Grekas", 765 | "email": "p@tchwork.com" 766 | }, 767 | { 768 | "name": "Symfony Community", 769 | "homepage": "https://symfony.com/contributors" 770 | } 771 | ], 772 | "description": "Generic abstractions related to translation", 773 | "homepage": "https://symfony.com", 774 | "keywords": [ 775 | "abstractions", 776 | "contracts", 777 | "decoupling", 778 | "interfaces", 779 | "interoperability", 780 | "standards" 781 | ], 782 | "time": "2019-11-18T17:27:11+00:00" 783 | }, 784 | { 785 | "name": "voku/portable-ascii", 786 | "version": "1.4.10", 787 | "source": { 788 | "type": "git", 789 | "url": "https://github.com/voku/portable-ascii.git", 790 | "reference": "240e93829a5f985fab0984a6e55ae5e26b78a334" 791 | }, 792 | "dist": { 793 | "type": "zip", 794 | "url": "https://api.github.com/repos/voku/portable-ascii/zipball/240e93829a5f985fab0984a6e55ae5e26b78a334", 795 | "reference": "240e93829a5f985fab0984a6e55ae5e26b78a334", 796 | "shasum": "" 797 | }, 798 | "require": { 799 | "php": ">=7.0.0" 800 | }, 801 | "require-dev": { 802 | "phpunit/phpunit": "~6.0 || ~7.0" 803 | }, 804 | "suggest": { 805 | "ext-intl": "Use Intl for transliterator_transliterate() support" 806 | }, 807 | "type": "library", 808 | "autoload": { 809 | "psr-4": { 810 | "voku\\": "src/voku/", 811 | "voku\\tests\\": "tests/" 812 | } 813 | }, 814 | "notification-url": "https://packagist.org/downloads/", 815 | "license": [ 816 | "MIT" 817 | ], 818 | "authors": [ 819 | { 820 | "name": "Lars Moelleken", 821 | "homepage": "http://www.moelleken.org/" 822 | } 823 | ], 824 | "description": "Portable ASCII library - performance optimized (ascii) string functions for php.", 825 | "homepage": "https://github.com/voku/portable-ascii", 826 | "keywords": [ 827 | "ascii", 828 | "clean", 829 | "php" 830 | ], 831 | "time": "2020-03-13T01:23:26+00:00" 832 | } 833 | ], 834 | "packages-dev": [], 835 | "aliases": [], 836 | "minimum-stability": "stable", 837 | "stability-flags": [], 838 | "prefer-stable": false, 839 | "prefer-lowest": false, 840 | "platform": { 841 | "php": "^7", 842 | "lib-curl": "*" 843 | }, 844 | "platform-dev": [] 845 | } 846 | -------------------------------------------------------------------------------- /public/.htaccess: -------------------------------------------------------------------------------- 1 | Options +FollowSymLinks 2 | RewriteEngine On 3 | RewriteCond %{REQUEST_FILENAME} !-d 4 | RewriteCond %{REQUEST_FILENAME} !-f 5 | RewriteRule ^ index.php [L] 6 | -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/attogram/justrefs/ccb8f8794d339fc3ab772ffd8db36a4146b4d764/public/favicon.ico -------------------------------------------------------------------------------- /public/icons/android-chrome-192x192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/attogram/justrefs/ccb8f8794d339fc3ab772ffd8db36a4146b4d764/public/icons/android-chrome-192x192.png -------------------------------------------------------------------------------- /public/icons/android-chrome-512x512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/attogram/justrefs/ccb8f8794d339fc3ab772ffd8db36a4146b4d764/public/icons/android-chrome-512x512.png -------------------------------------------------------------------------------- /public/icons/apple-touch-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/attogram/justrefs/ccb8f8794d339fc3ab772ffd8db36a4146b4d764/public/icons/apple-touch-icon.png -------------------------------------------------------------------------------- /public/icons/favicon-16x16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/attogram/justrefs/ccb8f8794d339fc3ab772ffd8db36a4146b4d764/public/icons/favicon-16x16.png -------------------------------------------------------------------------------- /public/icons/favicon-32x32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/attogram/justrefs/ccb8f8794d339fc3ab772ffd8db36a4146b4d764/public/icons/favicon-32x32.png -------------------------------------------------------------------------------- /public/icons/site.webmanifest: -------------------------------------------------------------------------------- 1 | {"name":"Just Refs","short_name":"Just Refs","icons":[{"src":"/justrefs/icons/android-chrome-192x192.png","sizes":"192x192","type":"image/png"},{"src":"/justrefs/icons/android-chrome-512x512.png","sizes":"512x512","type":"image/png"}],"theme_color":"#ffffff","background_color":"#ffffff","display":"standalone"} -------------------------------------------------------------------------------- /public/index.php: -------------------------------------------------------------------------------- 1 | route(); 16 | -------------------------------------------------------------------------------- /public/robots.txt: -------------------------------------------------------------------------------- 1 | User-agent: * 2 | Disallow: /justrefs/refresh/ 3 | -------------------------------------------------------------------------------- /public/style.css: -------------------------------------------------------------------------------- 1 | body { 2 | background-color:lightgrey; 3 | font-family:monospace; 4 | margin:0; 5 | padding:0; 6 | } 7 | footer { 8 | background-color:lightgrey; 9 | padding:10px; 10 | } 11 | ol, ul { 12 | list-style-position:inside; 13 | margin:0; 14 | padding:0; 15 | } 16 | ol { 17 | list-style-type:decimal-leading-zero; 18 | padding:7px 0 0 0; 19 | } 20 | pre { 21 | margin:0; 22 | } 23 | a:link { 24 | color:darkblue; 25 | text-decoration:none; 26 | } 27 | a:visited { 28 | color:darkblue; 29 | text-decoration:none; 30 | } 31 | a:hover { 32 | background-color:yellow; 33 | color:black; 34 | } 35 | a:active { 36 | background-color:yellow; 37 | color:black; 38 | } 39 | .missing, a.missing { 40 | color:blueviolet; 41 | } 42 | .head { 43 | background-color:lightgrey; 44 | font-size:115%; 45 | padding:8px 8px 8px 8px; 46 | } 47 | .body { 48 | background-color:white; 49 | padding:10px 10px 10px 30px; 50 | } 51 | .flex-container { 52 | display:flex; 53 | } 54 | .lcol { 55 | border-right:1px solid grey; 56 | padding-right:10px; 57 | } 58 | .rcol { 59 | padding-left:10px; 60 | } 61 | h1 { 62 | font-family:sans-serif; 63 | margin:0; 64 | padding-bottom:10px; 65 | padding-top:5px; 66 | } 67 | p { 68 | font-family:sans-serif; 69 | } 70 | hr { 71 | border-top:1px dotted lightgrey; 72 | } 73 | .red { 74 | color:darkred; 75 | } 76 | -------------------------------------------------------------------------------- /src/Base.php: -------------------------------------------------------------------------------- 1 | verbose = true; 129 | } 130 | if ($router instanceof \Attogram\Router\Router) { 131 | $this->router = $router; 132 | } 133 | if ($template instanceof Template) { 134 | $this->template = $template; 135 | } 136 | } 137 | 138 | /** 139 | * Verbose debug statement to STDOUT 140 | * @param string $message (optional) 141 | */ 142 | protected function verbose($message = '') 143 | { 144 | if ($this->verbose) { 145 | print '
' . $this->formatMessage($message) . '
'; 146 | } 147 | } 148 | 149 | /** 150 | * Error statement to STDOUT 151 | * @param string $message (optional) 152 | */ 153 | protected function error($message = '') 154 | { 155 | print '
ERROR: ' . $this->formatMessage($message) . '
'; 156 | } 157 | 158 | /** 159 | * @param string $message (optional) 160 | */ 161 | private function formatMessage($message = '') 162 | { 163 | return (new \DateTime())->format('u') . ': ' . get_class($this) 164 | . ': ' . htmlentities(print_r($message, true)); 165 | } 166 | 167 | /** 168 | * @param string $query 169 | * @return string 170 | */ 171 | public function getLink($query) 172 | { 173 | return 'r/' . $this->encodeLink($query); 174 | } 175 | 176 | /** 177 | * @param string $query 178 | * @return string 179 | * @see https://www.mediawiki.org/wiki/Manual:PAGENAMEE_encoding 180 | */ 181 | protected function encodeLink($query) 182 | { 183 | $replacers = [ 184 | ' ' => '_', 185 | '%' => '%25', // do first before any other %## replacers 186 | '"' => '%22', 187 | '&' => '%26', 188 | "'" => '%27', 189 | '+' => '%2B', 190 | '=' => '%3D', 191 | '?' => '%3F', 192 | '\\' => '%5C', 193 | '^' => '%5E', 194 | '`' => '%60', 195 | '~' => '%7E', 196 | ]; 197 | foreach ($replacers as $old => $new) { 198 | $query = str_replace($old, $new, $query); 199 | } 200 | 201 | return $query; 202 | } 203 | 204 | /** 205 | * set $this->topic to string from URL elements, or empty string 206 | * @return bool 207 | */ 208 | protected function setTopicFromUrl() 209 | { 210 | $this->topic = $this->router->getVar(0); 211 | $this->topic .= !empty($this->router->getVar(1)) ? '/' . $this->router->getVar(1) : ''; 212 | $this->topic .= !empty($this->router->getVar(2)) ? '/' . $this->router->getVar(2) : ''; 213 | $this->topic .= !empty($this->router->getVar(3)) ? '/' . $this->router->getVar(3) : ''; 214 | $this->topic .= !empty($this->router->getVar(4)) ? '/' . $this->router->getVar(4) : ''; 215 | 216 | if (!is_string($this->topic) || !strlen($this->topic)) { 217 | $this->topic = ''; 218 | 219 | return false; 220 | } 221 | // format query 222 | $this->topic = trim($this->topic); 223 | $this->topic = str_replace('_', ' ', $this->topic); 224 | $this->topic = urldecode($this->topic); 225 | if (!is_string($this->topic) || !strlen($this->topic)) { 226 | $this->topic = ''; 227 | 228 | return false; 229 | } 230 | 231 | return true; 232 | } 233 | 234 | protected function initFilesystem() 235 | { 236 | $this->filesystem = new Repository( 237 | new FileStore(new Filesystem(), self::CACHE_DIRECTORY) 238 | ); 239 | } 240 | 241 | protected function initMediawiki() 242 | { 243 | $this->mediawiki = new Mediawiki($this->verbose); 244 | } 245 | 246 | /** 247 | * @param string $name 248 | */ 249 | protected function startTimer($name) 250 | { 251 | $this->timer[$name] = microtime(true); 252 | } 253 | 254 | /** 255 | * @param string $name 256 | * @return float 257 | */ 258 | protected function endTimer($name) 259 | { 260 | if (empty($this->timer[$name])) { 261 | return 0.0; 262 | } 263 | 264 | return round(microtime(true) - $this->timer[$name], 4); 265 | } 266 | 267 | /** 268 | * @param string $message 269 | * @param string $refresh - refresh query link 270 | * @return void 271 | */ 272 | public function error404($message = 'Page Not Found', $refresh = '') 273 | { 274 | header('HTTP/1.0 404 Not Found'); 275 | $this->template->include('html_head'); 276 | $this->template->include('header'); 277 | print '

Error 404

' . $message . '

'; 278 | if ($refresh) { 279 | print '

Attempt Refresh

'; 281 | } 282 | print '
'; 283 | $this->template->include('footer'); 284 | 285 | exit; 286 | } 287 | } 288 | -------------------------------------------------------------------------------- /src/JustRefs.php: -------------------------------------------------------------------------------- 1 | startTimer('page'); 29 | $this->initRouter(); 30 | $this->initTemplate(); 31 | $match = $this->router->match(); 32 | if (!$match) { 33 | $this->error404('Page Not Found'); // exits 34 | } 35 | $this->match($match); 36 | } 37 | 38 | /** 39 | * @param string $match 40 | */ 41 | private function match($match) 42 | { 43 | switch ($match) { 44 | case self::TOPIC: 45 | (new Topic($this->verbose, $this->router, $this->template))->get(); 46 | break; 47 | case self::HOME: 48 | $this->setQueryFromGet(); 49 | if (empty($this->query)) { 50 | $this->template->include(self::HOME); 51 | break; 52 | } 53 | (new Search($this->verbose, null, $this->template))->get($this->query); 54 | break; 55 | case self::ABOUT: 56 | $this->template->set(self::TITLE, 'About this site'); 57 | $this->template->include(self::ABOUT); 58 | break; 59 | case self::REFRESH: 60 | (new Refresh($this->verbose, $this->router, $this->template))->get(); 61 | break; 62 | default: 63 | break; 64 | } 65 | } 66 | 67 | private function initRouter() 68 | { 69 | $this->router = new Router(); 70 | $this->router->allow('/', self::HOME); 71 | $this->router->allow('/r/?', self::TOPIC); 72 | $this->router->allow('/r/?/?', self::TOPIC); 73 | $this->router->allow('/r/?/?/?', self::TOPIC); 74 | $this->router->allow('/r/?/?/?/?', self::TOPIC); 75 | $this->router->allow('/about', self::ABOUT); 76 | $this->router->allow('/refresh', self::REFRESH); 77 | $this->router->allow('/refresh/?', self::REFRESH); 78 | $this->router->allow('/refresh/?/?', self::REFRESH); 79 | $this->router->allow('/refresh/?/?/?', self::REFRESH); 80 | $this->router->allow('/refresh/?/?/?/?', self::REFRESH); 81 | } 82 | 83 | private function initTemplate() 84 | { 85 | $this->template = new Template($this->verbose); 86 | $this->template->timer = $this->timer; 87 | $this->template->set(self::HOME, $this->router->getHome()); 88 | $this->template->set(self::TITLE, $this->siteName); 89 | $this->template->set('name', $this->siteName); 90 | $this->template->set('version', self::VERSION); 91 | } 92 | 93 | /** 94 | * set $this->query to string from _GET['q'], or empty string 95 | */ 96 | private function setQueryFromGet() 97 | { 98 | $this->query = $this->router->getGet('q'); 99 | if (!$this->query || !is_string($this->query)) { 100 | $this->query = ''; 101 | 102 | return; 103 | } 104 | $this->query = trim($this->query); 105 | if (!strlen($this->query)) { 106 | $this->query = ''; 107 | 108 | return; 109 | } 110 | } 111 | } 112 | -------------------------------------------------------------------------------- /src/Mediawiki.php: -------------------------------------------------------------------------------- 1 | getApi($this->api . $this->apiLinks . urlencode($query)); 49 | if (!$data || !is_array($data)) { 50 | $this->error('links: decode failed: ' . $query); 51 | 52 | return false; 53 | } 54 | $result = []; 55 | if (isset($data['error']) 56 | || !isset($data[self::PARSE][self::TITLE]) 57 | || empty($data[self::PARSE][self::TITLE]) 58 | ) { 59 | $result[self::TITLE] = $query; 60 | $result['error'] = true; 61 | 62 | return $result; // 404 Not Found 63 | } 64 | 65 | 66 | $result['cached'] = time(); // set cache time 67 | $result[self::TITLE] = $data[self::PARSE][self::TITLE]; // set title 68 | // set reference links 69 | $result[self::REFS] = isset($data[self::PARSE][self::EXTERNALLINKS]) 70 | ? $data[self::PARSE][self::EXTERNALLINKS] 71 | : []; 72 | // set related topics 73 | $result[self::TOPICS] = isset($data[self::PARSE][self::LINKS]) 74 | ? $data[self::PARSE][self::LINKS] 75 | : []; 76 | // set templates 77 | $result[self::TEMPLATES] = isset($data[self::PARSE][self::TEMPLATES]) 78 | ? $data[self::PARSE][self::TEMPLATES] 79 | : []; 80 | 81 | return $result; 82 | } 83 | 84 | /** 85 | * @param string $query 86 | * @return array|false 87 | */ 88 | public function search($query) 89 | { 90 | $data = $this->getApi($this->api . $this->apiSearch . urlencode($query)); 91 | if (!$data || !is_array($data) || empty($data[self::QUERY]) 92 | || empty($data[self::QUERY][self::SEARCH]) || !is_array($data[self::QUERY][self::SEARCH]) 93 | ) { 94 | $this->error('search: query failed'); 95 | 96 | return false; 97 | } 98 | $result = []; 99 | //$result['cached'] = time(); // @TODO set and use cache time 100 | foreach ($data[self::QUERY][self::SEARCH] as $topic) { 101 | if (!isset($topic[self::TITLE]) || !is_string($topic[self::TITLE])) { 102 | continue; 103 | } 104 | $result[] = $topic[self::TITLE]; 105 | } 106 | 107 | return $result; 108 | } 109 | 110 | /** 111 | * @param string $url 112 | * @return array|false 113 | */ 114 | private function getApi($url) 115 | { 116 | if (empty($url) || !is_string($url)) { 117 | $this->error('getApi: invalid url: ' . print_r($url, true)); 118 | return false; 119 | } 120 | $curl = curl_init($url); 121 | curl_setopt($curl, CURLOPT_RETURNTRANSFER, true); 122 | curl_setopt($curl, CURLOPT_USERAGENT, $this->userAgent); 123 | $jsonData = curl_exec($curl); 124 | curl_close($curl); 125 | if (empty($jsonData)) { 126 | $this->error("getApi: EMPTY RESULT: $url"); 127 | 128 | return false; 129 | } 130 | $data = @json_decode($jsonData, true); 131 | if (!is_array($data)) { 132 | $this->error("getApi: DECODE FAILED: url: $url jsonData: $jsonData"); 133 | 134 | return false; 135 | } 136 | $this->verbose("getApi: got: $url"); 137 | 138 | return $data; 139 | } 140 | } 141 | -------------------------------------------------------------------------------- /src/Refresh.php: -------------------------------------------------------------------------------- 1 | setTopicFromUrl(); 21 | if (!strlen($this->topic)) { 22 | $this->error404('Refresh Topic Not Found'); 23 | } 24 | $this->initFilesystem(); 25 | if (!$this->filesystem->has($this->topic)) { // does cache file exist? 26 | $this->error404('Cache File Not Found'); 27 | } 28 | $this->template->set(self::TITLE, 'Refresh'); 29 | if (empty($this->router->getPost())) { 30 | $this->ask(); 31 | 32 | return; 33 | } 34 | $this->answer(); 35 | } 36 | 37 | private function answer() 38 | { 39 | $answer = $this->router->getPost('d'); 40 | if (!strlen($answer)) { 41 | $this->error404('Answer Not Found'); 42 | } 43 | $submitTime = $this->router->getPost('c'); 44 | if (!$submitTime || (time() - intval($submitTime)) > 60) { 45 | $this->error404('Request Timed Out'); 46 | } 47 | $one = $this->router->getPost('a'); 48 | $two = $this->router->getPost('b'); 49 | if (!strlen($one) || !strlen($two) || (($one + $two) != $answer)) { 50 | $this->error404('Invalid Answer'); 51 | } 52 | $this->deleteCacheFile(); 53 | } 54 | 55 | private function deleteCacheFile() 56 | { 57 | if (!$this->filesystem->forget($this->topic)) { 58 | $this->error404('Deletion Failed'); 59 | } 60 | $this->template->include('html_head'); 61 | $this->template->include('header'); 62 | print '

OK - cache deleted

' 63 | . '

' 64 | . $this->topic . '

'; 65 | $this->template->include('footer'); 66 | } 67 | 68 | private function ask() 69 | { 70 | $this->template->include('html_head'); 71 | $this->template->include('header'); 72 | print '

' 74 | . $this->topic . ' is currently cached.

'; 75 | $letterOne = chr(rand(65, 90)); // random letter A-Z 76 | $numOne = rand(0, 10); // random number 0-10 77 | $letterTwo = chr(rand(65, 90)); // random letter A-Z 78 | $numTwo = rand(0, 10); // random number 0-10 79 | print '
' 80 | . '' 81 | . '' 82 | . '' 83 | . "If $letterOne = $numOne and $letterTwo = $numTwo then $letterOne + $letterTwo = " 84 | . '' 85 | . '


'; 86 | $this->template->include('footer'); 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /src/Search.php: -------------------------------------------------------------------------------- 1 | initFilesystem(); 32 | $cachedFile = 'search:' . mb_strtolower($query); 33 | $this->searchResults = $this->filesystem->get($cachedFile); 34 | if (!is_array($this->searchResults) || empty($this->searchResults)) { 35 | // No cached results - Get results from API 36 | $this->initMediawiki(); 37 | $this->searchResults = $this->mediawiki->search($query); 38 | if ($this->searchResults) { 39 | // Got API results - Save results to cache 40 | $this->filesystem->set($cachedFile, json_encode($this->searchResults), self::CACHE_TIME); 41 | } 42 | } 43 | $this->display(); 44 | } 45 | 46 | /** 47 | * Display search results 48 | */ 49 | private function display() 50 | { 51 | if (!is_array($this->searchResults) || empty($this->searchResults)) { 52 | // No results, set page status to 404 53 | header('HTTP/1.0 404 Not Found'); 54 | $this->searchResults = []; 55 | } 56 | $this->template->set(self::TITLE, 'search results - ' . $this->siteName); 57 | $this->template->include('html_head'); 58 | $this->template->include('header'); 59 | print '
' . count($this->searchResults) . ' results:
    '; 60 | foreach ($this->searchResults as $topic) { 61 | print '
  1. ' . $topic . '
  2. '; 62 | } 63 | print '
'; 64 | $this->template->include('footer'); 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /src/Template.php: -------------------------------------------------------------------------------- 1 | templateDirectory . $name . '.php'; 35 | if (!is_readable($template)) { 36 | return false; 37 | } 38 | try { 39 | include($template); 40 | } catch (Exception $exception) { 41 | $this->error($exception->getMessage()); 42 | 43 | return false; 44 | } 45 | 46 | return true; 47 | } 48 | 49 | /** 50 | * set a single var 51 | * @param string $name 52 | * @param mixed $value 53 | * @return bool 54 | */ 55 | public function set(string $name, $value): bool 56 | { 57 | if (!is_string($name)) { 58 | return false; 59 | } 60 | $this->vars[$name] = $value; 61 | 62 | return true; 63 | } 64 | 65 | /** 66 | * get a var 67 | * @param string $name 68 | * @return string|mixed 69 | */ 70 | public function get($name) 71 | { 72 | if (!isset($this->vars[$name])) { 73 | return ''; 74 | } 75 | 76 | return $this->vars[$name]; 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /src/Topic.php: -------------------------------------------------------------------------------- 1 | setTopicFromUrl()) { // build topic from URL 37 | $this->error404(self::ERROR_NOT_FOUND); 38 | } 39 | 40 | $this->initFilesystem(); 41 | 42 | if ($this->setDataFromCache()) { // get topic data from Cache 43 | if (!empty($this->data['error'])) { // if cache contains error report (404) 44 | $this->error404(self::ERROR_NOT_FOUND, $this->topic); 45 | 46 | return; 47 | } 48 | $this->display(); // show cached results 49 | 50 | return; 51 | } 52 | 53 | // If can get topic data from API 54 | if ($this->setDataFromApi()) { 55 | // save results to cache for CACHE_TIME seconds 56 | $this->filesystem->set($this->topic, json_encode($this->data), self::CACHE_TIME); 57 | if (!empty($this->data['error'])) { // if API reported an error 58 | $this->error404(self::ERROR_NOT_FOUND, $this->topic); 59 | 60 | return; 61 | } 62 | $this->display(); // show api results 63 | 64 | return; 65 | } 66 | 67 | $this->error404(self::ERROR_NOT_FOUND); 68 | } 69 | 70 | /** 71 | * set $this->data to array from cached file, or empty array 72 | * @return bool 73 | */ 74 | private function setDataFromCache() 75 | { 76 | $this->data = $this->filesystem->get($this->topic); 77 | if (!$this->data) { 78 | $this->data = []; 79 | 80 | return false; 81 | } 82 | 83 | $this->data = json_decode($this->data, true); // @TODO catch errors from decode 84 | 85 | return true; 86 | } 87 | 88 | /** 89 | * set $this->data to array from api response, or empty array 90 | * @return bool 91 | */ 92 | private function setDataFromApi() 93 | { 94 | $this->initMediawiki(); 95 | $this->data = $this->mediawiki->links($this->topic); 96 | if (!is_array($this->data)) { 97 | $this->data = []; 98 | 99 | return false; 100 | } 101 | 102 | return true; 103 | } 104 | 105 | private function display() 106 | { 107 | $this->setTemplateVars(); 108 | 109 | // set Extraction source url 110 | $this->template->set('source', $this->source . $this->encodeLink($this->data[self::TITLE])); 111 | $this->template->set('now', gmdate(self::DATE_FORMAT)); 112 | 113 | if (!isset($this->data['cached'])) { // if no cache time is set... 114 | $this->data['cached'] = time(); 115 | } 116 | $this->template->set('cached', gmdate(self::DATE_FORMAT, $this->data['cached'])); 117 | 118 | $this->template->set( 119 | 'refresh', // refresh link 120 | $this->template->get('home') . 'refresh/' . $this->encodeLink($this->data[self::TITLE]) 121 | ); 122 | $this->template->set('h1', $this->data[self::TITLE]); 123 | $this->template->set(self::TITLE, $this->data[self::TITLE] . ' - ' . $this->siteName); 124 | $this->template->include('topic'); 125 | } 126 | 127 | private function setTemplateVars() 128 | { 129 | $this->initVars(); 130 | $this->setNamespaces(); 131 | $this->setRefs(); 132 | $this->setTemplates(); 133 | $this->setTemplateExists(); 134 | $this->removeTemplateTopics(); 135 | foreach (array_keys($this->vars) as $index) { 136 | $this->template->set($index . '_count', count($this->vars[$index])); // set counts 137 | sort($this->vars[$index]); // sort var lists alphabetically 138 | $this->template->set($index . '_list', $this->listify($index)); // set html list 139 | } 140 | } 141 | 142 | private function initVars() 143 | { 144 | $namespaces = [ 145 | self::MAIN, self::TALK, self::MAIN_SECONDARY, self::TEMPLATE, self::TEMPLATE_TALK, self::TEMPLATE_SECONDARY, 146 | self::PORTAL, self::PORTAL_TALK, self::WIKIPEDIA, self::WIKIPEDIA_TALK, self::HELP, self::HELP_TALK, 147 | self::MODULE, self::MODULE_TALK, self::DRAFT, self::DRAFT_TALK, self::USER, self::USER_TALK, 148 | self::REFS, self::MISSING, self::EXISTS, 149 | ]; 150 | foreach ($namespaces as $index) { 151 | $this->vars[$index] = []; 152 | } 153 | } 154 | 155 | private function setNamespaces() 156 | { 157 | foreach ($this->data[self::TOPICS] as $topic) { 158 | if (!isset($topic[self::EXISTS])) { 159 | $this->vars[self::MISSING][] = $topic[self::ASTERISK]; // page does not exist 160 | } 161 | switch ($topic[self::NS]) { // @see https://en.wikipedia.org/wiki/Wikipedia:Namespace 162 | case '0': // Mainspace 163 | $this->vars[self::MAIN][] = $topic[self::ASTERISK]; 164 | break; 165 | case '1': // Talk 166 | $this->vars[self::TALK][] = $topic[self::ASTERISK]; 167 | break; 168 | case '2': // User 169 | $this->vars[self::USER][] = $topic[self::ASTERISK]; 170 | break; 171 | case '3': // User_talk 172 | $this->vars[self::USER_TALK][] = $topic[self::ASTERISK]; 173 | break; 174 | case '4': // Wikipedia 175 | $this->vars[self::WIKIPEDIA][] = $topic[self::ASTERISK]; 176 | break; 177 | case '5': // Wikipedia_talk 178 | $this->vars[self::WIKIPEDIA_TALK][] = $topic[self::ASTERISK]; 179 | break; 180 | case '10': // Template 181 | $this->vars[self::TEMPLATE][] = $topic[self::ASTERISK]; 182 | break; 183 | case '11': // Template_talk 184 | $this->vars[self::TEMPLATE_TALK][] = $topic[self::ASTERISK]; 185 | break; 186 | case '12': // Help 187 | $this->vars[self::HELP][] = $topic[self::ASTERISK]; 188 | break; 189 | case '13': // Help_talk 190 | $this->vars[self::HELP_TALK][] = $topic[self::ASTERISK]; 191 | break; 192 | case '100': // Portal 193 | $this->vars[self::PORTAL][] = $topic[self::ASTERISK]; 194 | break; 195 | case '101': // Portal_talk 196 | $this->vars[self::PORTAL_TALK][] = $topic[self::ASTERISK]; 197 | break; 198 | case '118': // Draft 199 | $this->vars[self::DRAFT][] = $topic[self::ASTERISK]; 200 | break; 201 | case '119': // Draft_talk 202 | $this->vars[self::DRAFT_TALK][] = $topic[self::ASTERISK]; 203 | break; 204 | case '828': // Module 205 | $this->vars[self::MODULE][] = $topic[self::ASTERISK]; 206 | break; 207 | case '829': // Module_talk 208 | $this->vars[self::MODULE_TALK][] = $topic[self::ASTERISK]; 209 | break; 210 | default: 211 | break; 212 | } 213 | } 214 | } 215 | 216 | private function setTemplateExists() 217 | { 218 | foreach ($this->vars[self::TEMPLATE] as $item) { 219 | if ($this->filesystem->has($item)) { 220 | $this->vars[self::EXISTS][] = $item; 221 | } 222 | } 223 | } 224 | 225 | private function setRefs() 226 | { 227 | foreach ($this->data[self::REFS] as $ref) { 228 | if (substr($ref, 0, 2) == '//') { 229 | $ref = 'https:' . $ref; 230 | } 231 | $this->vars[self::REFS][] = $ref; 232 | } 233 | } 234 | 235 | private function setTemplates() 236 | { 237 | foreach ($this->data[self::TEMPLATES] as $item) { 238 | switch ($item[self::NS]) { 239 | case '0': // Main 240 | if ($item[self::ASTERISK] != $this->topic) { 241 | $this->vars[self::MAIN][] = $item[self::ASTERISK]; 242 | } 243 | break; 244 | case '10': // Template: 245 | if (!in_array($item[self::ASTERISK], $this->vars[self::TEMPLATE])) { 246 | $this->vars[self::TEMPLATE_SECONDARY][] = $item[self::ASTERISK]; 247 | } 248 | break; 249 | case '828': // Module: 250 | $this->vars[self::MODULE][] = $item[self::ASTERISK]; 251 | break; 252 | default: 253 | break; 254 | } 255 | } 256 | } 257 | 258 | private function removeTemplateTopics() 259 | { 260 | if (empty($this->vars[self::MAIN]) 261 | || (empty($this->vars[self::TEMPLATE]) && $this->vars[self::TEMPLATE_SECONDARY]) 262 | ) { 263 | return; 264 | } 265 | foreach ($this->vars[self::TEMPLATE] as $template) { 266 | if ($template == $this->topic || (!in_array($template, $this->vars[self::EXISTS]))) { 267 | continue; // error: is self, or template not cached 268 | } 269 | $templateData = json_decode($this->filesystem->get($template), true); 270 | if (empty($templateData[self::TOPICS]) || !is_array($templateData[self::TOPICS])) { 271 | continue; // error: malformed data 272 | } 273 | $this->unsetDupeTopics($templateData[self::TOPICS]); 274 | } 275 | } 276 | 277 | /** 278 | * @param array $topics 279 | */ 280 | private function unsetDupeTopics(array $topics) 281 | { 282 | foreach ($topics as $exTopic) { 283 | if ($exTopic[self::NS] == '0' && in_array($exTopic[self::ASTERISK], $this->vars[self::MAIN])) { 284 | // main namespace only - remove this template topic from master topic list 285 | unset($this->vars[self::MAIN][array_search($exTopic[self::ASTERISK], $this->vars[self::MAIN])]); 286 | $this->vars[self::MAIN_SECONDARY][] = $exTopic[self::ASTERISK]; 287 | } 288 | } 289 | } 290 | 291 | /** 292 | * @param string $index - this->vars index 293 | * @return string - html fragment 294 | */ 295 | private function listify($index) 296 | { 297 | if (in_array($index, [self::EXISTS, self::MISSING]) // skip internal-usage vars 298 | || empty($this->vars[$index]) // Error - index not found, or index empty 299 | ) { 300 | return ' '; 301 | } 302 | $html = '
    '; 303 | foreach ($this->vars[$index] as $item) { 304 | if ($index == self::REFS) { // Link to external reference 305 | $html .= '
  1. ' . $item . '
  2. '; 306 | continue; 307 | } 308 | if (in_array($item, $this->vars[self::MISSING])) { // non-existing page 309 | $html .= '
  3. ' . $item . '
  4. '; 310 | continue; 311 | } 312 | // Link to internal page 313 | $class = ''; 314 | if ($index == self::TEMPLATE && !in_array($item, $this->vars[self::EXISTS])) { 315 | // template is not loaded, thus possible that secondary-topics not all set 316 | $class = ' class="missing"'; 317 | } 318 | $html .= '
  5. ' 320 | . $item . '
  6. '; 321 | } 322 | 323 | return $html . '
'; 324 | } 325 | } 326 | -------------------------------------------------------------------------------- /templates/about.php: -------------------------------------------------------------------------------- 1 | include('html_head'); 9 | $this->include('header'); 10 | ?>
11 |

12 | About get('name') ?> vget('version') ?> 13 |

14 |

15 | The goal of this website 16 | is to help students 17 | and researchers 18 | by extracting 19 | lists of references 20 | and related topics 21 | from any page on the 22 | English Wikipedia. 23 |

24 |

25 | This removes the distraction of the 26 | prose written by 27 | others, and allows concentrating on 28 | judging the quality 29 | of the references. 30 |

31 |

32 | get('name') ?> is not 33 | affiliated with the 34 | Wikimedia Foundation, 35 | or any Wikipedia website. 36 |

37 |

38 | get('name') ?> is an open source project. 39 | Find out more at 40 | <https://github.com/attogram/justrefs> 41 |

42 |

include('footer'); 44 | -------------------------------------------------------------------------------- /templates/custom/.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | !.gitignore 3 | !README.md 4 | -------------------------------------------------------------------------------- /templates/custom/README.md: -------------------------------------------------------------------------------- 1 | # Just Refs - Custom Templates 2 | 3 | Optional customization templates: 4 | 5 | * head.top.php - if file exists, included after HTML `` tag 6 | -------------------------------------------------------------------------------- /templates/footer.php: -------------------------------------------------------------------------------- 1 | 19 | 20 | -------------------------------------------------------------------------------- /templates/header.php: -------------------------------------------------------------------------------- 1 | 9 |
10 | get('name') ?> 11 |
12 |
13 | 14 |
15 |
16 |
17 | -------------------------------------------------------------------------------- /templates/home.php: -------------------------------------------------------------------------------- 1 | include('html_head'); 9 | ?>
10 |

Just Refs

11 |
    12 |
  • Extract just the references and related topics from any page on the English Wikipedia.
  • 13 |
  • Remove the distraction of prose written by others!
  • 14 |
  • More about this site.
  • 15 |
16 |

17 |
18 | 19 | 20 |
21 |

22 | Example Topics: 23 | 41 |
42 |
include('footer'); 44 | -------------------------------------------------------------------------------- /templates/html_head.php: -------------------------------------------------------------------------------- 1 | 9 | 10 | include('custom' . DIRECTORY_SEPARATOR . 'head.top'); ?> 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | <?= $this->get('title') ?> -------------------------------------------------------------------------------- /templates/topic.php: -------------------------------------------------------------------------------- 1 | include('html_head'); 9 | $this->include('header'); 10 | ?>
11 |

get('h1') ?>

12 |
13 | 32 |
33 |
34 |
35 | get('main_count') ?> Topics:
get('main_list') ?> 36 |
37 |
38 | get('refs_count') ?> References:
get('refs_list') ?> 39 |
40 |
41 |
42 |
43 |
44 | get('main_secondary_count') ?> Secondary-Topics:
get('main_secondary_list') ?> 45 |
46 |
47 | get('template_count') ?> Templates:
get('template_list') ?>
48 |
49 | get('portal_count') ?> Portals:
get('portal_list') ?> 50 |
51 |
52 |
53 |
54 |
55 | get('help_count') ?> Help:
get('help_list') ?> 56 |
57 | 58 | get('wikipedia_count') ?> Wikipedia:
get('wikipedia_list') ?> 59 |
60 |
61 | get('template_secondary_count') ?> Secondary-Templates: 62 |
get('template_secondary_list') ?>
63 |
64 | 65 | get('module_count') ?> Modules:
get('module_list') ?> 66 |
67 |
68 |
69 |
70 |
71 | get('draft_count') ?> Drafts:
get('draft_list') ?> 72 |
73 |
74 | get('user_count') ?> Users:
get('user_list') ?> 75 |
76 |
77 |
78 |
79 |
80 | get('talk_count') ?> Talk:
get('talk_list') ?>
81 |
82 | get('user_talk_count') ?> User talk:
get('user_talk_list') ?>
83 |
84 | get('wikipedia_talk_count') ?> Wikipedia talk:
get('wikipedia_talk_list') ?>
85 |
86 | get('help_talk_count') ?> Help talk:
get('help_talk_list') ?> 87 |
88 |
89 | get('portal_talk_count') ?> Portal talk:
get('portal_talk_list') ?>
90 |
91 | get('template_talk_count') ?> Template talk:
get('template_talk_list') ?>
92 |
93 | get('draft_talk_count') ?> Draft talk:
get('draft_talk_list') ?>
94 |
95 | get('module_talk_count') ?> Module talk:
get('module_talk_list') ?> 96 |
97 |
98 |
99 |
include('footer'); 101 | --------------------------------------------------------------------------------