├── .gitignore ├── ChangeLog ├── LICENSE ├── NEWS.rst ├── README.rst ├── build.xml ├── composer.json ├── composer.lock ├── data ├── config.default.php ├── config.php.dist └── templates │ ├── base.htm │ ├── delete.htm │ ├── display-file.htm │ ├── display-foot.htm │ ├── display-head.htm │ ├── display-sidebar-fork.htm │ ├── display-sidebar-history.htm │ ├── display-sidebar-owner.htm │ ├── display-sidebar-urls.htm │ ├── display.htm │ ├── doap.htm │ ├── edit-add-button.htm │ ├── edit-add.htm │ ├── edit-file.htm │ ├── edit.htm │ ├── embed-file.htm │ ├── embed-part-file.htm │ ├── embed.htm │ ├── exception.htm │ ├── feed-new.htm │ ├── feed-updated.htm │ ├── flashmessages.htm │ ├── forbidden.htm │ ├── fork-remote-multiple.htm │ ├── fork-remote-new.htm │ ├── fork-remote.htm │ ├── help.htm │ ├── list.htm │ ├── login.htm │ ├── new.htm │ ├── oembed.htm │ ├── pager.htm │ ├── repo-list.htm │ ├── repo-sidebar-list.htm │ ├── revision-head.htm │ ├── revision.htm │ ├── search.htm │ ├── tool.htm │ └── user.htm ├── scripts ├── build-rewritemap.php ├── index.php └── search.php ├── src ├── phorkie │ ├── Database.php │ ├── Database │ │ ├── Adapter │ │ │ ├── Elasticsearch │ │ │ │ ├── HTTPRequest.php │ │ │ │ ├── Indexer.php │ │ │ │ ├── Search.php │ │ │ │ └── Setup.php │ │ │ └── Null │ │ │ │ ├── Indexer.php │ │ │ │ ├── Search.php │ │ │ │ └── Setup.php │ │ ├── IIndexer.php │ │ ├── ISearch.php │ │ └── ISetup.php │ ├── Exception.php │ ├── Exception │ │ ├── Input.php │ │ └── NotFound.php │ ├── File.php │ ├── FlashMessage.php │ ├── ForkRemote.php │ ├── Forker.php │ ├── GitCommandBinary.php │ ├── Html │ │ └── Pager.php │ ├── HtmlHelper.php │ ├── HtmlParser.php │ ├── Login │ │ └── AutologinResponse.php │ ├── Notificator.php │ ├── Notificator │ │ ├── Linkback.php │ │ └── Webhook.php │ ├── Renderer │ │ ├── Cache.php │ │ ├── Geshi.php │ │ ├── Image.php │ │ ├── Markdown.php │ │ ├── Plaintext.php │ │ ├── ReStructuredText.php │ │ └── Unknown.php │ ├── Repositories.php │ ├── Repository.php │ ├── Repository │ │ ├── Commit.php │ │ ├── ConnectionInfo.php │ │ ├── LinkbackReceiver.php │ │ ├── Post.php │ │ ├── Remote.php │ │ └── Setup.php │ ├── Search │ │ └── Result.php │ ├── SetupCheck.php │ ├── Tool │ │ ├── Info.php │ │ ├── MIME │ │ │ └── Type │ │ │ │ └── PlainDetect.php │ │ ├── Manager.php │ │ ├── PHPlint.php │ │ ├── Result.php │ │ ├── Result │ │ │ └── Line.php │ │ └── Xmllint.php │ ├── Tools.php │ └── autoload.php └── stub-phar.php ├── tests ├── phorkie │ └── ToolsTest.php └── phpunit.xml └── www ├── .htaccess ├── css ├── bootstrap.min.css ├── embed.css ├── font-awesome.css ├── openid.css ├── phorkie.css └── widescreen.css ├── delete.php ├── display.php ├── doap.php ├── edit.php ├── embed-file.php ├── embed.php ├── favicon.ico ├── feed-new.php ├── feed-updated.php ├── font ├── fontawesome-webfont.eot └── fontawesome-webfont.woff ├── forbidden.php ├── fork-remote.php ├── fork.php ├── help.php ├── images └── openid-inputicon.gif ├── index.php ├── js ├── autologin.js ├── bootstrap.min.js ├── jquery-3.7.1.min.js └── phorkie.js ├── linkback.php ├── list.php ├── login.php ├── new.php ├── oembed.php ├── phorkie ├── anonymous.png ├── dot-g.png ├── dot-n.png └── dot-r.png ├── raw.php ├── revision.php ├── search.php ├── setup.php ├── tool.php ├── user.php ├── www-header.php └── www-security.php /.gitignore: -------------------------------------------------------------------------------- 1 | /build.properties 2 | /cache/ 3 | /data/config.php 4 | /dist/ 5 | /lib/ 6 | /NEWS.html 7 | /README.html 8 | /repos/ 9 | /src/gen-rewritemap.php 10 | /www/*.phar 11 | /www/*.phar.bz2 12 | /www/repos/ 13 | -------------------------------------------------------------------------------- /ChangeLog: -------------------------------------------------------------------------------- 1 | 2017-08-29 Christian Weiske 2 | 3 | * Fix owner name/email tracking 4 | * Fix gist title resolver on some PHP configurations 5 | * Make composer dependency installation work fine 6 | * New GeSHi version 1.0.9.0 7 | * Release version 0.8.1 8 | 9 | 2017-04-19 Christian Weiske 10 | 11 | * Add support for git 2.11 12 | * Fix 'Please tell me who you are' messages 13 | * Fix crash on /new when a paste has been deleted 14 | * Fix crash on broken git repositories 15 | * Fix memory leak 16 | * Move phorkie off sourceforge 17 | * Release version 0.8.0 18 | 19 | 2015-11-05 Christian Weiske 20 | 21 | * Add support for elasticsearch 2.0 22 | * Add text file detection for unknown file extensions 23 | * Release version 0.7.0 24 | 25 | 2015-07-15 Christian Weiske 26 | 27 | * Fix crash when renaming file 28 | * Fix jumping to file anchor after file rename 29 | * Release version 0.6.1 30 | 31 | 2015-07-08 Christian Weiske 32 | 33 | * Add simple cache for rendered files 34 | * Add "add file" button to display page 35 | * Add jumping to file after saving from single file edit mode 36 | * Adjust "additional options" layout and position 37 | * Fix autologin reload button; it reloads the current page now 38 | * Remove OpenID provider buttons 39 | * Release version 0.6 40 | 41 | 2015-02-03 Christian Weiske 42 | 43 | * Implement paste embedding via oEmbed 44 | * Release version 0.5 45 | 46 | 2015-01-29 Christian Weiske 47 | 48 | * Implement paste embedding via JavaScript 49 | * Implement single file editing 50 | * Implement automatic login 51 | * Implement Elasticsearch 1.3 compatibility 52 | * Work around PHP bug #68347 to parse ini files correctly 53 | * Move clone URLs to sidebar 54 | * Move additional button into text field 55 | 56 | 2014-07-15 Christian Weiske 57 | 58 | * Release version 0.4 59 | 60 | 2013-09-17 Christian Weiske 61 | 62 | * Add webhook support 63 | 64 | 2013-09-16 Elan Ruusamäe 65 | 66 | * Disable editor on commit with --no-edit 67 | 68 | 2013-08-20 Justin J. Novack 69 | 70 | * Add perPage settings into config 71 | 72 | 2013-08-20 Christian Weiske 73 | 74 | * Fix bug #41: AGPL link broken 75 | * Automatically focus OpenID field on login page 76 | 77 | 2013-08-20 Fredrik Nygren 78 | 79 | * Implement request #37: Add avatar to navbar and profile page 80 | 81 | 2012-10-24 Christian Weiske 82 | 83 | * Implement request #35: Store owner of paste 84 | * Fix bug #33: do not index login page 85 | 86 | 2012-10-24 Justin J. Novack 87 | 88 | * Local bootstrap, responsive design 89 | 90 | 2012-10-01 Christian Weiske 91 | 92 | * Fix bug #27: render .json files 93 | * Fix bug #31: forked pastes cannot be remote forked 94 | 95 | 2012-09-28 Christian Weiske 96 | 97 | * Check for OpenID package in SetupCheck 98 | * Fix bug #24: setupcheck: verify geshi installation 99 | * Fix bug #25: setupcheck: verify markdown 100 | * Release version 0.3.1 101 | 102 | 2012-09-28 Christian Weiske 103 | 104 | * Fix bug #23: "work dir not found" on failed remote fork 105 | * Fix bug #20: elasticsearch error on deletion 106 | * Release version 0.3.0 107 | 108 | 2012-09-27 Christian Weiske 109 | 110 | * Fix bug #22: Edited pastes not in "recently created" 111 | 112 | 2012-09-27 Christian Weiske 113 | 114 | * Release of version 0.3.0 115 | 116 | 2012-09-19 Christian Weiske 117 | 118 | * Implement request #13: remote forking support 119 | * Implement request #12: add link rel="vcs-git" 120 | 121 | 2012-09-18 Justin J. Novack 122 | 123 | * Add Markdown as a known file-type. 124 | 125 | 2012-09-17 Justin J. Novack 126 | 127 | * Implement request #5: OpenID authentication 128 | 129 | 2012-09-16 Christian Weiske 130 | 131 | * Implement request #12: DOAP documents for all pastes 132 | 133 | 2012-09-08 Christian Weiske 134 | 135 | * Fix bug #11: do not index edit, delete and tool pages 136 | 137 | 2012-08-22 Stephen Lang 138 | 139 | * Added nginx rewrite rules to README 140 | * Fixed bug where autodetect fails, wrong default set. 141 | 142 | 2012-06-06 Christian Weiske 143 | 144 | * Hide additional file fields by default, button to toggle their 145 | visibility 146 | * Fix bug #10: error when nothing submitted 147 | 148 | 2012-06-05 Christian Weiske 149 | 150 | * Implement request #9: autodetect file type 151 | 152 | 2012-05-25 Christian Weiske 153 | 154 | * Release of version 0.2.0 155 | 156 | 2012-05-07 Christian Weiske 157 | 158 | * Search via Elasticsearch 159 | * Search tips in sidebar 160 | * Automatic elasticsearch setup on startup 161 | * Use title instead of paste ID 162 | * Better pager 163 | * Implement request #3: Show new pastes in sidebar of "new paste" page 164 | 165 | 2012-05-04 Christian Weiske 166 | 167 | * Pager for listing results 168 | 169 | 2012-04-19 Christian Weiske 170 | 171 | * Release of version 0.1.0 172 | -------------------------------------------------------------------------------- /NEWS.rst: -------------------------------------------------------------------------------- 1 | Version 0.9.0 - 2023-03-23 2 | -------------------------- 3 | * Make phorkie and all dependencies compatible with PHP 8.2 4 | 5 | 6 | Version 0.8.1 - 2017-08-29 7 | -------------------------- 8 | * Fix owner name/email tracking 9 | * Fix gist title resolver on some PHP configurations 10 | * Make composer dependency installation work fine 11 | * New GeSHi version 1.0.9.0 12 | 13 | 14 | Version 0.8.0 - 2017-04-19 15 | -------------------------- 16 | * Add support for git 2.11 17 | * Fix 'Please tell me who you are' messages 18 | * Fix crash on /new when a paste has been deleted 19 | * Fix crash on broken git repositories 20 | * Fix memory leak 21 | * Move phorkie off sourceforge 22 | 23 | 24 | Version 0.7.0 - 2015-11-05 25 | -------------------------- 26 | * Add support for elasticsearch 2.0 27 | * Add text file detection for unknown file extensions 28 | 29 | 30 | Version 0.6.1 - 2015-07-15 31 | -------------------------- 32 | * Fix crash when renaming file 33 | * Fix jumping to file anchor after file rename 34 | 35 | 36 | Version 0.6.0 - 2015-07-08 37 | -------------------------- 38 | * Add simple cache for rendered files 39 | * Add "add file" button to display page 40 | * Add jumping to file after saving from single file edit mode 41 | * Adjust "additional options" layout and position 42 | * Fix autologin reload button; it reloads the current page now 43 | * Remove OpenID provider buttons 44 | 45 | 46 | Version 0.5.0 - 2015-01-29 47 | -------------------------- 48 | * Implement paste embedding via JavaScript 49 | * Implement paste embedding via oEmbed 50 | * Implement single file editing 51 | * Implement automatic login 52 | * Implement Elasticsearch 1.3 compatibility 53 | * Work around PHP bug #68347 to parse ini files correctly 54 | * Move clone URLs to sidebar 55 | * Move additional button into text field 56 | 57 | 58 | Version 0.4.0 - 2014-07-15 59 | -------------------------- 60 | * Fix bug #27: Render .json files 61 | * Fix bug #31: Forked pastes cannot be remote forked 62 | * Fix bug #33: Do not index login page 63 | * Fix bug #41: AGPL link broken 64 | * Fix bug #43: github gist cloning does not work 65 | * Fix bug #44: Anchors of files with spaces in their name 66 | * Fix renaming of binary files 67 | * Implement request #6: Atom feed for new and updated pastes 68 | * Implement request #7: Track and display remote forks 69 | * Implement request #21: Send linkbacks when forking remote pastes 70 | * Implement request #32: Distribute phorkie as .phar file with all dependencies 71 | * Implement request #34: Store last OpenID in cookie and pre-fill login form 72 | * Implement request #35: Store author of a paste 73 | * Implement request #37: Show user icon in navbar and profile page 74 | * Implement request #42: Single click remote forking with web+fork: url handler 75 | * Add autoconfiguration for public git clone urls over http 76 | * Add baseurl setting to make phorkie run in a subdirectory of a domain 77 | * Add setupcheck page that checks dependencies and other things 78 | * Add support for file names with directories in pastes 79 | * Add support for UTF-8 characters in file names 80 | * Add support for web hooks 81 | * Add support for forking HTTP and HTTPS git URLs 82 | * Extract gist titles 83 | * Make it possible to install dependencies via composer 84 | 85 | 86 | Version 0.3.1 - 2012-09-27 87 | -------------------------- 88 | * Check for OpenID package in SetupCheck 89 | * Fix bug #24: setupcheck: verify geshi installation 90 | * Fix bug #25: setupcheck: verify markdown 91 | 92 | 93 | Version 0.3.0 - 2012-09-27 94 | -------------------------- 95 | * Fix bug #10: error when nothing submitted [cweiske] 96 | * Fix bug #11: do not index edit, delete and tool pages [cweiske] 97 | * Fix bug #20: elasticsearch error on deletion [cweiske] 98 | * Fix bug #22: Edited pastes not in "recently created" [cweiske] 99 | * Fix bug #23: "work dir not found" on failed remote fork [cweiske] 100 | * Implement request #5: Add OpenID authentication [jnovack] 101 | * Implement request #9: autodetect file type [cweiske] 102 | * Implement request #12: DOAP and rel="vcs-git" support [cweiske] 103 | * Implement request #13: remote forking support [cweiske] 104 | * Hide additional file fields by default, button to toggle their visibility 105 | [cweiske] 106 | * Markdown support [jnovack] 107 | * Added nginx rewrite rules to README [skl] 108 | 109 | 110 | Version 0.2.0 - 2012-05-25 111 | -------------------------- 112 | * Elasticsearch support 113 | * Use title instead of paste ID 114 | * Pager for result listings 115 | * Implement request #3: Show new pastes in sidebar of "new paste" page 116 | 117 | 118 | Version 0.1.0 - 2012-04-19 119 | -------------------------- 120 | Initial version 121 | -------------------------------------------------------------------------------- /build.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 84 | 85 | 86 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 114 | 120 | 121 | 122 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 156 | 157 | 158 | 159 | 160 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "cweiske/phorkie", 3 | "type": "project", 4 | "license": "AGPL-3.0+", 5 | "homepage": "https://cweiske.de/phorkie.htm", 6 | "authors": [ 7 | { 8 | "name": "Christian Weiske", 9 | "email": "cweiske@cweiske.de", 10 | "homepage": "http://cweiske.de/" 11 | } 12 | ], 13 | "require": { 14 | "ext-mbstring": "*", 15 | 16 | "pear/date_humandiff": "~0.5", 17 | "pear/http_request2": "^2.2", 18 | "pear/pager": "^2.4", 19 | "pear/openid": "~0.5", 20 | "pear/services_libravatar": "~0.2", 21 | "pear/versioncontrol_git": "^0.7.0", 22 | "pear/net_url2": "^2.2.2", 23 | 24 | "cweiske/mime_type_plaindetect": "^0.0.4", 25 | 26 | "pear2/services_linkback": "^0.4.0", 27 | 28 | "geshi/geshi": "^1.0.9.0", 29 | "michelf/php-markdown": "~1.4", 30 | "twig/twig": "^1.15", 31 | "pear/system_command": "^1.1.1" 32 | }, 33 | "autoload": { 34 | "psr-0": { "phorkie\\": "src/" } 35 | }, 36 | "config": { 37 | "vendor-dir": "lib/" 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /data/config.default.php: -------------------------------------------------------------------------------- 1 | false, 19 | 'git' => array( 20 | 'public' => '%BASEURL%' . 'repos/git/', 21 | 'private' => null, 22 | ), 23 | 'cachedir' => $phorkieDir . 'cache/', 24 | 'gitdir' => $wwwDir . 'repos/git/', 25 | 'workdir' => $wwwDir . 'repos/work/', 26 | 'tpl' => __DIR__ . '/templates/', 27 | 'baseurl' => null, 28 | 'avatars' => true, 29 | 'css' => '', 30 | 'iconpng' => '',//phorkie browser icon (favicon) 31 | 'title' => 'phorkie', 32 | 'topbar' => '', 33 | 'setupcheck' => true, 34 | 'elasticsearch' => null, 35 | 'index' => 'new',//"new" or "list" 36 | 'perPage' => 10, 37 | 'randomIds' => false, 38 | 'defaultListPage' => 'last',//a number or "last" 39 | 'notificator' => array( 40 | /* send out pingback/webmentions when a remote paste is forked */ 41 | 'linkback' => true, 42 | 'webhook' => array( 43 | /* array of urls that get called when 44 | a paste is created, edited or deleted */ 45 | ) 46 | ), 47 | 'geshi' => 'geshi.php', 48 | ); 49 | $GLOBALS['phorkie']['auth'] = array( 50 | // 0 = public, no authentication, 1 = protect adds/edits/deletes, 51 | // 2 = require authentication 52 | 'securityLevel' => 0, 53 | 'listedUsersOnly' => false, 54 | 'users' => array(), // Array of OpenIDs that may login 55 | 'anonymousName' => 'Anonymous', // Email for non-authenticated commits 56 | 'anonymousEmail' => 'anonymous@phorkie', // Email for non-authenticated commits 57 | ); 58 | $GLOBALS['phorkie']['tools'] = array( 59 | '\\phorkie\\Tool_Xmllint' => true, 60 | '\\phorkie\\Tool_PHPlint' => true, 61 | ); 62 | /** 63 | * Array of supported file types / languages. 64 | * Key is the file extension 65 | */ 66 | $GLOBALS['phorkie']['languages'] = array( 67 | 'conf' => array( 68 | 'title' => 'Configuration', 69 | 'mime' => 'text/ini', 70 | 'geshi' => 'ini', 71 | 'show' => false 72 | ), 73 | 'css' => array( 74 | 'title' => 'CSS', 75 | 'mime' => 'text/css', 76 | 'geshi' => 'css' 77 | ), 78 | 'diff' => array( 79 | 'title' => 'Diff', 80 | 'mime' => 'text/diff', 81 | 'geshi' => 'diff' 82 | ), 83 | 'htm' => array( 84 | 'title' => 'HTML', 85 | 'mime' => 'text/html', 86 | 'geshi' => 'xml' 87 | ), 88 | 'html' => array( 89 | 'title' => 'HTML', 90 | 'mime' => 'text/html', 91 | 'geshi' => 'xml', 92 | 'show' => false 93 | ), 94 | 'jpg' => array( 95 | 'title' => 'JPEG image', 96 | 'mime' => 'image/jpeg', 97 | 'show' => false 98 | ), 99 | 'ini' => array( 100 | 'title' => 'Ini', 101 | 'mime' => 'text/ini', 102 | 'geshi' => 'ini' 103 | ), 104 | 'js' => array( 105 | 'title' => 'Javascript', 106 | 'mime' => 'application/javascript', 107 | 'geshi' => 'javascript' 108 | ), 109 | 'json' => array( 110 | 'title' => 'Javascript', 111 | 'mime' => 'application/javascript', 112 | 'geshi' => 'javascript', 113 | 'show' => false 114 | ), 115 | 'md' => array( 116 | 'title' => 'Markdown', 117 | 'mime' => 'text/x-markdown', 118 | 'renderer' => '\\phorkie\\Renderer_Markdown' 119 | ), 120 | 'pl' => array( 121 | 'title' => 'Perl', 122 | 'mime' => 'application/x-perl', 123 | 'geshi' => 'pl' 124 | ), 125 | 'php' => array( 126 | 'title' => 'PHP', 127 | 'mime' => 'text/x-php', 128 | 'geshi' => 'php' 129 | ), 130 | 'png' => array( 131 | 'title' => 'PNG image', 132 | 'mime' => 'image/png', 133 | 'show' => false 134 | ), 135 | 'rb' => array( 136 | 'title' => 'Ruby/Rails', 137 | 'mime' => 'text/x-ruby', /* Is this an acceptable mime type? */ 138 | 'geshi' => 'rails' 139 | ), 140 | 'rst' => array( 141 | 'title' => 'reStructuredText', 142 | 'mime' => 'text/x-rst', 143 | 'geshi' => 'rst', 144 | 'renderer' => '\\phorkie\\Renderer_ReStructuredText', 145 | ), 146 | 'sh' => array( 147 | 'title' => 'Shell script (Bash)', 148 | 'mime' => 'text/x-shellscript', 149 | 'geshi' => 'bash' 150 | ), 151 | 'sql' => array( 152 | 'title' => 'SQL', 153 | 'mime' => 'text/x-sql', 154 | 'geshi' => 'sql' 155 | ), 156 | 'svg' => array( 157 | 'title' => 'SVG image', 158 | 'mime' => 'image/svg+xml', 159 | 'show' => false 160 | ), 161 | 'txt' => array( 162 | 'title' => 'Text (plain)', 163 | 'mime' => 'text/plain', 164 | 'geshi' => 'txt', 165 | 'renderer' => '\\phorkie\\Renderer_Plaintext' 166 | ), 167 | 'ts' => array( 168 | 'title' => 'TypoScript', 169 | 'mime' => 'text/x-typoscript',/* TODO: correct type */ 170 | 'geshi' => 'typoscript' 171 | ), 172 | 'wsdl' => array( 173 | 'title' => 'WSDL', 174 | 'mime' => 'application/wsdl+xml', 175 | 'geshi' => 'xml' 176 | ), 177 | 'xml' => array( 178 | 'title' => 'XML', 179 | 'mime' => 'text/xml', 180 | 'geshi' => 'xml' 181 | ), 182 | 'xsl' => array( 183 | 'title' => 'eXtensible Stylesheet Language', 184 | 'mime' => 'text/xml', 185 | 'geshi' => 'xml', 186 | 'show' => false 187 | ), 188 | ); 189 | 190 | //needed for UTF-8 characters in file names 191 | setlocale(LC_CTYPE, 'en_US.UTF_8'); 192 | ?> 193 | -------------------------------------------------------------------------------- /data/config.php.dist: -------------------------------------------------------------------------------- 1 | 20 | -------------------------------------------------------------------------------- /data/templates/base.htm: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | {% if css %} 11 | 12 | {% endif %} 13 | {% if iconpng %} 14 | 15 | {% else %} 16 | 17 | {% endif %} 18 | {% block title %}{% endblock %} - {{title}} 19 | 20 | 21 | 22 | {% block meta %}{% endblock %} 23 | 24 | 25 | {{topbar|raw}} 26 | 75 | 76 |
77 |
78 |
79 | {% if suggestSetupCheck %} 80 |
81 | No configuration file found. 82 | Visit the setup check page for more information. 83 |
84 | {% endif %} 85 | {% block content %}{% endblock %} 86 |
87 |
88 |
89 | {% block sidebar %}{% endblock %} 90 |
91 |
92 |
93 | 94 | 100 | {% if autologin %} 101 | 102 | {% endif %} 103 | 104 | 105 | -------------------------------------------------------------------------------- /data/templates/delete.htm: -------------------------------------------------------------------------------- 1 | {% extends "base.htm" %} 2 | {% block title %} 3 | Confirm deletion of repository #{{repo.id}} 4 | {% endblock %} 5 | 6 | {% block meta %} 7 | 8 | {% endblock %} 9 | 10 | {% block content %} 11 |

Delete paste #{{repo.id}}?

12 |
13 |

14 | Do you really want to delete the following paste? 15 |

16 |

17 | {{repo.getTitle}} 18 |

19 | 20 |
21 |
22 | Cancel 23 |
24 |
25 |
26 | 30 |
31 |
32 |
33 |
34 | {% endblock %} 35 | -------------------------------------------------------------------------------- /data/templates/display-file.htm: -------------------------------------------------------------------------------- 1 |
2 |
3 | raw 4 | {% for toolinfo in file.getToolInfos %} 5 | {{toolinfo.getTitle}} 6 | {% endfor %} 7 | 8 |

{{file.getFilename}}

9 |
10 | {{file.getRenderedContent(toolres)|raw}} 11 |
12 | -------------------------------------------------------------------------------- /data/templates/display-foot.htm: -------------------------------------------------------------------------------- 1 |
2 |
3 | 10 | 15 |
16 | -------------------------------------------------------------------------------- /data/templates/display-head.htm: -------------------------------------------------------------------------------- 1 | {% include 'flashmessages.htm' %} 2 | 3 |

{{repo.getTitle}}

4 |
5 |
6 | edit 7 |
8 |
9 |
10 |
11 | {% if htmlhelper.mayWriteLocally %} 12 | 13 | {% else %} 14 | Fork to remote system 15 | {% endif %} 16 | 19 | 26 |
27 |
28 |
29 |
30 | -------------------------------------------------------------------------------- /data/templates/display-sidebar-fork.htm: -------------------------------------------------------------------------------- 1 | {% set conns = repo.getConnectionInfo() %} 2 | {% if conns.isFork() %} 3 | {% set origin = conns.getOrigin() %} 4 |

Fork of

5 |

6 | 7 | 8 | {% set webpage = origin.getWebURL() %} 9 | {% if webpage %} 10 | {{origin.getTitle()}} 11 | {% set webpagedomain = htmlhelper.getDomain(webpage) %} 12 | {% if webpagedomain and domain != webpagedomain %} 13 |
{{webpagedomain}} 14 | {% endif %} 15 | {% else %} 16 | {{origin.getTitle()}} 17 | {% endif %} 18 |

19 | {% endif %} 20 | {% if conns.hasForks() %} 21 |

Forks

22 |
    23 | {% for remote in conns.getForks %} 24 |
  • 25 | {% set cloneUrl = remote.getCloneUrl() %} 26 | {% if cloneUrl %} 27 | 28 | {% endif %} 29 | 30 | {% set webpage = remote.getWebURL() %} 31 | {% if webpage %} 32 | {{remote.getTitle()}} 33 | {% set webpagedomain = htmlhelper.getDomain(webpage) %} 34 | {% if webpagedomain and domain != webpagedomain %} 35 |
    {{webpagedomain}} 36 | {% endif %} 37 | {% else %} 38 | {{remote.getTitle()}} 39 | {% endif %} 40 |
  • 41 | {% endfor %} 42 |
43 | {% endif %} -------------------------------------------------------------------------------- /data/templates/display-sidebar-history.htm: -------------------------------------------------------------------------------- 1 |

History

2 | 3 |
    4 | {% for commit in repo.getHistory %} 5 |
  • 6 | {% spaceless %} 7 | {% for dot in commit.getDots %} 8 | 9 | {% endfor %} 10 | {% endspaceless %} 11 | {{commit.hash|slice(0, 6)}} 12 | {{commit.committerName}} 13 | 14 | 15 | {{dh.get(commit.committerTime)}} 16 | 17 |
  • 18 | {% else %} 19 |

    No commits yet

    20 | {% endfor %} 21 |
22 | -------------------------------------------------------------------------------- /data/templates/display-sidebar-owner.htm: -------------------------------------------------------------------------------- 1 | {% set owner = repo.getOwner %} 2 |
3 | {{owner.name}} 6 | {{owner.name}} 7 |
owner 8 | 9 |
10 | -------------------------------------------------------------------------------- /data/templates/display-sidebar-urls.htm: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | {% if repo.getCloneURL(true) %} 5 | 6 | 7 | {% endif %} 8 | 9 | {% if repo.getCloneURL(false) %} 10 | 11 | 12 | {% endif %} 13 | -------------------------------------------------------------------------------- /data/templates/display.htm: -------------------------------------------------------------------------------- 1 | {% extends "base.htm" %} 2 | {% block title %}{{repo.getTitle}}{% endblock %} 3 | 4 | {% block meta %} 5 | 6 | 7 | 8 | {% if repo.getCloneURL(true) %} 9 | 10 | {% endif %} 11 | {% if repo.getCloneURL(false) %} 12 | 13 | {% endif %} 14 | {% endblock %} 15 | 16 | {% block content %} 17 | {% include 'display-head.htm' %} 18 | 19 | {% for file in repo.getFiles %} 20 | {% include 'display-file.htm' %} 21 | {% endfor %} 22 | 23 | {% include 'display-foot.htm' %} 24 | {% endblock %} 25 | 26 | {% block sidebar %} 27 | {% include 'display-sidebar-owner.htm' %} 28 | {% include 'display-sidebar-fork.htm' %} 29 | {% include 'display-sidebar-history.htm' %} 30 | {% include 'display-sidebar-urls.htm' %} 31 | {% endblock %} 32 | -------------------------------------------------------------------------------- /data/templates/doap.htm: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | {{repo.getTitle}} 7 | 8 | {{date}} 9 | {% set owner = repo.getOwner() %} 10 | 11 | 12 | {{owner.name}} 13 | 14 | 15 | 16 | {% if repo.getCloneURL(true) %} 17 | 18 | 19 | 20 | 21 | 22 | 23 | {%endif %} 24 | 25 | 26 | -------------------------------------------------------------------------------- /data/templates/edit-add-button.htm: -------------------------------------------------------------------------------- 1 | 5 | -------------------------------------------------------------------------------- /data/templates/edit-add.htm: -------------------------------------------------------------------------------- 1 | {% include 'edit-file.htm' with {'file': null, 'fileid': 'new', 'newfile': true} %} 2 | 3 | 24 | -------------------------------------------------------------------------------- /data/templates/edit-file.htm: -------------------------------------------------------------------------------- 1 |
2 | {% if not file or file.isText %} 3 | 4 | {% else %} 5 |

6 | Binary files cannot be edited. 7 |

8 | {% endif %} 9 |
10 |
11 | 14 |
15 |
16 |
17 |
18 | 19 | 20 |
21 |
22 | 23 | 26 |
27 | 30 |
31 |
32 |
33 | 34 | 35 |
36 | {% if not newfile %} 37 |
38 | 39 | 40 |
41 | {% endif %} 42 |
43 |
44 |
45 |
46 | -------------------------------------------------------------------------------- /data/templates/edit.htm: -------------------------------------------------------------------------------- 1 | {% extends "base.htm" %} 2 | {% block title %}Edit paste{% endblock %} 3 | 4 | {% block meta %} 5 | 6 | {% endblock %} 7 | 8 | {% block content %} 9 |
10 |
11 |
12 | 13 |
14 | 15 |
16 |
17 | 18 | {% for fileid, file in repo.getFiles %} 19 | {% if not singlefile or file == singlefile %} 20 | {% include 'edit-file.htm' with {'file': file, 'fileid': fileid, 'newfile': false} %} 21 | {% endif %} 22 | {% endfor %} 23 | 24 | {% if singlefile == "newfile" %} 25 | {% include 'edit-file.htm' with {'file': null, 'fileid': 'newfile', 'newfile': true} %} 26 | {% endif %} 27 | 28 | {% include 'edit-add.htm' %} 29 | 30 |
31 |
32 | Cancel 33 |
34 |
35 | {% include 'edit-add-button.htm' %} 36 |
37 |
38 | 42 |
43 |
44 | 45 |
46 | 51 | {% endblock %} 52 | 53 | {% block sidebar %} 54 | {% include 'display-sidebar-history.htm' %} 55 | {% endblock %} 56 | -------------------------------------------------------------------------------- /data/templates/embed-file.htm: -------------------------------------------------------------------------------- 1 | /* embedding {{file.getFilename()}} of {{repo.getLink('display', null, true)}} */ 2 | var me = document.getElementById('phork-script-{{repo.id}}-{{file.getFilename()}}'); 3 | me.insertAdjacentHTML( 4 | 'afterend', 5 | '' 6 | + '
' 7 | + {% filter json_encode(constant('JSON_UNESCAPED_SLASHES'))|raw -%}{% include 'embed-part-file.htm' %}{%- endfilter %} 8 | + '
' 9 | ); 10 | -------------------------------------------------------------------------------- /data/templates/embed-part-file.htm: -------------------------------------------------------------------------------- 1 |
2 |
3 | {{file.getRenderedContent(toolres)|raw}} 4 |
5 | 9 |
10 | -------------------------------------------------------------------------------- /data/templates/embed.htm: -------------------------------------------------------------------------------- 1 | /* embedding all files of {{repo.getLink('display', null, true)}} */ 2 | var me = document.getElementById('phork-script-{{repo.id}}'); 3 | me.insertAdjacentHTML( 4 | 'afterend', 5 | '' 6 | + '
' 7 | {% for file in repo.getFiles %} 8 | + {% filter json_encode(constant('JSON_UNESCAPED_SLASHES'))|raw -%}{% include 'embed-part-file.htm' %}{%- endfilter %} 9 | {% endfor %} 10 | + '
' 11 | ); 12 | -------------------------------------------------------------------------------- /data/templates/exception.htm: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Error - phorkie 7 | 8 | 9 | 10 |
11 |

Error

12 |

13 | {{exception.getMessage}} 14 |

15 | {% if debug %} 16 |
{{exception.getTraceAsString}}
17 | {% endif %} 18 |
19 | 20 | 21 | -------------------------------------------------------------------------------- /data/templates/feed-new.htm: -------------------------------------------------------------------------------- 1 | 2 | 3 | {{title}}: New pastes 4 | 5 | 6 | {{url}} 7 | {% set repo = pastes.repos.0 %} 8 | {{repo.crdate|date('c')}} 9 | 10 | {% for repo in pastes.repos %} 11 | 12 | {{repo.getLink('display', null, true)}} 13 | {{repo.getTitle}} 14 | {{repo.crdate|date('c')}} 15 | 16 | 17 | {% set owner=repo.getOwner() %} 18 | {{owner.name}} 19 | {{owner.email}} 20 | 21 | 22 | {% endfor %} 23 | -------------------------------------------------------------------------------- /data/templates/feed-updated.htm: -------------------------------------------------------------------------------- 1 | 2 | 3 | {{title}}: Updated pastes 4 | 5 | 6 | {{url}} 7 | {% set repo = pastes.repos.0 %} 8 | {{repo.modate|date('c')}} 9 | 10 | {% for repo in pastes.repos %} 11 | 12 | {{repo.getLink('display', null, true)}} 13 | {{repo.getTitle}} 14 | {{repo.crdate|date('c')}} 15 | {% set commit = repo.getHistory().0 %} 16 | {{commit.committerTime|date('c')}} 17 | 18 | 19 | {{commit.committerName}} 20 | {{commit.committerEmail}} 21 | 22 | {% spaceless %} 23 | {% if commit.filesChanged %} 24 | {{ntext(commit.filesChanged, "%d file", "%d files")}} changed{% if commit.linesAdded %},{% endif %} 25 | {% endif %} 26 | {% if commit.linesAdded %} 27 | {{ntext(commit.linesAdded, "%d line", "%d lines")}} added{% if commit.linesDeleted %},{% endif %} 28 | {% endif %} 29 | {% if commit.linesDeleted %} 30 | {{ntext(commit.linesDeleted, "%d line", "%d lines")}} deleted 31 | {% endif %} 32 | {% endspaceless %} 33 | 34 | {% endfor %} 35 | -------------------------------------------------------------------------------- /data/templates/flashmessages.htm: -------------------------------------------------------------------------------- 1 | {% for msgdata in flashmessages %} 2 |
3 | × 4 | {{msgdata.msg}} 5 |
6 | {% endfor %} -------------------------------------------------------------------------------- /data/templates/forbidden.htm: -------------------------------------------------------------------------------- 1 | {% extends "base.htm" %} 2 | {% block title %}Access Denied{% endblock %} 3 | 4 | {% block meta %} 5 | 6 | {% endblock %} 7 | 8 | {% block content %} 9 | 10 |

Access Denied

11 | {% if identity %} 12 |

13 | You are logged in with the following OpenID: 14 |

15 |

16 | {{identity}} 17 |

18 |

19 | Unfortunately, your OpenID is not unlocked. 20 | Contact the site administrator to get access. 21 |

22 | {% else %} 23 |

24 | We're sorry; but you have to log in to access this page. 25 |

26 | {% endif %} 27 | {% endblock %} 28 | 29 | -------------------------------------------------------------------------------- /data/templates/fork-remote-multiple.htm: -------------------------------------------------------------------------------- 1 |
2 | 3 |
4 |

5 | The URL you provided contains links to several Git repositories. 6 | Select one of them. 7 |

8 | 21 |
22 |
23 | 26 |
27 | 28 |
-------------------------------------------------------------------------------- /data/templates/fork-remote-new.htm: -------------------------------------------------------------------------------- 1 |
2 |

3 | Copy a paste from a remote server: 4 | Just paste the website or git clone URL. 5 |

6 |
7 | 8 |
9 | 10 | 13 |
14 |
15 |
16 | -------------------------------------------------------------------------------- /data/templates/fork-remote.htm: -------------------------------------------------------------------------------- 1 | {% extends "base.htm" %} 2 | {% block title %}Fork remote paste{% endblock %} 3 | 4 | {% block meta %} 5 | 6 | {% endblock %} 7 | 8 | {% block content %} 9 |

Fork remote paste

10 | {% if error %} 11 |
12 | 13 | Error {{error}} 14 |
15 | {% endif %} 16 | 17 | {% include 'fork-remote-new.htm' %} 18 | 19 | {% if urls %} 20 | {% include 'fork-remote-multiple.htm' %} 21 | {% endif %} 22 | 23 | {% endblock %} 24 | -------------------------------------------------------------------------------- /data/templates/help.htm: -------------------------------------------------------------------------------- 1 | {% extends "base.htm" %} 2 | {% block title %}phorkie help{% endblock %} 3 | 4 | {% block content %} 5 |

Help on phorkie

6 | 7 |

Remote forking

8 | 20 | 21 |
22 |
23 |

24 | Phorkie allows cloning/forking of pastes on other servers 25 | - other phorkie instances, github gists or simply any remote 26 | git repository. 27 |

28 |

29 | You can go to fork-remote and enter the 30 | URL of the paste on the other server. 31 |

32 |

33 | It's way easier to simply click a "fork" button that automatically 34 | takes you to this phorkie's remote paste page, prefilled with 35 | the correct git URL. 36 |

37 |

38 | To make this work, click the register button on the right. 39 | It will register a "protocol handler" for "web+fork:"-URLs, 40 | making phorkie chime in whenever a web+fork URL is clicked. 41 |

42 |
43 | 48 |
49 | 50 |

Setup check

51 |

52 | You need to activate public clone URLs in your config file. 53 | Otherwise forking onto remote systems will not work. 54 |

55 | {% if publicGitUrl %} 56 |
57 | OK! A public git URL prefix is configured: {{publicGitUrl}} 58 |
59 | {% else %} 60 |
61 | Error! No public git URL prefix configured. 62 |
63 | {% endif %} 64 | {% endblock %} 65 | -------------------------------------------------------------------------------- /data/templates/list.htm: -------------------------------------------------------------------------------- 1 | {% extends "base.htm" %} 2 | {% block title %}List of all pastes{% endblock %} 3 | 4 | {% block meta %} 5 | 6 | 7 | {% endblock %} 8 | 9 | {% block content %} 10 |
11 | 16 | 17 | {% include 'pager.htm' %} 18 | 19 | {% endblock %} 20 | 21 | {% block sidebar %} 22 | {% if recents.results %} 23 |

Recently updated

24 |
    25 | {% for repo in recents.repos %} 26 | {% include 'repo-sidebar-list.htm' %} 27 | {% endfor %} 28 |
29 | {% endif %} 30 | {% endblock %} 31 | -------------------------------------------------------------------------------- /data/templates/login.htm: -------------------------------------------------------------------------------- 1 | {% extends "base.htm" %} 2 | {% block title %}Login{% endblock %} 3 | 4 | {% block meta %} 5 | 6 | {% endblock %} 7 | 8 | {% block content %} 9 | 10 | 11 | 12 |
13 | 14 |
15 | Sign-in 16 |
17 |
18 |

19 | 20 | 21 |
22 |
23 |
24 | 25 |
26 | 29 | {% endblock %} 30 | -------------------------------------------------------------------------------- /data/templates/new.htm: -------------------------------------------------------------------------------- 1 | {% extends "base.htm" %} 2 | {% block title %}New paste{% endblock %} 3 | 4 | {% block content %} 5 |
6 |
7 |
8 | 9 |
10 | 11 |
12 |
13 | 14 | {% include 'edit-file.htm' with {'file': file[1], 'fileid': 0, 'newfile': true} %} 15 | {% include 'edit-add.htm' %} 16 | 17 |
18 |
19 |
20 | {% include 'edit-add-button.htm' %} 21 |
22 |
23 | 27 |
28 |
29 | 30 |
31 | 32 | {% include 'fork-remote-new.htm' %} 33 | 34 | 39 | {% endblock %} 40 | 41 | 42 | {% block sidebar %} 43 | {% if recents.results %} 44 |

Recently updated

45 |
    46 | {% for repo in recents.repos %} 47 | {% include 'repo-sidebar-list.htm' %} 48 | {% endfor %} 49 |
50 | {% endif %} 51 | {% endblock %} 52 | -------------------------------------------------------------------------------- /data/templates/oembed.htm: -------------------------------------------------------------------------------- 1 | 2 | 3 |
4 | {% for file in repo.getFiles %} 5 | {% include 'embed-part-file.htm' %} 6 | {% endfor %} 7 |
8 | -------------------------------------------------------------------------------- /data/templates/pager.htm: -------------------------------------------------------------------------------- 1 | {% set links = pager.getLinks() %} 2 | {% if pager.numPages > 1 %} 3 | 48 | {% endif %} 49 | -------------------------------------------------------------------------------- /data/templates/repo-list.htm: -------------------------------------------------------------------------------- 1 |
  • 2 | 3 | {{repo.id}} 4 | {{repo.getDescription}} 5 | 6 |
  • 7 | -------------------------------------------------------------------------------- /data/templates/repo-sidebar-list.htm: -------------------------------------------------------------------------------- 1 |
  • 2 | 3 | {{repo.getTitle}} 4 |
    5 | {{dh.get(repo.modate)}} 6 |
  • 7 | -------------------------------------------------------------------------------- /data/templates/revision-head.htm: -------------------------------------------------------------------------------- 1 |

    {{repo.getTitle}}

    2 |
    3 | 8 |
    9 |

    10 | revision {{repo.hash}} 11 |

    12 |
    13 |
    14 |
    15 |
    16 | -------------------------------------------------------------------------------- /data/templates/revision.htm: -------------------------------------------------------------------------------- 1 | {% extends "base.htm" %} 2 | {% block title %}{{repo.getTitle}} - revision {{repo.hash}}{% endblock %} 3 | 4 | {% block meta %} 5 | 6 | {% endblock %} 7 | 8 | {% block content %} 9 | {% include 'revision-head.htm' %} 10 | 11 | {% for file in repo.getFiles %} 12 | {% include 'display-file.htm' %} 13 | {% endfor %} 14 | 15 | {% endblock %} 16 | 17 | {% block sidebar %} 18 | {% include 'display-sidebar-history.htm' %} 19 | {% endblock %} 20 | -------------------------------------------------------------------------------- /data/templates/search.htm: -------------------------------------------------------------------------------- 1 | {% extends "base.htm" %} 2 | {% block title %}Search results{% endblock %} 3 | 4 | {% block meta %} 5 | 6 | {% endblock %} 7 | 8 | {% block content %} 9 | {% if sres.results == 0 %} 10 |

    11 | Sorry, no results for "{{query}}". 12 |

    13 | {% else %} 14 |

    15 | Found {{sres.results}} search results for "{{query}}": 16 |

    17 | 22 | {% include 'pager.htm' %} 23 | {% endif %} 24 | 25 | {% endblock %} 26 | 27 | {% block sidebar %} 28 |

    Search tips

    29 |
    30 |
    Exclusion
    31 |
    +foo -bar
    32 |
    Logical OR
    33 |
    foo OR bar
    34 |
    Quoting
    35 |
    "foo bar"
    36 |
    Partial words
    37 |
    foo*
    38 |
    Particular fields only
    39 |
    description:foo
    40 |
    name:foo.txt
    41 |
    extension:rst
    42 |
    content:Hello
    43 |
    44 | {% endblock %} 45 | -------------------------------------------------------------------------------- /data/templates/tool.htm: -------------------------------------------------------------------------------- 1 | {% extends "base.htm" %} 2 | {% block title %} 3 | Tool results: {{repo.getTitle}} 4 | {% endblock %} 5 | 6 | {% block meta %} 7 | 8 | {% endblock %} 9 | 10 | {% block content %} 11 |

    Tool results: {{repo.getTitle}}

    12 |
    13 |
    14 | edit 15 | back 16 |
    17 |
    18 | 19 | {% for line in toolres.annotations.general %} 20 |
    21 | {{line.message}} 22 |
    23 | {% endfor %} 24 | 25 | {% include 'display-file.htm' %} 26 | 27 |
    28 | {% for number,lineinfos in toolres.annotations if number != 'general' %} 29 | {% for line in lineinfos %} 30 |
    31 | Line #{{number}}: {{line.message}} 32 |
    33 | {% endfor %} 34 | {% endfor %} 35 |
    36 | 37 | {% include 'display-foot.htm' %} 38 | {% endblock %} 39 | 40 | -------------------------------------------------------------------------------- /data/templates/user.htm: -------------------------------------------------------------------------------- 1 | {% extends "base.htm" %} 2 | {% block title %}Profile data{% endblock %} 3 | 4 | {% block meta %} 5 | 6 | {% endblock %} 7 | 8 | {% block content %} 9 | 10 |

    User profile

    11 |
    12 |
    OpenID
    13 |
    {{ identity }}
    14 | 15 |
    Name
    16 |
    {{ name }}
    17 | 18 |
    Email
    19 |
    {{ email }}
    20 | 21 |
    Avatar image
    22 |
    23 |
    24 | 25 |

    26 | You may change this data with your OpenID provider. 27 | The avatar image is loaded from libravatar.org, 28 | or perhaps from your own avatar server. 29 |

    30 | {% endblock %} 31 | -------------------------------------------------------------------------------- /scripts/build-rewritemap.php: -------------------------------------------------------------------------------- 1 | \n" 20 | ); 21 | ?> 22 | -------------------------------------------------------------------------------- /scripts/index.php: -------------------------------------------------------------------------------- 1 | prefix == '\phorkie\Database_Adapter_Null') { 18 | echo "Error: No search adapter configured.\n"; 19 | exit(1); 20 | } 21 | 22 | $idx = $db->getIndexer(); 23 | 24 | //create mapping 25 | echo "Index reset\n"; 26 | $db->getSetup()->reset(); 27 | 28 | 29 | $rs = new Repositories(); 30 | list($repos, $count) = $rs->getList(0, 10000); 31 | foreach ($repos as $repo) { 32 | echo 'Indexing ' . $repo->id . "\n"; 33 | $commits = $repo->getHistory(); 34 | $first = count($commits)-1; 35 | $idx->addRepo($repo, $commits[$first]->committerTime, $commits[0]->committerTime); 36 | } 37 | ?> 38 | -------------------------------------------------------------------------------- /scripts/search.php: -------------------------------------------------------------------------------- 1 | setBody( 19 | json_encode( 20 | (object)array( 21 | 'from' => 0, 22 | 'size' => 2, 23 | 'query' => (object)array( 24 | 'bool' => (object)array( 25 | 'should' => array( 26 | (object)array( 27 | 'query_string' => (object)array( 28 | 'query' => 'test' 29 | ), 30 | ), 31 | (object)array( 32 | 'has_child' => (object)array( 33 | 'type' => 'file', 34 | 'query' => (object)array( 35 | 'query_string' => (object)array( 36 | 'query' => 'test' 37 | ) 38 | ) 39 | ) 40 | ) 41 | ) 42 | ), 43 | ) 44 | ) 45 | ) 46 | ); 47 | $res = $r->send(); 48 | echo $res->getBody() . "\n"; 49 | ?> 50 | -------------------------------------------------------------------------------- /src/phorkie/Database.php: -------------------------------------------------------------------------------- 1 | adapter = 'Elasticsearch'; 13 | $this->prefix = '\phorkie\Database_Adapter_Elasticsearch'; 14 | } 15 | } 16 | public function getSearch() 17 | { 18 | $class = $this->prefix . '_Search'; 19 | return new $class(); 20 | } 21 | 22 | public function getIndexer() 23 | { 24 | $class = $this->prefix . '_Indexer'; 25 | return new $class(); 26 | } 27 | 28 | public function getSetup() 29 | { 30 | $class = $this->prefix . '_Setup'; 31 | return new $class(); 32 | } 33 | } 34 | 35 | ?> 36 | -------------------------------------------------------------------------------- /src/phorkie/Database/Adapter/Elasticsearch/HTTPRequest.php: -------------------------------------------------------------------------------- 1 | getStatus() / 100); 12 | if ($mainCode === 2) { 13 | return $res; 14 | } 15 | 16 | if ($this->allow404 && $res->getStatus() == 404) { 17 | return $res; 18 | } 19 | $js = json_decode($res->getBody()); 20 | if (isset($js->error)) { 21 | $error = json_encode($js->error); 22 | } else { 23 | $error = $res->getBody(); 24 | } 25 | 26 | throw new Exception( 27 | 'Error in elasticsearch communication at ' 28 | . $this->getMethod() . ' ' . (string) $this->getUrl() 29 | . ' (status code ' . $res->getStatus() . '): ' 30 | . $error 31 | ); 32 | } 33 | } 34 | 35 | ?> 36 | -------------------------------------------------------------------------------- /src/phorkie/Database/Adapter/Elasticsearch/Indexer.php: -------------------------------------------------------------------------------- 1 | searchInstance = $GLOBALS['phorkie']['cfg']['elasticsearch']; 9 | } 10 | 11 | 12 | public function addRepo(Repository $repo, $crdate = null, $modate = null) 13 | { 14 | if ($crdate == null) { 15 | $crdate = time(); 16 | } 17 | if ($modate == null) { 18 | $modate = time(); 19 | } 20 | $this->updateRepo($repo, $crdate, $modate); 21 | } 22 | 23 | public function updateRepo(Repository $repo, $crdate = null, $modate = null) 24 | { 25 | //add repository 26 | $r = new Database_Adapter_Elasticsearch_HTTPRequest( 27 | $this->searchInstance . 'repo/' . $repo->id, 28 | \HTTP_Request2::METHOD_PUT 29 | ); 30 | $repoData = array( 31 | 'id' => $repo->id, 32 | 'description' => $repo->getDescription(), 33 | 'tstamp' => gmdate('c', time()), 34 | ); 35 | if ($crdate == null) { 36 | $crdate = $this->getCrDate($repo); 37 | } 38 | if ($crdate !== null) { 39 | $repoData['crdate'] = gmdate('c', $crdate); 40 | } 41 | if ($modate == null) { 42 | $modate = $this->getMoDate($repo); 43 | } 44 | if ($modate !== null) { 45 | $repoData['modate'] = gmdate('c', $modate); 46 | } 47 | 48 | $r->setBody(json_encode((object)$repoData)); 49 | $r->send(); 50 | 51 | //add files 52 | //clean up before adding files; files might have been deleted 53 | $this->deleteRepoFiles($repo); 54 | 55 | foreach ($repo->getFiles() as $file) { 56 | $r = new Database_Adapter_Elasticsearch_HTTPRequest( 57 | $this->searchInstance . 'file/?parent=' . $repo->id, 58 | \HTTP_Request2::METHOD_POST 59 | ); 60 | $r->setBody( 61 | json_encode( 62 | (object)array( 63 | 'name' => $file->getFilename(), 64 | 'extension' => $file->getExt(), 65 | 'content' => $file->isText() ? $file->getContent() : '', 66 | ) 67 | ) 68 | ); 69 | $r->send(); 70 | } 71 | } 72 | 73 | /** 74 | * When updating the repository, we don't have a creation date. 75 | * We need to keep it, but elasticsearch does not have a simple way 76 | * to update some fields only (without using a custom script). 77 | * 78 | * @return integer Unix timestamp 79 | */ 80 | protected function getCrDate(Repository $repo) 81 | { 82 | $r = new Database_Adapter_Elasticsearch_HTTPRequest( 83 | $this->searchInstance . 'repo/' . $repo->id, 84 | \HTTP_Request2::METHOD_GET 85 | ); 86 | $json = json_decode($r->send()->getBody()); 87 | 88 | if (!isset($json->_source->crdate)) { 89 | return null; 90 | } 91 | 92 | return strtotime($json->_source->crdate); 93 | } 94 | 95 | /** 96 | * When updating the repository, we don't have a modification date. 97 | * We need to keep it, but elasticsearch does not have a simple way 98 | * to update some fields only (without using a custom script). 99 | * 100 | * @return integer Unix timestamp 101 | */ 102 | protected function getMoDate(Repository $repo) 103 | { 104 | $r = new Database_Adapter_Elasticsearch_HTTPRequest( 105 | $this->searchInstance . 'repo/' . $repo->id, 106 | \HTTP_Request2::METHOD_GET 107 | ); 108 | $json = json_decode($r->send()->getBody()); 109 | 110 | if (!isset($json->_source->modate)) { 111 | return null; 112 | } 113 | 114 | return strtotime($json->_source->modate); 115 | } 116 | 117 | public function deleteAllRepos() 118 | { 119 | $r = new Database_Adapter_Elasticsearch_HTTPRequest( 120 | $this->searchInstance . 'repo', 121 | \HTTP_Request2::METHOD_DELETE 122 | ); 123 | $r->send(); 124 | 125 | $r = new Database_Adapter_Elasticsearch_HTTPRequest( 126 | $this->searchInstance . 'file', 127 | \HTTP_Request2::METHOD_DELETE 128 | ); 129 | $r->send(); 130 | } 131 | 132 | public function deleteRepo(Repository $repo) 133 | { 134 | //delete repository from index 135 | $r = new Database_Adapter_Elasticsearch_HTTPRequest( 136 | $this->searchInstance . 'repo/' . $repo->id, 137 | \HTTP_Request2::METHOD_DELETE 138 | ); 139 | $r->allow404 = true; 140 | $r->send(); 141 | 142 | $this->deleteRepoFiles($repo); 143 | } 144 | 145 | protected function deleteRepoFiles(Repository $repo) 146 | { 147 | //delete files of that repository 148 | $r = new Database_Adapter_Elasticsearch_HTTPRequest( 149 | $this->searchInstance . 'file/_query' 150 | . '?q=_parent:' . $repo->id, 151 | \HTTP_Request2::METHOD_DELETE 152 | ); 153 | $r->send(); 154 | } 155 | 156 | } 157 | 158 | ?> 159 | -------------------------------------------------------------------------------- /src/phorkie/Database/Adapter/Elasticsearch/Search.php: -------------------------------------------------------------------------------- 1 | array('id', 'asc'), 8 | 'crdate' => array('crdate', 'desc'), 9 | 'modate' => array('modate', 'desc'), 10 | 'tstamp' => array('tstamp', 'desc'), 11 | ); 12 | 13 | public function __construct() 14 | { 15 | $this->searchInstance = $GLOBALS['phorkie']['cfg']['elasticsearch']; 16 | } 17 | 18 | /** 19 | * List all repositories 20 | * 21 | * @param integer $page Page of search results, starting with 0 22 | * @param integer $perPage Number of results per page 23 | * @param string $sort Sort order. Allowed values: 24 | * - id - repository id 25 | * - crdate - creation date 26 | * - modate - modification date 27 | * - tstamp - last index date 28 | * 29 | * @return Search_Result Search result object 30 | */ 31 | public function listAll($page = 0, $perPage = 10, $sort = 'id', $sortOrder = null) 32 | { 33 | list($sortField, $orderField) = $this->getSortField($sort, $sortOrder); 34 | $r = new Database_Adapter_Elasticsearch_HTTPRequest( 35 | $this->searchInstance . 'repo/_search', 36 | \HTTP_Request2::METHOD_GET 37 | ); 38 | $r->setBody( 39 | json_encode( 40 | (object)array( 41 | 'from' => $page * $perPage, 42 | 'size' => $perPage, 43 | 'sort' => array( 44 | $sortField => $orderField 45 | ), 46 | 'query' => (object)array( 47 | 'match_all' => (object)array() 48 | ), 49 | ) 50 | ) 51 | ); 52 | $httpRes = $r->send(); 53 | $jRes = json_decode($httpRes->getBody()); 54 | if (isset($jRes->error)) { 55 | throw new Exception( 56 | 'Search exception: ' . $jRes->error, $jRes->status 57 | ); 58 | } 59 | 60 | $sres = new Search_Result(); 61 | $sres->results = $jRes->hits->total; 62 | $sres->page = $page; 63 | $sres->perPage = $perPage; 64 | 65 | foreach ($jRes->hits->hits as $hit) { 66 | $r = new Repository(); 67 | try { 68 | $r->loadById($hit->_source->id); 69 | } catch (Exception_NotFound $e) { 70 | continue; 71 | } 72 | $r->crdate = strtotime($hit->_source->crdate); 73 | $r->modate = strtotime($hit->_source->modate); 74 | $sres->repos[] = $r; 75 | } 76 | 77 | return $sres; 78 | } 79 | 80 | 81 | /** 82 | * Search for a given term and return repositories that contain it 83 | * in their description, file names or file content 84 | * 85 | * @param string $term Search term 86 | * @param integer $page Page of search results, starting with 0 87 | * @param integer $perPage Number of results per page 88 | * 89 | * @return Search_Result Search result object 90 | */ 91 | public function search($term, $page = 0, $perPage = 10) 92 | { 93 | $r = new Database_Adapter_Elasticsearch_HTTPRequest( 94 | $this->searchInstance . 'repo/_search', 95 | \HTTP_Request2::METHOD_GET 96 | ); 97 | $r->setBody( 98 | json_encode( 99 | (object)array( 100 | 'from' => $page * $perPage, 101 | 'size' => $perPage, 102 | 'query' => (object)array( 103 | 'bool' => (object)array( 104 | 'should' => array( 105 | (object)array( 106 | 'query_string' => (object)array( 107 | 'query' => $term, 108 | 'default_operator' => 'AND' 109 | ), 110 | ), 111 | (object)array( 112 | 'has_child' => (object)array( 113 | 'type' => 'file', 114 | 'query' => (object)array( 115 | 'query_string' => (object)array( 116 | 'query' => $term, 117 | 'default_operator' => 'AND' 118 | ) 119 | ) 120 | ) 121 | ) 122 | ) 123 | ) 124 | ) 125 | ) 126 | ) 127 | ); 128 | $httpRes = $r->send(); 129 | $jRes = json_decode($httpRes->getBody()); 130 | if (isset($jRes->error)) { 131 | throw new Exception( 132 | 'Search exception: ' . $jRes->error, $jRes->status 133 | ); 134 | } 135 | 136 | $sres = new Search_Result(); 137 | $sres->results = $jRes->hits->total; 138 | $sres->page = $page; 139 | $sres->perPage = $perPage; 140 | 141 | foreach ($jRes->hits->hits as $hit) { 142 | $r = new Repository(); 143 | //FIXME: error handling. what about deleted repos? 144 | $r->loadById($hit->_source->id); 145 | $sres->repos[] = $r; 146 | } 147 | 148 | return $sres; 149 | } 150 | 151 | protected function getSortField($sort, $sortOrder) 152 | { 153 | if (!isset(self::$sortMap[$sort])) { 154 | throw new Exception('Invalid sort parameter: ' . $sort); 155 | } 156 | if ($sortOrder !== 'asc' && $sortOrder !== 'desc') { 157 | throw new Exception('Invalid sortOrder parameter: ' . $sortOrder); 158 | } 159 | 160 | $data = self::$sortMap[$sort]; 161 | if ($sortOrder !== null) { 162 | $data[1] = $sortOrder; 163 | } 164 | return $data; 165 | } 166 | } 167 | 168 | ?> 169 | -------------------------------------------------------------------------------- /src/phorkie/Database/Adapter/Elasticsearch/Setup.php: -------------------------------------------------------------------------------- 1 | searchInstance = $GLOBALS['phorkie']['cfg']['elasticsearch']; 9 | } 10 | 11 | public function setup() 12 | { 13 | $r = new \HTTP_Request2( 14 | $this->searchInstance . '/_mapping', \HTTP_Request2::METHOD_GET 15 | ); 16 | $res = $r->send(); 17 | if ($res->getStatus() == 404) { 18 | $this->reset(); 19 | } 20 | } 21 | 22 | public function reset() 23 | { 24 | $r = new \HTTP_Request2( 25 | $this->searchInstance, 26 | \HTTP_Request2::METHOD_DELETE 27 | ); 28 | $r->send(); 29 | 30 | $r = new Database_Adapter_Elasticsearch_HTTPRequest( 31 | $this->searchInstance, 32 | \HTTP_Request2::METHOD_PUT 33 | ); 34 | $r->send(); 35 | 36 | //mapping for files 37 | $r = new Database_Adapter_Elasticsearch_HTTPRequest( 38 | $this->searchInstance . 'file/_mapping', 39 | \HTTP_Request2::METHOD_PUT 40 | ); 41 | $r->setBody( 42 | json_encode( 43 | (object)array( 44 | 'file' => (object)array( 45 | '_parent' => (object)array( 46 | 'type' => 'repo' 47 | ), 48 | 'properties' => (object)array( 49 | 'name' => (object)array( 50 | 'type' => 'string', 51 | 'boost' => 1.5 52 | ), 53 | 'extension' => (object)array( 54 | 'type' => 'string', 55 | 'boost' => 1.0 56 | ), 57 | 'content' => (object)array( 58 | 'type' => 'string', 59 | 'boost' => 0.8 60 | ) 61 | ) 62 | ) 63 | ) 64 | ) 65 | ); 66 | $r->send(); 67 | 68 | //create mapping 69 | //mapping for repositories 70 | $r = new Database_Adapter_Elasticsearch_HTTPRequest( 71 | $this->searchInstance . 'repo/_mapping', 72 | \HTTP_Request2::METHOD_PUT 73 | ); 74 | $r->setBody( 75 | json_encode( 76 | (object)array( 77 | 'repo' => (object)array( 78 | '_timestamp' => (object)array( 79 | 'enabled' => true, 80 | //'path' => 'tstamp', 81 | ), 82 | 'properties' => (object)array( 83 | 'id' => (object)array( 84 | 'type' => 'long' 85 | ), 86 | 'description' => (object)array( 87 | 'type' => 'string', 88 | 'boost' => 2.0 89 | ), 90 | 'crdate' => (object)array( 91 | //creation date 92 | 'type' => 'date', 93 | ), 94 | 'modate' => (object)array( 95 | //modification date 96 | 'type' => 'date', 97 | ), 98 | 'tstamp' => (object)array( 99 | //last indexed date 100 | 'type' => 'date', 101 | ) 102 | ) 103 | ) 104 | ) 105 | ) 106 | ); 107 | $r->send(); 108 | } 109 | 110 | } 111 | 112 | ?> 113 | -------------------------------------------------------------------------------- /src/phorkie/Database/Adapter/Null/Indexer.php: -------------------------------------------------------------------------------- 1 | 24 | -------------------------------------------------------------------------------- /src/phorkie/Database/Adapter/Null/Search.php: -------------------------------------------------------------------------------- 1 | results = 0; 10 | $sres->page = $page; 11 | $sres->perPage = $perPage; 12 | return $sres; 13 | } 14 | 15 | public function listAll($page = 0, $perPage = 10, $sort = 'id', $sortOrder = null) 16 | { 17 | $sres = new Search_Result(); 18 | $sres->results = 0; 19 | $sres->page = $page; 20 | $sres->perPage = $perPage; 21 | return $sres; 22 | } 23 | } 24 | 25 | ?> 26 | -------------------------------------------------------------------------------- /src/phorkie/Database/Adapter/Null/Setup.php: -------------------------------------------------------------------------------- 1 | 16 | -------------------------------------------------------------------------------- /src/phorkie/Database/IIndexer.php: -------------------------------------------------------------------------------- 1 | 13 | -------------------------------------------------------------------------------- /src/phorkie/Database/ISearch.php: -------------------------------------------------------------------------------- 1 | 26 | -------------------------------------------------------------------------------- /src/phorkie/Database/ISetup.php: -------------------------------------------------------------------------------- 1 | 11 | -------------------------------------------------------------------------------- /src/phorkie/Exception.php: -------------------------------------------------------------------------------- 1 | 10 | -------------------------------------------------------------------------------- /src/phorkie/Exception/Input.php: -------------------------------------------------------------------------------- 1 | httpStatusCode = 400; 14 | } 15 | } 16 | 17 | ?> 18 | -------------------------------------------------------------------------------- /src/phorkie/Exception/NotFound.php: -------------------------------------------------------------------------------- 1 | httpStatusCode = 404; 13 | } 14 | } 15 | 16 | ?> 17 | -------------------------------------------------------------------------------- /src/phorkie/File.php: -------------------------------------------------------------------------------- 1 | path = $path; 28 | $this->repo = $repo; 29 | } 30 | 31 | /** 32 | * Get filename relative to the repository path 33 | * 34 | * @return string 35 | */ 36 | public function getFilename() 37 | { 38 | return $this->path; 39 | } 40 | 41 | /** 42 | * Get the filename usable as HTML anchor. 43 | * 44 | * @return string 45 | */ 46 | function getAnchorName() 47 | { 48 | return str_replace(' ', '-', $this->getFilename()); 49 | } 50 | 51 | /** 52 | * Return the full path to the file 53 | * 54 | * @return string 55 | */ 56 | public function getFullPath() 57 | { 58 | return $this->repo->workDir . '/' . $this->path; 59 | } 60 | 61 | /** 62 | * Get file extension without dot 63 | * 64 | * @return string 65 | */ 66 | public function getExt() 67 | { 68 | return strtolower(substr($this->path, strrpos($this->path, '.') + 1)); 69 | } 70 | 71 | public function getContent() 72 | { 73 | if ($this->repo->hash) { 74 | //quick hack until https://pear.php.net/bugs/bug.php?id=19385 is fixed 75 | $cmd = new GitCommandBinary($this->repo->getVc()); 76 | $cmd->setSubCommand('show'); 77 | return $cmd 78 | ->addArgument($this->repo->hash . ':' . $this->path) 79 | ->execute(); 80 | } 81 | 82 | return file_get_contents($this->getFullPath()); 83 | } 84 | 85 | public function getRenderedContent(Tool_Result $res = null) 86 | { 87 | $cache = new Renderer_Cache(); 88 | return $cache->toHtml($this, $res); 89 | } 90 | 91 | /** 92 | * Get a link to the file 93 | * 94 | * @param string $type Link type. Supported are: 95 | * - "display" 96 | * - "raw" 97 | * - "tool" 98 | * @param string $option Additional option, e.g. tool name 99 | * @param boolean $full Return full URL or normal relative 100 | * 101 | * @return string 102 | */ 103 | public function getLink($type, $option = null, $full = false) 104 | { 105 | if ($type == 'raw') { 106 | if ($this->repo->hash === null) { 107 | $link = $this->repo->id . '/raw/' . $this->getFilename(); 108 | } else { 109 | $link = $this->repo->id . '/rev-raw/' . $this->repo->hash 110 | . '/' . $this->getFilename(); 111 | } 112 | } else if ($type == 'tool') { 113 | $link = $this->repo->id 114 | . '/tool/' . $option 115 | . '/' . $this->getFilename(); 116 | } else if ($type == 'display') { 117 | $link = $this->repo->id . '#' . $this->getFilename(); 118 | } else { 119 | throw new Exception('Unknown type'); 120 | } 121 | 122 | if ($full) { 123 | $link = Tools::fullUrl($link); 124 | } 125 | return $link; 126 | } 127 | 128 | /** 129 | * @return string Mime type of file, NULL if no type detected 130 | */ 131 | public function getMimeType() 132 | { 133 | $ext = $this->getExt(); 134 | if (isset($GLOBALS['phorkie']['languages'][$ext])) { 135 | return $GLOBALS['phorkie']['languages'][$ext]['mime']; 136 | } 137 | 138 | $mte = new \MIME_Type_Extension(); 139 | $type = $mte->getMIMEType($this->getFilename()); 140 | if (!\PEAR::isError($type)) { 141 | return $type; 142 | } 143 | return null; 144 | } 145 | 146 | /** 147 | * @return array Array of Tool_Info objects 148 | */ 149 | public function getToolInfos() 150 | { 151 | if ($this->repo->hash !== null) { 152 | return array(); 153 | } 154 | 155 | $tm = new Tool_Manager(); 156 | return $tm->getSuitable($this); 157 | } 158 | 159 | /** 160 | * Tells if the file contains textual content and is editable. 161 | * 162 | * @return boolean 163 | */ 164 | public function isText() 165 | { 166 | $ext = $this->getExt(); 167 | if ($ext == '') { 168 | return $this->isNonBinary(); 169 | } 170 | 171 | $type = $this->getMimeType(); 172 | if ($type === null) { 173 | return $this->isNonBinary(); 174 | } 175 | return substr($type, 0, 5) === 'text/' 176 | || $type == 'application/javascript' 177 | || substr($type, -4) == '+xml' 178 | || substr($type, -5) == '+json'; 179 | } 180 | 181 | /** 182 | * Look at the file's bytes and guess if it's binary or not. 183 | * 184 | * @return boolean True if it's most likely plain text 185 | */ 186 | public function isNonBinary() 187 | { 188 | $fp = fopen($this->getFullPath(), 'r'); 189 | if (!$fp) { 190 | return false; 191 | } 192 | 193 | //When multibyte extension is not installed, 194 | // we only allow files with ASCII characters. 195 | // Files with UTF-8 characters will not be detected as text. 196 | $hasMb = function_exists('mb_detect_encoding'); 197 | 198 | $pos = 0; 199 | $data = ''; 200 | while (false !== ($char = fgetc($fp)) && ++$pos < 100) { 201 | $data .= $char; 202 | if (!$hasMb && ord($char) > 128) { 203 | fclose($fp); 204 | return false; 205 | } 206 | } 207 | fclose($fp); 208 | 209 | if (!$hasMb) { 210 | return true; 211 | } 212 | 213 | if (mb_detect_encoding($data) === false) { 214 | return false; 215 | } 216 | return true; 217 | } 218 | } 219 | 220 | ?> 221 | -------------------------------------------------------------------------------- /src/phorkie/FlashMessage.php: -------------------------------------------------------------------------------- 1 | $msg, 10 | 'type' => $type 11 | ); 12 | } 13 | 14 | public static function getAll() 15 | { 16 | if (!isset($_SESSION['flashmessages']) 17 | || !is_array($_SESSION['flashmessages']) 18 | ) { 19 | return array(); 20 | } 21 | 22 | $msgs = $_SESSION['flashmessages']; 23 | unset($_SESSION['flashmessages']); 24 | return $msgs; 25 | } 26 | } 27 | ?> 28 | -------------------------------------------------------------------------------- /src/phorkie/ForkRemote.php: -------------------------------------------------------------------------------- 1 | url = trim($url); 26 | } 27 | 28 | public function parse() 29 | { 30 | $hp = new HtmlParser(); 31 | $ret = $hp->extractGitUrls($this->url); 32 | $this->arGitUrls = $hp->getGitUrls(); 33 | $this->error = $hp->error; 34 | 35 | return $ret; 36 | } 37 | 38 | /** 39 | * Iterate through all git urls and return one if there is only 40 | * one supported one. 41 | * 42 | * @return mixed Boolean false or array with keys "url" and "title" 43 | */ 44 | public function getUniqueGitUrl() 45 | { 46 | $nFound = 0; 47 | foreach ($this->arGitUrls as $title => $arUrls) { 48 | foreach ($arUrls as $url) { 49 | $nFound++; 50 | $uniqueUrl = array('url' => $url, 'title' => $title); 51 | } 52 | } 53 | 54 | if ($nFound == 1) { 55 | return $uniqueUrl; 56 | } 57 | return false; 58 | } 59 | 60 | public function getGitUrls() 61 | { 62 | return $this->arGitUrls; 63 | } 64 | 65 | /** 66 | * Get the URL from which the git URL was derived, often 67 | * the HTTP URL. 68 | * 69 | * @return string 70 | */ 71 | public function getUrl() 72 | { 73 | return $this->url; 74 | } 75 | 76 | public function setUrl($url) 77 | { 78 | $this->url = $url; 79 | } 80 | } 81 | ?> 82 | -------------------------------------------------------------------------------- /src/phorkie/Forker.php: -------------------------------------------------------------------------------- 1 | fork($repo->gitDir); 9 | 10 | \copy($repo->gitDir . '/description', $new->gitDir . '/description'); 11 | $new->getVc() 12 | ->getCommand('config') 13 | ->addArgument('remote.origin.title') 14 | ->addArgument(file_get_contents($repo->gitDir . '/description')) 15 | ->execute(); 16 | 17 | $this->index($new); 18 | 19 | $not = new Notificator(); 20 | $not->create($new); 21 | 22 | return $new; 23 | } 24 | 25 | public function forkRemote($cloneUrl, $originalUrl, $title = null) 26 | { 27 | $new = $this->fork($cloneUrl); 28 | 29 | $new->getVc() 30 | ->getCommand('config') 31 | ->addArgument('remote.origin.title') 32 | ->addArgument($title) 33 | ->execute(); 34 | if ($originalUrl != $cloneUrl) { 35 | $new->getVc() 36 | ->getCommand('config') 37 | ->addArgument('remote.origin.homepage') 38 | ->addArgument($originalUrl) 39 | ->execute(); 40 | } 41 | 42 | if ($title === null) { 43 | $title = 'Fork of ' . $originalUrl; 44 | } 45 | file_put_contents($new->gitDir . '/description', $title); 46 | 47 | $this->index($new); 48 | 49 | $not = new Notificator(); 50 | $not->create($new); 51 | 52 | return $new; 53 | } 54 | 55 | 56 | protected function fork($pathOrUrl) 57 | { 58 | $rs = new Repositories(); 59 | $new = $rs->createNew(); 60 | $vc = $new->getVc(); 61 | 62 | //VersionControl_Git wants an existing dir, git clone not 63 | \rmdir($new->gitDir); 64 | 65 | $cmd = $vc->getCommand('clone') 66 | //this should be setOption, but it fails with a = between name and value 67 | ->addArgument('--separate-git-dir') 68 | ->addArgument( 69 | $GLOBALS['phorkie']['cfg']['gitdir'] . '/' . $new->id . '.git' 70 | ) 71 | ->addArgument($pathOrUrl) 72 | ->addArgument($new->workDir); 73 | try { 74 | $cmd->execute(); 75 | } catch (\Exception $e) { 76 | //clean up, we've got no workdir otherwise 77 | $new->delete(); 78 | throw $e; 79 | } 80 | 81 | $rs = new Repository_Setup($new); 82 | $rs->afterInit(); 83 | 84 | //update info for dumb git HTTP transport 85 | //the post-update hook should do that IMO, but does not somehow 86 | $vc->getCommand('update-server-info')->execute(); 87 | 88 | return $new; 89 | } 90 | 91 | protected function index($repo) 92 | { 93 | $db = new Database(); 94 | $db->getIndexer()->addRepo($repo); 95 | } 96 | } 97 | 98 | ?> 99 | -------------------------------------------------------------------------------- /src/phorkie/GitCommandBinary.php: -------------------------------------------------------------------------------- 1 | 16 | -------------------------------------------------------------------------------- /src/phorkie/Html/Pager.php: -------------------------------------------------------------------------------- 1 | pager = \Pager::factory( 25 | array( 26 | 'mode' => 'Sliding', 27 | 'perPage' => $perPage, 28 | 'delta' => 2, 29 | 'totalItems' => $itemCount, 30 | 'currentPage' => $currentPage, 31 | 'urlVar' => 'page', 32 | 'append' => $append, 33 | 'path' => '', 34 | 'fileName' => $filename, 35 | 'separator' => '###', 36 | 'spacesBeforeSeparator' => 0, 37 | 'spacesAfterSeparator' => 0, 38 | 'curPageSpanPre' => '', 39 | 'curPageSpanPost' => '', 40 | 'firstPagePre' => '', 41 | 'firstPageText' => 'first', 42 | 'firstPagePost' => '', 43 | 'lastPagePre' => '', 44 | 'lastPageText' => 'last', 45 | 'lastPagePost' => '', 46 | 'prevImg' => '« prev', 47 | 'nextImg' => 'next »', 48 | ) 49 | ); 50 | } 51 | 52 | 53 | public function getLinks() 54 | { 55 | $arLinks = $this->pager->getLinks(); 56 | $arLinks['pages'] = explode('###', $arLinks['pages']); 57 | return $arLinks; 58 | } 59 | 60 | public function numPages() 61 | { 62 | return $this->pager->numPages(); 63 | } 64 | } 65 | 66 | ?> 67 | -------------------------------------------------------------------------------- /src/phorkie/HtmlHelper.php: -------------------------------------------------------------------------------- 1 | detectHttps(); 16 | return $s->url( 17 | $email, 18 | array( 19 | 'size' => $size, 20 | 'default' => Tools::fullUrl('phorkie/anonymous.png') 21 | ) 22 | ); 23 | } 24 | 25 | public function getLanguageOptions(File $file = null) 26 | { 27 | $html = ''; 28 | $fileExt = null; 29 | if ($file !== null) { 30 | $fileExt = $file->getExt(); 31 | } 32 | foreach ($GLOBALS['phorkie']['languages'] as $ext => $arLang) { 33 | if (isset($arLang['show']) && !$arLang['show']) { 34 | continue; 35 | } 36 | $html .= sprintf( 37 | '', 38 | $ext, 39 | $fileExt == $ext ? ' selected="selected"' : '', 40 | $arLang['title'] 41 | ) . "\n"; 42 | } 43 | return $html; 44 | } 45 | 46 | public function getDomain($url) 47 | { 48 | return parse_url($url, PHP_URL_HOST); 49 | } 50 | 51 | public function fullUrl($path = '') 52 | { 53 | return Tools::fullUrl($path); 54 | } 55 | 56 | public function mayWriteLocally() 57 | { 58 | if ($GLOBALS['phorkie']['auth']['securityLevel'] == 0) { 59 | //everyone may do everything 60 | return true; 61 | } 62 | 63 | $logged_in = false; 64 | if (!isset($_SESSION['identity'])) { 65 | //not logged in 66 | } else if ($GLOBALS['phorkie']['auth']['listedUsersOnly']) { 67 | if (in_array($_SESSION['identity'], $GLOBALS['phorkie']['auth']['users'])) { 68 | $logged_in = true; 69 | } 70 | } else { 71 | //session identity exists, no special checks required 72 | $logged_in = true; 73 | } 74 | 75 | return $logged_in; 76 | } 77 | 78 | public function getRepositoryEmbedCode(Repository $repo) 79 | { 80 | return ''; 83 | } 84 | } 85 | 86 | ?> 87 | -------------------------------------------------------------------------------- /src/phorkie/HtmlParser.php: -------------------------------------------------------------------------------- 1 | error = 'Empty fork URL'; 38 | return false; 39 | } 40 | 41 | $arUrl = parse_url($url); 42 | $scheme = isset($arUrl['scheme']) ? $arUrl['scheme'] : ''; 43 | 44 | if ($scheme == 'https' && isset($arUrl['host']) 45 | && $arUrl['host'] == 'gist.github.com' 46 | ) { 47 | //https://gist.github.com/cweiske/2400389 48 | // clone URL: https://gist.github.com/2400389.git 49 | $parts = explode('/', ltrim($arUrl['path'], '/')); 50 | if (count($parts == 2)) { 51 | //we only want the number, not the user name 52 | $path = $parts[1]; 53 | } else { 54 | $path = ltrim($arUrl['path'], '/'); 55 | } 56 | $title = $this->getHtmlTitle($url); 57 | if ($title === null) { 58 | $this->arGitUrls[][] = 'https://gist.github.com/' 59 | . $path . '.git'; 60 | } else { 61 | $this->arGitUrls[$title][] = 'https://gist.github.com/' 62 | . $path . '.git'; 63 | } 64 | return true; 65 | } 66 | 67 | switch ($scheme) { 68 | case 'git': 69 | //clearly a git url 70 | $this->arGitUrls = array(array($url)); 71 | return true; 72 | 73 | case 'ssh': 74 | //FIXME: maybe loosen this when we know how to skip the 75 | //"do you trust this server" question of ssh 76 | $this->error = 'ssh:// URLs are not supported'; 77 | return false; 78 | 79 | case 'http': 80 | case 'https': 81 | return $this->extractUrlsFromHtml($url, $html); 82 | } 83 | 84 | $this->error = 'Unknown URLs scheme: ' . $scheme; 85 | return false; 86 | } 87 | 88 | protected function extractUrlsFromHtml($url, $html = null) 89 | { 90 | //HTML is not necessarily well-formed, and Gitorious has many problems 91 | // in this regard 92 | //$sx = simplexml_load_file($url); 93 | 94 | libxml_use_internal_errors(true); 95 | $domDoc = new \DOMDocument(); 96 | if ($html === null) { 97 | $domDoc->loadHTMLFile($url); 98 | } else { 99 | $domDoc->loadHTML($html); 100 | } 101 | $sx = simplexml_import_dom($domDoc); 102 | //FIXME: handle network error 103 | 104 | $elems = $sx->xpath('//*[@rel="vcs-git"]'); 105 | $titles = $sx->xpath('/html/head/title'); 106 | $pageTitle = $this->cleanPageTitle((string) reset($titles)); 107 | 108 | $count = $anonymous = 0; 109 | foreach ($elems as $elem) { 110 | if (!isset($elem['href'])) { 111 | continue; 112 | } 113 | $str = (string)$elem; 114 | if (isset($elem['title'])) { 115 | // 116 | $title = (string)$elem['title']; 117 | } else if ($str != '') { 118 | //title 119 | $title = $str; 120 | } else if ($pageTitle != '') { 121 | $title = $pageTitle; 122 | } else { 123 | $title = 'Unnamed repository #' . ++$anonymous; 124 | } 125 | $url = (string)$elem['href']; 126 | if ($this->isSupported($url)) { 127 | ++$count; 128 | $this->arGitUrls[$title][] = $url; 129 | } 130 | } 131 | 132 | if ($count > 0) { 133 | return true; 134 | } 135 | 136 | $this->error = 'No git:// clone URL found'; 137 | return false; 138 | } 139 | 140 | public function getGitUrls() 141 | { 142 | return $this->arGitUrls; 143 | } 144 | 145 | /** 146 | * Remove application names from HTML page titles 147 | * 148 | * @param string $title HTML page title 149 | * 150 | * @return string Cleaned HTML page title 151 | */ 152 | protected function cleanPageTitle($title) 153 | { 154 | $title = trim($title); 155 | if (substr($title, -9) == '- phorkie') { 156 | $title = trim(substr($title, 0, -9)); 157 | } 158 | 159 | return $title; 160 | } 161 | 162 | public function isSupported($url) 163 | { 164 | $scheme = parse_url($url, PHP_URL_SCHEME); 165 | return $scheme == 'git' 166 | || $scheme == 'http' || $scheme == 'https'; 167 | } 168 | 169 | /** 170 | * Extract the title from a HTML URL 171 | * 172 | * @param string $url URL to a HTML page 173 | * 174 | * @return string|null NULL on error, title otherwise 175 | */ 176 | public function getHtmlTitle($url) 177 | { 178 | libxml_use_internal_errors(true); 179 | //allow loading URLs in DOMDocument 180 | libxml_disable_entity_loader(false); 181 | $doc = \DOMDocument::loadHTMLFile($url); 182 | if ($doc === false) { 183 | return null; 184 | } 185 | $sx = simplexml_import_dom($doc); 186 | $title = (string) $sx->head->title; 187 | if ($title == '') { 188 | return null; 189 | } 190 | return $title; 191 | } 192 | } 193 | ?> 194 | -------------------------------------------------------------------------------- /src/phorkie/Login/AutologinResponse.php: -------------------------------------------------------------------------------- 1 | status = $status; 26 | $this->message = $message; 27 | } 28 | 29 | public function send() 30 | { 31 | if ($this->status == 'error') { 32 | //Cookie to prevent trying autologin again and again. 33 | // After 1 hour the cookie expires and autologin is tried again. 34 | setcookie('tried-autologin', '1', time() + 60 * 60); 35 | } 36 | 37 | $data = htmlspecialchars(json_encode($this), ENT_NOQUOTES); 38 | header('Content-type: text/html'); 39 | echo << 41 | 42 | Autologin response 43 | 46 | 47 | 48 | 49 | 50 | XML; 51 | } 52 | } 53 | ?> 54 | -------------------------------------------------------------------------------- /src/phorkie/Notificator.php: -------------------------------------------------------------------------------- 1 | loadNotificators(); 14 | } 15 | 16 | protected function loadNotificators() 17 | { 18 | foreach ($GLOBALS['phorkie']['cfg']['notificator'] as $type => $config) { 19 | $class = '\\phorkie\\Notificator_' . ucfirst($type); 20 | $this->notificators[] = new $class($config); 21 | } 22 | } 23 | 24 | /** 25 | * A repository has been created 26 | */ 27 | public function create(Repository $repo) 28 | { 29 | $this->send('create', $repo); 30 | } 31 | 32 | /** 33 | * A repository has been modified 34 | */ 35 | public function edit(Repository $repo) 36 | { 37 | $this->send('edit', $repo); 38 | } 39 | 40 | /** 41 | * A repository has been deleted 42 | */ 43 | public function delete(Repository $repo) 44 | { 45 | $this->send('delete', $repo); 46 | } 47 | 48 | /** 49 | * Call all notificator plugins 50 | */ 51 | protected function send($event, Repository $repo) 52 | { 53 | foreach ($this->notificators as $notificator) { 54 | $notificator->send($event, $repo); 55 | } 56 | } 57 | } 58 | ?> 59 | -------------------------------------------------------------------------------- /src/phorkie/Notificator/Linkback.php: -------------------------------------------------------------------------------- 1 | config = $config; 14 | } 15 | 16 | /** 17 | * Send linkback on "create" events to remote repositories 18 | */ 19 | public function send($event, Repository $repo) 20 | { 21 | if ($this->config === false) { 22 | return; 23 | } 24 | 25 | if ($event != 'create') { 26 | return; 27 | } 28 | 29 | $origin = $repo->getConnectionInfo()->getOrigin(); 30 | if ($origin === null) { 31 | return; 32 | } 33 | $originWebUrl = $origin->getWebUrl(true); 34 | if ($originWebUrl === null) { 35 | return; 36 | } 37 | 38 | 39 | $this->pbc = new \PEAR2\Services\Linkback\Client(); 40 | $req = $this->pbc->getRequest(); 41 | $req->setConfig( 42 | array( 43 | 'ssl_verify_peer' => false, 44 | 'ssl_verify_host' => false 45 | ) 46 | ); 47 | $this->pbc->setRequestTemplate($req); 48 | $req->setHeader('user-agent', 'phorkie'); 49 | try { 50 | $res = $this->pbc->send( 51 | $repo->getLink('display', null, true), 52 | $originWebUrl 53 | ); 54 | } catch (\Exception $e) { 55 | //FIXME: log errors 56 | } 57 | } 58 | } 59 | ?> 60 | -------------------------------------------------------------------------------- /src/phorkie/Notificator/Webhook.php: -------------------------------------------------------------------------------- 1 | config = $config; 14 | } 15 | 16 | /** 17 | * Call webhook URLs with our payload 18 | */ 19 | public function send($event, Repository $repo) 20 | { 21 | if (count($this->config) == 0) { 22 | return; 23 | } 24 | 25 | /* slightly inspired by 26 | https://help.github.com/articles/post-receive-hooks */ 27 | $payload = (object) array( 28 | 'event' => $event, 29 | 'author' => array( 30 | 'name' => $_SESSION['name'], 31 | 'email' => $_SESSION['email'] 32 | ), 33 | 'repository' => array( 34 | 'name' => $repo->getTitle(), 35 | 'url' => $repo->getLink('display', null, true), 36 | 'description' => $repo->getDescription(), 37 | 'owner' => $repo->getOwner() 38 | ) 39 | ); 40 | foreach ($this->config as $url) { 41 | $req = new \HTTP_Request2($url); 42 | $req->setMethod(\HTTP_Request2::METHOD_POST) 43 | ->setHeader('Content-Type: application/vnd.phorkie.webhook+json') 44 | ->setBody(json_encode($payload)); 45 | try { 46 | $response = $req->send(); 47 | //FIXME log response codes != 200 48 | } catch (HTTP_Request2_Exception $e) { 49 | //FIXME log exceptions 50 | } 51 | } 52 | } 53 | } 54 | ?> 55 | -------------------------------------------------------------------------------- /src/phorkie/Renderer/Cache.php: -------------------------------------------------------------------------------- 1 | getCacheFile($file); 20 | if ($res === null && $cacheFile !== null) { 21 | $html = $this->loadHtmlFromCache($cacheFile); 22 | } 23 | if ($html === null) { 24 | $html = $this->renderFile($file, $res); 25 | if ($res === null && $cacheFile !== null) { 26 | $this->storeHtmlIntoCache($cacheFile, $html); 27 | } 28 | } 29 | return $html; 30 | } 31 | 32 | protected function renderFile(File $file, Tool_Result $res = null) 33 | { 34 | $ext = $file->getExt(); 35 | $class = '\\phorkie\\Renderer_Unknown'; 36 | 37 | if (isset($GLOBALS['phorkie']['languages'][$ext]['renderer'])) { 38 | $class = $GLOBALS['phorkie']['languages'][$ext]['renderer']; 39 | } else if ($file->isText()) { 40 | $class = '\\phorkie\\Renderer_Geshi'; 41 | } else if (isset($GLOBALS['phorkie']['languages'][$ext]['mime'])) { 42 | $type = $GLOBALS['phorkie']['languages'][$ext]['mime']; 43 | if (substr($type, 0, 6) == 'image/') { 44 | $class = '\\phorkie\\Renderer_Image'; 45 | } 46 | } 47 | 48 | $rend = new $class(); 49 | return $rend->toHtml($file, $res); 50 | } 51 | 52 | /** 53 | * @return null|string NULL when there is no cache, string with HTML 54 | * otherwise 55 | */ 56 | protected function loadHtmlFromCache($cacheFile) 57 | { 58 | if (!file_exists($cacheFile)) { 59 | return null; 60 | } 61 | return file_get_contents($cacheFile); 62 | } 63 | 64 | protected function storeHtmlIntoCache($cacheFile, $html) 65 | { 66 | file_put_contents($cacheFile, $html); 67 | } 68 | 69 | protected function getCacheFile(File $file) 70 | { 71 | if (!$GLOBALS['phorkie']['cfg']['cachedir'] 72 | || !is_dir($GLOBALS['phorkie']['cfg']['cachedir']) 73 | ) { 74 | return null; 75 | } 76 | 77 | return $GLOBALS['phorkie']['cfg']['cachedir'] 78 | . '/' . $file->repo->id 79 | . '-' . $file->repo->hash 80 | . '-' . str_replace('/', '-', $file->getFilename()) 81 | . '.html'; 82 | } 83 | } 84 | ?> 85 | -------------------------------------------------------------------------------- /src/phorkie/Renderer/Geshi.php: -------------------------------------------------------------------------------- 1 | getContent(), $this->getType($file)); 24 | $geshi->enable_line_numbers(GESHI_NORMAL_LINE_NUMBERS); 25 | $geshi->set_header_type(GESHI_HEADER_PRE_TABLE); 26 | $geshi->enable_classes(); 27 | $geshi->set_line_style('color: #DDD;'); 28 | 29 | if ($res !== null) { 30 | $geshi->highlight_lines_extra(array_keys($res->annotations)); 31 | $geshi->set_highlight_lines_extra_style('background-color: #F2DEDE'); 32 | } 33 | 34 | return '' 36 | . '
    ' 37 | . str_replace(' ', ' ', $geshi->parse_code()) 38 | . '
    '; 39 | } 40 | 41 | /** 42 | * Returns the type of the file, as used by Geshi 43 | * 44 | * @return string 45 | */ 46 | public function getType($file) 47 | { 48 | $ext = $file->getExt(); 49 | if (isset($GLOBALS['phorkie']['languages'][$ext]['geshi'])) { 50 | $ext = $GLOBALS['phorkie']['languages'][$ext]['geshi']; 51 | } 52 | 53 | return $ext; 54 | } 55 | 56 | } 57 | 58 | ?> 59 | -------------------------------------------------------------------------------- /src/phorkie/Renderer/Image.php: -------------------------------------------------------------------------------- 1 | ' 17 | . '' 21 | . ''; 22 | } 23 | } 24 | ?> 25 | -------------------------------------------------------------------------------- /src/phorkie/Renderer/Markdown.php: -------------------------------------------------------------------------------- 1 | getContent() 20 | ); 21 | } else { 22 | //PEAR-installed version 1.0.2 has a different API 23 | require_once 'markdown.php'; 24 | $markdown = \markdown($file->getContent()); 25 | } 26 | 27 | return '
    ' 28 | . $markdown 29 | . '
    '; 30 | } 31 | } 32 | 33 | ?> 34 | -------------------------------------------------------------------------------- /src/phorkie/Renderer/Plaintext.php: -------------------------------------------------------------------------------- 1 |
    '
    19 |             . htmlspecialchars($file->getContent())
    20 |             . '
    ' . "\n"; 21 | return $html; 22 | } 23 | } 24 | 25 | ?> 26 | -------------------------------------------------------------------------------- /src/phorkie/Renderer/ReStructuredText.php: -------------------------------------------------------------------------------- 1 | array('pipe', 'r'),//stdin 20 | 1 => array('pipe', 'w'),//stdout 21 | 2 => array('pipe', 'w') //stderr 22 | ); 23 | $process = proc_open('rst2html', $descriptorspec, $pipes); 24 | if (!is_resource($process)) { 25 | return '
    ' 26 | . 'Cannot open process to execute rst2html' 27 | . '
    '; 28 | } 29 | 30 | fwrite($pipes[0], $file->getContent()); 31 | fclose($pipes[0]); 32 | 33 | $html = stream_get_contents($pipes[1]); 34 | fclose($pipes[1]); 35 | 36 | $errors = stream_get_contents($pipes[2]); 37 | fclose($pipes[2]); 38 | 39 | $retval = proc_close($process); 40 | 41 | //cheap extraction of the rst html body 42 | $html = substr($html, strpos($html, '') + 6); 43 | $html = substr($html, 0, strpos($html, '')); 44 | 45 | if ($retval != 0) { 46 | $html = '
    ' 47 | . 'rst2html encountered some error; return value ' 48 | . $retval . '
    ' 49 | . 'Error message: ' . $errors 50 | . '
    ' 51 | . $html; 52 | } 53 | 54 | return $html; 55 | } 56 | } 57 | 58 | ?> 59 | -------------------------------------------------------------------------------- /src/phorkie/Renderer/Unknown.php: -------------------------------------------------------------------------------- 1 | ' 17 | . 'No idea how to display this file' 18 | . ''; 19 | } 20 | } 21 | ?> 22 | -------------------------------------------------------------------------------- /src/phorkie/Repositories.php: -------------------------------------------------------------------------------- 1 | workDir = $GLOBALS['phorkie']['cfg']['workdir']; 9 | $this->gitDir = $GLOBALS['phorkie']['cfg']['gitdir']; 10 | } 11 | 12 | public function createNew(): Repository 13 | { 14 | chdir($this->gitDir); 15 | $dirs = glob('*.git', GLOB_ONLYDIR); 16 | foreach ($dirs as $key => $dir) { 17 | $dirs[$key] = substr($dir, 0, -4); 18 | } 19 | sort($dirs, SORT_NUMERIC); 20 | 21 | if ($GLOBALS['phorkie']['cfg']['randomIds']) { 22 | $n = end($dirs) + mt_rand(65536, 16777216); 23 | } else { 24 | $n = end($dirs) + 1; 25 | } 26 | 27 | chdir($this->workDir); 28 | $dir = $this->workDir . '/' . $n . '/'; 29 | mkdir($dir, fileperms($this->workDir) & 0777); 30 | $r = new Repository(); 31 | $r->id = $n; 32 | $r->workDir = $dir; 33 | $r->gitDir = $this->gitDir . '/' . $n . '.git/'; 34 | mkdir($r->gitDir, fileperms($this->gitDir) & 0777); 35 | 36 | return $r; 37 | } 38 | 39 | /** 40 | * Get a list of repository objects 41 | * 42 | * @param integer $page Page number, beginning with 0, or "last" 43 | * @param integer $perPage Number of repositories per page 44 | * 45 | * @return array Array of Repositories first, number of repositories second 46 | */ 47 | public function getList($page = 0, $perPage = 10): array 48 | { 49 | chdir($this->gitDir); 50 | $dirs = glob('*.git', GLOB_ONLYDIR); 51 | sort($dirs, SORT_NUMERIC); 52 | if ($page === 'last') { 53 | //always show the last 10 54 | $page = intval(count($dirs) / $perPage); 55 | $start = count($dirs) - $perPage; 56 | if ($start < 0) { 57 | $start = 0; 58 | } 59 | $some = array_slice($dirs, $start, $perPage); 60 | } else { 61 | $some = array_slice($dirs, $page * $perPage, $perPage); 62 | } 63 | 64 | $repos = array(); 65 | foreach ($some as $oneDir) { 66 | $r = new Repository(); 67 | try { 68 | $r->loadById(substr($oneDir, 0, -4)); 69 | } catch (\VersionControl_Git_Exception $e) { 70 | if (strpos($e->getMessage(), 'does not have any commits') !== false) { 71 | //the git repo is broken as the initial commit 72 | // has not been finished 73 | continue; 74 | } 75 | throw $e; 76 | } 77 | $repos[] = $r; 78 | } 79 | return array($repos, count($dirs), $page); 80 | } 81 | } 82 | 83 | ?> 84 | -------------------------------------------------------------------------------- /src/phorkie/Repository/Commit.php: -------------------------------------------------------------------------------- 1 | getIconUrl($this->committerEmail); 21 | } 22 | 23 | /** 24 | * @return array Array with 7 fields, each has either "r", "g" or "n" 25 | * ("red", "green" or "none") 26 | */ 27 | public function getDots() 28 | { 29 | $r = $this->getDotNum($this->linesDeleted); 30 | $g = $this->getDotNum($this->linesAdded); 31 | $sum = $r + $g; 32 | if ($sum > 7) { 33 | $quot = ceil($sum / 7); 34 | $r = intval($r / $quot); 35 | $g = intval($g / $quot); 36 | } 37 | $string = str_repeat('g', $g) 38 | . str_repeat('r', $r) 39 | . str_repeat('n', 7 - $g - $r); 40 | 41 | return str_split($string); 42 | } 43 | 44 | public function getDotNum($lines) 45 | { 46 | if ($lines == 0) { 47 | return 0; 48 | } else if ($lines == 1) { 49 | return 1; 50 | } else if ($lines == 2) { 51 | return 2; 52 | } else if ($lines == 3) { 53 | return 3; 54 | } else if ($lines == 4) { 55 | return 4; 56 | } else if ($lines < 10) { 57 | return 5; 58 | } else if ($lines < 50) { 59 | return 6; 60 | } 61 | return 7; 62 | } 63 | } 64 | 65 | ?> 66 | -------------------------------------------------------------------------------- /src/phorkie/Repository/ConnectionInfo.php: -------------------------------------------------------------------------------- 1 | repo = $repo; 13 | //we need raw parsing; https://bugs.php.net/bug.php?id=68347 14 | $this->arConfig = parse_ini_file( 15 | $this->repo->gitDir . '/config', true, INI_SCANNER_RAW 16 | ); 17 | } 18 | 19 | public function isFork() 20 | { 21 | return $this->getOrigin() !== null; 22 | } 23 | 24 | public function hasForks() 25 | { 26 | return count($this->getForks()) > 0; 27 | } 28 | 29 | 30 | public function getOrigin() 31 | { 32 | return $this->getRemote('origin'); 33 | } 34 | 35 | /** 36 | * @return Repository_Remote|null NULL if the remote does not exist, array 37 | * with repository information otherwise 38 | */ 39 | public function getRemote($name) 40 | { 41 | if (!isset($this->arConfig['remote "' . $name . '"'])) { 42 | return null; 43 | } 44 | return new Repository_Remote($name, $this->arConfig['remote "' . $name . '"']); 45 | } 46 | 47 | public function getForks() 48 | { 49 | $arForks = array(); 50 | foreach ($this->arConfig as $name => $data) { 51 | if (substr($name, 0, 13) != 'remote "fork-') { 52 | continue; 53 | } 54 | $arForks[substr($name, 8, -1)] = new Repository_Remote( 55 | substr($name, 8, -1), $data 56 | ); 57 | } 58 | return $arForks; 59 | } 60 | } 61 | ?> 62 | -------------------------------------------------------------------------------- /src/phorkie/Repository/LinkbackReceiver.php: -------------------------------------------------------------------------------- 1 | repo = $repo; 12 | } 13 | 14 | /** 15 | * Stores the linkback as remote fork in the paste repository. 16 | * 17 | * @param string $target Target URI that should be linked in $source 18 | * @param string $source Linkback source URI that should link to target 19 | * @param string $sourceBody Content of $source URI 20 | * @param object $res HTTP response from fetching $source 21 | * 22 | * @return void 23 | * 24 | * @throws SPb\Exception When storing the linkback fatally failed 25 | */ 26 | public function storeLinkback( 27 | $target, $source, $sourceBody, \HTTP_Request2_Response $res 28 | ) { 29 | //FIXME: deleted 30 | //FIXME: cleanuptask 31 | 32 | $hp = new HtmlParser(); 33 | $ok = $hp->extractGitUrls($source, $sourceBody); 34 | if ($ok === false) { 35 | //failed to extract git URL from linkback source 36 | //FIXME: send exception 37 | //$hp->error 38 | return; 39 | } 40 | 41 | $ci = $this->repo->getConnectionInfo(); 42 | $forks = $ci->getForks(); 43 | 44 | $arRemoteCloneUrls = $this->localizeGitUrls($hp->getGitUrls()); 45 | 46 | $remoteCloneUrl = $remoteTitle = null; 47 | if (count($arRemoteCloneUrls)) { 48 | reset($arRemoteCloneUrls); 49 | $remoteCloneUrl = key($arRemoteCloneUrls); 50 | $remoteTitle = current($arRemoteCloneUrls); 51 | } 52 | $remoteid = 'fork-' . uniqid(); 53 | //check if we already know this remote 54 | foreach ($forks as $remote) { 55 | if (isset($arRemoteCloneUrls[$remote->getCloneUrl()])) { 56 | $remoteTitle = $arRemoteCloneUrls[$remote->getCloneUrl()]; 57 | $remoteid = $remote->getName(); 58 | break; 59 | } else if ($source == $remote->getWebURL(true)) { 60 | $remoteid = $remote->getName(); 61 | break; 62 | } 63 | } 64 | 65 | $vc = $this->repo->getVc(); 66 | if (!$this->isLocalWebUrl($source)) { 67 | //only add remote homepage; we can calculate local ones ourselves 68 | $vc->getCommand('config') 69 | ->addArgument('remote.' . $remoteid . '.homepage') 70 | ->addArgument($source) 71 | ->execute(); 72 | } 73 | if ($remoteTitle !== null) { 74 | $vc->getCommand('config') 75 | ->addArgument('remote.' . $remoteid . '.title') 76 | ->addArgument($remoteTitle) 77 | ->execute(); 78 | } 79 | if ($remoteCloneUrl !== null) { 80 | $vc->getCommand('config') 81 | ->addArgument('remote.' . $remoteid . '.url') 82 | ->addArgument($remoteCloneUrl) 83 | ->execute(); 84 | } 85 | } 86 | 87 | /** 88 | * Check if the given full URL is the URL of a local repository 89 | * 90 | * @return Repository 91 | */ 92 | protected function isLocalWebUrl($url) 93 | { 94 | $base = Tools::fullUrl(); 95 | if (substr($url, 0, strlen($base)) != $base) { 96 | //base does not match 97 | return false; 98 | } 99 | 100 | $remainder = substr($url, strlen($base)); 101 | if (!is_numeric($remainder)) { 102 | return false; 103 | } 104 | try { 105 | $repo = new Repository(); 106 | $repo->loadById($remainder); 107 | } catch (\Exception $e) { 108 | return false; 109 | } 110 | return true; 111 | } 112 | 113 | /** 114 | * Convert an array of git urls to local URLs if possible and serialize them 115 | * into a simple array. 116 | * 117 | * @param array $arGitUrls Array of array of urls. Main key is the title of 118 | * the URL array. 119 | * 120 | * @return array Key is the git clone URL, value the title of the remote 121 | */ 122 | protected function localizeGitUrls($arGitUrls) 123 | { 124 | $pub = $pri = null; 125 | if (isset($GLOBALS['phorkie']['cfg']['git']['public'])) { 126 | $pub = $GLOBALS['phorkie']['cfg']['git']['public']; 127 | } 128 | if (isset($GLOBALS['phorkie']['cfg']['git']['private'])) { 129 | $pri = $GLOBALS['phorkie']['cfg']['git']['private']; 130 | } 131 | 132 | $arRemoteCloneUrls = array(); 133 | foreach ($arGitUrls as $remoteTitle => $arUrls) { 134 | foreach ($arUrls as $remoteCloneUrl) { 135 | if ($pub !== null 136 | && substr($remoteCloneUrl, 0, strlen($pub)) == $pub 137 | && substr($remoteCloneUrl, -4) == '.git' 138 | ) { 139 | $id = substr($remoteCloneUrl, strlen($pub), -4); 140 | $repo = new Repository(); 141 | try { 142 | $repo->loadById($id); 143 | $arRemoteCloneUrls[$repo->gitDir] = $remoteTitle; 144 | } catch (Exception $e) { 145 | } 146 | } else if ($pri !== null 147 | && substr($remoteCloneUrl, 0, strlen($pri)) == $pri 148 | && substr($remoteCloneUrl, -4) == '.git' 149 | ) { 150 | $id = substr($remoteCloneUrl, strlen($pri), -4); 151 | $repo = new Repository(); 152 | try { 153 | $repo->loadById($id); 154 | $arRemoteCloneUrls[$repo->gitDir] = $remoteTitle; 155 | } catch (Exception $e) { 156 | } 157 | } else { 158 | $arRemoteCloneUrls[$remoteCloneUrl] = $remoteTitle; 159 | } 160 | } 161 | } 162 | return $arRemoteCloneUrls; 163 | } 164 | } 165 | ?> 166 | -------------------------------------------------------------------------------- /src/phorkie/Repository/Remote.php: -------------------------------------------------------------------------------- 1 | name = $name; 12 | $this->arConfig = $arConfig; 13 | } 14 | 15 | 16 | public function getName() 17 | { 18 | return $this->name; 19 | } 20 | 21 | public function getTitle() 22 | { 23 | if (isset($this->arConfig['title'])) { 24 | return $this->arConfig['title']; 25 | } 26 | if ($this->isLocal()) { 27 | $local = $this->getLocalRepository(); 28 | if ($local !== null) { 29 | return $local->getTitle(); 30 | } 31 | return 'deleted local paste'; 32 | } 33 | 34 | return 'untitled repository'; 35 | } 36 | 37 | public function getCloneURL() 38 | { 39 | if ($this->isLocal()) { 40 | $local = $this->getLocalRepository(); 41 | if ($local !== null) { 42 | return $local->getCloneURL(); 43 | } 44 | } 45 | 46 | if (isset($this->arConfig['url'])) { 47 | return $this->arConfig['url']; 48 | } 49 | return null; 50 | } 51 | 52 | public function getWebURL($full = false) 53 | { 54 | if (isset($this->arConfig['homepage'])) { 55 | return $this->arConfig['homepage']; 56 | } 57 | 58 | if ($this->isLocal()) { 59 | $local = $this->getLocalRepository(); 60 | if ($local !== null) { 61 | return $local->getLink('display', null, $full); 62 | } 63 | } 64 | 65 | return null; 66 | } 67 | 68 | /** 69 | * Tells you if this remote repository is a paste on the local server 70 | * 71 | * @return boolean True of false 72 | */ 73 | public function isLocal() 74 | { 75 | return isset($this->arConfig['url']) 76 | && $this->arConfig['url'][0] == '/'; 77 | } 78 | 79 | /** 80 | * If this remote is a local paste, then we'll get the repository object 81 | * returned 82 | * 83 | * @return Repository Repository object or NULL 84 | */ 85 | public function getLocalRepository() 86 | { 87 | if (!file_exists($this->arConfig['url'] . '/config')) { 88 | return null; 89 | } 90 | $dir = basename($this->arConfig['url']); 91 | if (substr($dir, -4) != '.git') { 92 | //phorks are bare repositories "123.git" 93 | return null; 94 | } 95 | $repo = new Repository(); 96 | $repo->loadById(substr($dir, 0, -4)); 97 | return $repo; 98 | } 99 | 100 | } 101 | 102 | ?> 103 | -------------------------------------------------------------------------------- /src/phorkie/Repository/Setup.php: -------------------------------------------------------------------------------- 1 | repo = $repo; 11 | } 12 | 13 | /** 14 | * Should be called right after a repository has been created, 15 | * either by "git init" or "git clone". 16 | * Takes care of removing hook example files and creating 17 | * the git daemon export file 18 | * 19 | * @return void 20 | */ 21 | public function afterInit() 22 | { 23 | foreach (glob($this->repo->gitDir . '/hooks/*') as $hookfile) { 24 | unlink($hookfile); 25 | } 26 | touch($this->repo->gitDir . '/git-daemon-export-ok'); 27 | 28 | $vc = $this->repo->getVc(); 29 | 30 | file_put_contents( 31 | $this->repo->gitDir . '/hooks/post-update', 32 | <<repo->gitDir . '/hooks/post-update', 0755); 41 | 42 | //keep track of owner 43 | $vc->getCommand('config') 44 | ->addArgument('owner.name') 45 | ->addArgument($_SESSION['name']) 46 | ->execute(); 47 | $vc->getCommand('config') 48 | ->addArgument('owner.email') 49 | ->addArgument($_SESSION['email']) 50 | ->execute(); 51 | } 52 | 53 | } 54 | 55 | ?> 56 | -------------------------------------------------------------------------------- /src/phorkie/Search/Result.php: -------------------------------------------------------------------------------- 1 | repos; 14 | } 15 | 16 | /** 17 | * Returns the number of results 18 | * 19 | * @return integer Number of results 20 | */ 21 | public function getResults() 22 | { 23 | return $this->results; 24 | } 25 | 26 | /** 27 | * Returns the number of the current page, 0 based 28 | * 29 | * @return integer Number of current page 30 | */ 31 | public function getPage() 32 | { 33 | return $this->page; 34 | } 35 | 36 | /** 37 | * Returns the search results per page 38 | * 39 | * @return integer Number of results per page 40 | */ 41 | public function getPerPage() 42 | { 43 | return $this->perPage; 44 | } 45 | 46 | public function getLink($query) 47 | { 48 | return 'search?q=' . urlencode($query); 49 | } 50 | } 51 | 52 | ?> 53 | -------------------------------------------------------------------------------- /src/phorkie/SetupCheck.php: -------------------------------------------------------------------------------- 1 | 'VersionControl_Git', 8 | 'twig/twig' => 'Twig_Autoloader', 9 | 'pear/date_humanDiff' => 'Date_HumanDiff', 10 | 'pear/http_request2' => 'HTTP_Request2', 11 | 'pear/openid' => 'OpenID', 12 | 'pear/pager' => 'Pager', 13 | 'pear/services_libravatar' => 'Services_Libravatar', 14 | 'pear2/services_linkback' => '\\PEAR2\\Services\\Linkback\\Client', 15 | 'cweiske/mime_type_plaindetect' => 'MIME_Type_PlainDetect', 16 | ); 17 | 18 | protected $writableDirs; 19 | protected $elasticsearch; 20 | 21 | public $messages = array(); 22 | 23 | public function __construct() 24 | { 25 | $cfg = $GLOBALS['phorkie']['cfg']; 26 | $this->writableDirs = array( 27 | 'gitdir' => Tools::foldPath($cfg['gitdir']), 28 | 'workdir' => Tools::foldPath($cfg['workdir']), 29 | 'cachedir' => Tools::foldPath($cfg['cachedir']), 30 | ); 31 | $this->elasticsearch = $cfg['elasticsearch']; 32 | } 33 | 34 | public static function run() 35 | { 36 | $sc = new self(); 37 | $sc->checkConfigFiles(); 38 | $sc->checkDeps(); 39 | $sc->checkDirs(); 40 | $sc->checkGit(); 41 | $sc->checkDatabase(); 42 | $sc->checkMimeTypeDetection(); 43 | $sc->checkRemoteForking(); 44 | 45 | return $sc->messages; 46 | } 47 | 48 | public function checkConfigFiles() 49 | { 50 | if (!isset($GLOBALS['phorkie']['cfgfiles']) 51 | || count($GLOBALS['phorkie']['cfgfiles']) == 0 52 | ) { 53 | $this->info('No config files registered'); 54 | return; 55 | } 56 | 57 | foreach ($GLOBALS['phorkie']['cfgfiles'] as $file => $loaded) { 58 | if ($loaded) { 59 | $this->ok('Loaded config file: ' . Tools::foldPath($file)); 60 | } else { 61 | $this->info( 62 | 'Possible config file: ' . Tools::foldPath($file) 63 | . ' (not loaded)' 64 | ); 65 | } 66 | } 67 | } 68 | 69 | public function checkDeps() 70 | { 71 | foreach ($this->deps as $package => $class) { 72 | if (!class_exists($class, true)) { 73 | $this->fail('Composer package not installed: ' . $package); 74 | } 75 | } 76 | 77 | if (!class_exists('GeSHi', true)) { 78 | $geshi = stream_resolve_include_path( 79 | $GLOBALS['phorkie']['cfg']['geshi'] 80 | ); 81 | if ($geshi === false) { 82 | $this->fail('GeSHi not available'); 83 | } 84 | } 85 | 86 | if (!class_exists('\\Michelf\\Markdown', true)) { 87 | //PEAR-installed version 1.0.2 has a different API 88 | $markdown = stream_resolve_include_path('markdown.php'); 89 | if ($markdown === false) { 90 | $this->fail('Markdown renderer not available'); 91 | } 92 | } 93 | } 94 | 95 | public function checkDirs() 96 | { 97 | foreach ($this->writableDirs as $name => $dir) { 98 | if (!is_dir($dir)) { 99 | $this->fail($name . ' directory does not exist at ' . $dir); 100 | } else if (!is_writable($dir)) { 101 | $this->fail($name . ' directory is not writable at ' . $dir); 102 | } 103 | } 104 | } 105 | 106 | public function checkGit() 107 | { 108 | $line = exec('git --version', $lines, $retval); 109 | if ($retval !== 0) { 110 | $this->fail('Running git executable failed.'); 111 | } 112 | if (!preg_match('#^git version ([0-9.]+(rc[0-9]+)?)(?: \(Apple Git-\d+\))?$#', $line, $matches)) { 113 | $this->fail('git version output format unexpected: ' . $line); 114 | return; 115 | } 116 | if (version_compare($matches[1], '1.7.5') < 0) { 117 | $this->fail( 118 | 'git version needs to be at least 1.7.5, got: ' . $matches[1] 119 | ); 120 | } 121 | } 122 | 123 | public function checkDatabase() 124 | { 125 | if ($this->elasticsearch == '') { 126 | return; 127 | } 128 | 129 | $es = parse_url($this->elasticsearch); 130 | if (!preg_match("#/.+/#", $es['path'], $matches)) { 131 | $this->fail( 132 | 'Improper elasticsearch url. Elasticsearch requires a' 133 | . ' search domain to store your data.' 134 | . ' (e.g. http://localhost:9200/phorkie/)' 135 | ); 136 | } 137 | $dbs = new Database(); 138 | $dbs->getSetup()->setup(); 139 | } 140 | 141 | public function checkMimeTypeDetection() 142 | { 143 | $rp = new Repository_Post(); 144 | $type = $rp->getType('', true); 145 | if ($type != 'php') { 146 | $msg = 'MIME type detection fails'; 147 | if ($type instanceof \PEAR_Error) { 148 | $msg .= '. Error: ' . $type->getMessage(); 149 | } 150 | $this->fail($msg); 151 | } 152 | } 153 | 154 | public function checkRemoteForking() 155 | { 156 | if (!isset($GLOBALS['phorkie']['cfg']['git']['public']) 157 | || $GLOBALS['phorkie']['cfg']['git']['public'] == '' 158 | ) { 159 | $this->fail( 160 | 'No public git URL prefix configured.' 161 | . ' Remote forking will not work' 162 | ); 163 | } 164 | } 165 | 166 | public function fail($msg) 167 | { 168 | $this->messages[] = array('error', $msg); 169 | } 170 | 171 | public function info($msg) 172 | { 173 | $this->messages[] = array('info', $msg); 174 | } 175 | 176 | public function ok($msg) 177 | { 178 | $this->messages[] = array('ok', $msg); 179 | } 180 | } 181 | 182 | ?> 183 | -------------------------------------------------------------------------------- /src/phorkie/Tool/Info.php: -------------------------------------------------------------------------------- 1 | class = $class; 11 | } 12 | 13 | /** 14 | * Format the tool path 15 | * 16 | * @param File $file 17 | * 18 | * @return string 19 | */ 20 | public function getLink(File $file) 21 | { 22 | return $file->getLink('tool', $this->stripPrefix($this->class)); 23 | } 24 | 25 | /** 26 | * Clean namespace from class 27 | * 28 | * @return string 29 | */ 30 | public function getTitle() 31 | { 32 | return $this->stripPrefix($this->class); 33 | } 34 | 35 | /** 36 | * Removes custom namespace prefix 37 | * 38 | * @param string $class Class of object 39 | * 40 | * @return string 41 | */ 42 | protected function stripPrefix($class) 43 | { 44 | $prefix = '\\phorkie\\Tool_'; 45 | if (substr($class, 0, strlen($prefix)) === $prefix) { 46 | return substr($class, strlen($prefix)); 47 | } 48 | return $class; 49 | } 50 | } 51 | 52 | ?> 53 | -------------------------------------------------------------------------------- /src/phorkie/Tool/MIME/Type/PlainDetect.php: -------------------------------------------------------------------------------- 1 | 32 | -------------------------------------------------------------------------------- /src/phorkie/Tool/Manager.php: -------------------------------------------------------------------------------- 1 | getExt(); 10 | $suitables = array(); 11 | foreach ($GLOBALS['phorkie']['tools'] as $class => $arSetup) { 12 | if (array_search($ext, $class::$arSupportedExtensions) !== false) { 13 | $suitables[] = new Tool_Info($class); 14 | } 15 | } 16 | return $suitables; 17 | } 18 | 19 | /** 20 | * Returns the class name from a tool name 21 | * 22 | * @param string $name Full class name or short name without 23 | * 'phorkie\\Tool_' prefix 24 | * 25 | * @return string Class name or NULL if not found 26 | */ 27 | public function getClass($name) 28 | { 29 | if (strpos($name, '\\') === false && strpos($name, '_') === false) { 30 | return '\\phorkie\\Tool_' . $name; 31 | } 32 | return $name; 33 | } 34 | 35 | public function loadTool($name) 36 | { 37 | $class = $this->getClass($name); 38 | if (!class_exists($class, true)) { 39 | throw new Exception('Tool does not exist: ' . $class); 40 | } 41 | 42 | return new $class(); 43 | } 44 | } 45 | 46 | ?> 47 | -------------------------------------------------------------------------------- /src/phorkie/Tool/PHPlint.php: -------------------------------------------------------------------------------- 1 | getFullPath(); 13 | $fpathlen = strlen($fpath); 14 | 15 | $res = new Tool_Result(); 16 | $cmd = 'php -l ' . escapeshellarg($fpath) . ' 2>&1'; 17 | exec($cmd, $output, $retval); 18 | if ($retval == 0) { 19 | $res->annotations['general'][] = new Tool_Result_Line( 20 | 'No syntax errors detected', 'ok' 21 | ); 22 | return $res; 23 | } 24 | 25 | $regex = '#^(.+) in ' . preg_quote($fpath) . ' on line ([0-9]+)$#'; 26 | for ($i = 0; $i < count($output) - 1; $i++) { 27 | $line = $output[$i]; 28 | if (!preg_match($regex, trim($line), $matches)) { 29 | throw new Exception('"php -l" does not behave as expected: ' . $line); 30 | } 31 | $msg = $matches[1]; 32 | $linenum = $matches[2]; 33 | $res->annotations[$linenum][] = new Tool_Result_Line( 34 | $msg, 'error' 35 | ); 36 | } 37 | 38 | $res->annotations['general'][] = new Tool_Result_Line( 39 | 'PHP code has syntax errors', 'error' 40 | ); 41 | 42 | return $res; 43 | } 44 | } 45 | 46 | ?> 47 | -------------------------------------------------------------------------------- /src/phorkie/Tool/Result.php: -------------------------------------------------------------------------------- 1 | 10 | -------------------------------------------------------------------------------- /src/phorkie/Tool/Result/Line.php: -------------------------------------------------------------------------------- 1 | message = $message; 12 | $this->setLevel($level); 13 | } 14 | 15 | public function setLevel($level) 16 | { 17 | if ($level !== 'ok' && $level !== 'error' && $level !== 'warning') { 18 | throw new Exception('Invalid result line level: ' . $level); 19 | } 20 | $this->level = $level; 21 | } 22 | 23 | public function getAlertLevel() 24 | { 25 | static $map = array( 26 | 'error' => 'alert-error', 27 | 'ok' => 'alert-success', 28 | 'warning' => '', 29 | ); 30 | return $map[$this->level]; 31 | } 32 | } 33 | 34 | ?> 35 | -------------------------------------------------------------------------------- /src/phorkie/Tool/Xmllint.php: -------------------------------------------------------------------------------- 1 | getFullPath(); 13 | $fpathlen = strlen($fpath); 14 | 15 | $res = new Tool_Result(); 16 | $cmd = 'xmllint --noout ' . escapeshellarg($fpath) . ' 2>&1'; 17 | exec($cmd, $output, $retval); 18 | if ($retval == 0) { 19 | $res->annotations['general'][] = new Tool_Result_Line( 20 | 'XML is well-formed', 'ok' 21 | ); 22 | return $res; 23 | } 24 | 25 | for ($i = 0; $i < count($output); $i += 3) { 26 | $line = $output[$i]; 27 | if (substr($line, 0, $fpathlen) != $fpath) { 28 | throw new Exception('xmllint does not behave as expected: ' . $line); 29 | } 30 | list($linenum, $msg) = explode(':', substr($line, $fpathlen + 1), 2); 31 | $res->annotations[$linenum][] = new Tool_Result_Line( 32 | $msg, 'error' 33 | ); 34 | } 35 | 36 | $res->annotations['general'][] = new Tool_Result_Line( 37 | 'XML is not well-formed', 'error' 38 | ); 39 | 40 | return $res; 41 | } 42 | } 43 | 44 | ?> 45 | -------------------------------------------------------------------------------- /src/phorkie/Tools.php: -------------------------------------------------------------------------------- 1 | 137 | -------------------------------------------------------------------------------- /src/phorkie/autoload.php: -------------------------------------------------------------------------------- 1 | 6 | */ 7 | if (file_exists(__DIR__ . '/../../lib/PEAR.php')) { 8 | //phing-installed dependencies available ("phing collectdeps") 9 | set_include_path( 10 | __DIR__ . '/../' 11 | . PATH_SEPARATOR . __DIR__ . '/../../lib/' 12 | . PATH_SEPARATOR . '.' 13 | ); 14 | } else if (file_exists(__DIR__ . '/../../lib/autoload.php')) { 15 | //composer-installed dependencies available 16 | set_include_path( 17 | __DIR__ . '/../' 18 | . PATH_SEPARATOR . '.' 19 | ); 20 | require_once __DIR__ . '/../../lib/autoload.php'; 21 | } else { 22 | //use default include path for dependencies 23 | set_include_path( 24 | __DIR__ . '/../' 25 | . PATH_SEPARATOR . get_include_path() 26 | ); 27 | } 28 | 29 | spl_autoload_register( 30 | function ($class) { 31 | $file = str_replace(array('\\', '_'), '/', $class) . '.php'; 32 | if (stream_resolve_include_path($file)) { 33 | require $file; 34 | } 35 | } 36 | ); 37 | ?> 38 | -------------------------------------------------------------------------------- /src/stub-phar.php: -------------------------------------------------------------------------------- 1 | 10 | * @copyright 2014 Christian Weiske 11 | * @license http://www.gnu.org/licenses/agpl.html GNU AGPL v3 12 | * @link https://cweiske.de/phorkie.htm 13 | */ 14 | if (!in_array('phar', stream_get_wrappers()) || !class_exists('Phar', false)) { 15 | echo "Phar extension not avaiable\n"; 16 | exit(255); 17 | } 18 | 19 | $web = 'www/index.php'; 20 | //FIXME 21 | $cli = 'scripts/index.php'; 22 | 23 | /** 24 | * Rewrite the HTTP request path to an internal file. 25 | * Maps "" and "/" to "www/index.php". 26 | * 27 | * @param string $path Path from the browser, relative to the .phar 28 | * 29 | * @return string Internal path. 30 | */ 31 | function rewritePath($path) 32 | { 33 | if ($path == '') { 34 | //we need a / to get the relative links on index.php work 35 | if (!isset($_SERVER['REQUEST_SCHEME'])) { 36 | $_SERVER['REQUEST_SCHEME'] = 'http'; 37 | } 38 | $url = $_SERVER['REQUEST_SCHEME'] . '://' 39 | . $_SERVER['HTTP_HOST'] 40 | . preg_replace('/[?#].*$/', '', $_SERVER['REQUEST_URI']) 41 | . '/'; 42 | header('Location: ' . $url); 43 | exit(0); 44 | } else if ($path == '/') { 45 | return 'www/index.php'; 46 | } 47 | 48 | $path = rewriteWithHtaccess($path); 49 | 50 | if (substr($path, -4) == '.css' 51 | || substr($path, -3) == '.js' 52 | || substr($path, 0, 9) == '/phorkie/' 53 | ) { 54 | header('Expires: ' . date('r', time() + 86400 * 7)); 55 | } 56 | return 'www' . $path; 57 | } 58 | 59 | function rewriteWithHtaccess($path) 60 | { 61 | //remove the leading slash / 62 | $cpath = substr($path, 1); 63 | $bFoundMatch = false; 64 | $map = include('phar://' . __FILE__ . '/src/gen-rewritemap.php'); 65 | foreach ($map as $pattern => $replace) { 66 | if (preg_match($pattern, $cpath, $matches)) { 67 | $bFoundMatch = true; 68 | break; 69 | } 70 | } 71 | if (!$bFoundMatch) { 72 | return $path; 73 | } 74 | $newcpath = preg_replace($pattern, $replace, $cpath); 75 | if (strpos($newcpath, '?') === false) { 76 | return '/' . $newcpath; 77 | } 78 | list($cfile, $getParams) = explode('?', $newcpath, 2); 79 | if ($getParams != '') { 80 | parse_str($getParams, $_GET); 81 | } 82 | return '/' . $cfile; 83 | } 84 | 85 | //Phar::interceptFileFuncs(); 86 | set_include_path( 87 | 'phar://' . __FILE__ 88 | . PATH_SEPARATOR . 'phar://' . __FILE__ . '/lib/' 89 | ); 90 | Phar::webPhar(null, $web, null, array(), 'rewritePath'); 91 | 92 | //TODO: implement CLI script runner 93 | echo "phorkie can only be used in the browser\n"; 94 | exit(1); 95 | __HALT_COMPILER(); 96 | ?> 97 | -------------------------------------------------------------------------------- /tests/phorkie/ToolsTest.php: -------------------------------------------------------------------------------- 1 | assertEquals( 16 | '/phar/phorkie-0.4.0.phar/', 17 | Tools::detectBaseUrl() 18 | ); 19 | } 20 | 21 | public function testDetectBaseUrlRoot() 22 | { 23 | $_SERVER['REQUEST_URI'] = '/new'; 24 | $_SERVER['SCRIPT_NAME'] = '/new.php'; 25 | $this->assertEquals('/', Tools::detectBaseUrl()); 26 | } 27 | 28 | public function testDetectBaseUrlRootWithPhp() 29 | { 30 | $_SERVER['REQUEST_URI'] = '/new.php'; 31 | $_SERVER['SCRIPT_NAME'] = '/new.php'; 32 | $this->assertEquals('/', Tools::detectBaseUrl()); 33 | } 34 | 35 | public function testDetectBaseUrlSubdir() 36 | { 37 | $_SERVER['REQUEST_URI'] = '/foo/new'; 38 | $_SERVER['SCRIPT_NAME'] = '/new.php'; 39 | $this->assertEquals('/foo/', Tools::detectBaseUrl()); 40 | } 41 | 42 | public function testDetectBaseUrlEdit() 43 | { 44 | $_GET['id'] = 82; 45 | $_SERVER['REQUEST_URI'] = '/82/edit'; 46 | $_SERVER['SCRIPT_NAME'] = '/edit.php'; 47 | $this->assertEquals('/', Tools::detectBaseUrl()); 48 | } 49 | 50 | public function testDetectBaseUrlEditSubdir() 51 | { 52 | $_GET['id'] = 82; 53 | $_SERVER['REQUEST_URI'] = '/foo/82/edit'; 54 | $_SERVER['SCRIPT_NAME'] = '/edit.php'; 55 | $this->assertEquals('/foo/', Tools::detectBaseUrl()); 56 | } 57 | 58 | public function testFoldPathParentSingle() 59 | { 60 | $this->assertEquals( 61 | '/path/to/foo', 62 | Tools::foldPath('/path/to/bar/../foo') 63 | ); 64 | } 65 | 66 | public function testFoldPathParentDouble() 67 | { 68 | $this->assertEquals( 69 | '/path/to/foo', 70 | Tools::foldPath('/path/to/foo/bar/../../foo') 71 | ); 72 | } 73 | 74 | public function testFoldPathCurrentSingle() 75 | { 76 | $this->assertEquals( 77 | '/path/to/foo/', 78 | Tools::foldPath('/path/to/foo/./') 79 | ); 80 | } 81 | 82 | public function testFoldPathCurrentThrice() 83 | { 84 | $this->assertEquals( 85 | '/path/to/foo/', 86 | Tools::foldPath('/path/././to/foo/./') 87 | ); 88 | } 89 | } 90 | ?> 91 | -------------------------------------------------------------------------------- /tests/phpunit.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | ../src/ 4 | 5 | 6 | 7 | ../src/ 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /www/.htaccess: -------------------------------------------------------------------------------- 1 | # http://serverfault.com/questions/57243/apache-mod-rewrite-fails-when-file-by-same-name-exists 2 | Options -MultiViews 3 | 4 | RewriteEngine On 5 | RewriteBase / 6 | #RewriteCond %{REQUEST_FILENAME} -f 7 | 8 | RewriteRule ^([0-9]+)$ display.php?id=$1 9 | RewriteRule ^([0-9]+)/$ $1 [R] 10 | RewriteRule ^([0-9]+)/delete$ delete.php?id=$1 11 | RewriteRule ^([0-9]+)/delete/confirm$ delete.php?id=$1&confirm=1 12 | RewriteRule ^([0-9]+)/doap$ doap.php?id=$1 13 | RewriteRule ^([0-9]+)/edit$ edit.php?id=$1 14 | RewriteRule ^([0-9]+)/edit/(.+)$ edit.php?id=$1&file=$2 [B] 15 | RewriteRule ^([0-9]+)/embed$ embed.php?id=$1 16 | RewriteRule ^([0-9]+)/embed/(.+)$ embed-file.php?id=$1&file=$2 [B] 17 | RewriteRule ^([0-9]+)/fork$ fork.php?id=$1 18 | RewriteRule ^([0-9]+)/linkback$ linkback.php?id=$1 19 | RewriteRule ^([0-9]+)/raw/(.+)$ raw.php?id=$1&file=$2 [B] 20 | RewriteRule ^([0-9]+)/rev/(.+)$ revision.php?id=$1&rev=$2 [B] 21 | RewriteRule ^([0-9]+)/rev-raw/([^/]+)/(.+)$ raw.php?id=$1&rev=$2&file=$3 [B] 22 | RewriteRule ^([0-9]+)/tool/([^/]+)/(.+)$ tool.php?id=$1&tool=$2&file=$3 [B] 23 | 24 | RewriteRule ^fork-remote$ fork-remote.php 25 | RewriteRule ^help$ help.php 26 | RewriteRule ^new$ new.php 27 | 28 | RewriteRule ^feed/new$ feed-new.php 29 | RewriteRule ^feed/updated$ feed-updated.php 30 | 31 | RewriteRule ^list$ list.php 32 | RewriteRule ^list/([0-9]+)$ list.php?page=$1 [B] 33 | 34 | RewriteRule ^search$ search.php 35 | RewriteRule ^search/([0-9]+)$ search.php?page=$1 [B] 36 | 37 | RewriteRule ^login$ login.php 38 | RewriteRule ^setup$ setup.php 39 | RewriteRule ^user$ user.php 40 | -------------------------------------------------------------------------------- /www/css/embed.css: -------------------------------------------------------------------------------- 1 | .phork-file { 2 | border: 1px solid #DDD; 3 | border-bottom: 1px solid #CCC; 4 | border-radius: 3px; 5 | padding: 0px; 6 | background-color: white; 7 | margin-bottom: 2ex; 8 | margin-top: 2ex; 9 | } 10 | .phork-content { 11 | padding: 0.6ex; 12 | } 13 | .phork-content > .code { 14 | overflow-x: auto; 15 | } 16 | .phork-file .code pre { 17 | border: none; 18 | background-color: inherit; 19 | word-break: normal; 20 | word-wrap: normal; 21 | white-space: pre; 22 | color: black; 23 | font: normal normal 1em/1.2em monospace; 24 | padding: 0px; 25 | margin: 0px; 26 | width: auto; 27 | } 28 | .phork-file .code .ln pre { 29 | margin-right: 1ex; 30 | color: #DDD; 31 | } 32 | .phork-meta { 33 | background-color: #EEE; 34 | font-size: 75%; 35 | padding: 0.5ex 1ex; 36 | margin: none; 37 | } 38 | .phork-meta a { 39 | font-family: sans-serif; 40 | text-decoration: none; 41 | color: #888; 42 | font-weight: bold; 43 | } -------------------------------------------------------------------------------- /www/css/openid.css: -------------------------------------------------------------------------------- 1 | body { 2 | font-family:"Helvetica Neue", Helvetica, Arial, sans-serif; 3 | } 4 | #openid_form { 5 | width: 470px; 6 | } 7 | #openid_form legend { 8 | font-weight: bold; 9 | } 10 | #openid_choice { 11 | display: none; 12 | } 13 | #openid_input_area { 14 | clear: both; 15 | } 16 | #openid_btns { 17 | height: 66px; 18 | margin-bottom: 10px; 19 | } 20 | #openid_btns br { 21 | clear: both; 22 | } 23 | #openid_highlight { 24 | padding: 3px; 25 | background-color: #FFFCC9; 26 | float: left; 27 | } 28 | #openid_url { 29 | margin: 0px !important; 30 | width: 250px; 31 | background: #FFF url(../images/openid-inputicon.gif) no-repeat scroll 0 50%; 32 | padding-left:18px; 33 | } 34 | .openid_large_btn { 35 | width: 100px; 36 | height: 60px; 37 | border: 1px solid #DDD; 38 | margin: 3px; 39 | float: left; 40 | } 41 | .openid_small_btn { 42 | width: 24px; 43 | height: 24px; 44 | border: 1px solid #DDD; 45 | margin: 3px; 46 | float: left; 47 | } 48 | .google { 49 | background: #FFF url(../images/google.gif) no-repeat center center; 50 | } 51 | .yahoo { 52 | background: #FFF url(../images/yahoo.gif) no-repeat center center; 53 | } 54 | 55 | a.openid_large_btn:hover { 56 | outline: none; 57 | border: 1px solid #030303; 58 | } 59 | a.openid_large_btn:focus { 60 | -moz-outline-style: none; 61 | } 62 | .openid_selected { 63 | border: 4px solid #DDD; 64 | } 65 | -------------------------------------------------------------------------------- /www/css/phorkie.css: -------------------------------------------------------------------------------- 1 | /* show IDs for anchors */ 2 | a.anchorlink:before { 3 | font-size: smaller; 4 | content: '_'; 5 | color: transparent; 6 | } 7 | h1[id]:hover a.anchorlink:before, 8 | h2[id]:hover a.anchorlink:before, 9 | h3[id]:hover a.anchorlink:before, 10 | h4[id]:hover a.anchorlink:before, 11 | h5[id]:hover a.anchorlink:before, 12 | h6[id]:hover a.anchorlink:before { 13 | content: "\00B6";/* pilcrow */ 14 | color: #888; 15 | font-size: smaller; 16 | } 17 | a.anchorlink { 18 | text-decoration: none; 19 | margin-left: 0.5em; 20 | font-size: smaller; 21 | } 22 | .navbar li a.brand { 23 | /*float: right;*/ 24 | margin-left: -14px; 25 | color: #DDA; 26 | text-shadow: 0 0 30px rgba(255, 255, 255, .9); 27 | } 28 | .navbar li a.brand:hover { 29 | color: #FFA; 30 | } 31 | 32 | @media (min-width: 980px) and (max-width: 1199px) { 33 | .navbar .container { 34 | width: 940px; 35 | } 36 | } 37 | 38 | @media (min-width: 768px) and (max-width: 979px) { 39 | .navbar .container { 40 | width: 724px; 41 | } 42 | } 43 | 44 | .navbar + .container { 45 | margin-top: 2ex; 46 | } 47 | 48 | .footer { 49 | margin-top: 36px; 50 | margin-bottom: 0px; 51 | border-top: 1px solid #DDD; 52 | color: #999; 53 | text-align: center; 54 | } 55 | 56 | h1 { 57 | margin-top: 0ex; 58 | margin-bottom: 0.5ex; 59 | } 60 | 61 | .repo-info { 62 | margin-bottom: 2ex; 63 | } 64 | .repo-info form { 65 | margin-bottom: 0px; 66 | } 67 | .urlinfo { 68 | padding-bottom: 0px; 69 | } 70 | 71 | .file { 72 | margin-top: 2ex; 73 | } 74 | .file .header { 75 | padding: 0ex 0ex 0ex 1ex; 76 | margin-bottom: 1em; 77 | background-color: whiteSmoke; 78 | border: 1px solid #EEE; 79 | border: 1px solid rgba(0, 0, 0, 0.05); 80 | -webkit-border-radius: 4px; 81 | -moz-border-radius: 4px; 82 | border-radius: 4px; 83 | } 84 | .file .header h3 { 85 | margin: 0px; 86 | font-size: 1.2em; 87 | } 88 | .file .header .btn-mini { 89 | margin-left: 2px; 90 | } 91 | .file .code { 92 | margin-left: 2em; 93 | } 94 | .file .image { 95 | margin-bottom: 2ex; 96 | } 97 | .file > .document { 98 | margin-left: 1em; 99 | } 100 | 101 | .code pre { 102 | border: none; 103 | background-color: inherit; 104 | word-break: normal; 105 | word-wrap: normal; 106 | white-space: pre; 107 | color: black; 108 | font: normal normal 1em/1.2em monospace; 109 | padding: 0px; 110 | margin: 0px; 111 | margin-bottom: 2ex; 112 | } 113 | .code pre.txt { 114 | white-space: pre-wrap; 115 | } 116 | .code { 117 | overflow-x: auto; 118 | } 119 | .code .ln pre { 120 | margin-right: 1ex; 121 | color: #DDD; 122 | } 123 | 124 | div.annotations div.alert { 125 | margin-bottom: 1ex; 126 | } 127 | 128 | ul.history li { 129 | padding-left: 2px; 130 | padding-bottom: 1px; 131 | } 132 | ul.history li.active { 133 | background-color: #EEE; 134 | border-radius: 3px; 135 | } 136 | ul.history a.hash { 137 | font-family: monospace; 138 | } 139 | 140 | 141 | ul.pager { 142 | margin-top: 2ex; 143 | } 144 | 145 | .formbuttons { 146 | margin-top: 2ex; 147 | } 148 | 149 | form textarea.content { 150 | width: 100%; 151 | box-sizing: border-box; 152 | font-family: monospace; 153 | margin-bottom: 0px; 154 | margin-top: 2ex; 155 | } 156 | .content-details { 157 | margin-top: 12px; 158 | } 159 | .content-details .additional-btn { 160 | margin-top: -12px; 161 | } 162 | form .allwidth { 163 | box-sizing: border-box; 164 | } 165 | form .allwidth label { 166 | width: 20%; 167 | float: left; 168 | } 169 | form input#description { 170 | box-sizing: border-box; 171 | height: 2em; 172 | width: 100%; 173 | } 174 | 175 | form label.inline { 176 | display: inline; 177 | } 178 | 179 | .form-horizontal .control-label { 180 | text-align: left; 181 | width: 8em; 182 | } 183 | 184 | .form-horizontal .controls { 185 | margin-left: 8em; 186 | } 187 | 188 | input.fullwidthtext { 189 | width: 100%; 190 | box-sizing: border-box; 191 | height: auto; 192 | } 193 | 194 | .btn [class^="icon-"], .btn [class*=" icon-"] { 195 | line-height: 1.1em; 196 | } 197 | 198 | .content-padding-fix { 199 | height: 1em; 200 | } 201 | 202 | .indent { 203 | margin-left: 10px; 204 | } 205 | 206 | .nobr { 207 | white-space:nowrap; 208 | } 209 | 210 | .avatar-large { 211 | border-radius: 5px; 212 | } 213 | .avatar-small { 214 | border-radius: 3px; 215 | } 216 | .avatar-tiny { 217 | border-radius: 2px; 218 | } 219 | 220 | .forkdomain { 221 | margin-left: 2ex; 222 | } -------------------------------------------------------------------------------- /www/css/widescreen.css: -------------------------------------------------------------------------------- 1 | @media (min-width: 1200px) { 2 | .span12, .container { 3 | width: 1170px; 4 | } 5 | .navbar .container { 6 | width: 1170px; 7 | } 8 | } 9 | @media (min-width: 1400px) { 10 | .span12, .container { 11 | width: 1370px; 12 | } 13 | .navbar .container { 14 | width: 1370px; 15 | } 16 | .span9 { 17 | width: 1070px; 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /www/delete.php: -------------------------------------------------------------------------------- 1 | loadFromRequest(); 11 | 12 | if (isset($_GET['confirm']) && $_GET['confirm'] == 1) { 13 | if ($_SERVER['REQUEST_METHOD'] !== 'POST') { 14 | throw new Exception_Input('Deleting only possible via POST'); 15 | } 16 | $repo->delete(); 17 | redirect(Tools::fullUrl()); 18 | } 19 | 20 | render( 21 | 'delete', 22 | array('repo' => $repo) 23 | ); 24 | ?> 25 | -------------------------------------------------------------------------------- /www/display.php: -------------------------------------------------------------------------------- 1 | loadFromRequest(); 11 | 12 | header('X-Pingback: ' . $repo->getLink('linkback', null, true)); 13 | header( 14 | 'Link: <' . $repo->getLink('linkback', null, true) . '>;' 15 | . 'rel="http://webmention.org/"' 16 | ); 17 | 18 | render( 19 | 'display', 20 | array( 21 | 'repo' => $repo, 22 | 'dh' => new \Date_HumanDiff(), 23 | 'htmlhelper' => new HtmlHelper(), 24 | 'domain' => $_SERVER['HTTP_HOST'], 25 | 'flashmessages' => FlashMessage::getAll(), 26 | ) 27 | ); 28 | ?> 29 | -------------------------------------------------------------------------------- /www/doap.php: -------------------------------------------------------------------------------- 1 | loadFromRequest(); 12 | 13 | $history = $repo->getHistory(); 14 | 15 | header('Content-Type: application/rdf+xml'); 16 | render( 17 | 'doap', 18 | array( 19 | 'repo' => $repo, 20 | 'date' => date('Y-m-d', end($history)->committerTime), 21 | 'link' => Tools::fullUrl($repo->getLink('display')) 22 | ) 23 | ); 24 | ?> 25 | -------------------------------------------------------------------------------- /www/edit.php: -------------------------------------------------------------------------------- 1 | loadFromRequest(); 11 | 12 | $file = null; 13 | if (isset($_GET['file'])) { 14 | if ($_GET['file'] == 'newfile') { 15 | $file = 'newfile'; 16 | } else { 17 | $file = $repo->getFileByName($_GET['file']); 18 | } 19 | } 20 | 21 | $repopo = new Repository_Post($repo); 22 | if ($repopo->process($_POST, $_SESSION)) { 23 | $anchor = ''; 24 | if ($file instanceof File) { 25 | if (isset($repopo->renameMap[$file->getFilename()])) { 26 | $anchor = '#' 27 | . $repo->getFileByName( 28 | $repopo->renameMap[$file->getFilename()] 29 | )->getAnchorName(); 30 | } else { 31 | $anchor = '#' . $file->getAnchorName(); 32 | } 33 | } else if ($file === 'newfile' && $repopo->newfileName) { 34 | $anchor = '#' . $repo->getFileByName($repopo->newfileName)->getAnchorName(); 35 | } 36 | redirect($repo->getLink('display', null, true) . $anchor); 37 | } 38 | 39 | $actionFile = null; 40 | if ($file instanceof File) { 41 | $actionFile = $file->getFilename(); 42 | } else if ($file === 'newfile') { 43 | $actionFile = 'newfile'; 44 | } 45 | 46 | render( 47 | 'edit', 48 | array( 49 | 'repo' => $repo, 50 | 'singlefile' => $file, 51 | 'dh' => new \Date_HumanDiff(), 52 | 'htmlhelper' => new HtmlHelper(), 53 | 'formaction' => $repo->getLink('edit', $actionFile) 54 | ) 55 | ); 56 | ?> 57 | -------------------------------------------------------------------------------- /www/embed-file.php: -------------------------------------------------------------------------------- 1 | loadFromRequest(); 12 | 13 | if (!isset($_GET['file']) || $_GET['file'] == '') { 14 | throw new Exception_Input('File name missing'); 15 | } 16 | 17 | $file = $repo->getFileByName($_GET['file']); 18 | header('Content-Type: text/javascript'); 19 | header('Expires: ' . date('r', time() + 3600)); 20 | render( 21 | 'embed-file', 22 | array( 23 | 'repo' => $repo, 24 | 'file' => $file, 25 | ) 26 | ); 27 | ?> 28 | -------------------------------------------------------------------------------- /www/embed.php: -------------------------------------------------------------------------------- 1 | loadFromRequest(); 12 | 13 | header('Content-Type: text/javascript'); 14 | header('Expires: ' . date('r', time() + 3600)); 15 | render( 16 | 'embed', 17 | array( 18 | 'repo' => $repo, 19 | ) 20 | ); 21 | ?> 22 | -------------------------------------------------------------------------------- /www/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cweiske/phorkie/04e78a7eb3d992ff8af4b472144c339dca956911/www/favicon.ico -------------------------------------------------------------------------------- /www/feed-new.php: -------------------------------------------------------------------------------- 1 | $db->getSearch()->listAll(0, 10, 'crdate', 'desc'), 15 | 'url' => Tools::fullUrl(), 16 | 'feedurl' => Tools::fullUrl('feed/new'), 17 | ) 18 | ); 19 | ?> 20 | -------------------------------------------------------------------------------- /www/feed-updated.php: -------------------------------------------------------------------------------- 1 | $db->getSearch()->listAll(0, 10, 'modate', 'desc'), 15 | 'url' => Tools::fullUrl(), 16 | 'feedurl' => Tools::fullUrl('feed/updated'), 17 | ) 18 | ); 19 | ?> 20 | -------------------------------------------------------------------------------- /www/font/fontawesome-webfont.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cweiske/phorkie/04e78a7eb3d992ff8af4b472144c339dca956911/www/font/fontawesome-webfont.eot -------------------------------------------------------------------------------- /www/font/fontawesome-webfont.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cweiske/phorkie/04e78a7eb3d992ff8af4b472144c339dca956911/www/font/fontawesome-webfont.woff -------------------------------------------------------------------------------- /www/forbidden.php: -------------------------------------------------------------------------------- 1 | isset($_SESSION['identity']) ? $_SESSION['identity'] : null 12 | ) 13 | ); 14 | exit(); 15 | ?> 16 | -------------------------------------------------------------------------------- /www/fork-remote.php: -------------------------------------------------------------------------------- 1 | parse()) { 18 | //no url found 19 | $error = $fr->error; 20 | } else if (false !== ($gitUrl = $fr->getUniqueGitUrl())) { 21 | if (isset($_POST['orig_url'])) { 22 | $fr->setUrl($_POST['orig_url']); 23 | } 24 | $forker = new Forker(); 25 | try { 26 | $new = $forker->forkRemote( 27 | $gitUrl['url'], $fr->getUrl(), $gitUrl['title'] 28 | ); 29 | FlashMessage::save('Remote paste has been forked'); 30 | redirect($new->getLink('display', null, true)); 31 | } catch (\Exception $e) { 32 | $error = $e->getMessage(); 33 | } 34 | } else { 35 | //multiple urls found 36 | $urls = $fr->getGitUrls(); 37 | } 38 | } 39 | 40 | $selsize = 0; 41 | if (is_array($urls)) { 42 | foreach ($urls as $group) { 43 | ++$selsize; 44 | if (count($group) > 1) { 45 | $selsize += count($group); 46 | } 47 | } 48 | } 49 | 50 | render( 51 | 'fork-remote', 52 | array( 53 | 'remote_url' => isset($_REQUEST['remote_url']) ? $_REQUEST['remote_url'] : '', 54 | 'error' => $error, 55 | 'urls' => $urls, 56 | 'urlselsize' => $selsize, 57 | ) 58 | ); 59 | ?> 60 | -------------------------------------------------------------------------------- /www/fork.php: -------------------------------------------------------------------------------- 1 | loadFromRequest(); 15 | 16 | $forker = new Forker(); 17 | $new = $forker->forkLocal($repo); 18 | 19 | FlashMessage::save('Paste has been forked'); 20 | redirect($new->getLink('display', null, true)); 21 | ?> 22 | -------------------------------------------------------------------------------- /www/help.php: -------------------------------------------------------------------------------- 1 | new HtmlHelper(), 13 | 'publicGitUrl' => @$GLOBALS['phorkie']['cfg']['git']['public'], 14 | ) 15 | ); 16 | ?> 17 | -------------------------------------------------------------------------------- /www/images/openid-inputicon.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cweiske/phorkie/04e78a7eb3d992ff8af4b472144c339dca956911/www/images/openid-inputicon.gif -------------------------------------------------------------------------------- /www/index.php: -------------------------------------------------------------------------------- 1 | 14 | -------------------------------------------------------------------------------- /www/js/autologin.js: -------------------------------------------------------------------------------- 1 | jQuery(function($) { 2 | document.getElementsByTagName("body")[0] 3 | .insertAdjacentHTML( 4 | 'beforeend', 5 | '' 8 | ); 9 | /*; 10 | $.ajax("../login.php?autologin=1") 11 | .done(function() { 12 | alert("success"); 13 | }) 14 | .fail(function() { 15 | alert("error"); 16 | }); 17 | */ 18 | }); 19 | 20 | function notifyAutologin(data) 21 | { 22 | if (data.status != 'ok') { 23 | return; 24 | } 25 | document.getElementsByTagName("body")[0] 26 | .insertAdjacentHTML( 27 | 'beforeend', 28 | '' 34 | ); 35 | $('#autologinnotifier').click(function(event) { 36 | $(this).fadeOut(); 37 | }); 38 | $('#autologinnotifier').hide().fadeIn('slow'); 39 | } 40 | -------------------------------------------------------------------------------- /www/js/phorkie.js: -------------------------------------------------------------------------------- 1 | function filenameChange(elem, id) { 2 | var filename = elem.value; 3 | var hasExt = filename.indexOf(".") != -1; 4 | if (hasExt) { 5 | $('#typeselect_' + id).hide(); 6 | $('#typetext_' + id).show(); 7 | } else { 8 | $('#typeselect_' + id).show(); 9 | $('#typetext_' + id).hide(); 10 | } 11 | } 12 | 13 | function initEdit() 14 | { 15 | initFilenames(); 16 | initAdditionals(); 17 | $('.filegroup:visible:last textarea').focus(); 18 | } 19 | function initFilenames() 20 | { 21 | $('input.filename').each( 22 | function(num, elem) { 23 | var id = elem.id; 24 | var pos = id.indexOf('_'); 25 | if (pos != -1) { 26 | var elemNum = id.substr(pos + 1); 27 | if (elemNum != 'new') { 28 | filenameChange(elem, elemNum); 29 | } 30 | } 31 | } 32 | ); 33 | } 34 | function initAdditionals() 35 | { 36 | $('a.additional-btn').each( 37 | function(num, elem) { 38 | toggleAdditional(elem, 0); 39 | $(elem).show(); 40 | } 41 | ); 42 | } 43 | 44 | function toggleAdditional(elem, time) 45 | { 46 | if (undefined == time) { 47 | time = 'fast'; 48 | } 49 | var jt = jQuery(elem); 50 | jt.children('i').toggleClass('icon-chevron-down') 51 | .toggleClass('icon-chevron-up'); 52 | jt.parents('.row-fluid').children('.additional').slideToggle(time); 53 | //jt.parents('.row-fluid').children('.additional').animate(time); 54 | } 55 | -------------------------------------------------------------------------------- /www/linkback.php: -------------------------------------------------------------------------------- 1 | loadFromRequest(); 11 | 12 | $s = new \PEAR2\Services\Linkback\Server(); 13 | $s->addCallback(new Repository_LinkbackReceiver($repo)); 14 | $s->run(); 15 | ?> -------------------------------------------------------------------------------- /www/list.php: -------------------------------------------------------------------------------- 1 | getList($page, $perPage); 20 | 21 | $pager = new Html_Pager( 22 | $repoCount, $perPage, $page + 1, 'list/%d' 23 | ); 24 | 25 | $db = new Database(); 26 | render( 27 | 'list', 28 | array( 29 | 'repos' => $repos, 30 | 'pager' => $pager, 31 | 'recents' => $db->getSearch()->listAll(0, 5, 'modate', 'desc'), 32 | 'dh' => new \Date_HumanDiff(), 33 | ) 34 | ); 35 | ?> 36 | -------------------------------------------------------------------------------- /www/new.php: -------------------------------------------------------------------------------- 1 | process($_POST, $_SESSION)) { 18 | redirect($repopo->repo->getLink('display', null, true)); 19 | } 20 | 21 | $phork = array( 22 | '1' => new File(null, null) 23 | ); 24 | $db = new Database(); 25 | render( 26 | 'new', 27 | array( 28 | 'files' => $phork, 29 | 'description' => '', 30 | 'htmlhelper' => new HtmlHelper(), 31 | 'recents' => $db->getSearch()->listAll(0, 5, 'modate', 'desc'), 32 | 'dh' => new \Date_HumanDiff(), 33 | ) 34 | ); 35 | ?> 36 | -------------------------------------------------------------------------------- /www/oembed.php: -------------------------------------------------------------------------------- 1 | loadById($id); 55 | 56 | if ($format == 'json') { 57 | $data = new \stdClass(); 58 | } else { 59 | $data = new \SimpleXMLElement( 60 | '' 61 | . '' 62 | ); 63 | } 64 | $data->type = 'rich'; 65 | $data->version = '1.0'; 66 | 67 | $data->provider_name = 'phorkie'; 68 | $data->provider_url = Tools::fullUrl(); 69 | 70 | $data->title = $repo->getTitle(); 71 | $author = $repo->getOwner(); 72 | $data->author_name = $author['name']; 73 | $data->cache_age = 86400; 74 | 75 | $data->width = $maxWidth; 76 | $data->height = $maxHeight; 77 | 78 | $data->html = render('oembed', array('repo' => $repo), true); 79 | 80 | if ($format == 'json') { 81 | header('Content-type: application/json'); 82 | echo json_encode($data) . "\n"; 83 | } else { 84 | header('Content-type: text/xml'); 85 | echo $data->asXML();; 86 | } 87 | ?> 88 | -------------------------------------------------------------------------------- /www/phorkie/anonymous.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cweiske/phorkie/04e78a7eb3d992ff8af4b472144c339dca956911/www/phorkie/anonymous.png -------------------------------------------------------------------------------- /www/phorkie/dot-g.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cweiske/phorkie/04e78a7eb3d992ff8af4b472144c339dca956911/www/phorkie/dot-g.png -------------------------------------------------------------------------------- /www/phorkie/dot-n.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cweiske/phorkie/04e78a7eb3d992ff8af4b472144c339dca956911/www/phorkie/dot-n.png -------------------------------------------------------------------------------- /www/phorkie/dot-r.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cweiske/phorkie/04e78a7eb3d992ff8af4b472144c339dca956911/www/phorkie/dot-r.png -------------------------------------------------------------------------------- /www/raw.php: -------------------------------------------------------------------------------- 1 | loadFromRequest(); 11 | 12 | if (!isset($_GET['file']) || $_GET['file'] == '') { 13 | throw new Exception_Input('File name missing'); 14 | } 15 | 16 | $file = $repo->getFileByName($_GET['file']); 17 | $mimetype = $file->getMimeType(); 18 | if ($mimetype === null) { 19 | $mimetype = 'text/plain'; 20 | } 21 | header('Content-Type: ' . $mimetype); 22 | if ($repo->hash === null) { 23 | //IIRC readfile is not so memory-intensive for big files 24 | readfile($file->getFullPath()); 25 | } else { 26 | echo $file->getContent(); 27 | } 28 | ?> 29 | -------------------------------------------------------------------------------- /www/revision.php: -------------------------------------------------------------------------------- 1 | loadFromRequest(); 11 | 12 | render( 13 | 'revision', 14 | array( 15 | 'repo' => $repo, 16 | 'dh' => new \Date_HumanDiff(), 17 | ) 18 | ); 19 | ?> 20 | -------------------------------------------------------------------------------- /www/search.php: -------------------------------------------------------------------------------- 1 | getSearch(); 27 | 28 | $sres = $search->search($query, $page, $perPage); 29 | 30 | $pager = new Html_Pager( 31 | $sres->getResults(), $perPage, $page + 1, $sres->getLink($query) 32 | ); 33 | render( 34 | 'search', 35 | array( 36 | 'query' => $query, 37 | 'sres' => $sres, 38 | 'pager' => $pager 39 | ) 40 | ); 41 | ?> 42 | -------------------------------------------------------------------------------- /www/setup.php: -------------------------------------------------------------------------------- 1 | 35 | 36 | 37 | 38 | phorkie setup check 39 | 40 | 41 | 42 | 43 | 44 | 45 | 70 | 71 | 72 |
    73 |
    74 |
    75 | 76 | 79 |

    Check results

    80 | 81 |
      82 | HTM; 83 | $stateMap = array( 84 | 'ok' => 'success', 85 | 'info' => 'info', 86 | 'error' => 'danger' 87 | ); 88 | foreach ($messages as $arMessage) { 89 | list($type, $message) = $arMessage; 90 | $out .= '
    • '; 92 | $out .= htmlspecialchars($message); 93 | $out .= '
    • ' . "\n"; 94 | } 95 | $out .= << 97 | HTM; 98 | 99 | if (array_sum($GLOBALS['phorkie']['cfgfiles']) == 0) { 100 | //no config file loaded 101 | reset($GLOBALS['phorkie']['cfgfiles']); 102 | $cfgFilePath = key($GLOBALS['phorkie']['cfgfiles']); 103 | 104 | $cfgFilePath = Tools::foldPath($cfgFilePath); 105 | $cfgFileTemplate = htmlspecialchars( 106 | file_get_contents(__DIR__ . '/../data/config.php.dist') 107 | ); 108 | $cfgFileLines = count(explode("\n", $cfgFileTemplate)); 109 | 110 | $out .= <<Configuration file 112 |

      113 | Phorkie did not find a configuration file. 114 | Please create one at 115 |

      116 |
      $cfgFilePath
      117 |

      118 | from the following template: 119 |

      120 | 121 |

      122 | Remove the leading // from a line if you want to adjust it. 123 |

      124 | HTM; 125 | } 126 | 127 | $out .= << 129 | back to the index 130 |

      131 |
    132 |
    133 |
    134 | 135 | 141 | 142 | 143 | 144 | HTM; 145 | echo $out; 146 | ?> 147 | -------------------------------------------------------------------------------- /www/tool.php: -------------------------------------------------------------------------------- 1 | loadFromRequest(); 9 | 10 | if (!isset($_GET['file']) || $_GET['file'] == '') { 11 | throw new Exception_Input('File name missing'); 12 | } 13 | $file = $repo->getFileByName($_GET['file']); 14 | 15 | if (!isset($_GET['tool']) || $_GET['tool'] == '') { 16 | throw new Exception_Input('Tool name missing'); 17 | } 18 | 19 | $tm = new Tool_Manager(); 20 | $tool = $tm->loadTool($_GET['tool']); 21 | 22 | $res = $tool->run($file); 23 | 24 | render( 25 | 'tool', 26 | array( 27 | 'repo' => $repo, 28 | 'file' => $file, 29 | 'toolres' => $res, 30 | ) 31 | ); 32 | 33 | ?> 34 | -------------------------------------------------------------------------------- /www/user.php: -------------------------------------------------------------------------------- 1 | $_SESSION['identity'], 16 | 'name' => $_SESSION['name'], 17 | 'email' => $_SESSION['email'] 18 | ) 19 | ); 20 | ?> 21 | -------------------------------------------------------------------------------- /www/www-header.php: -------------------------------------------------------------------------------- 1 | httpStatusCode); 12 | } else { 13 | header('HTTP/1.0 500 Internal server error'); 14 | } 15 | 16 | if (!isset($GLOBALS['twig'])) { 17 | echo '

    Exception

    '; 18 | echo '

    ' . $e->getMessage() . '

    '; 19 | echo "\n"; 20 | exit(); 21 | } 22 | 23 | render( 24 | 'exception', 25 | array( 26 | 'exception' => $e, 27 | 'debug' => $GLOBALS['phorkie']['cfg']['debug'] 28 | ) 29 | ); 30 | exit(); 31 | } 32 | ); 33 | 34 | require_once __DIR__ . '/../data/config.default.php'; 35 | $pharFile = \Phar::running(); 36 | if ($pharFile == '') { 37 | $cfgFilePath = __DIR__ . '/../data/config.php'; 38 | } else { 39 | //remove phar:// from the path 40 | $cfgFilePath = substr($pharFile, 7) . '.config.php'; 41 | } 42 | $GLOBALS['phorkie']['cfgfiles'][$cfgFilePath] = false; 43 | $GLOBALS['phorkie']['suggestSetupCheck'] = false; 44 | if (file_exists($cfgFilePath)) { 45 | $GLOBALS['phorkie']['cfgfiles'][$cfgFilePath] = true; 46 | require_once $cfgFilePath; 47 | } else if ($GLOBALS['phorkie']['cfg']['setupcheck']) { 48 | $GLOBALS['phorkie']['suggestSetupCheck'] = true; 49 | } 50 | 51 | if ($GLOBALS['phorkie']['cfg']['baseurl'] === null) { 52 | $GLOBALS['phorkie']['cfg']['baseurl'] = Tools::detectBaseUrl(); 53 | if (substr($GLOBALS['phorkie']['cfg']['git']['public'], 0, 9) == '%BASEURL%') { 54 | //make autoconfig work 55 | $GLOBALS['phorkie']['cfg']['git']['public'] = Tools::fullUrlNoPhar( 56 | substr($GLOBALS['phorkie']['cfg']['git']['public'], 9) 57 | ); 58 | } 59 | } 60 | 61 | // Set/Get git commit session variables 62 | $_SESSION['ipaddr'] = $_SERVER['REMOTE_ADDR']; 63 | if (!isset($_SESSION['name'])) { 64 | $_SESSION['name'] = $GLOBALS['phorkie']['auth']['anonymousName']; 65 | } 66 | if (!isset($_SESSION['email'])) { 67 | $_SESSION['email'] = $GLOBALS['phorkie']['auth']['anonymousEmail']; 68 | } 69 | 70 | \Twig_Autoloader::register(); 71 | 72 | $loader = new \Twig_Loader_Filesystem($GLOBALS['phorkie']['cfg']['tpl']); 73 | $twig = new \Twig_Environment( 74 | $loader, 75 | array( 76 | //'cache' => '/path/to/compilation_cache', 77 | 'debug' => true 78 | ) 79 | ); 80 | $twig->addFunction('ntext', new \Twig_Function_Function('\phorkie\ntext')); 81 | function ntext($value, $singular, $plural) 82 | { 83 | if (abs($value) == 1) { 84 | return sprintf($singular, $value); 85 | } 86 | return sprintf($plural, $value); 87 | } 88 | //$twig->addExtension(new \Twig_Extension_Debug()); 89 | 90 | if (!isset($noSecurityCheck) || $noSecurityCheck !== true) { 91 | require __DIR__ . '/www-security.php'; 92 | } 93 | 94 | function render($tplname, $vars = array(), $return = false) 95 | { 96 | $vars['baseurl'] = '/'; 97 | if (!empty($GLOBALS['phorkie']['cfg']['baseurl'])) { 98 | $vars['baseurl'] = $GLOBALS['phorkie']['cfg']['baseurl']; 99 | } 100 | $vars['css'] = $GLOBALS['phorkie']['cfg']['css']; 101 | $vars['iconpng'] = $GLOBALS['phorkie']['cfg']['iconpng']; 102 | $vars['title'] = $GLOBALS['phorkie']['cfg']['title']; 103 | $vars['topbar'] = $GLOBALS['phorkie']['cfg']['topbar']; 104 | if (isset($_SESSION['identity'])) { 105 | $vars['identity'] = $_SESSION['identity']; 106 | $vars['name'] = $_SESSION['name']; 107 | $vars['email'] = $_SESSION['email']; 108 | } else if (isset($_COOKIE['lastopenid']) 109 | && !isset($_COOKIE['tried-autologin']) 110 | ) { 111 | $vars['autologin'] = true; 112 | } 113 | $vars['db'] = new Database(); 114 | if (!isset($vars['htmlhelper'])) { 115 | $vars['htmlhelper'] = new HtmlHelper(); 116 | } 117 | $vars['suggestSetupCheck'] = $GLOBALS['phorkie']['suggestSetupCheck']; 118 | 119 | $template = $GLOBALS['twig']->loadTemplate($tplname . '.htm'); 120 | 121 | if ($return) { 122 | return $template->render($vars); 123 | } else { 124 | echo $template->render($vars); 125 | } 126 | } 127 | 128 | function redirect($target) 129 | { 130 | header('Location: ' . $target); 131 | exit(); 132 | } 133 | ?> 134 | -------------------------------------------------------------------------------- /www/www-security.php: -------------------------------------------------------------------------------- 1 | 46 | --------------------------------------------------------------------------------