diff --git a/.gitignore b/.gitignore index b811090..3172655 100644 --- a/.gitignore +++ b/.gitignore @@ -2,30 +2,28 @@ *.md # All the previous revisions *.md.r* + # Include the README !README.md # ...but ignore READMEs in the build folder build/README.md + # ...and the backup peppermint.json files created when using the settings GUI peppermint.json.bak -# And the Module API Docs +# But keep the useful markdown files !Module_API_Docs.md -# And the changelog !Changelog.md -# And the development notes !Development.md -# The page index +# Ignore the php documentor cache folder +docs/ModuleApiCache + +# Ignore the various indexes created by pepperminty wiki pageindex.json -# The id index idindex.json -# The search index invindex.json -# The recent changes list recent-changes.json -# The statistics cache file build/statsindex.json -# The new settings file peppermint.json # The comments files build/*.comments.json diff --git a/Makefile b/Makefile index eee4e42..9794e3d 100644 --- a/Makefile +++ b/Makefile @@ -1,6 +1,6 @@ .DEFAULT_GOAL := peppermint -.PHONY: setupApiDoc peppermint docs gh-pages +.PHONY: setupApiDoc peppermint docs rest_docs module_api_docs ApiDocPresent := $(shell sh -c apidoc --help 1\>/dev/null && rm -rf doc/) @@ -8,11 +8,20 @@ peppermint: @echo [peppermint/build] Rebuilding Pepperminty Wiki php build.php -docs: setupApiDoc +docs: rest_docs module_api_docs + +rest_docs: setupApiDoc @echo [peppermint/docs] Building docs - apidoc -o './RestApiDocs/' --config apidoc.json -f '.*\.php' -e index.php + apidoc -o './docs/RestApi/' --config apidoc.json -f '.*\.php' -e index.php rm -rf doc/ +module_api_docs: phpdoc + @echo [peppermint/module api docs] Updating module api docs + php phpdoc run --directory . --target docs/ModuleApi --cache-folder docs/ModuleApiCache --ignore build/,php_error.php,Parsedown*,*.html --title "Pepperminty Wiki Module API" --visibility public + +phpdoc: + curl -L https://phpdoc.org/phpDocumentor.phar -o phpdoc + setupApiDoc: @echo [peppermint] Checking for apiDoc ifndef ApiDocPresent @@ -21,16 +30,3 @@ ifndef ApiDocPresent npm install apidoc --global endif @echo [peppermint] Check complete - -gh-pages: - @echo [peppermint/gh-pages] Syncing master branch with gh-pages branch. - @echo [peppermint/gh-pages] Making sure the working directory is clean. - # From http://unix.stackexchange.com/a/155077/64687 - git diff --exit-code - git diff --cached --exit-code - - git checkout gh-pages - git rebase master - git push origin gh-pages - git checkout master - @echo '[peppermint/gh-pages] Sync complete.' diff --git a/Module_API_Docs.md b/Module_API_Docs.md index 90739f9..10fb87c 100644 --- a/Module_API_Docs.md +++ b/Module_API_Docs.md @@ -1,195 +1,28 @@ -Module API Documentation -======================== -The core of Pepperminty Wik exposes several global objects and functions that you can use to write your own modules. This page documents these objects and functions so that you can create your own modules more easily. +# Developer Documentation +The core of Pepperminty Wiki exposes several global objects, classes, functions, and miscellaneous files that you can use to write your own modules. This page documents these them so that you can create your own modules more easily. -Indexes -------- -Pepperminty Wiki maintains several indexes containing various information about the current site that you can utilise. Some of them also have an 'API' of sorts that you can use to interact with them. +## Table of Contents + - [Rest API](#rest-api) + - [Module API](#module-api) + - [Global Variables](#global-variables) + - [Files](#files) + - [`pageindex.json`](#pageindex.json) + - [`idindex.json`](#idindex.json) + - [`invindex.json`](#invindex.json) + - [`recent-changes.json`](#recent-changes.json) + - [`statsindex.json`](#statsindex.json) -### `pageindex.json` -This is by _far_ the most important index. It contains an entry for each page, under which a number of interesting pieces of information are stored. It's automatically loaded into the global variable `$pageindex` too, so you don't even have to read it in. Here's an example pageindex: +## Rest API +The REST api provided by Pepperminty Wiki itself is documented for bot owners and software developers alike over on GitHub pages [here](https://sbrl.github.io/Pepperminty-Wiki/docs/RestAPI). -```json -{ - "Internal link": { - "filename": "Internal link.md", - "size": 120, - "lastmodified": 1446019377, - "lasteditor": "admin", - "tags": [ - "testing", - "test tag with spaces", - "really really really really really really long tag" - ] - }, - "Main Page": { - "filename": "Main Page.md", - "size": 151, - "lastmodified": 1446388276, - "lasteditor": "admin", - "tags": [] - }, - "Internal link\/Sub": { - "filename": "Internal link\/Sub.md", - "size": 35, - "lastmodified": 1446370194, - "lasteditor": "admin", - "tags": [ - "test" - ] - }, - "Files\/AJ Scr.png": { - "filename": "Files\/AJ Scr.png.md", - "size": 29, - "lastmodified": 1445501914, - "lasteditor": "admin", - "uploadedfile": true, - "uploadedfilepath": "Files\/AJ Scr.png", - "uploadedfilemime": "image\/png" - } -} -``` +## Module API +The main PHP-based module API is documented with php documentor. The docs can be found [here](https://sbrl.github.io/Pepperminty-Wiki/docs/ModuleApi), hosted on GitHub Pages. -Currently, Pepperminty Wiki is configured to pretty print the json in the pageindex when saving it to disk, so if you find yourself saving the pageindex please do the same. +This documentation covers all the functions and classes available in both the Pepperminty Wiki core, and the modules stored in this repository - as well as mentioning which module they are a part of. -Now that alternate data storage directories are supported, the `$entry->filename` will *not* contain the `$env->storage_prefix` prefix. You will need to add this manually if you use it. +There are one or two additional things that haven't made their way into the module api docs, which are detailed below: -### `idindex.json` -The id index converts page ids into page names and vice versa. It's loaded into the global variable `$idindex`, but you normally wouldn't need to touch that, as there's a seamless API that you can use instead: - -#### `ids::getid($pagename)` -Gets the id associated with the given pagename. If it doesn't exist it will be created. - -#### `ids::getpagename($id)` -Gets the page name associated with the given id. If it doesn't exist `false` will be returned. - -Functions ---------- - -### `register_module($module_info)` -Register a new module with Pepperminty Wiki. This is the most important function. Here's an example: - -```php - "Human readable module name", // The name of your module, will be shown to users - "version" => "0.1", // The version number - "author" => "Author Name", // Your name - "description" => "Module Description", // A description of your module. Shown in the module downloader. - "id" => "module-id", // An id for your module name. Should be filename safe with no spaces or capital letters. - "code" => function() { - // Insert your module's code here - } -]); - -?> - -``` - -The function that you provide will be executed after the initial setup has been completed. - -### `module_exists($id)` -Checks to see if a module with the given id is currently loaded. Very useful for providing optional integration with other modules. Note that this may return false if all the modules haven't been loaded yet. All the modules are guaranteed to be loaded by the time the code in the `"code"` function is executed. - -```php - "Human readable module name", - "version" => "0.1", - "author" => "Author Name", - "description" => "Module Description", - "id" => "module-id", - "code" => function() { - add_action("action_name", function() { - exit("Hello, World!"); - }); - } -]); - -?> - -``` - -The above adds an action called `action_name`, which, when requested, outputs the text `Hello, World!`. - -### `register_save_preprocessor($function)` -Registers a function to be called every time a page is edited. The function will be passed the following parameters: - -1. A reference to the pageindex entry that is about to be saved. -2. The new text that is to replace the old text. -3. The old text that the new text is going to replace. - -If you make the function that you pass here take the new text that is to be saved in as a reference, you may alter it before it is saved to disk. - -### `page_renderer` -You probably want your module to output a nice user-friendly page instead of a simple text-based one. Luckily, Pepperminty Wiki has a system to let you do that. - -#### `page_renderer::render_main($title, $content)` -This is the main page rendering function you'll want to use. It renders and returns a page much the same as the default `view` action does. Here's an example: - -```php - - -``` - - -#### `page_renderer::render_minimal($title, $content)` -Similar to the above, but renders a printable page instead. For an example, click the "Printable" button at the top of any page on Pepperminty Wiki. - -```php - - -``` - -#### `page_renderer::render_username($name)` -Renders a username. Currently all this function does is prepend `$settings->admin_display_char` to the username if they are an admin. Example: - -```php - -``` - -#### `page_renderer::AddJSLink(string $scriptUrl)` -Add a remote JS script to rendered pages. Links added via this method translate to something like ``. -```php - -``` - -#### `page_renderer::AddJSSnippet(string $snippet)` -Adds a snippet of javascript to generated pages. The snippet in question will be guaranteed to run after the DOM content has loaded (but the `onload` event may not have fired yet). Snippets added via this method will be translated into something like ``. - -```php - -``` - -#### `page_renderer::register_part_preprocessor($code)` +### `page_renderer::register_part_preprocessor($code)` This function's use is more complicated to explain. Pepperminty Wiki renders pages with a very simple templating system. For example, in the template a page's content is denoted by `{content}`. A function registered here will be passed all the components of a page _just_ before they are dropped into the template. Note that the function you pass in here should take a *reference* to the components, as the return value of the function passed is discarded. Here's an example: ```php @@ -251,9 +84,8 @@ register_module([ ``` -Variables ---------- -There are a number of global variables floatign around that can give you a lot of information about the current request. ~~I will be tidying them up into a single `$env` object soon.~~ Most of the below have been tidied up into a single `$env` object now! Below is a table of all the variables Pepperminty Wiki has lying around: +### Global Variables +There are a number of global variables floating around that can give you a lot of information about the current request. ~~I will be tidying them up into a single `$env` object soon.~~ Most of the below have been tidied up into a single `$env` object now! Below is a table of all the variables Pepperminty Wiki has lying around: Variable | Description ------------------------|------------------------------------------ @@ -265,3 +97,67 @@ Variable | Description `$env->action` | The current action. `$settings` | The settings object from the top of the file. `$pageindex` | Contains a list of all the pages that Pepperminty Wiki currently knows about, along with information about each page. Exists to improve performance. + + +## Files +Pepperminty Wiki maintains several files (most of which are indexes) containing various information about the current site that you can utilise. Some of them also have an 'API' of sorts that you can use to interact with them - which is documented in the [module api](#module-api) above. + +### `pageindex.json` +This is by _far_ the most important index. It contains an entry for each page, under which a number of interesting pieces of information are stored. It's automatically loaded into the global variable `$pageindex` too, so you don't even have to read it in. Here's an example pageindex: + +```json +{ + "Internal link": { + "filename": "Internal link.md", + "size": 120, + "lastmodified": 1446019377, + "lasteditor": "admin", + "tags": [ + "testing", + "test tag with spaces", + "really really really really really really long tag" + ] + }, + "Main Page": { + "filename": "Main Page.md", + "size": 151, + "lastmodified": 1446388276, + "lasteditor": "admin", + "tags": [] + }, + "Internal link\/Sub": { + "filename": "Internal link\/Sub.md", + "size": 35, + "lastmodified": 1446370194, + "lasteditor": "admin", + "tags": [ + "test" + ] + }, + "Files\/AJ Scr.png": { + "filename": "Files\/AJ Scr.png.md", + "size": 29, + "lastmodified": 1445501914, + "lasteditor": "admin", + "uploadedfile": true, + "uploadedfilepath": "Files\/AJ Scr.png", + "uploadedfilemime": "image\/png" + } +} +``` + +Currently, Pepperminty Wiki is configured to pretty print the json in the pageindex when saving it to disk, so if you find yourself saving the pageindex please do the same. + +Now that alternate data storage directories are supported, the `$entry->filename` will *not* contain the `$env->storage_prefix` prefix. You will need to add this manually if you use it. + +### `idindex.json` +The id index converts page ids into page names and vice versa. It's loaded into the global variable `$idindex`, but you normally wouldn't need to touch that, as there's a seamless API that you can use instead, which can be found under the `ids` class. + +### `invindex.json` +This is the main search index. Obviously, it's only present if the `feature-search` module is loaded and active. It can be interacted with though the `search` class that the `feature-search` module exposes. + +### `recent-changes.json` +This is not loaded automatically, but it contains a list of recent changes that have occurred throughout the wiki. You don't have to fiddle with it directly though if you just want to add a new change, because the `feature-recent-changes` module has a fewe handy methods you can use for that purpose. + +### `statsindex.json` +This file is brand new as of v0.15, and contains the most recently calculated statistics about the wiki. The `feature-stats` module oversees the regeneration of this file. Consult if you need access to such statistics that might be somewhat expensive to calculate. diff --git a/README.md b/README.md index b1e39d7..a8c0631 100644 --- a/README.md +++ b/README.md @@ -171,7 +171,7 @@ Key | Value | Explanation ## Module API Reference I have documented (most of) the current API that you can use to create your own modules. You can find it in the [Module_API_Docs.md](https://github.com/sbrl/Pepperminty-Wiki/blob/master/Module_API_Docs.md) file in this repository. -I've also documented Pepperminty Wiki's entire REST API using [apiDoc](http://apidocjs.com/). You can view the docs [here](https://sbrl.github.io/Pepperminty-Wiki/RestApiDocs/). +I've also documented Pepperminty Wiki's entire REST API using [apiDoc](http://apidocjs.com/). You can view the docs [here](https://sbrl.github.io/Pepperminty-Wiki/docs/RestApiDocs/). If you do create a module, I'd love to hear about it. Even better, [send a pull request](https://github.com/sbrl/Pepperminty-Wiki/pulls/new)! diff --git a/RestApiDocs/api_data.js b/RestApiDocs/api_data.js deleted file mode 100644 index fa18bf6..0000000 --- a/RestApiDocs/api_data.js +++ /dev/null @@ -1,1418 +0,0 @@ -define({ "api": [ - { - "type": "post", - "url": "?action=checklogin", - "title": "Perform a login", - "name": "CheckLogin", - "group": "Authorisation", - "permission": [ - { - "name": "Anonymous", - "title": "Anybody may use this call.", - "description": "" - } - ], - "parameter": { - "fields": { - "Parameter": [ - { - "group": "Parameter", - "type": "string", - "optional": false, - "field": "user", - "description": "

The user name to login with.

" - }, - { - "group": "Parameter", - "type": "string", - "optional": false, - "field": "password", - "description": "

The password to login with.

" - }, - { - "group": "Parameter", - "type": "string", - "optional": false, - "field": "returnto", - "description": "

The URL to redirect to upon a successful login.

" - } - ] - } - }, - "error": { - "fields": { - "Error 4xx": [ - { - "group": "Error 4xx", - "optional": false, - "field": "InvalidCredentialsError", - "description": "

The supplied credentials were invalid. Note that this error is actually a redirect to ?action=login&failed=yes (with the returnto parameter appended if you supplied one)

" - } - ] - } - }, - "version": "0.0.0", - "filename": "./modules/page-login.php", - "groupTitle": "Authorisation" - }, - { - "type": "get", - "url": "?action=login[&failed=yes][&returnto={someUrl}]", - "title": "Get the login page", - "name": "Login", - "group": "Authorisation", - "permission": [ - { - "name": "Anonymous", - "title": "Anybody may use this call.", - "description": "" - } - ], - "parameter": { - "fields": { - "Parameter": [ - { - "group": "Parameter", - "type": "string", - "optional": false, - "field": "failed", - "description": "

Setting to yes causes a login failure message to be displayed above the login form.

" - }, - { - "group": "Parameter", - "type": "string", - "optional": false, - "field": "returnto", - "description": "

Set to the url to redirect to upon a successful login.

" - } - ] - } - }, - "version": "0.0.0", - "filename": "./modules/page-login.php", - "groupTitle": "Authorisation" - }, - { - "type": "post", - "url": "?action=logout", - "title": "Logout", - "description": "

Logout. Make sure that your bot requests this URL when it is finished - this call not only clears your cookies but also clears the server's session file as well. Note that you can request this when you are already logged out and it will completely wipe your session on the server.

", - "name": "Logout", - "group": "Authorisation", - "permission": [ - { - "name": "Anonymous", - "title": "Anybody may use this call.", - "description": "" - } - ], - "version": "0.0.0", - "filename": "./modules/page-logout.php", - "groupTitle": "Authorisation" - }, - { - "type": "post", - "url": "?action=comment", - "title": "Comment on a page", - "name": "Comment", - "group": "Comment", - "permission": [ - { - "name": "User", - "title": "Only users loggged in may use this call.", - "description": "" - } - ], - "description": "

Posts a comment on a page, optionally in reply to another comment. Currently, comments must be made by a logged-in user.

", - "parameter": { - "fields": { - "Parameter": [ - { - "group": "Parameter", - "type": "string", - "optional": false, - "field": "message", - "description": "

The comment text. Supports the same syntax that the renderer of the main page supports. The default is extended markdown - see the help page of the specific wiki for more information.

" - }, - { - "group": "Parameter", - "type": "string", - "optional": false, - "field": "replyto", - "description": "

Optional. If specified the comment will be posted in reply to the comment with the specified id.

" - } - ] - } - }, - "error": { - "fields": { - "Error 4xx": [ - { - "group": "Error 4xx", - "optional": false, - "field": "CommentNotFound", - "description": "

The comment to reply to was not found.

" - } - ] - } - }, - "version": "0.0.0", - "filename": "./modules/feature-comments.php", - "groupTitle": "Comment" - }, - { - "type": "post", - "url": "?action=delete", - "title": "Delete a page", - "description": "

Delete a page and all its associated data.

", - "name": "DeletePage", - "group": "Page", - "permission": [ - { - "name": "Moderator", - "title": "Only users loggged with a moderator account may use this call.", - "description": "" - } - ], - "parameter": { - "fields": { - "Parameter": [ - { - "group": "Parameter", - "type": "string", - "optional": false, - "field": "page", - "description": "

The name of the page to delete.

" - }, - { - "group": "Parameter", - "type": "string", - "optional": false, - "field": "delete", - "description": "

Set to 'yes' to actually delete the page.

" - } - ] - } - }, - "error": { - "fields": { - "Error 4xx": [ - { - "group": "Error 4xx", - "optional": false, - "field": "PageNonExistentError", - "description": "

The specified page doesn't exist

" - }, - { - "group": "Error 4xx", - "optional": false, - "field": "UserNotModeratorError", - "description": "

You weren't loggged in as a moderator before sending this request.

" - } - ] - } - }, - "version": "0.0.0", - "filename": "./modules/page-delete.php", - "groupTitle": "Page" - }, - { - "type": "get", - "url": "?action=edit&page={pageName}[&newpage=yes]", - "title": "Get an editing page", - "description": "

Gets an editing page for a given page. If you don't have permission to edit the page in question, a view source pagee is returned instead.

", - "name": "EditPage", - "group": "Page", - "permission": [ - { - "name": "Anonymous", - "title": "Anybody may use this call.", - "description": "" - } - ], - "parameter": { - "fields": { - "Parameter": [ - { - "group": "Parameter", - "type": "string", - "optional": false, - "field": "newpage", - "description": "

Set to 'yes' if a new page is being created. Only affects a few bits of text here and there, and the HTTP response code recieved on success from the save action.

" - }, - { - "group": "Parameter", - "type": "string", - "optional": false, - "field": "page", - "description": "

The page to operate on.

" - } - ] - } - }, - "version": "0.0.0", - "filename": "./modules/page-edit.php", - "groupTitle": "Page" - }, - { - "type": "post", - "url": "?action=save&page={pageName}", - "title": "Save an edit to a page.", - "description": "

Saves an edit to a page. If an edit conflict is encountered, then a conflict resolution page is returned instead.

", - "name": "EditPage", - "group": "Page", - "permission": [ - { - "name": "Anonymous", - "title": "Anybody may use this call.", - "description": "" - } - ], - "parameter": { - "fields": { - "Parameter": [ - { - "group": "Parameter", - "type": "string", - "optional": false, - "field": "newpage", - "description": "

GET only. Set to 'yes' to indicate that this is a new page that is being saved. Only affects the HTTP response code you recieve upon success.

" - }, - { - "group": "Parameter", - "type": "string", - "optional": false, - "field": "content", - "description": "

POST only. The new content to save to the given filename.

" - }, - { - "group": "Parameter", - "type": "string", - "optional": false, - "field": "tags", - "description": "

POST only. A comma-separated list of tags to assign to the current page. Will replace the existing list of tags, if any are present.

" - }, - { - "group": "Parameter", - "type": "string", - "optional": false, - "field": "prev-content-hash", - "description": "

POST only. The hash of the original content before editing. If this hash is found to be different to a hash computed of the currentl saved content, a conflict resolution page will be returned instead of saving the provided content.

" - }, - { - "group": "Parameter", - "type": "string", - "optional": false, - "field": "page", - "description": "

The page to operate on.

" - } - ] - } - }, - "error": { - "fields": { - "Error 4xx": [ - { - "group": "Error 4xx", - "optional": false, - "field": "UnsufficientPermissionError", - "description": "

You don't currently have sufficient permissions to save an edit.

" - } - ] - } - }, - "version": "0.0.0", - "filename": "./modules/page-edit.php", - "groupTitle": "Page" - }, - { - "type": "get", - "url": "?action=history&page={pageName}", - "title": "Get a list of revisions for a page", - "name": "History", - "group": "Page", - "permission": [ - { - "name": "Anonymous", - "title": "Anybody may use this call.", - "description": "" - } - ], - "parameter": { - "fields": { - "Parameter": [ - { - "group": "Parameter", - "type": "string", - "optional": false, - "field": "page", - "description": "

The page name to return a revision list for.

" - } - ] - } - }, - "version": "0.0.0", - "filename": "./modules/feature-history.php", - "groupTitle": "Page" - }, - { - "type": "get", - "url": "?action=list", - "title": "List all pages", - "description": "

Gets a list of all the pages currently stored on the wiki.

", - "name": "ListPages", - "group": "Page", - "permission": [ - { - "name": "Anonymous", - "title": "Anybody may use this call.", - "description": "" - } - ], - "version": "0.0.0", - "filename": "./modules/page-list.php", - "groupTitle": "Page" - }, - { - "type": "get", - "url": "?action=move[&new_name={newPageName}]", - "title": "Move a page", - "name": "Move", - "group": "Page", - "permission": [ - { - "name": "Moderator", - "title": "Only users loggged with a moderator account may use this call.", - "description": "" - } - ], - "parameter": { - "fields": { - "Parameter": [ - { - "group": "Parameter", - "type": "string", - "optional": false, - "field": "new_name", - "description": "

The new name to move the page to. If not set a page will be returned containing a move page form.

" - } - ] - } - }, - "error": { - "fields": { - "Error 4xx": [ - { - "group": "Error 4xx", - "optional": false, - "field": "EditingDisabledError", - "description": "

Editing is disabled on this wiki, so pages can't be moved.

" - }, - { - "group": "Error 4xx", - "optional": false, - "field": "PageExistsAtDestinationError", - "description": "

A page already exists with the specified new name.

" - }, - { - "group": "Error 4xx", - "optional": false, - "field": "NonExistentPageError", - "description": "

The page you're trying to move doesn't exist in the first place.

" - }, - { - "group": "Error 4xx", - "optional": false, - "field": "PreExistingFileError", - "description": "

A pre-existing file on the server's file system was detected.

" - }, - { - "group": "Error 4xx", - "optional": false, - "field": "UserNotModeratorError", - "description": "

You weren't loggged in as a moderator before sending this request.

" - } - ] - } - }, - "version": "0.0.0", - "filename": "./modules/page-move.php", - "groupTitle": "Page" - }, - { - "type": "get", - "url": "?action=protect&page={pageName}", - "title": "Toggle the protection of a page.", - "name": "Protect", - "group": "Page", - "permission": [ - { - "name": "Moderator", - "title": "Only users loggged with a moderator account may use this call.", - "description": "" - } - ], - "parameter": { - "fields": { - "Parameter": [ - { - "group": "Parameter", - "type": "string", - "optional": false, - "field": "page", - "description": "

The page name to toggle the protection of.

" - } - ] - } - }, - "version": "0.0.0", - "filename": "./modules/action-protect.php", - "groupTitle": "Page" - }, - { - "type": "get", - "url": "?action=raw&page={pageName}", - "title": "Get the raw source code of a page", - "name": "RawSource", - "group": "Page", - "permission": [ - { - "name": "Anonymous", - "title": "Anybody may use this call.", - "description": "" - } - ], - "parameter": { - "fields": { - "Parameter": [ - { - "group": "Parameter", - "type": "string", - "optional": false, - "field": "page", - "description": "

The page to return the source of.

" - } - ] - } - }, - "version": "0.0.0", - "filename": "./modules/action-raw.php", - "groupTitle": "Page" - }, - { - "type": "get", - "url": "?action=random", - "title": "Redirects to a random page.", - "name": "RawSource", - "group": "Page", - "permission": [ - { - "name": "Anonymous", - "title": "Anybody may use this call.", - "description": "" - } - ], - "version": "0.0.0", - "filename": "./modules/action-random.php", - "groupTitle": "Page" - }, - { - "type": "get", - "url": "?action=raw&page={pageName}", - "title": "Get the raw source code of a page", - "name": "RawSource", - "group": "Page", - "permission": [ - { - "name": "Anonymous", - "title": "Anybody may use this call.", - "description": "" - } - ], - "parameter": { - "fields": { - "Parameter": [ - { - "group": "Parameter", - "type": "string", - "optional": false, - "field": "page", - "description": "

The page to return the source of.

" - } - ] - } - }, - "version": "0.0.0", - "filename": "./modules/api-status.php", - "groupTitle": "Page" - }, - { - "type": "get", - "url": "?action=view[&page={pageName}][&revision=rid][&printable=yes]", - "title": "View a page", - "name": "View", - "group": "Page", - "permission": [ - { - "name": "Anonymous", - "title": "Anybody may use this call.", - "description": "" - } - ], - "parameter": { - "fields": { - "Parameter": [ - { - "group": "Parameter", - "type": "number", - "optional": false, - "field": "revision", - "description": "

The revision number to display.

" - }, - { - "group": "Parameter", - "type": "string", - "optional": false, - "field": "mode", - "description": "

Optional. The display mode to use. Can hld the following values: 'normal' - The default. Sends a normal page. 'printable' - Sends a printable version of the page. 'contentonly' - Sends only the content of the page, not the extra stuff around it. 'parsedsourceonly' - Sends only the raw rendered source of the page, as it appears just after it has come out of the page parser. Useful for writing external tools (see also the raw action).

" - }, - { - "group": "Parameter", - "type": "string", - "optional": false, - "field": "page", - "description": "

The page to operate on.

" - } - ] - } - }, - "error": { - "fields": { - "Error 4xx": [ - { - "group": "Error 4xx", - "optional": false, - "field": "NonExistentPageError", - "description": "

The page doesn't exist and editing is disabled in the wiki's settings. If editing isn't disabled, you will be redirected to the edit page instead.

" - }, - { - "group": "Error 4xx", - "optional": false, - "field": "NonExistentRevisionError", - "description": "

The specified revision was not found.

" - } - ] - } - }, - "version": "0.0.0", - "filename": "./modules/page-view.php", - "groupTitle": "Page" - }, - { - "type": "get", - "url": "?action=suggest-pages", - "title": "Get search suggestions for a query", - "name": "OpenSearchDescription", - "group": "Search", - "permission": [ - { - "name": "Anonymous", - "title": "Anybody may use this call.", - "description": "" - } - ], - "parameter": { - "fields": { - "Parameter": [ - { - "group": "Parameter", - "type": "string", - "optional": false, - "field": "text", - "description": "

The search query string to get search suggestions for.

" - } - ] - } - }, - "version": "0.0.0", - "filename": "./modules/feature-search.php", - "groupTitle": "Search" - }, - { - "type": "get", - "url": "?action=opensearch-description", - "title": "Get the opensearch description file", - "name": "OpenSearchDescription", - "group": "Search", - "permission": [ - { - "name": "Anonymous", - "title": "Anybody may use this call.", - "description": "" - } - ], - "version": "0.0.0", - "filename": "./modules/feature-search.php", - "groupTitle": "Search" - }, - { - "type": "get", - "url": "?action=query-searchindex&query={text}", - "title": "Inspect the internals of the search results for a query", - "name": "Search", - "group": "Search", - "permission": [ - { - "name": "Anonymous", - "title": "Anybody may use this call.", - "description": "" - } - ], - "parameter": { - "fields": { - "Parameter": [ - { - "group": "Parameter", - "type": "string", - "optional": false, - "field": "query", - "description": "

The query string to search for.

" - } - ] - } - }, - "version": "0.0.0", - "filename": "./modules/feature-search.php", - "groupTitle": "Search" - }, - { - "type": "get", - "url": "?action=search&query={text}", - "title": "Search the wiki for a given query string", - "name": "Search", - "group": "Search", - "permission": [ - { - "name": "Anonymous", - "title": "Anybody may use this call.", - "description": "" - } - ], - "parameter": { - "fields": { - "Parameter": [ - { - "group": "Parameter", - "type": "string", - "optional": false, - "field": "query", - "description": "

The query string to search for.

" - } - ] - } - }, - "version": "0.0.0", - "filename": "./modules/feature-search.php", - "groupTitle": "Search" - }, - { - "type": "get", - "url": "?action=index&page={pageName}", - "title": "Get an index of words for a given page", - "name": "SearchIndex", - "group": "Search", - "permission": [ - { - "name": "Anonymous", - "title": "Anybody may use this call.", - "description": "" - } - ], - "parameter": { - "fields": { - "Parameter": [ - { - "group": "Parameter", - "type": "string", - "optional": false, - "field": "page", - "description": "

The page to generate a word index page.

" - } - ] - } - }, - "version": "0.0.0", - "filename": "./modules/feature-search.php", - "groupTitle": "Search" - }, - { - "type": "get", - "url": "?action=invindex-rebuild", - "title": "Rebuild the inverted search index from scratch", - "description": "

Causes the inverted search index to be completely rebuilt from scratch. Can take a while for large wikis!

", - "name": "SearchInvindexRebuild", - "group": "Search", - "permission": [ - { - "name": "Anonymous", - "title": "Anybody may use this call.", - "description": "" - } - ], - "version": "0.0.0", - "filename": "./modules/feature-search.php", - "groupTitle": "Search" - }, - { - "type": "get", - "url": "?action=idindex-show", - "title": "Show the id index", - "description": "

Outputs the id index. Useful if you need to verify that it's working as expected.

", - "name": "SearchShowIdIndex", - "group": "Search", - "permission": [ - { - "name": "Anonymous", - "title": "Anybody may use this call.", - "description": "" - } - ], - "version": "0.0.0", - "filename": "./modules/feature-search.php", - "groupTitle": "Search" - }, - { - "type": "post", - "url": "?action=change-password", - "title": "Change your password", - "name": "ChangePassword", - "group": "Settings", - "permission": [ - { - "name": "User", - "title": "Only users loggged in may use this call.", - "description": "" - } - ], - "parameter": { - "fields": { - "Parameter": [ - { - "group": "Parameter", - "type": "string", - "optional": false, - "field": "current-pass", - "description": "

Your current password.

" - }, - { - "group": "Parameter", - "type": "string", - "optional": false, - "field": "new-pass", - "description": "

Your new password.

" - }, - { - "group": "Parameter", - "type": "string", - "optional": false, - "field": "new-pass-confirm", - "description": "

Your new password again, to make sure you've typed it correctly.

" - } - ] - } - }, - "error": { - "fields": { - "Error 4xx": [ - { - "group": "Error 4xx", - "optional": false, - "field": "PasswordMismatchError", - "description": "

The new password fields don't match.

" - } - ] - } - }, - "version": "0.0.0", - "filename": "./modules/feature-user-preferences.php", - "groupTitle": "Settings" - }, - { - "type": "get", - "url": "?action=user-preferences", - "title": "Get a user preferences configuration page", - "name": "UserPreferences", - "group": "Settings", - "permission": [ - { - "name": "User", - "title": "Only users loggged in may use this call.", - "description": "" - } - ], - "version": "0.0.0", - "filename": "./modules/feature-user-preferences.php", - "groupTitle": "Settings" - }, - { - "type": "post", - "url": "?action=save-preferences", - "title": "Save your user preferences", - "name": "UserPreferencesSave", - "group": "Settings", - "permission": [ - { - "name": "User", - "title": "Only users loggged in may use this call.", - "description": "" - } - ], - "version": "0.0.0", - "filename": "./modules/feature-user-preferences.php", - "groupTitle": "Settings" - }, - { - "type": "get", - "url": "?action=recentchanges", - "title": "Get a list of recent changes", - "name": "RecentChanges", - "group": "Stats", - "permission": [ - { - "name": "Anonymous", - "title": "Anybody may use this call.", - "description": "" - } - ], - "version": "0.0.0", - "filename": "./modules/feature-recent-changes.php", - "groupTitle": "Stats" - }, - { - "type": "get", - "url": "?action=avatar&user={username}[&size={size}]", - "title": "Get a user's avatar", - "name": "Avatar", - "group": "Upload", - "permission": [ - { - "name": "Anonymous", - "title": "Anybody may use this call.", - "description": "" - } - ], - "parameter": { - "fields": { - "Parameter": [ - { - "group": "Parameter", - "type": "string", - "optional": false, - "field": "user", - "description": "

The username to fetch the avatar for

" - }, - { - "group": "Parameter", - "type": "string", - "optional": false, - "field": "size", - "description": "

The preferred size of the avatar

" - } - ] - } - }, - "version": "0.0.0", - "filename": "./modules/feature-user-preferences.php", - "groupTitle": "Upload" - }, - { - "type": "get", - "url": "?action=preview&page={pageName}[&size={someSize}]", - "title": "Get a preview of a file", - "name": "PreviewFile", - "group": "Upload", - "permission": [ - { - "name": "Anonymous", - "title": "Anybody may use this call.", - "description": "" - } - ], - "parameter": { - "fields": { - "Parameter": [ - { - "group": "Parameter", - "type": "string", - "optional": false, - "field": "page", - "description": "

The name of the file to preview.

" - }, - { - "group": "Parameter", - "type": "number", - "optional": false, - "field": "size", - "description": "

Optional. The size fo the resulting preview. Will be clamped to fit within the bounds specified in the wiki's settings. May also be set to the keyword 'original', which will cause the original file to be returned with it's appropriate mime type instead.

" - } - ] - } - }, - "error": { - "fields": { - "Error 4xx": [ - { - "group": "Error 4xx", - "optional": false, - "field": "PreviewNoFileError", - "description": "

No file was found associated with the specified page.

" - }, - { - "group": "Error 4xx", - "optional": false, - "field": "PreviewUnknownFileTypeError", - "description": "

Pepperminty Wiki was unable to generate a preview for the requested file's type.

" - } - ] - } - }, - "version": "0.0.0", - "filename": "./modules/feature-upload.php", - "groupTitle": "Upload" - }, - { - "type": "post", - "url": "?action=upload", - "title": "Upload a file", - "name": "UploadFile", - "group": "Upload", - "permission": [ - { - "name": "User", - "title": "Only users loggged in may use this call.", - "description": "" - } - ], - "parameter": { - "fields": { - "Parameter": [ - { - "group": "Parameter", - "type": "string", - "optional": false, - "field": "name", - "description": "

The name of the file to upload.

" - }, - { - "group": "Parameter", - "type": "string", - "optional": false, - "field": "description", - "description": "

A description of the file.

" - }, - { - "group": "Parameter", - "type": "file", - "optional": false, - "field": "file", - "description": "

The file to upload.

" - }, - { - "group": "Parameter", - "type": "boolean", - "optional": false, - "field": "avatar", - "description": "

Whether this upload should be uploaded as the current user's avatar. If specified, any filenames provided will be ignored.

" - } - ] - } - }, - "error": { - "fields": { - "Error 4xx": [ - { - "group": "Error 4xx", - "optional": false, - "field": "UploadsDisabledError", - "description": "

Uploads are currently disabled in the wiki's settings.

" - }, - { - "group": "Error 4xx", - "optional": false, - "field": "UnknownFileTypeError", - "description": "

The type of the file you uploaded is not currently allowed in the wiki's settings.

" - }, - { - "group": "Error 4xx", - "optional": false, - "field": "ImageDimensionsFiledError", - "description": "

PeppermintyWiki couldn't obtain the dimensions of the image you uploaded.

" - }, - { - "group": "Error 4xx", - "optional": false, - "field": "DangerousFileError", - "description": "

The file uploaded appears to be dangerous.

" - }, - { - "group": "Error 4xx", - "optional": false, - "field": "DuplicateFileError", - "description": "

The filename specified is a duplicate of a file that already exists.

" - }, - { - "group": "Error 4xx", - "optional": false, - "field": "FileTamperedError", - "description": "

Pepperminty Wiki couldn't verify that the file wasn't tampered with during theupload process.

" - }, - { - "group": "Error 4xx", - "optional": false, - "field": "UserNotLoggedInError", - "description": "

You didn't log in before sending this request.

" - } - ] - } - }, - "version": "0.0.0", - "filename": "./modules/feature-upload.php", - "groupTitle": "Upload" - }, - { - "type": "get", - "url": "?action=upload[&avatar=yes]", - "title": "Get a page to let you upload a file.", - "name": "UploadFilePage", - "group": "Upload", - "permission": [ - { - "name": "User", - "title": "Only users loggged in may use this call.", - "description": "" - } - ], - "parameter": { - "fields": { - "Parameter": [ - { - "group": "Parameter", - "type": "boolean", - "optional": false, - "field": "avatar", - "description": "

Optional. If true then a special page to upload your avatar is displayed instead.

" - } - ] - } - }, - "version": "0.0.0", - "filename": "./modules/feature-upload.php", - "groupTitle": "Upload" - }, - { - "type": "get", - "url": "?action=configure", - "title": "Get a page to change the global wiki settings", - "name": "ConfigureSettings", - "group": "Utility", - "permission": [ - { - "name": "Moderator", - "title": "Only users loggged with a moderator account may use this call.", - "description": "" - } - ], - "version": "0.0.0", - "filename": "./modules/feature-guiconfig.php", - "groupTitle": "Utility" - }, - { - "type": "post", - "url": "?action=configure-save", - "title": "Save changes to the global wiki settings", - "name": "ConfigureSettings", - "group": "Utility", - "permission": [ - { - "name": "Moderator", - "title": "Only users loggged with a moderator account may use this call.", - "description": "" - } - ], - "version": "0.0.0", - "filename": "./modules/feature-guiconfig.php", - "groupTitle": "Utility" - }, - { - "type": "get", - "url": "?action=credits", - "title": "Get the credits page", - "name": "Credits", - "group": "Utility", - "permission": [ - { - "name": "Anonymous", - "title": "Anybody may use this call.", - "description": "" - } - ], - "version": "0.0.0", - "filename": "./modules/page-credits.php", - "groupTitle": "Utility" - }, - { - "type": "get", - "url": "?action=debug", - "title": "Get a debug dump", - "name": "Debug", - "group": "Utility", - "permission": [ - { - "name": "Moderator", - "title": "Only users loggged with a moderator account may use this call.", - "description": "" - } - ], - "version": "0.0.0", - "filename": "./modules/page-debug-info.php", - "groupTitle": "Utility", - "error": { - "fields": { - "Error 4xx": [ - { - "group": "Error 4xx", - "optional": false, - "field": "UserNotModeratorError", - "description": "

You weren't loggged in as a moderator before sending this request.

" - } - ] - } - } - }, - { - "type": "get", - "url": "?action=export", - "title": "Export the all the wiki's content", - "description": "

Export all the wiki's content. Please ask for permission before making a request to this URI. Note that some wikis may only allow moderators to export content.

", - "name": "Export", - "group": "Utility", - "permission": [ - { - "name": "Anonymous", - "title": "Anybody may use this call.", - "description": "" - } - ], - "error": { - "fields": { - "Error 4xx": [ - { - "group": "Error 4xx", - "optional": false, - "field": "InsufficientExportPermissionsError", - "description": "

The wiki has the export_allow_only_admins option turned on, and you aren't logged into a moderator account.

" - }, - { - "group": "Error 4xx", - "optional": false, - "field": "CouldntOpenTempFileError", - "description": "

Pepperminty Wiki couldn't open a temporary file to send the compressed archive to.

" - }, - { - "group": "Error 4xx", - "optional": false, - "field": "CouldntCloseTempFileError", - "description": "

Pepperminty Wiki couldn't close the temporary file to finish creating the zip archive ready for downloading.

" - } - ] - } - }, - "version": "0.0.0", - "filename": "./modules/page-export.php", - "groupTitle": "Utility" - }, - { - "type": "get", - "url": "?action=hash&string={text}", - "title": "Hash a password", - "name": "Hash", - "group": "Utility", - "permission": [ - { - "name": "Anonymous", - "title": "Anybody may use this call.", - "description": "" - } - ], - "parameter": { - "fields": { - "Parameter": [ - { - "group": "Parameter", - "type": "string", - "optional": false, - "field": "string", - "description": "

The string to hash.

" - }, - { - "group": "Parameter", - "type": "boolean", - "optional": false, - "field": "raw", - "description": "

Whether to return the hashed password as a raw string instead of as part of an HTML page.

" - } - ] - } - }, - "error": { - "fields": { - "Error 4xx": [ - { - "group": "Error 4xx", - "optional": false, - "field": "ParamNotFound", - "description": "

The string parameter was not specified.

" - } - ] - } - }, - "version": "0.0.0", - "filename": "./modules/action-hash.php", - "groupTitle": "Utility" - }, - { - "type": "get", - "url": "?action=help[&dev=yes]", - "title": "Get a help page", - "description": "

Get a customised help page. This page will be slightly different for every wiki, depending on their name, settings, and installed modules.

", - "name": "Help", - "group": "Utility", - "permission": [ - { - "name": "Anonymous", - "title": "Anybody may use this call.", - "description": "" - } - ], - "parameter": { - "fields": { - "Parameter": [ - { - "group": "Parameter", - "type": "string", - "optional": false, - "field": "dev", - "description": "

Set to 'yes' to get a developer help page instead. The developer help page gives some general information about which modules and help page sections are registered, and other various (non-sensitive) settings.

" - } - ] - } - }, - "version": "0.0.0", - "filename": "./modules/page-help.php", - "groupTitle": "Utility" - }, - { - "type": "get", - "url": "?action=list-tags[&tag=]", - "title": "Get a list of tags or pages with a certain tag", - "description": "

Gets a list of all tags on the wiki. Adding the tag parameter causes a list of pages with the given tag to be returned instead.

", - "name": "ListTags", - "group": "Utility", - "permission": [ - { - "name": "Anonymous", - "title": "Anybody may use this call.", - "description": "" - } - ], - "parameter": { - "fields": { - "Parameter": [ - { - "group": "Parameter", - "type": "string", - "optional": false, - "field": "tag", - "description": "

Optional. If provided a list of all the pages with that tag is returned instead.

" - } - ] - } - }, - "version": "0.0.0", - "filename": "./modules/page-list.php", - "groupTitle": "Utility" - }, - { - "type": "get", - "url": "?action=update[do=yes]", - "title": "Update the wiki", - "description": "

Update the wiki by downloading a new version of Pepperminty Wiki from the URL specified in the settings. Note that unless you change the url from it's default, all custom modules installed will be removed. Note also that this plugin is currently out of date. Use with extreme caution!

", - "name": "Update", - "group": "Utility", - "permission": [ - { - "name": "Moderator", - "title": "Only users loggged with a moderator account may use this call.", - "description": "" - } - ], - "parameter": { - "fields": { - "Parameter": [ - { - "group": "Parameter", - "type": "string", - "optional": false, - "field": "do", - "description": "

Set to 'yes' to actually do the upgrade. Omission causes a page asking whether an update is desired instead.

" - }, - { - "group": "Parameter", - "type": "string", - "optional": false, - "field": "secret", - "description": "

The wiki's secret string that's stored in the settings.

" - }, - { - "group": "Parameter", - "optional": false, - "field": "InvalidSecretError", - "description": "

The supplied secret doesn't match up with the secret stored in the wiki's settings.

" - } - ] - } - }, - "version": "0.0.0", - "filename": "./modules/page-update.php", - "groupTitle": "Utility", - "error": { - "fields": { - "Error 4xx": [ - { - "group": "Error 4xx", - "optional": false, - "field": "UserNotModeratorError", - "description": "

You weren't loggged in as a moderator before sending this request.

" - } - ] - } - } - }, - { - "type": "get", - "url": "?action=user-list[format=json]", - "title": "List all users", - "name": "UserList", - "group": "Utility", - "permission": [ - { - "name": "Anonymous", - "title": "Anybody may use this call.", - "description": "" - } - ], - "version": "0.0.0", - "filename": "./modules/page-user-list.php", - "groupTitle": "Utility" - } -] }); diff --git a/RestApiDocs/api_data.json b/RestApiDocs/api_data.json deleted file mode 100644 index b76e097..0000000 --- a/RestApiDocs/api_data.json +++ /dev/null @@ -1,1418 +0,0 @@ -[ - { - "type": "post", - "url": "?action=checklogin", - "title": "Perform a login", - "name": "CheckLogin", - "group": "Authorisation", - "permission": [ - { - "name": "Anonymous", - "title": "Anybody may use this call.", - "description": "" - } - ], - "parameter": { - "fields": { - "Parameter": [ - { - "group": "Parameter", - "type": "string", - "optional": false, - "field": "user", - "description": "

The user name to login with.

" - }, - { - "group": "Parameter", - "type": "string", - "optional": false, - "field": "password", - "description": "

The password to login with.

" - }, - { - "group": "Parameter", - "type": "string", - "optional": false, - "field": "returnto", - "description": "

The URL to redirect to upon a successful login.

" - } - ] - } - }, - "error": { - "fields": { - "Error 4xx": [ - { - "group": "Error 4xx", - "optional": false, - "field": "InvalidCredentialsError", - "description": "

The supplied credentials were invalid. Note that this error is actually a redirect to ?action=login&failed=yes (with the returnto parameter appended if you supplied one)

" - } - ] - } - }, - "version": "0.0.0", - "filename": "./modules/page-login.php", - "groupTitle": "Authorisation" - }, - { - "type": "get", - "url": "?action=login[&failed=yes][&returnto={someUrl}]", - "title": "Get the login page", - "name": "Login", - "group": "Authorisation", - "permission": [ - { - "name": "Anonymous", - "title": "Anybody may use this call.", - "description": "" - } - ], - "parameter": { - "fields": { - "Parameter": [ - { - "group": "Parameter", - "type": "string", - "optional": false, - "field": "failed", - "description": "

Setting to yes causes a login failure message to be displayed above the login form.

" - }, - { - "group": "Parameter", - "type": "string", - "optional": false, - "field": "returnto", - "description": "

Set to the url to redirect to upon a successful login.

" - } - ] - } - }, - "version": "0.0.0", - "filename": "./modules/page-login.php", - "groupTitle": "Authorisation" - }, - { - "type": "post", - "url": "?action=logout", - "title": "Logout", - "description": "

Logout. Make sure that your bot requests this URL when it is finished - this call not only clears your cookies but also clears the server's session file as well. Note that you can request this when you are already logged out and it will completely wipe your session on the server.

", - "name": "Logout", - "group": "Authorisation", - "permission": [ - { - "name": "Anonymous", - "title": "Anybody may use this call.", - "description": "" - } - ], - "version": "0.0.0", - "filename": "./modules/page-logout.php", - "groupTitle": "Authorisation" - }, - { - "type": "post", - "url": "?action=comment", - "title": "Comment on a page", - "name": "Comment", - "group": "Comment", - "permission": [ - { - "name": "User", - "title": "Only users loggged in may use this call.", - "description": "" - } - ], - "description": "

Posts a comment on a page, optionally in reply to another comment. Currently, comments must be made by a logged-in user.

", - "parameter": { - "fields": { - "Parameter": [ - { - "group": "Parameter", - "type": "string", - "optional": false, - "field": "message", - "description": "

The comment text. Supports the same syntax that the renderer of the main page supports. The default is extended markdown - see the help page of the specific wiki for more information.

" - }, - { - "group": "Parameter", - "type": "string", - "optional": false, - "field": "replyto", - "description": "

Optional. If specified the comment will be posted in reply to the comment with the specified id.

" - } - ] - } - }, - "error": { - "fields": { - "Error 4xx": [ - { - "group": "Error 4xx", - "optional": false, - "field": "CommentNotFound", - "description": "

The comment to reply to was not found.

" - } - ] - } - }, - "version": "0.0.0", - "filename": "./modules/feature-comments.php", - "groupTitle": "Comment" - }, - { - "type": "post", - "url": "?action=delete", - "title": "Delete a page", - "description": "

Delete a page and all its associated data.

", - "name": "DeletePage", - "group": "Page", - "permission": [ - { - "name": "Moderator", - "title": "Only users loggged with a moderator account may use this call.", - "description": "" - } - ], - "parameter": { - "fields": { - "Parameter": [ - { - "group": "Parameter", - "type": "string", - "optional": false, - "field": "page", - "description": "

The name of the page to delete.

" - }, - { - "group": "Parameter", - "type": "string", - "optional": false, - "field": "delete", - "description": "

Set to 'yes' to actually delete the page.

" - } - ] - } - }, - "error": { - "fields": { - "Error 4xx": [ - { - "group": "Error 4xx", - "optional": false, - "field": "PageNonExistentError", - "description": "

The specified page doesn't exist

" - }, - { - "group": "Error 4xx", - "optional": false, - "field": "UserNotModeratorError", - "description": "

You weren't loggged in as a moderator before sending this request.

" - } - ] - } - }, - "version": "0.0.0", - "filename": "./modules/page-delete.php", - "groupTitle": "Page" - }, - { - "type": "get", - "url": "?action=edit&page={pageName}[&newpage=yes]", - "title": "Get an editing page", - "description": "

Gets an editing page for a given page. If you don't have permission to edit the page in question, a view source pagee is returned instead.

", - "name": "EditPage", - "group": "Page", - "permission": [ - { - "name": "Anonymous", - "title": "Anybody may use this call.", - "description": "" - } - ], - "parameter": { - "fields": { - "Parameter": [ - { - "group": "Parameter", - "type": "string", - "optional": false, - "field": "newpage", - "description": "

Set to 'yes' if a new page is being created. Only affects a few bits of text here and there, and the HTTP response code recieved on success from the save action.

" - }, - { - "group": "Parameter", - "type": "string", - "optional": false, - "field": "page", - "description": "

The page to operate on.

" - } - ] - } - }, - "version": "0.0.0", - "filename": "./modules/page-edit.php", - "groupTitle": "Page" - }, - { - "type": "post", - "url": "?action=save&page={pageName}", - "title": "Save an edit to a page.", - "description": "

Saves an edit to a page. If an edit conflict is encountered, then a conflict resolution page is returned instead.

", - "name": "EditPage", - "group": "Page", - "permission": [ - { - "name": "Anonymous", - "title": "Anybody may use this call.", - "description": "" - } - ], - "parameter": { - "fields": { - "Parameter": [ - { - "group": "Parameter", - "type": "string", - "optional": false, - "field": "newpage", - "description": "

GET only. Set to 'yes' to indicate that this is a new page that is being saved. Only affects the HTTP response code you recieve upon success.

" - }, - { - "group": "Parameter", - "type": "string", - "optional": false, - "field": "content", - "description": "

POST only. The new content to save to the given filename.

" - }, - { - "group": "Parameter", - "type": "string", - "optional": false, - "field": "tags", - "description": "

POST only. A comma-separated list of tags to assign to the current page. Will replace the existing list of tags, if any are present.

" - }, - { - "group": "Parameter", - "type": "string", - "optional": false, - "field": "prev-content-hash", - "description": "

POST only. The hash of the original content before editing. If this hash is found to be different to a hash computed of the currentl saved content, a conflict resolution page will be returned instead of saving the provided content.

" - }, - { - "group": "Parameter", - "type": "string", - "optional": false, - "field": "page", - "description": "

The page to operate on.

" - } - ] - } - }, - "error": { - "fields": { - "Error 4xx": [ - { - "group": "Error 4xx", - "optional": false, - "field": "UnsufficientPermissionError", - "description": "

You don't currently have sufficient permissions to save an edit.

" - } - ] - } - }, - "version": "0.0.0", - "filename": "./modules/page-edit.php", - "groupTitle": "Page" - }, - { - "type": "get", - "url": "?action=history&page={pageName}", - "title": "Get a list of revisions for a page", - "name": "History", - "group": "Page", - "permission": [ - { - "name": "Anonymous", - "title": "Anybody may use this call.", - "description": "" - } - ], - "parameter": { - "fields": { - "Parameter": [ - { - "group": "Parameter", - "type": "string", - "optional": false, - "field": "page", - "description": "

The page name to return a revision list for.

" - } - ] - } - }, - "version": "0.0.0", - "filename": "./modules/feature-history.php", - "groupTitle": "Page" - }, - { - "type": "get", - "url": "?action=list", - "title": "List all pages", - "description": "

Gets a list of all the pages currently stored on the wiki.

", - "name": "ListPages", - "group": "Page", - "permission": [ - { - "name": "Anonymous", - "title": "Anybody may use this call.", - "description": "" - } - ], - "version": "0.0.0", - "filename": "./modules/page-list.php", - "groupTitle": "Page" - }, - { - "type": "get", - "url": "?action=move[&new_name={newPageName}]", - "title": "Move a page", - "name": "Move", - "group": "Page", - "permission": [ - { - "name": "Moderator", - "title": "Only users loggged with a moderator account may use this call.", - "description": "" - } - ], - "parameter": { - "fields": { - "Parameter": [ - { - "group": "Parameter", - "type": "string", - "optional": false, - "field": "new_name", - "description": "

The new name to move the page to. If not set a page will be returned containing a move page form.

" - } - ] - } - }, - "error": { - "fields": { - "Error 4xx": [ - { - "group": "Error 4xx", - "optional": false, - "field": "EditingDisabledError", - "description": "

Editing is disabled on this wiki, so pages can't be moved.

" - }, - { - "group": "Error 4xx", - "optional": false, - "field": "PageExistsAtDestinationError", - "description": "

A page already exists with the specified new name.

" - }, - { - "group": "Error 4xx", - "optional": false, - "field": "NonExistentPageError", - "description": "

The page you're trying to move doesn't exist in the first place.

" - }, - { - "group": "Error 4xx", - "optional": false, - "field": "PreExistingFileError", - "description": "

A pre-existing file on the server's file system was detected.

" - }, - { - "group": "Error 4xx", - "optional": false, - "field": "UserNotModeratorError", - "description": "

You weren't loggged in as a moderator before sending this request.

" - } - ] - } - }, - "version": "0.0.0", - "filename": "./modules/page-move.php", - "groupTitle": "Page" - }, - { - "type": "get", - "url": "?action=protect&page={pageName}", - "title": "Toggle the protection of a page.", - "name": "Protect", - "group": "Page", - "permission": [ - { - "name": "Moderator", - "title": "Only users loggged with a moderator account may use this call.", - "description": "" - } - ], - "parameter": { - "fields": { - "Parameter": [ - { - "group": "Parameter", - "type": "string", - "optional": false, - "field": "page", - "description": "

The page name to toggle the protection of.

" - } - ] - } - }, - "version": "0.0.0", - "filename": "./modules/action-protect.php", - "groupTitle": "Page" - }, - { - "type": "get", - "url": "?action=raw&page={pageName}", - "title": "Get the raw source code of a page", - "name": "RawSource", - "group": "Page", - "permission": [ - { - "name": "Anonymous", - "title": "Anybody may use this call.", - "description": "" - } - ], - "parameter": { - "fields": { - "Parameter": [ - { - "group": "Parameter", - "type": "string", - "optional": false, - "field": "page", - "description": "

The page to return the source of.

" - } - ] - } - }, - "version": "0.0.0", - "filename": "./modules/action-raw.php", - "groupTitle": "Page" - }, - { - "type": "get", - "url": "?action=random", - "title": "Redirects to a random page.", - "name": "RawSource", - "group": "Page", - "permission": [ - { - "name": "Anonymous", - "title": "Anybody may use this call.", - "description": "" - } - ], - "version": "0.0.0", - "filename": "./modules/action-random.php", - "groupTitle": "Page" - }, - { - "type": "get", - "url": "?action=raw&page={pageName}", - "title": "Get the raw source code of a page", - "name": "RawSource", - "group": "Page", - "permission": [ - { - "name": "Anonymous", - "title": "Anybody may use this call.", - "description": "" - } - ], - "parameter": { - "fields": { - "Parameter": [ - { - "group": "Parameter", - "type": "string", - "optional": false, - "field": "page", - "description": "

The page to return the source of.

" - } - ] - } - }, - "version": "0.0.0", - "filename": "./modules/api-status.php", - "groupTitle": "Page" - }, - { - "type": "get", - "url": "?action=view[&page={pageName}][&revision=rid][&printable=yes]", - "title": "View a page", - "name": "View", - "group": "Page", - "permission": [ - { - "name": "Anonymous", - "title": "Anybody may use this call.", - "description": "" - } - ], - "parameter": { - "fields": { - "Parameter": [ - { - "group": "Parameter", - "type": "number", - "optional": false, - "field": "revision", - "description": "

The revision number to display.

" - }, - { - "group": "Parameter", - "type": "string", - "optional": false, - "field": "mode", - "description": "

Optional. The display mode to use. Can hld the following values: 'normal' - The default. Sends a normal page. 'printable' - Sends a printable version of the page. 'contentonly' - Sends only the content of the page, not the extra stuff around it. 'parsedsourceonly' - Sends only the raw rendered source of the page, as it appears just after it has come out of the page parser. Useful for writing external tools (see also the raw action).

" - }, - { - "group": "Parameter", - "type": "string", - "optional": false, - "field": "page", - "description": "

The page to operate on.

" - } - ] - } - }, - "error": { - "fields": { - "Error 4xx": [ - { - "group": "Error 4xx", - "optional": false, - "field": "NonExistentPageError", - "description": "

The page doesn't exist and editing is disabled in the wiki's settings. If editing isn't disabled, you will be redirected to the edit page instead.

" - }, - { - "group": "Error 4xx", - "optional": false, - "field": "NonExistentRevisionError", - "description": "

The specified revision was not found.

" - } - ] - } - }, - "version": "0.0.0", - "filename": "./modules/page-view.php", - "groupTitle": "Page" - }, - { - "type": "get", - "url": "?action=suggest-pages", - "title": "Get search suggestions for a query", - "name": "OpenSearchDescription", - "group": "Search", - "permission": [ - { - "name": "Anonymous", - "title": "Anybody may use this call.", - "description": "" - } - ], - "parameter": { - "fields": { - "Parameter": [ - { - "group": "Parameter", - "type": "string", - "optional": false, - "field": "text", - "description": "

The search query string to get search suggestions for.

" - } - ] - } - }, - "version": "0.0.0", - "filename": "./modules/feature-search.php", - "groupTitle": "Search" - }, - { - "type": "get", - "url": "?action=opensearch-description", - "title": "Get the opensearch description file", - "name": "OpenSearchDescription", - "group": "Search", - "permission": [ - { - "name": "Anonymous", - "title": "Anybody may use this call.", - "description": "" - } - ], - "version": "0.0.0", - "filename": "./modules/feature-search.php", - "groupTitle": "Search" - }, - { - "type": "get", - "url": "?action=query-searchindex&query={text}", - "title": "Inspect the internals of the search results for a query", - "name": "Search", - "group": "Search", - "permission": [ - { - "name": "Anonymous", - "title": "Anybody may use this call.", - "description": "" - } - ], - "parameter": { - "fields": { - "Parameter": [ - { - "group": "Parameter", - "type": "string", - "optional": false, - "field": "query", - "description": "

The query string to search for.

" - } - ] - } - }, - "version": "0.0.0", - "filename": "./modules/feature-search.php", - "groupTitle": "Search" - }, - { - "type": "get", - "url": "?action=search&query={text}", - "title": "Search the wiki for a given query string", - "name": "Search", - "group": "Search", - "permission": [ - { - "name": "Anonymous", - "title": "Anybody may use this call.", - "description": "" - } - ], - "parameter": { - "fields": { - "Parameter": [ - { - "group": "Parameter", - "type": "string", - "optional": false, - "field": "query", - "description": "

The query string to search for.

" - } - ] - } - }, - "version": "0.0.0", - "filename": "./modules/feature-search.php", - "groupTitle": "Search" - }, - { - "type": "get", - "url": "?action=index&page={pageName}", - "title": "Get an index of words for a given page", - "name": "SearchIndex", - "group": "Search", - "permission": [ - { - "name": "Anonymous", - "title": "Anybody may use this call.", - "description": "" - } - ], - "parameter": { - "fields": { - "Parameter": [ - { - "group": "Parameter", - "type": "string", - "optional": false, - "field": "page", - "description": "

The page to generate a word index page.

" - } - ] - } - }, - "version": "0.0.0", - "filename": "./modules/feature-search.php", - "groupTitle": "Search" - }, - { - "type": "get", - "url": "?action=invindex-rebuild", - "title": "Rebuild the inverted search index from scratch", - "description": "

Causes the inverted search index to be completely rebuilt from scratch. Can take a while for large wikis!

", - "name": "SearchInvindexRebuild", - "group": "Search", - "permission": [ - { - "name": "Anonymous", - "title": "Anybody may use this call.", - "description": "" - } - ], - "version": "0.0.0", - "filename": "./modules/feature-search.php", - "groupTitle": "Search" - }, - { - "type": "get", - "url": "?action=idindex-show", - "title": "Show the id index", - "description": "

Outputs the id index. Useful if you need to verify that it's working as expected.

", - "name": "SearchShowIdIndex", - "group": "Search", - "permission": [ - { - "name": "Anonymous", - "title": "Anybody may use this call.", - "description": "" - } - ], - "version": "0.0.0", - "filename": "./modules/feature-search.php", - "groupTitle": "Search" - }, - { - "type": "post", - "url": "?action=change-password", - "title": "Change your password", - "name": "ChangePassword", - "group": "Settings", - "permission": [ - { - "name": "User", - "title": "Only users loggged in may use this call.", - "description": "" - } - ], - "parameter": { - "fields": { - "Parameter": [ - { - "group": "Parameter", - "type": "string", - "optional": false, - "field": "current-pass", - "description": "

Your current password.

" - }, - { - "group": "Parameter", - "type": "string", - "optional": false, - "field": "new-pass", - "description": "

Your new password.

" - }, - { - "group": "Parameter", - "type": "string", - "optional": false, - "field": "new-pass-confirm", - "description": "

Your new password again, to make sure you've typed it correctly.

" - } - ] - } - }, - "error": { - "fields": { - "Error 4xx": [ - { - "group": "Error 4xx", - "optional": false, - "field": "PasswordMismatchError", - "description": "

The new password fields don't match.

" - } - ] - } - }, - "version": "0.0.0", - "filename": "./modules/feature-user-preferences.php", - "groupTitle": "Settings" - }, - { - "type": "get", - "url": "?action=user-preferences", - "title": "Get a user preferences configuration page", - "name": "UserPreferences", - "group": "Settings", - "permission": [ - { - "name": "User", - "title": "Only users loggged in may use this call.", - "description": "" - } - ], - "version": "0.0.0", - "filename": "./modules/feature-user-preferences.php", - "groupTitle": "Settings" - }, - { - "type": "post", - "url": "?action=save-preferences", - "title": "Save your user preferences", - "name": "UserPreferencesSave", - "group": "Settings", - "permission": [ - { - "name": "User", - "title": "Only users loggged in may use this call.", - "description": "" - } - ], - "version": "0.0.0", - "filename": "./modules/feature-user-preferences.php", - "groupTitle": "Settings" - }, - { - "type": "get", - "url": "?action=recentchanges", - "title": "Get a list of recent changes", - "name": "RecentChanges", - "group": "Stats", - "permission": [ - { - "name": "Anonymous", - "title": "Anybody may use this call.", - "description": "" - } - ], - "version": "0.0.0", - "filename": "./modules/feature-recent-changes.php", - "groupTitle": "Stats" - }, - { - "type": "get", - "url": "?action=avatar&user={username}[&size={size}]", - "title": "Get a user's avatar", - "name": "Avatar", - "group": "Upload", - "permission": [ - { - "name": "Anonymous", - "title": "Anybody may use this call.", - "description": "" - } - ], - "parameter": { - "fields": { - "Parameter": [ - { - "group": "Parameter", - "type": "string", - "optional": false, - "field": "user", - "description": "

The username to fetch the avatar for

" - }, - { - "group": "Parameter", - "type": "string", - "optional": false, - "field": "size", - "description": "

The preferred size of the avatar

" - } - ] - } - }, - "version": "0.0.0", - "filename": "./modules/feature-user-preferences.php", - "groupTitle": "Upload" - }, - { - "type": "get", - "url": "?action=preview&page={pageName}[&size={someSize}]", - "title": "Get a preview of a file", - "name": "PreviewFile", - "group": "Upload", - "permission": [ - { - "name": "Anonymous", - "title": "Anybody may use this call.", - "description": "" - } - ], - "parameter": { - "fields": { - "Parameter": [ - { - "group": "Parameter", - "type": "string", - "optional": false, - "field": "page", - "description": "

The name of the file to preview.

" - }, - { - "group": "Parameter", - "type": "number", - "optional": false, - "field": "size", - "description": "

Optional. The size fo the resulting preview. Will be clamped to fit within the bounds specified in the wiki's settings. May also be set to the keyword 'original', which will cause the original file to be returned with it's appropriate mime type instead.

" - } - ] - } - }, - "error": { - "fields": { - "Error 4xx": [ - { - "group": "Error 4xx", - "optional": false, - "field": "PreviewNoFileError", - "description": "

No file was found associated with the specified page.

" - }, - { - "group": "Error 4xx", - "optional": false, - "field": "PreviewUnknownFileTypeError", - "description": "

Pepperminty Wiki was unable to generate a preview for the requested file's type.

" - } - ] - } - }, - "version": "0.0.0", - "filename": "./modules/feature-upload.php", - "groupTitle": "Upload" - }, - { - "type": "post", - "url": "?action=upload", - "title": "Upload a file", - "name": "UploadFile", - "group": "Upload", - "permission": [ - { - "name": "User", - "title": "Only users loggged in may use this call.", - "description": "" - } - ], - "parameter": { - "fields": { - "Parameter": [ - { - "group": "Parameter", - "type": "string", - "optional": false, - "field": "name", - "description": "

The name of the file to upload.

" - }, - { - "group": "Parameter", - "type": "string", - "optional": false, - "field": "description", - "description": "

A description of the file.

" - }, - { - "group": "Parameter", - "type": "file", - "optional": false, - "field": "file", - "description": "

The file to upload.

" - }, - { - "group": "Parameter", - "type": "boolean", - "optional": false, - "field": "avatar", - "description": "

Whether this upload should be uploaded as the current user's avatar. If specified, any filenames provided will be ignored.

" - } - ] - } - }, - "error": { - "fields": { - "Error 4xx": [ - { - "group": "Error 4xx", - "optional": false, - "field": "UploadsDisabledError", - "description": "

Uploads are currently disabled in the wiki's settings.

" - }, - { - "group": "Error 4xx", - "optional": false, - "field": "UnknownFileTypeError", - "description": "

The type of the file you uploaded is not currently allowed in the wiki's settings.

" - }, - { - "group": "Error 4xx", - "optional": false, - "field": "ImageDimensionsFiledError", - "description": "

PeppermintyWiki couldn't obtain the dimensions of the image you uploaded.

" - }, - { - "group": "Error 4xx", - "optional": false, - "field": "DangerousFileError", - "description": "

The file uploaded appears to be dangerous.

" - }, - { - "group": "Error 4xx", - "optional": false, - "field": "DuplicateFileError", - "description": "

The filename specified is a duplicate of a file that already exists.

" - }, - { - "group": "Error 4xx", - "optional": false, - "field": "FileTamperedError", - "description": "

Pepperminty Wiki couldn't verify that the file wasn't tampered with during theupload process.

" - }, - { - "group": "Error 4xx", - "optional": false, - "field": "UserNotLoggedInError", - "description": "

You didn't log in before sending this request.

" - } - ] - } - }, - "version": "0.0.0", - "filename": "./modules/feature-upload.php", - "groupTitle": "Upload" - }, - { - "type": "get", - "url": "?action=upload[&avatar=yes]", - "title": "Get a page to let you upload a file.", - "name": "UploadFilePage", - "group": "Upload", - "permission": [ - { - "name": "User", - "title": "Only users loggged in may use this call.", - "description": "" - } - ], - "parameter": { - "fields": { - "Parameter": [ - { - "group": "Parameter", - "type": "boolean", - "optional": false, - "field": "avatar", - "description": "

Optional. If true then a special page to upload your avatar is displayed instead.

" - } - ] - } - }, - "version": "0.0.0", - "filename": "./modules/feature-upload.php", - "groupTitle": "Upload" - }, - { - "type": "get", - "url": "?action=configure", - "title": "Get a page to change the global wiki settings", - "name": "ConfigureSettings", - "group": "Utility", - "permission": [ - { - "name": "Moderator", - "title": "Only users loggged with a moderator account may use this call.", - "description": "" - } - ], - "version": "0.0.0", - "filename": "./modules/feature-guiconfig.php", - "groupTitle": "Utility" - }, - { - "type": "post", - "url": "?action=configure-save", - "title": "Save changes to the global wiki settings", - "name": "ConfigureSettings", - "group": "Utility", - "permission": [ - { - "name": "Moderator", - "title": "Only users loggged with a moderator account may use this call.", - "description": "" - } - ], - "version": "0.0.0", - "filename": "./modules/feature-guiconfig.php", - "groupTitle": "Utility" - }, - { - "type": "get", - "url": "?action=credits", - "title": "Get the credits page", - "name": "Credits", - "group": "Utility", - "permission": [ - { - "name": "Anonymous", - "title": "Anybody may use this call.", - "description": "" - } - ], - "version": "0.0.0", - "filename": "./modules/page-credits.php", - "groupTitle": "Utility" - }, - { - "type": "get", - "url": "?action=debug", - "title": "Get a debug dump", - "name": "Debug", - "group": "Utility", - "permission": [ - { - "name": "Moderator", - "title": "Only users loggged with a moderator account may use this call.", - "description": "" - } - ], - "version": "0.0.0", - "filename": "./modules/page-debug-info.php", - "groupTitle": "Utility", - "error": { - "fields": { - "Error 4xx": [ - { - "group": "Error 4xx", - "optional": false, - "field": "UserNotModeratorError", - "description": "

You weren't loggged in as a moderator before sending this request.

" - } - ] - } - } - }, - { - "type": "get", - "url": "?action=export", - "title": "Export the all the wiki's content", - "description": "

Export all the wiki's content. Please ask for permission before making a request to this URI. Note that some wikis may only allow moderators to export content.

", - "name": "Export", - "group": "Utility", - "permission": [ - { - "name": "Anonymous", - "title": "Anybody may use this call.", - "description": "" - } - ], - "error": { - "fields": { - "Error 4xx": [ - { - "group": "Error 4xx", - "optional": false, - "field": "InsufficientExportPermissionsError", - "description": "

The wiki has the export_allow_only_admins option turned on, and you aren't logged into a moderator account.

" - }, - { - "group": "Error 4xx", - "optional": false, - "field": "CouldntOpenTempFileError", - "description": "

Pepperminty Wiki couldn't open a temporary file to send the compressed archive to.

" - }, - { - "group": "Error 4xx", - "optional": false, - "field": "CouldntCloseTempFileError", - "description": "

Pepperminty Wiki couldn't close the temporary file to finish creating the zip archive ready for downloading.

" - } - ] - } - }, - "version": "0.0.0", - "filename": "./modules/page-export.php", - "groupTitle": "Utility" - }, - { - "type": "get", - "url": "?action=hash&string={text}", - "title": "Hash a password", - "name": "Hash", - "group": "Utility", - "permission": [ - { - "name": "Anonymous", - "title": "Anybody may use this call.", - "description": "" - } - ], - "parameter": { - "fields": { - "Parameter": [ - { - "group": "Parameter", - "type": "string", - "optional": false, - "field": "string", - "description": "

The string to hash.

" - }, - { - "group": "Parameter", - "type": "boolean", - "optional": false, - "field": "raw", - "description": "

Whether to return the hashed password as a raw string instead of as part of an HTML page.

" - } - ] - } - }, - "error": { - "fields": { - "Error 4xx": [ - { - "group": "Error 4xx", - "optional": false, - "field": "ParamNotFound", - "description": "

The string parameter was not specified.

" - } - ] - } - }, - "version": "0.0.0", - "filename": "./modules/action-hash.php", - "groupTitle": "Utility" - }, - { - "type": "get", - "url": "?action=help[&dev=yes]", - "title": "Get a help page", - "description": "

Get a customised help page. This page will be slightly different for every wiki, depending on their name, settings, and installed modules.

", - "name": "Help", - "group": "Utility", - "permission": [ - { - "name": "Anonymous", - "title": "Anybody may use this call.", - "description": "" - } - ], - "parameter": { - "fields": { - "Parameter": [ - { - "group": "Parameter", - "type": "string", - "optional": false, - "field": "dev", - "description": "

Set to 'yes' to get a developer help page instead. The developer help page gives some general information about which modules and help page sections are registered, and other various (non-sensitive) settings.

" - } - ] - } - }, - "version": "0.0.0", - "filename": "./modules/page-help.php", - "groupTitle": "Utility" - }, - { - "type": "get", - "url": "?action=list-tags[&tag=]", - "title": "Get a list of tags or pages with a certain tag", - "description": "

Gets a list of all tags on the wiki. Adding the tag parameter causes a list of pages with the given tag to be returned instead.

", - "name": "ListTags", - "group": "Utility", - "permission": [ - { - "name": "Anonymous", - "title": "Anybody may use this call.", - "description": "" - } - ], - "parameter": { - "fields": { - "Parameter": [ - { - "group": "Parameter", - "type": "string", - "optional": false, - "field": "tag", - "description": "

Optional. If provided a list of all the pages with that tag is returned instead.

" - } - ] - } - }, - "version": "0.0.0", - "filename": "./modules/page-list.php", - "groupTitle": "Utility" - }, - { - "type": "get", - "url": "?action=update[do=yes]", - "title": "Update the wiki", - "description": "

Update the wiki by downloading a new version of Pepperminty Wiki from the URL specified in the settings. Note that unless you change the url from it's default, all custom modules installed will be removed. Note also that this plugin is currently out of date. Use with extreme caution!

", - "name": "Update", - "group": "Utility", - "permission": [ - { - "name": "Moderator", - "title": "Only users loggged with a moderator account may use this call.", - "description": "" - } - ], - "parameter": { - "fields": { - "Parameter": [ - { - "group": "Parameter", - "type": "string", - "optional": false, - "field": "do", - "description": "

Set to 'yes' to actually do the upgrade. Omission causes a page asking whether an update is desired instead.

" - }, - { - "group": "Parameter", - "type": "string", - "optional": false, - "field": "secret", - "description": "

The wiki's secret string that's stored in the settings.

" - }, - { - "group": "Parameter", - "optional": false, - "field": "InvalidSecretError", - "description": "

The supplied secret doesn't match up with the secret stored in the wiki's settings.

" - } - ] - } - }, - "version": "0.0.0", - "filename": "./modules/page-update.php", - "groupTitle": "Utility", - "error": { - "fields": { - "Error 4xx": [ - { - "group": "Error 4xx", - "optional": false, - "field": "UserNotModeratorError", - "description": "

You weren't loggged in as a moderator before sending this request.

" - } - ] - } - } - }, - { - "type": "get", - "url": "?action=user-list[format=json]", - "title": "List all users", - "name": "UserList", - "group": "Utility", - "permission": [ - { - "name": "Anonymous", - "title": "Anybody may use this call.", - "description": "" - } - ], - "version": "0.0.0", - "filename": "./modules/page-user-list.php", - "groupTitle": "Utility" - } -] diff --git a/core.php b/core.php index cad4e91..5a29d84 100644 --- a/core.php +++ b/core.php @@ -1123,6 +1123,13 @@ class page_renderer /** * Registers a function as a part post processor. + * This function's use is more complicated to explain. Pepperminty Wiki + * renders pages with a very simple templating system. For example, in the + * template a page's content is denoted by `{content}`. A function + * registered here will be passed all the components of a page _just_ + * before they are dropped into the template. Note that the function you + * pass in here should take a *reference* to the components, as the return + * value of the function passed is discarded. * @package core * @param function $function The part preprocessor to register. */ diff --git a/docs/ModuleApi/.htaccess b/docs/ModuleApi/.htaccess new file mode 100644 index 0000000..7b01f9b --- /dev/null +++ b/docs/ModuleApi/.htaccess @@ -0,0 +1,5 @@ +# Fixes a vulnerability in CentOS: http://stackoverflow.com/questions/20533279/prevent-php-from-parsing-non-php-files-such-as-somefile-php-txt + + RemoveHandler .php + ForceType text/plain + \ No newline at end of file diff --git a/docs/ModuleApi/classes/PeppermintParsedown.html b/docs/ModuleApi/classes/PeppermintParsedown.html new file mode 100644 index 0000000..efd2f0c --- /dev/null +++ b/docs/ModuleApi/classes/PeppermintParsedown.html @@ -0,0 +1,376 @@ + + + + + + Pepperminty Wiki Module API + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ +
+
+
+
+
+ + + +

\PeppermintParsedown

+

+ + + +
+

Summary

+
+
+ Methods +
+
+ Properties +
+
+ Constants +
+
+
+
+ __construct()
+ setInternalLinkBase()
+
+
+ No public properties found +
+
+ No constants found +
+
+
+
+ No protected methods found +
+
+ No protected properties found +
+
+ N/A +
+
+
+
+ No private methods found +
+
+ No private properties found +
+
+ N/A +
+
+
+
+ +
+ + + + +
+

Methods

+ +
+ +
+
+ +
+

__construct()

+ +
__construct() 
+

+ + + + + +
+
+ +
+ +
+
+ +
+

setInternalLinkBase()

+ +
setInternalLinkBase(string  $url) 
+

Sets the base url to be used for internal links. '%s' will be replaced +with a URL encoded version of the page name.

+ + +

Parameters

+ + + + + + +
string$url

The url to use when parsing internal links.

+ + + +
+
+ +
+ +
+
+ + + + +
+ + + diff --git a/docs/ModuleApi/classes/Slimdown.html b/docs/ModuleApi/classes/Slimdown.html new file mode 100644 index 0000000..96f7d57 --- /dev/null +++ b/docs/ModuleApi/classes/Slimdown.html @@ -0,0 +1,425 @@ + + + + + + Pepperminty Wiki Module API + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ +
+
+
+
+
+ + + +

\Slimdown

+

Modified by Starbeamrainbowlabs (starbeamrainbowlabs)

+

Changed bold to use single asterisks +Changed italics to use single underscores +Added one to add the heading levels (no

tags allowed) +Added wiki style internal link parsing +Added wiki style internal link parsing with display text +Added image support

+ + +
+

Summary

+
+
+ Methods +
+
+ Properties +
+
+ Constants +
+
+
+
+ add_rule()
+ render()
+
+
+ $rules
+
+
+ No constants found +
+
+
+
+ No protected methods found +
+
+ No protected properties found +
+
+ N/A +
+
+
+
+ No private methods found +
+
+ No private properties found +
+
+ N/A +
+
+
+

+ +
+ + + +
+
+

Properties

+
+ +
+ +
+
+ +
+

$rules

+
$rules : 
+

+ + +

Type

+ +
+
+ +
+ + + +
+

Methods

+ +
+ +
+
+ +
+

add_rule()

+ +
add_rule(  $regex,   $replacement) 
+

Add a rule.

+ + +

Parameters

+ + + + + + + + + + + +
$regex
$replacement
+ + + +
+
+ +
+ +
+
+ +
+

render()

+ +
render(  $text) 
+

Render some Markdown into HTML.

+ + +

Parameters

+ + + + + + +
$text
+ + + +
+
+ +
+ +
+
+ + + + +
+ + + diff --git a/docs/ModuleApi/classes/ids.html b/docs/ModuleApi/classes/ids.html new file mode 100644 index 0000000..d78f542 --- /dev/null +++ b/docs/ModuleApi/classes/ids.html @@ -0,0 +1,536 @@ + + + + + + Pepperminty Wiki Module API + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ +
+
+
+
+
+ + + +

\ids

+

Provides an interface to interact with page ids.

+ + + +
+

Summary

+
+
+ Methods +
+
+ Properties +
+
+ Constants +
+
+
+
+ getid()
+ getpagename()
+ movepagename()
+ deletepagename()
+ clear()
+
+
+ No public properties found +
+
+ No constants found +
+
+
+
+ No protected methods found +
+
+ No protected properties found +
+
+ N/A +
+
+
+
+ No private methods found +
+
+ No private properties found +
+
+ N/A +
+
+
+
+ +
+ + + + +
+

Methods

+ +
+ +
+
+ +
+

getid()

+ +
getid(string  $pagename) : integer
+

Gets the page id associated with the given page name.

+

If it doesn't exist in the id index, it will be added.

+ +

Parameters

+ + + + + + +
string$pagename

The name of the page to fetch the id for.

+ + +

Returns

+ integer + —

The id for the specified page name.

+ +
+
+ +
+ +
+
+ +
+

getpagename()

+ +
getpagename(integer  $id) : string
+

Gets the page name associated with the given page id.

+

Be warned that if the id index is cleared (e.g. when the search index is +rebuilt from scratch), the id associated with a page name may change!

+ +

Parameters

+ + + + + + +
integer$id

The id to fetch the page name for.

+ + +

Returns

+ string + —

The page name currently associated with the specified id.

+ +
+
+ +
+ +
+
+ +
+

movepagename()

+ +
movepagename(string  $oldpagename, string  $newpagename) 
+

Moves a page in the id index from $oldpagename to $newpagename.

+

Note that this function doesn't perform any special checks to make sure +that the destination name doesn't already exist.

+ +

Parameters

+ + + + + + + + + + + +
string$oldpagename

The old page name to move.

string$newpagename

The new pagee name to move the old page name to.

+ + + +
+
+ +
+ +
+
+ +
+

deletepagename()

+ +
deletepagename(string  $pagename) 
+

Removes the given page name from the id index.

+

Note that this function doesn't handle multiple entries with the same +name. Also note that it may get re-added during a search reindex if the +page still exists.

+ +

Parameters

+ + + + + + +
string$pagename

The page name to delete from the id index.

+ + + +
+
+ +
+ +
+
+ +
+

clear()

+ +
clear() 
+

Clears the id index completely.

+

Will break the inverted search index! Make sure you rebuild the search +index (if the search module is installed, of course) if you want search +to still work. Of course, note that will re-add all the pages to the id +index.

+ + + + +
+
+ +
+ +
+
+ + + + +
+ + + diff --git a/docs/ModuleApi/classes/page_renderer.html b/docs/ModuleApi/classes/page_renderer.html new file mode 100644 index 0000000..efe9a95 --- /dev/null +++ b/docs/ModuleApi/classes/page_renderer.html @@ -0,0 +1,948 @@ + + + + + + Pepperminty Wiki Module API + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ +
+
+
+
+
+ + + +

\page_renderer

+

Renders the HTML page that is sent to the client.

+ + + +
+

Summary

+
+
+ Methods +
+
+ Properties +
+
+ Constants +
+
+
+
+ register_part_preprocessor()
+ render()
+ render_main()
+ render_minimal()
+ get_header_html()
+ get_css_as_html()
+ AddJSLink()
+ AddJSSnippet()
+ render_navigation_bar()
+ render_username()
+ generate_all_pages_datalist()
+
+
+ $html_template
+ $main_content_template
+ $minimal_content_template
+ $nav_divider
+
+
+ No constants found +
+
+
+
+ No protected methods found +
+
+ No protected properties found +
+
+ N/A +
+
+
+
+ No private methods found +
+
+ No private properties found +
+
+ N/A +
+
+
+
+ +
+ + + +
+
+

Properties

+
+ +
+ +
+
+ +
+

$html_template

+
$html_template : string
+

The root HTML template that all pages are built from.

+ + +

Type

+ string +
+
+ +
+ +
+
+ +
+

$main_content_template

+
$main_content_template : string
+

The main content template that is used to render normal wiki pages.

+ + +

Type

+ string +
+
+ +
+ +
+
+ +
+

$minimal_content_template

+
$minimal_content_template : string
+

A specially minified content template that doesn't include the navbar and +other elements not suiltable for printing.

+ + +

Type

+ string +
+
+ +
+ +
+
+ +
+

$nav_divider

+
$nav_divider : string
+

The navigation bar divider.

+ + +

Type

+ string +
+
+ +
+ + + +
+

Methods

+ +
+ +
+
+ +
+

register_part_preprocessor()

+ +
register_part_preprocessor(\function  $function) 
+

Registers a function as a part post processor.

+ + +

Parameters

+ + + + + + +
\function$function

The part preprocessor to register.

+ + + +
+
+ +
+ +
+
+ +
+

render()

+ +
render(string  $title, string  $content, boolean  $body_template = false) : string
+

Renders a HTML page with the content specified.

+ + +

Parameters

+ + + + + + + + + + + + + + + + +
string$title

The title of the page.

string$content

The (HTML) content of the page.

boolean$body_template

The HTML content template to use.

+ + +

Returns

+ string + —

The rendered HTML, ready to send to the client :-)

+ +
+
+ +
+ +
+
+ +
+

render_main()

+ +
render_main(string  $title, string  $content) : string
+

Renders a normal HTML page.

+ + +

Parameters

+ + + + + + + + + + + +
string$title

The title of the page.

string$content

The content of the page.

+ + +

Returns

+ string + —

The rendered page.

+ +
+
+ +
+ +
+
+ +
+

render_minimal()

+ +
render_minimal(string  $title, string  $content) : string
+

Renders a minimal HTML page. Useful for printable pages.

+ + +

Parameters

+ + + + + + + + + + + +
string$title

The title of the page.

string$content

The content of the page.

+ + +

Returns

+ string + —

The rendered page.

+ +
+
+ +
+ +
+
+ +
+

get_header_html()

+ +
get_header_html() : string
+

Renders the header HTML.

+ + + + +

Returns

+ string + —

The rendered HTML that goes in the header.

+ +
+
+ +
+ +
+
+ +
+

get_css_as_html()

+ +
get_css_as_html() : string
+

Renders all the CSS as HTML.

+ + + + +

Returns

+ string + —

The css as HTML, ready to be included in the HTML header.

+ +
+
+ +
+ +
+
+ +
+

AddJSLink()

+ +
AddJSLink(string  $scriptUrl) 
+

Adds the specified url to a javascript file as a reference to the page.

+ + +

Parameters

+ + + + + + +
string$scriptUrl

The url of the javascript file to reference.

+ + + +
+
+ +
+ +
+
+ +
+

AddJSSnippet()

+ +
AddJSSnippet(string  $script) 
+

Adds a javascript snippet to the page.

+ + +

Parameters

+ + + + + + +
string$script

The snippet of javascript to add.

+ + + +
+
+ +
+ +
+
+ +
+

render_navigation_bar()

+ +
render_navigation_bar(array  $nav_links, array  $nav_links_extra, string  $class = "") 
+

Renders a navigation bar from an array of links. See +$settings->nav_links for format information.

+ + +

Parameters

+ + + + + + + + + + + + + + + + +
array$nav_links

The links to add to the navigation bar.

array$nav_links_extra

The extra nav links to add to +the "More..." menu.

string$class

The class(es) to assign to the rendered +navigation bar.

+ + + +
+
+ +
+ +
+
+ +
+

render_username()

+ +
render_username(string  $name) : string
+

Renders a username for inclusion in a page.

+ + +

Parameters

+ + + + + + +
string$name

The username to render.

+ + +

Returns

+ string + —

The username rendered in HTML.

+ +
+
+ +
+ +
+
+ +
+

generate_all_pages_datalist()

+ +
generate_all_pages_datalist() : string
+

Renders the datalist for the search box as HTML.

+ + + + +

Returns

+ string + —

The search box datalist as HTML.

+ +
+
+ +
+ +
+
+ + + + +
+ + + diff --git a/docs/ModuleApi/classes/search.html b/docs/ModuleApi/classes/search.html new file mode 100644 index 0000000..4d0b6e7 --- /dev/null +++ b/docs/ModuleApi/classes/search.html @@ -0,0 +1,902 @@ + + + + + + Pepperminty Wiki Module API + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ +
+
+
+
+
+ + + +

\search

+

+ + + +
+

Summary

+
+
+ Methods +
+
+ Properties +
+
+ Constants +
+
+
+
+ index()
+ tokenize()
+ strip_markup()
+ rebuild_invindex()
+ sort_index()
+ compare_indexes()
+ load_invindex()
+ measure_invindex_load_time()
+ merge_into_invindex()
+ delete_entry()
+ save_invindex()
+ query_invindex()
+ extract_context()
+ highlight_context()
+
+
+ $stop_words
+
+
+ No constants found +
+
+
+
+ No protected methods found +
+
+ No protected properties found +
+
+ N/A +
+
+
+
+ No private methods found +
+
+ No private properties found +
+
+ N/A +
+
+
+
+ +
+ + + +
+
+

Properties

+
+ +
+ +
+
+ +
+

$stop_words

+
$stop_words : 
+

+ + +

Type

+ +
+
+ +
+ + + +
+

Methods

+ +
+ +
+
+ +
+

index()

+ +
index(  $source) 
+

+ + +

Parameters

+ + + + + + +
$source
+ + + +
+
+ +
+ +
+
+ +
+

tokenize()

+ +
tokenize(  $source) 
+

+ + +

Parameters

+ + + + + + +
$source
+ + + +
+
+ +
+ +
+
+ +
+

strip_markup()

+ +
strip_markup(  $source) 
+

+ + +

Parameters

+ + + + + + +
$source
+ + + +
+
+ +
+ +
+
+ +
+

rebuild_invindex()

+ +
rebuild_invindex(  $output = true) 
+

+ + +

Parameters

+ + + + + + +
$output
+ + + +
+
+ +
+ +
+
+ +
+

sort_index()

+ +
sort_index(  $index) 
+

+ + +

Parameters

+ + + + + + +
$index
+ + + +
+
+ +
+ +
+
+ +
+

compare_indexes()

+ +
compare_indexes(  $oldindex,   $newindex,   $changed,   $removed) 
+

+ + +

Parameters

+ + + + + + + + + + + + + + + + + + + + + +
$oldindex
$newindex
$changed
$removed
+ + + +
+
+ +
+ +
+
+ +
+

load_invindex()

+ +
load_invindex(  $invindex_filename) 
+

+ + +

Parameters

+ + + + + + +
$invindex_filename
+ + + +
+
+ +
+ +
+
+ +
+

measure_invindex_load_time()

+ +
measure_invindex_load_time(  $invindex_filename) 
+

+ + +

Parameters

+ + + + + + +
$invindex_filename
+ + + +
+
+ +
+ +
+
+ +
+

merge_into_invindex()

+ +
merge_into_invindex(  $invindex,   $pageid,   $index,   $removals = array()) 
+

+ + +

Parameters

+ + + + + + + + + + + + + + + + + + + + + +
$invindex
$pageid
$index
$removals
+ + + +
+
+ +
+ +
+
+ +
+

delete_entry()

+ +
delete_entry(  $invindex, \number  $pageid) 
+

Deletes the given pageid from the given pageindex.

+ + +

Parameters

+ + + + + + + + + + + +
$invindex
\number$pageid

The pageid to remove.

+ + + +
+
+ +
+ +
+
+ +
+

save_invindex()

+ +
save_invindex(  $filename,   $invindex) 
+

+ + +

Parameters

+ + + + + + + + + + + +
$filename
$invindex
+ + + +
+
+ +
+ +
+
+ +
+

query_invindex()

+ +
query_invindex(  $query,   $invindex) 
+

+ + +

Parameters

+ + + + + + + + + + + +
$query
$invindex
+ + + +
+
+ +
+ +
+
+ +
+

extract_context()

+ +
extract_context(  $query,   $source) 
+

+ + +

Parameters

+ + + + + + + + + + + +
$query
$source
+ + + +
+
+ +
+ +
+
+ +
+

highlight_context()

+ +
highlight_context(  $query,   $context) 
+

+ + +

Parameters

+ + + + + + + + + + + +
$query
$context
+ + + +
+
+ +
+ +
+
+ + + + +
+ + + diff --git a/docs/ModuleApi/css/bootstrap-combined.no-icons.min.css b/docs/ModuleApi/css/bootstrap-combined.no-icons.min.css new file mode 100644 index 0000000..5ab243e --- /dev/null +++ b/docs/ModuleApi/css/bootstrap-combined.no-icons.min.css @@ -0,0 +1,732 @@ +/*! + * Bootstrap v2.3.2 + * + * Copyright 2012 Twitter, Inc + * Licensed under the Apache License v2.0 + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Designed and built with all the love in the world @twitter by @mdo and @fat. + */ +.clearfix{*zoom:1;}.clearfix:before,.clearfix:after{display:table;content:"";line-height:0;} +.clearfix:after{clear:both;} +.hide-text{font:0/0 a;color:transparent;text-shadow:none;background-color:transparent;border:0;} +.input-block-level{display:block;width:100%;min-height:30px;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box;} +article,aside,details,figcaption,figure,footer,header,hgroup,nav,section{display:block;} +audio,canvas,video{display:inline-block;*display:inline;*zoom:1;} +audio:not([controls]){display:none;} +html{font-size:100%;-webkit-text-size-adjust:100%;-ms-text-size-adjust:100%;} +a:focus{outline:thin dotted #333;outline:5px auto -webkit-focus-ring-color;outline-offset:-2px;} +a:hover,a:active{outline:0;} +sub,sup{position:relative;font-size:75%;line-height:0;vertical-align:baseline;} +sup{top:-0.5em;} +sub{bottom:-0.25em;} +img{max-width:100%;width:auto\9;height:auto;vertical-align:middle;border:0;-ms-interpolation-mode:bicubic;} +#map_canvas img,.google-maps img{max-width:none;} +button,input,select,textarea{margin:0;font-size:100%;vertical-align:middle;} +button,input{*overflow:visible;line-height:normal;} +button::-moz-focus-inner,input::-moz-focus-inner{padding:0;border:0;} +button,html input[type="button"],input[type="reset"],input[type="submit"]{-webkit-appearance:button;cursor:pointer;} +label,select,button,input[type="button"],input[type="reset"],input[type="submit"],input[type="radio"],input[type="checkbox"]{cursor:pointer;} +input[type="search"]{-webkit-box-sizing:content-box;-moz-box-sizing:content-box;box-sizing:content-box;-webkit-appearance:textfield;} +input[type="search"]::-webkit-search-decoration,input[type="search"]::-webkit-search-cancel-button{-webkit-appearance:none;} +textarea{overflow:auto;vertical-align:top;} +@media print{*{text-shadow:none !important;color:#000 !important;background:transparent !important;box-shadow:none !important;} a,a:visited{text-decoration:underline;} a[href]:after{content:" (" attr(href) ")";} abbr[title]:after{content:" (" attr(title) ")";} .ir a:after,a[href^="javascript:"]:after,a[href^="#"]:after{content:"";} pre,blockquote{border:1px solid #999;page-break-inside:avoid;} thead{display:table-header-group;} tr,img{page-break-inside:avoid;} img{max-width:100% !important;} @page {margin:0.5cm;}p,h2,h3{orphans:3;widows:3;} h2,h3{page-break-after:avoid;}}body{margin:0;font-family:"Helvetica Neue",Helvetica,Arial,sans-serif;font-size:14px;line-height:20px;color:#333333;background-color:#ffffff;} +a{color:#0088cc;text-decoration:none;} +a:hover,a:focus{color:#005580;text-decoration:underline;} +.img-rounded{-webkit-border-radius:6px;-moz-border-radius:6px;border-radius:6px;} +.img-polaroid{padding:4px;background-color:#fff;border:1px solid #ccc;border:1px solid rgba(0, 0, 0, 0.2);-webkit-box-shadow:0 1px 3px rgba(0, 0, 0, 0.1);-moz-box-shadow:0 1px 3px rgba(0, 0, 0, 0.1);box-shadow:0 1px 3px rgba(0, 0, 0, 0.1);} +.img-circle{-webkit-border-radius:500px;-moz-border-radius:500px;border-radius:500px;} +.row{margin-left:-20px;*zoom:1;}.row:before,.row:after{display:table;content:"";line-height:0;} +.row:after{clear:both;} +[class*="span"]{float:left;min-height:1px;margin-left:20px;} +.container,.navbar-static-top .container,.navbar-fixed-top .container,.navbar-fixed-bottom .container{width:940px;} +.span12{width:940px;} +.span11{width:860px;} +.span10{width:780px;} +.span9{width:700px;} +.span8{width:620px;} +.span7{width:540px;} +.span6{width:460px;} +.span5{width:380px;} +.span4{width:300px;} +.span3{width:220px;} +.span2{width:140px;} +.span1{width:60px;} +.offset12{margin-left:980px;} +.offset11{margin-left:900px;} +.offset10{margin-left:820px;} +.offset9{margin-left:740px;} +.offset8{margin-left:660px;} +.offset7{margin-left:580px;} +.offset6{margin-left:500px;} +.offset5{margin-left:420px;} +.offset4{margin-left:340px;} +.offset3{margin-left:260px;} +.offset2{margin-left:180px;} +.offset1{margin-left:100px;} +.row-fluid{width:100%;*zoom:1;}.row-fluid:before,.row-fluid:after{display:table;content:"";line-height:0;} +.row-fluid:after{clear:both;} +.row-fluid [class*="span"]{display:block;width:100%;min-height:30px;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box;float:left;margin-left:2.127659574468085%;*margin-left:2.074468085106383%;} +.row-fluid [class*="span"]:first-child{margin-left:0;} +.row-fluid .controls-row [class*="span"]+[class*="span"]{margin-left:2.127659574468085%;} +.row-fluid .span12{width:100%;*width:99.94680851063829%;} +.row-fluid .span11{width:91.48936170212765%;*width:91.43617021276594%;} +.row-fluid .span10{width:82.97872340425532%;*width:82.92553191489361%;} +.row-fluid .span9{width:74.46808510638297%;*width:74.41489361702126%;} +.row-fluid .span8{width:65.95744680851064%;*width:65.90425531914893%;} +.row-fluid .span7{width:57.44680851063829%;*width:57.39361702127659%;} +.row-fluid .span6{width:48.93617021276595%;*width:48.88297872340425%;} +.row-fluid .span5{width:40.42553191489362%;*width:40.37234042553192%;} +.row-fluid .span4{width:31.914893617021278%;*width:31.861702127659576%;} +.row-fluid .span3{width:23.404255319148934%;*width:23.351063829787233%;} +.row-fluid .span2{width:14.893617021276595%;*width:14.840425531914894%;} +.row-fluid .span1{width:6.382978723404255%;*width:6.329787234042553%;} +.row-fluid .offset12{margin-left:104.25531914893617%;*margin-left:104.14893617021275%;} +.row-fluid .offset12:first-child{margin-left:102.12765957446808%;*margin-left:102.02127659574467%;} +.row-fluid .offset11{margin-left:95.74468085106382%;*margin-left:95.6382978723404%;} +.row-fluid .offset11:first-child{margin-left:93.61702127659574%;*margin-left:93.51063829787232%;} +.row-fluid .offset10{margin-left:87.23404255319149%;*margin-left:87.12765957446807%;} +.row-fluid .offset10:first-child{margin-left:85.1063829787234%;*margin-left:84.99999999999999%;} +.row-fluid .offset9{margin-left:78.72340425531914%;*margin-left:78.61702127659572%;} +.row-fluid .offset9:first-child{margin-left:76.59574468085106%;*margin-left:76.48936170212764%;} +.row-fluid .offset8{margin-left:70.2127659574468%;*margin-left:70.10638297872339%;} +.row-fluid .offset8:first-child{margin-left:68.08510638297872%;*margin-left:67.9787234042553%;} +.row-fluid .offset7{margin-left:61.70212765957446%;*margin-left:61.59574468085106%;} +.row-fluid .offset7:first-child{margin-left:59.574468085106375%;*margin-left:59.46808510638297%;} +.row-fluid .offset6{margin-left:53.191489361702125%;*margin-left:53.085106382978715%;} +.row-fluid .offset6:first-child{margin-left:51.063829787234035%;*margin-left:50.95744680851063%;} +.row-fluid .offset5{margin-left:44.68085106382979%;*margin-left:44.57446808510638%;} +.row-fluid .offset5:first-child{margin-left:42.5531914893617%;*margin-left:42.4468085106383%;} +.row-fluid .offset4{margin-left:36.170212765957444%;*margin-left:36.06382978723405%;} +.row-fluid .offset4:first-child{margin-left:34.04255319148936%;*margin-left:33.93617021276596%;} +.row-fluid .offset3{margin-left:27.659574468085104%;*margin-left:27.5531914893617%;} +.row-fluid .offset3:first-child{margin-left:25.53191489361702%;*margin-left:25.425531914893618%;} +.row-fluid .offset2{margin-left:19.148936170212764%;*margin-left:19.04255319148936%;} +.row-fluid .offset2:first-child{margin-left:17.02127659574468%;*margin-left:16.914893617021278%;} +.row-fluid .offset1{margin-left:10.638297872340425%;*margin-left:10.53191489361702%;} +.row-fluid .offset1:first-child{margin-left:8.51063829787234%;*margin-left:8.404255319148938%;} +[class*="span"].hide,.row-fluid [class*="span"].hide{display:none;} +[class*="span"].pull-right,.row-fluid [class*="span"].pull-right{float:right;} +.container{margin-right:auto;margin-left:auto;*zoom:1;}.container:before,.container:after{display:table;content:"";line-height:0;} +.container:after{clear:both;} +.container-fluid{padding-right:20px;padding-left:20px;*zoom:1;}.container-fluid:before,.container-fluid:after{display:table;content:"";line-height:0;} +.container-fluid:after{clear:both;} +p{margin:0 0 10px;} +.lead{margin-bottom:20px;font-size:21px;font-weight:200;line-height:30px;} +small{font-size:85%;} +strong{font-weight:bold;} +em{font-style:italic;} +cite{font-style:normal;} +.muted{color:#999999;} +a.muted:hover,a.muted:focus{color:#808080;} +.text-warning{color:#c09853;} +a.text-warning:hover,a.text-warning:focus{color:#a47e3c;} +.text-error{color:#b94a48;} +a.text-error:hover,a.text-error:focus{color:#953b39;} +.text-info{color:#3a87ad;} +a.text-info:hover,a.text-info:focus{color:#2d6987;} +.text-success{color:#468847;} +a.text-success:hover,a.text-success:focus{color:#356635;} +.text-left{text-align:left;} +.text-right{text-align:right;} +.text-center{text-align:center;} +h1,h2,h3,h4,h5,h6{margin:10px 0;font-family:inherit;font-weight:bold;line-height:20px;color:inherit;text-rendering:optimizelegibility;}h1 small,h2 small,h3 small,h4 small,h5 small,h6 small{font-weight:normal;line-height:1;color:#999999;} +h1,h2,h3{line-height:40px;} +h1{font-size:38.5px;} +h2{font-size:31.5px;} +h3{font-size:24.5px;} +h4{font-size:17.5px;} +h5{font-size:14px;} +h6{font-size:11.9px;} +h1 small{font-size:24.5px;} +h2 small{font-size:17.5px;} +h3 small{font-size:14px;} +h4 small{font-size:14px;} +.page-header{padding-bottom:9px;margin:20px 0 30px;border-bottom:1px solid #eeeeee;} +ul,ol{padding:0;margin:0 0 10px 25px;} +ul ul,ul ol,ol ol,ol ul{margin-bottom:0;} +li{line-height:20px;} +ul.unstyled,ol.unstyled{margin-left:0;list-style:none;} +ul.inline,ol.inline{margin-left:0;list-style:none;}ul.inline>li,ol.inline>li{display:inline-block;*display:inline;*zoom:1;padding-left:5px;padding-right:5px;} +dl{margin-bottom:20px;} +dt,dd{line-height:20px;} +dt{font-weight:bold;} +dd{margin-left:10px;} +.dl-horizontal{*zoom:1;}.dl-horizontal:before,.dl-horizontal:after{display:table;content:"";line-height:0;} +.dl-horizontal:after{clear:both;} +.dl-horizontal dt{float:left;width:160px;clear:left;text-align:right;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;} +.dl-horizontal dd{margin-left:180px;} +hr{margin:20px 0;border:0;border-top:1px solid #eeeeee;border-bottom:1px solid #ffffff;} +abbr[title],abbr[data-original-title]{cursor:help;border-bottom:1px dotted #999999;} +abbr.initialism{font-size:90%;text-transform:uppercase;} +blockquote{padding:0 0 0 15px;margin:0 0 20px;border-left:5px solid #eeeeee;}blockquote p{margin-bottom:0;font-size:17.5px;font-weight:300;line-height:1.25;} +blockquote small{display:block;line-height:20px;color:#999999;}blockquote small:before{content:'\2014 \00A0';} +blockquote.pull-right{float:right;padding-right:15px;padding-left:0;border-right:5px solid #eeeeee;border-left:0;}blockquote.pull-right p,blockquote.pull-right small{text-align:right;} +blockquote.pull-right small:before{content:'';} +blockquote.pull-right small:after{content:'\00A0 \2014';} +q:before,q:after,blockquote:before,blockquote:after{content:"";} +address{display:block;margin-bottom:20px;font-style:normal;line-height:20px;} +code,pre{padding:0 3px 2px;font-family:Monaco,Menlo,Consolas,"Courier New",monospace;font-size:12px;color:#333333;-webkit-border-radius:3px;-moz-border-radius:3px;border-radius:3px;} +code{padding:2px 4px;color:#d14;background-color:#f7f7f9;border:1px solid #e1e1e8;white-space:nowrap;} +pre{display:block;padding:9.5px;margin:0 0 10px;font-size:13px;line-height:20px;word-break:break-all;word-wrap:break-word;white-space:pre;white-space:pre-wrap;background-color:#f5f5f5;border:1px solid #ccc;border:1px solid rgba(0, 0, 0, 0.15);-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;}pre.prettyprint{margin-bottom:20px;} +pre code{padding:0;color:inherit;white-space:pre;white-space:pre-wrap;background-color:transparent;border:0;} +.pre-scrollable{max-height:340px;overflow-y:scroll;} +.label,.badge{display:inline-block;padding:2px 4px;font-size:11.844px;font-weight:bold;line-height:14px;color:#ffffff;vertical-align:baseline;white-space:nowrap;text-shadow:0 -1px 0 rgba(0, 0, 0, 0.25);background-color:#999999;} +.label{-webkit-border-radius:3px;-moz-border-radius:3px;border-radius:3px;} +.badge{padding-left:9px;padding-right:9px;-webkit-border-radius:9px;-moz-border-radius:9px;border-radius:9px;} +.label:empty,.badge:empty{display:none;} +a.label:hover,a.label:focus,a.badge:hover,a.badge:focus{color:#ffffff;text-decoration:none;cursor:pointer;} +.label-important,.badge-important{background-color:#b94a48;} +.label-important[href],.badge-important[href]{background-color:#953b39;} +.label-warning,.badge-warning{background-color:#f89406;} +.label-warning[href],.badge-warning[href]{background-color:#c67605;} +.label-success,.badge-success{background-color:#468847;} +.label-success[href],.badge-success[href]{background-color:#356635;} +.label-info,.badge-info{background-color:#3a87ad;} +.label-info[href],.badge-info[href]{background-color:#2d6987;} +.label-inverse,.badge-inverse{background-color:#333333;} +.label-inverse[href],.badge-inverse[href]{background-color:#1a1a1a;} +.btn .label,.btn .badge{position:relative;top:-1px;} +.btn-mini .label,.btn-mini .badge{top:0;} +table{max-width:100%;background-color:transparent;border-collapse:collapse;border-spacing:0;} +.table{width:100%;margin-bottom:20px;}.table th,.table td{padding:8px;line-height:20px;text-align:left;vertical-align:top;border-top:1px solid #dddddd;} +.table th{font-weight:bold;} +.table thead th{vertical-align:bottom;} +.table caption+thead tr:first-child th,.table caption+thead tr:first-child td,.table colgroup+thead tr:first-child th,.table colgroup+thead tr:first-child td,.table thead:first-child tr:first-child th,.table thead:first-child tr:first-child td{border-top:0;} +.table tbody+tbody{border-top:2px solid #dddddd;} +.table .table{background-color:#ffffff;} +.table-condensed th,.table-condensed td{padding:4px 5px;} +.table-bordered{border:1px solid #dddddd;border-collapse:separate;*border-collapse:collapse;border-left:0;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;}.table-bordered th,.table-bordered td{border-left:1px solid #dddddd;} +.table-bordered caption+thead tr:first-child th,.table-bordered caption+tbody tr:first-child th,.table-bordered caption+tbody tr:first-child td,.table-bordered colgroup+thead tr:first-child th,.table-bordered colgroup+tbody tr:first-child th,.table-bordered colgroup+tbody tr:first-child td,.table-bordered thead:first-child tr:first-child th,.table-bordered tbody:first-child tr:first-child th,.table-bordered tbody:first-child tr:first-child td{border-top:0;} +.table-bordered thead:first-child tr:first-child>th:first-child,.table-bordered tbody:first-child tr:first-child>td:first-child,.table-bordered tbody:first-child tr:first-child>th:first-child{-webkit-border-top-left-radius:4px;-moz-border-radius-topleft:4px;border-top-left-radius:4px;} +.table-bordered thead:first-child tr:first-child>th:last-child,.table-bordered tbody:first-child tr:first-child>td:last-child,.table-bordered tbody:first-child tr:first-child>th:last-child{-webkit-border-top-right-radius:4px;-moz-border-radius-topright:4px;border-top-right-radius:4px;} +.table-bordered thead:last-child tr:last-child>th:first-child,.table-bordered tbody:last-child tr:last-child>td:first-child,.table-bordered tbody:last-child tr:last-child>th:first-child,.table-bordered tfoot:last-child tr:last-child>td:first-child,.table-bordered tfoot:last-child tr:last-child>th:first-child{-webkit-border-bottom-left-radius:4px;-moz-border-radius-bottomleft:4px;border-bottom-left-radius:4px;} +.table-bordered thead:last-child tr:last-child>th:last-child,.table-bordered tbody:last-child tr:last-child>td:last-child,.table-bordered tbody:last-child tr:last-child>th:last-child,.table-bordered tfoot:last-child tr:last-child>td:last-child,.table-bordered tfoot:last-child tr:last-child>th:last-child{-webkit-border-bottom-right-radius:4px;-moz-border-radius-bottomright:4px;border-bottom-right-radius:4px;} +.table-bordered tfoot+tbody:last-child tr:last-child td:first-child{-webkit-border-bottom-left-radius:0;-moz-border-radius-bottomleft:0;border-bottom-left-radius:0;} +.table-bordered tfoot+tbody:last-child tr:last-child td:last-child{-webkit-border-bottom-right-radius:0;-moz-border-radius-bottomright:0;border-bottom-right-radius:0;} +.table-bordered caption+thead tr:first-child th:first-child,.table-bordered caption+tbody tr:first-child td:first-child,.table-bordered colgroup+thead tr:first-child th:first-child,.table-bordered colgroup+tbody tr:first-child td:first-child{-webkit-border-top-left-radius:4px;-moz-border-radius-topleft:4px;border-top-left-radius:4px;} +.table-bordered caption+thead tr:first-child th:last-child,.table-bordered caption+tbody tr:first-child td:last-child,.table-bordered colgroup+thead tr:first-child th:last-child,.table-bordered colgroup+tbody tr:first-child td:last-child{-webkit-border-top-right-radius:4px;-moz-border-radius-topright:4px;border-top-right-radius:4px;} +.table-striped tbody>tr:nth-child(odd)>td,.table-striped tbody>tr:nth-child(odd)>th{background-color:#f9f9f9;} +.table-hover tbody tr:hover>td,.table-hover tbody tr:hover>th{background-color:#f5f5f5;} +table td[class*="span"],table th[class*="span"],.row-fluid table td[class*="span"],.row-fluid table th[class*="span"]{display:table-cell;float:none;margin-left:0;} +.table td.span1,.table th.span1{float:none;width:44px;margin-left:0;} +.table td.span2,.table th.span2{float:none;width:124px;margin-left:0;} +.table td.span3,.table th.span3{float:none;width:204px;margin-left:0;} +.table td.span4,.table th.span4{float:none;width:284px;margin-left:0;} +.table td.span5,.table th.span5{float:none;width:364px;margin-left:0;} +.table td.span6,.table th.span6{float:none;width:444px;margin-left:0;} +.table td.span7,.table th.span7{float:none;width:524px;margin-left:0;} +.table td.span8,.table th.span8{float:none;width:604px;margin-left:0;} +.table td.span9,.table th.span9{float:none;width:684px;margin-left:0;} +.table td.span10,.table th.span10{float:none;width:764px;margin-left:0;} +.table td.span11,.table th.span11{float:none;width:844px;margin-left:0;} +.table td.span12,.table th.span12{float:none;width:924px;margin-left:0;} +.table tbody tr.success>td{background-color:#dff0d8;} +.table tbody tr.error>td{background-color:#f2dede;} +.table tbody tr.warning>td{background-color:#fcf8e3;} +.table tbody tr.info>td{background-color:#d9edf7;} +.table-hover tbody tr.success:hover>td{background-color:#d0e9c6;} +.table-hover tbody tr.error:hover>td{background-color:#ebcccc;} +.table-hover tbody tr.warning:hover>td{background-color:#faf2cc;} +.table-hover tbody tr.info:hover>td{background-color:#c4e3f3;} +form{margin:0 0 20px;} +fieldset{padding:0;margin:0;border:0;} +legend{display:block;width:100%;padding:0;margin-bottom:20px;font-size:21px;line-height:40px;color:#333333;border:0;border-bottom:1px solid #e5e5e5;}legend small{font-size:15px;color:#999999;} +label,input,button,select,textarea{font-size:14px;font-weight:normal;line-height:20px;} +input,button,select,textarea{font-family:"Helvetica Neue",Helvetica,Arial,sans-serif;} +label{display:block;margin-bottom:5px;} +select,textarea,input[type="text"],input[type="password"],input[type="datetime"],input[type="datetime-local"],input[type="date"],input[type="month"],input[type="time"],input[type="week"],input[type="number"],input[type="email"],input[type="url"],input[type="search"],input[type="tel"],input[type="color"],.uneditable-input{display:inline-block;height:20px;padding:4px 6px;margin-bottom:10px;font-size:14px;line-height:20px;color:#555555;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;vertical-align:middle;} +input,textarea,.uneditable-input{width:206px;} +textarea{height:auto;} +textarea,input[type="text"],input[type="password"],input[type="datetime"],input[type="datetime-local"],input[type="date"],input[type="month"],input[type="time"],input[type="week"],input[type="number"],input[type="email"],input[type="url"],input[type="search"],input[type="tel"],input[type="color"],.uneditable-input{background-color:#ffffff;border:1px solid #cccccc;-webkit-box-shadow:inset 0 1px 1px rgba(0, 0, 0, 0.075);-moz-box-shadow:inset 0 1px 1px rgba(0, 0, 0, 0.075);box-shadow:inset 0 1px 1px rgba(0, 0, 0, 0.075);-webkit-transition:border linear .2s, box-shadow linear .2s;-moz-transition:border linear .2s, box-shadow linear .2s;-o-transition:border linear .2s, box-shadow linear .2s;transition:border linear .2s, box-shadow linear .2s;}textarea:focus,input[type="text"]:focus,input[type="password"]:focus,input[type="datetime"]:focus,input[type="datetime-local"]:focus,input[type="date"]:focus,input[type="month"]:focus,input[type="time"]:focus,input[type="week"]:focus,input[type="number"]:focus,input[type="email"]:focus,input[type="url"]:focus,input[type="search"]:focus,input[type="tel"]:focus,input[type="color"]:focus,.uneditable-input:focus{border-color:rgba(82, 168, 236, 0.8);outline:0;outline:thin dotted \9;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075), 0 0 8px rgba(82,168,236,.6);-moz-box-shadow:inset 0 1px 1px rgba(0,0,0,.075), 0 0 8px rgba(82,168,236,.6);box-shadow:inset 0 1px 1px rgba(0,0,0,.075), 0 0 8px rgba(82,168,236,.6);} +input[type="radio"],input[type="checkbox"]{margin:4px 0 0;*margin-top:0;margin-top:1px \9;line-height:normal;} +input[type="file"],input[type="image"],input[type="submit"],input[type="reset"],input[type="button"],input[type="radio"],input[type="checkbox"]{width:auto;} +select,input[type="file"]{height:30px;*margin-top:4px;line-height:30px;} +select{width:220px;border:1px solid #cccccc;background-color:#ffffff;} +select[multiple],select[size]{height:auto;} +select:focus,input[type="file"]:focus,input[type="radio"]:focus,input[type="checkbox"]:focus{outline:thin dotted #333;outline:5px auto -webkit-focus-ring-color;outline-offset:-2px;} +.uneditable-input,.uneditable-textarea{color:#999999;background-color:#fcfcfc;border-color:#cccccc;-webkit-box-shadow:inset 0 1px 2px rgba(0, 0, 0, 0.025);-moz-box-shadow:inset 0 1px 2px rgba(0, 0, 0, 0.025);box-shadow:inset 0 1px 2px rgba(0, 0, 0, 0.025);cursor:not-allowed;} +.uneditable-input{overflow:hidden;white-space:nowrap;} +.uneditable-textarea{width:auto;height:auto;} +input:-moz-placeholder,textarea:-moz-placeholder{color:#999999;} +input:-ms-input-placeholder,textarea:-ms-input-placeholder{color:#999999;} +input::-webkit-input-placeholder,textarea::-webkit-input-placeholder{color:#999999;} +.radio,.checkbox{min-height:20px;padding-left:20px;} +.radio input[type="radio"],.checkbox input[type="checkbox"]{float:left;margin-left:-20px;} +.controls>.radio:first-child,.controls>.checkbox:first-child{padding-top:5px;} +.radio.inline,.checkbox.inline{display:inline-block;padding-top:5px;margin-bottom:0;vertical-align:middle;} +.radio.inline+.radio.inline,.checkbox.inline+.checkbox.inline{margin-left:10px;} +.input-mini{width:60px;} +.input-small{width:90px;} +.input-medium{width:150px;} +.input-large{width:210px;} +.input-xlarge{width:270px;} +.input-xxlarge{width:530px;} +input[class*="span"],select[class*="span"],textarea[class*="span"],.uneditable-input[class*="span"],.row-fluid input[class*="span"],.row-fluid select[class*="span"],.row-fluid textarea[class*="span"],.row-fluid .uneditable-input[class*="span"]{float:none;margin-left:0;} +.input-append input[class*="span"],.input-append .uneditable-input[class*="span"],.input-prepend input[class*="span"],.input-prepend .uneditable-input[class*="span"],.row-fluid input[class*="span"],.row-fluid select[class*="span"],.row-fluid textarea[class*="span"],.row-fluid .uneditable-input[class*="span"],.row-fluid .input-prepend [class*="span"],.row-fluid .input-append [class*="span"]{display:inline-block;} +input,textarea,.uneditable-input{margin-left:0;} +.controls-row [class*="span"]+[class*="span"]{margin-left:20px;} +input.span12,textarea.span12,.uneditable-input.span12{width:926px;} +input.span11,textarea.span11,.uneditable-input.span11{width:846px;} +input.span10,textarea.span10,.uneditable-input.span10{width:766px;} +input.span9,textarea.span9,.uneditable-input.span9{width:686px;} +input.span8,textarea.span8,.uneditable-input.span8{width:606px;} +input.span7,textarea.span7,.uneditable-input.span7{width:526px;} +input.span6,textarea.span6,.uneditable-input.span6{width:446px;} +input.span5,textarea.span5,.uneditable-input.span5{width:366px;} +input.span4,textarea.span4,.uneditable-input.span4{width:286px;} +input.span3,textarea.span3,.uneditable-input.span3{width:206px;} +input.span2,textarea.span2,.uneditable-input.span2{width:126px;} +input.span1,textarea.span1,.uneditable-input.span1{width:46px;} +.controls-row{*zoom:1;}.controls-row:before,.controls-row:after{display:table;content:"";line-height:0;} +.controls-row:after{clear:both;} +.controls-row [class*="span"],.row-fluid .controls-row [class*="span"]{float:left;} +.controls-row .checkbox[class*="span"],.controls-row .radio[class*="span"]{padding-top:5px;} +input[disabled],select[disabled],textarea[disabled],input[readonly],select[readonly],textarea[readonly]{cursor:not-allowed;background-color:#eeeeee;} +input[type="radio"][disabled],input[type="checkbox"][disabled],input[type="radio"][readonly],input[type="checkbox"][readonly]{background-color:transparent;} +.control-group.warning .control-label,.control-group.warning .help-block,.control-group.warning .help-inline{color:#c09853;} +.control-group.warning .checkbox,.control-group.warning .radio,.control-group.warning input,.control-group.warning select,.control-group.warning textarea{color:#c09853;} +.control-group.warning input,.control-group.warning select,.control-group.warning textarea{border-color:#c09853;-webkit-box-shadow:inset 0 1px 1px rgba(0, 0, 0, 0.075);-moz-box-shadow:inset 0 1px 1px rgba(0, 0, 0, 0.075);box-shadow:inset 0 1px 1px rgba(0, 0, 0, 0.075);}.control-group.warning input:focus,.control-group.warning select:focus,.control-group.warning textarea:focus{border-color:#a47e3c;-webkit-box-shadow:inset 0 1px 1px rgba(0, 0, 0, 0.075),0 0 6px #dbc59e;-moz-box-shadow:inset 0 1px 1px rgba(0, 0, 0, 0.075),0 0 6px #dbc59e;box-shadow:inset 0 1px 1px rgba(0, 0, 0, 0.075),0 0 6px #dbc59e;} +.control-group.warning .input-prepend .add-on,.control-group.warning .input-append .add-on{color:#c09853;background-color:#fcf8e3;border-color:#c09853;} +.control-group.error .control-label,.control-group.error .help-block,.control-group.error .help-inline{color:#b94a48;} +.control-group.error .checkbox,.control-group.error .radio,.control-group.error input,.control-group.error select,.control-group.error textarea{color:#b94a48;} +.control-group.error input,.control-group.error select,.control-group.error textarea{border-color:#b94a48;-webkit-box-shadow:inset 0 1px 1px rgba(0, 0, 0, 0.075);-moz-box-shadow:inset 0 1px 1px rgba(0, 0, 0, 0.075);box-shadow:inset 0 1px 1px rgba(0, 0, 0, 0.075);}.control-group.error input:focus,.control-group.error select:focus,.control-group.error textarea:focus{border-color:#953b39;-webkit-box-shadow:inset 0 1px 1px rgba(0, 0, 0, 0.075),0 0 6px #d59392;-moz-box-shadow:inset 0 1px 1px rgba(0, 0, 0, 0.075),0 0 6px #d59392;box-shadow:inset 0 1px 1px rgba(0, 0, 0, 0.075),0 0 6px #d59392;} +.control-group.error .input-prepend .add-on,.control-group.error .input-append .add-on{color:#b94a48;background-color:#f2dede;border-color:#b94a48;} +.control-group.success .control-label,.control-group.success .help-block,.control-group.success .help-inline{color:#468847;} +.control-group.success .checkbox,.control-group.success .radio,.control-group.success input,.control-group.success select,.control-group.success textarea{color:#468847;} +.control-group.success input,.control-group.success select,.control-group.success textarea{border-color:#468847;-webkit-box-shadow:inset 0 1px 1px rgba(0, 0, 0, 0.075);-moz-box-shadow:inset 0 1px 1px rgba(0, 0, 0, 0.075);box-shadow:inset 0 1px 1px rgba(0, 0, 0, 0.075);}.control-group.success input:focus,.control-group.success select:focus,.control-group.success textarea:focus{border-color:#356635;-webkit-box-shadow:inset 0 1px 1px rgba(0, 0, 0, 0.075),0 0 6px #7aba7b;-moz-box-shadow:inset 0 1px 1px rgba(0, 0, 0, 0.075),0 0 6px #7aba7b;box-shadow:inset 0 1px 1px rgba(0, 0, 0, 0.075),0 0 6px #7aba7b;} +.control-group.success .input-prepend .add-on,.control-group.success .input-append .add-on{color:#468847;background-color:#dff0d8;border-color:#468847;} +.control-group.info .control-label,.control-group.info .help-block,.control-group.info .help-inline{color:#3a87ad;} +.control-group.info .checkbox,.control-group.info .radio,.control-group.info input,.control-group.info select,.control-group.info textarea{color:#3a87ad;} +.control-group.info input,.control-group.info select,.control-group.info textarea{border-color:#3a87ad;-webkit-box-shadow:inset 0 1px 1px rgba(0, 0, 0, 0.075);-moz-box-shadow:inset 0 1px 1px rgba(0, 0, 0, 0.075);box-shadow:inset 0 1px 1px rgba(0, 0, 0, 0.075);}.control-group.info input:focus,.control-group.info select:focus,.control-group.info textarea:focus{border-color:#2d6987;-webkit-box-shadow:inset 0 1px 1px rgba(0, 0, 0, 0.075),0 0 6px #7ab5d3;-moz-box-shadow:inset 0 1px 1px rgba(0, 0, 0, 0.075),0 0 6px #7ab5d3;box-shadow:inset 0 1px 1px rgba(0, 0, 0, 0.075),0 0 6px #7ab5d3;} +.control-group.info .input-prepend .add-on,.control-group.info .input-append .add-on{color:#3a87ad;background-color:#d9edf7;border-color:#3a87ad;} +input:focus:invalid,textarea:focus:invalid,select:focus:invalid{color:#b94a48;border-color:#ee5f5b;}input:focus:invalid:focus,textarea:focus:invalid:focus,select:focus:invalid:focus{border-color:#e9322d;-webkit-box-shadow:0 0 6px #f8b9b7;-moz-box-shadow:0 0 6px #f8b9b7;box-shadow:0 0 6px #f8b9b7;} +.form-actions{padding:19px 20px 20px;margin-top:20px;margin-bottom:20px;background-color:#f5f5f5;border-top:1px solid #e5e5e5;*zoom:1;}.form-actions:before,.form-actions:after{display:table;content:"";line-height:0;} +.form-actions:after{clear:both;} +.help-block,.help-inline{color:#595959;} +.help-block{display:block;margin-bottom:10px;} +.help-inline{display:inline-block;*display:inline;*zoom:1;vertical-align:middle;padding-left:5px;} +.input-append,.input-prepend{display:inline-block;margin-bottom:10px;vertical-align:middle;font-size:0;white-space:nowrap;}.input-append input,.input-prepend input,.input-append select,.input-prepend select,.input-append .uneditable-input,.input-prepend .uneditable-input,.input-append .dropdown-menu,.input-prepend .dropdown-menu,.input-append .popover,.input-prepend .popover{font-size:14px;} +.input-append input,.input-prepend input,.input-append select,.input-prepend select,.input-append .uneditable-input,.input-prepend .uneditable-input{position:relative;margin-bottom:0;*margin-left:0;vertical-align:top;-webkit-border-radius:0 4px 4px 0;-moz-border-radius:0 4px 4px 0;border-radius:0 4px 4px 0;}.input-append input:focus,.input-prepend input:focus,.input-append select:focus,.input-prepend select:focus,.input-append .uneditable-input:focus,.input-prepend .uneditable-input:focus{z-index:2;} +.input-append .add-on,.input-prepend .add-on{display:inline-block;width:auto;height:20px;min-width:16px;padding:4px 5px;font-size:14px;font-weight:normal;line-height:20px;text-align:center;text-shadow:0 1px 0 #ffffff;background-color:#eeeeee;border:1px solid #ccc;} +.input-append .add-on,.input-prepend .add-on,.input-append .btn,.input-prepend .btn,.input-append .btn-group>.dropdown-toggle,.input-prepend .btn-group>.dropdown-toggle{vertical-align:top;-webkit-border-radius:0;-moz-border-radius:0;border-radius:0;} +.input-append .active,.input-prepend .active{background-color:#a9dba9;border-color:#46a546;} +.input-prepend .add-on,.input-prepend .btn{margin-right:-1px;} +.input-prepend .add-on:first-child,.input-prepend .btn:first-child{-webkit-border-radius:4px 0 0 4px;-moz-border-radius:4px 0 0 4px;border-radius:4px 0 0 4px;} +.input-append input,.input-append select,.input-append .uneditable-input{-webkit-border-radius:4px 0 0 4px;-moz-border-radius:4px 0 0 4px;border-radius:4px 0 0 4px;}.input-append input+.btn-group .btn:last-child,.input-append select+.btn-group .btn:last-child,.input-append .uneditable-input+.btn-group .btn:last-child{-webkit-border-radius:0 4px 4px 0;-moz-border-radius:0 4px 4px 0;border-radius:0 4px 4px 0;} +.input-append .add-on,.input-append .btn,.input-append .btn-group{margin-left:-1px;} +.input-append .add-on:last-child,.input-append .btn:last-child,.input-append .btn-group:last-child>.dropdown-toggle{-webkit-border-radius:0 4px 4px 0;-moz-border-radius:0 4px 4px 0;border-radius:0 4px 4px 0;} +.input-prepend.input-append input,.input-prepend.input-append select,.input-prepend.input-append .uneditable-input{-webkit-border-radius:0;-moz-border-radius:0;border-radius:0;}.input-prepend.input-append input+.btn-group .btn,.input-prepend.input-append select+.btn-group .btn,.input-prepend.input-append .uneditable-input+.btn-group .btn{-webkit-border-radius:0 4px 4px 0;-moz-border-radius:0 4px 4px 0;border-radius:0 4px 4px 0;} +.input-prepend.input-append .add-on:first-child,.input-prepend.input-append .btn:first-child{margin-right:-1px;-webkit-border-radius:4px 0 0 4px;-moz-border-radius:4px 0 0 4px;border-radius:4px 0 0 4px;} +.input-prepend.input-append .add-on:last-child,.input-prepend.input-append .btn:last-child{margin-left:-1px;-webkit-border-radius:0 4px 4px 0;-moz-border-radius:0 4px 4px 0;border-radius:0 4px 4px 0;} +.input-prepend.input-append .btn-group:first-child{margin-left:0;} +input.search-query{padding-right:14px;padding-right:4px \9;padding-left:14px;padding-left:4px \9;margin-bottom:0;-webkit-border-radius:15px;-moz-border-radius:15px;border-radius:15px;} +.form-search .input-append .search-query,.form-search .input-prepend .search-query{-webkit-border-radius:0;-moz-border-radius:0;border-radius:0;} +.form-search .input-append .search-query{-webkit-border-radius:14px 0 0 14px;-moz-border-radius:14px 0 0 14px;border-radius:14px 0 0 14px;} +.form-search .input-append .btn{-webkit-border-radius:0 14px 14px 0;-moz-border-radius:0 14px 14px 0;border-radius:0 14px 14px 0;} +.form-search .input-prepend .search-query{-webkit-border-radius:0 14px 14px 0;-moz-border-radius:0 14px 14px 0;border-radius:0 14px 14px 0;} +.form-search .input-prepend .btn{-webkit-border-radius:14px 0 0 14px;-moz-border-radius:14px 0 0 14px;border-radius:14px 0 0 14px;} +.form-search input,.form-inline input,.form-horizontal input,.form-search textarea,.form-inline textarea,.form-horizontal textarea,.form-search select,.form-inline select,.form-horizontal select,.form-search .help-inline,.form-inline .help-inline,.form-horizontal .help-inline,.form-search .uneditable-input,.form-inline .uneditable-input,.form-horizontal .uneditable-input,.form-search .input-prepend,.form-inline .input-prepend,.form-horizontal .input-prepend,.form-search .input-append,.form-inline .input-append,.form-horizontal .input-append{display:inline-block;*display:inline;*zoom:1;margin-bottom:0;vertical-align:middle;} +.form-search .hide,.form-inline .hide,.form-horizontal .hide{display:none;} +.form-search label,.form-inline label,.form-search .btn-group,.form-inline .btn-group{display:inline-block;} +.form-search .input-append,.form-inline .input-append,.form-search .input-prepend,.form-inline .input-prepend{margin-bottom:0;} +.form-search .radio,.form-search .checkbox,.form-inline .radio,.form-inline .checkbox{padding-left:0;margin-bottom:0;vertical-align:middle;} +.form-search .radio input[type="radio"],.form-search .checkbox input[type="checkbox"],.form-inline .radio input[type="radio"],.form-inline .checkbox input[type="checkbox"]{float:left;margin-right:3px;margin-left:0;} +.control-group{margin-bottom:10px;} +legend+.control-group{margin-top:20px;-webkit-margin-top-collapse:separate;} +.form-horizontal .control-group{margin-bottom:20px;*zoom:1;}.form-horizontal .control-group:before,.form-horizontal .control-group:after{display:table;content:"";line-height:0;} +.form-horizontal .control-group:after{clear:both;} +.form-horizontal .control-label{float:left;width:160px;padding-top:5px;text-align:right;} +.form-horizontal .controls{*display:inline-block;*padding-left:20px;margin-left:180px;*margin-left:0;}.form-horizontal .controls:first-child{*padding-left:180px;} +.form-horizontal .help-block{margin-bottom:0;} +.form-horizontal input+.help-block,.form-horizontal select+.help-block,.form-horizontal textarea+.help-block,.form-horizontal .uneditable-input+.help-block,.form-horizontal .input-prepend+.help-block,.form-horizontal .input-append+.help-block{margin-top:10px;} +.form-horizontal .form-actions{padding-left:180px;} +.btn{display:inline-block;*display:inline;*zoom:1;padding:4px 12px;margin-bottom:0;font-size:14px;line-height:20px;text-align:center;vertical-align:middle;cursor:pointer;color:#333333;text-shadow:0 1px 1px rgba(255, 255, 255, 0.75);background-color:#f5f5f5;background-image:-moz-linear-gradient(top, #ffffff, #e6e6e6);background-image:-webkit-gradient(linear, 0 0, 0 100%, from(#ffffff), to(#e6e6e6));background-image:-webkit-linear-gradient(top, #ffffff, #e6e6e6);background-image:-o-linear-gradient(top, #ffffff, #e6e6e6);background-image:linear-gradient(to bottom, #ffffff, #e6e6e6);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffffff', endColorstr='#ffe6e6e6', GradientType=0);border-color:#e6e6e6 #e6e6e6 #bfbfbf;border-color:rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25);*background-color:#e6e6e6;filter:progid:DXImageTransform.Microsoft.gradient(enabled = false);border:1px solid #cccccc;*border:0;border-bottom-color:#b3b3b3;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;*margin-left:.3em;-webkit-box-shadow:inset 0 1px 0 rgba(255,255,255,.2), 0 1px 2px rgba(0,0,0,.05);-moz-box-shadow:inset 0 1px 0 rgba(255,255,255,.2), 0 1px 2px rgba(0,0,0,.05);box-shadow:inset 0 1px 0 rgba(255,255,255,.2), 0 1px 2px rgba(0,0,0,.05);}.btn:hover,.btn:focus,.btn:active,.btn.active,.btn.disabled,.btn[disabled]{color:#333333;background-color:#e6e6e6;*background-color:#d9d9d9;} +.btn:active,.btn.active{background-color:#cccccc \9;} +.btn:first-child{*margin-left:0;} +.btn:hover,.btn:focus{color:#333333;text-decoration:none;background-position:0 -15px;-webkit-transition:background-position 0.1s linear;-moz-transition:background-position 0.1s linear;-o-transition:background-position 0.1s linear;transition:background-position 0.1s linear;} +.btn:focus{outline:thin dotted #333;outline:5px auto -webkit-focus-ring-color;outline-offset:-2px;} +.btn.active,.btn:active{background-image:none;outline:0;-webkit-box-shadow:inset 0 2px 4px rgba(0,0,0,.15), 0 1px 2px rgba(0,0,0,.05);-moz-box-shadow:inset 0 2px 4px rgba(0,0,0,.15), 0 1px 2px rgba(0,0,0,.05);box-shadow:inset 0 2px 4px rgba(0,0,0,.15), 0 1px 2px rgba(0,0,0,.05);} +.btn.disabled,.btn[disabled]{cursor:default;background-image:none;opacity:0.65;filter:alpha(opacity=65);-webkit-box-shadow:none;-moz-box-shadow:none;box-shadow:none;} +.btn-large{padding:11px 19px;font-size:17.5px;-webkit-border-radius:6px;-moz-border-radius:6px;border-radius:6px;} +.btn-large [class^="icon-"],.btn-large [class*=" icon-"]{margin-top:4px;} +.btn-small{padding:2px 10px;font-size:11.9px;-webkit-border-radius:3px;-moz-border-radius:3px;border-radius:3px;} +.btn-small [class^="icon-"],.btn-small [class*=" icon-"]{margin-top:0;} +.btn-mini [class^="icon-"],.btn-mini [class*=" icon-"]{margin-top:-1px;} +.btn-mini{padding:0 6px;font-size:10.5px;-webkit-border-radius:3px;-moz-border-radius:3px;border-radius:3px;} +.btn-block{display:block;width:100%;padding-left:0;padding-right:0;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box;} +.btn-block+.btn-block{margin-top:5px;} +input[type="submit"].btn-block,input[type="reset"].btn-block,input[type="button"].btn-block{width:100%;} +.btn-primary.active,.btn-warning.active,.btn-danger.active,.btn-success.active,.btn-info.active,.btn-inverse.active{color:rgba(255, 255, 255, 0.75);} +.btn-primary{color:#ffffff;text-shadow:0 -1px 0 rgba(0, 0, 0, 0.25);background-color:#006dcc;background-image:-moz-linear-gradient(top, #0088cc, #0044cc);background-image:-webkit-gradient(linear, 0 0, 0 100%, from(#0088cc), to(#0044cc));background-image:-webkit-linear-gradient(top, #0088cc, #0044cc);background-image:-o-linear-gradient(top, #0088cc, #0044cc);background-image:linear-gradient(to bottom, #0088cc, #0044cc);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff0088cc', endColorstr='#ff0044cc', GradientType=0);border-color:#0044cc #0044cc #002a80;border-color:rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25);*background-color:#0044cc;filter:progid:DXImageTransform.Microsoft.gradient(enabled = false);}.btn-primary:hover,.btn-primary:focus,.btn-primary:active,.btn-primary.active,.btn-primary.disabled,.btn-primary[disabled]{color:#ffffff;background-color:#0044cc;*background-color:#003bb3;} +.btn-primary:active,.btn-primary.active{background-color:#003399 \9;} +.btn-warning{color:#ffffff;text-shadow:0 -1px 0 rgba(0, 0, 0, 0.25);background-color:#faa732;background-image:-moz-linear-gradient(top, #fbb450, #f89406);background-image:-webkit-gradient(linear, 0 0, 0 100%, from(#fbb450), to(#f89406));background-image:-webkit-linear-gradient(top, #fbb450, #f89406);background-image:-o-linear-gradient(top, #fbb450, #f89406);background-image:linear-gradient(to bottom, #fbb450, #f89406);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fffbb450', endColorstr='#fff89406', GradientType=0);border-color:#f89406 #f89406 #ad6704;border-color:rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25);*background-color:#f89406;filter:progid:DXImageTransform.Microsoft.gradient(enabled = false);}.btn-warning:hover,.btn-warning:focus,.btn-warning:active,.btn-warning.active,.btn-warning.disabled,.btn-warning[disabled]{color:#ffffff;background-color:#f89406;*background-color:#df8505;} +.btn-warning:active,.btn-warning.active{background-color:#c67605 \9;} +.btn-danger{color:#ffffff;text-shadow:0 -1px 0 rgba(0, 0, 0, 0.25);background-color:#da4f49;background-image:-moz-linear-gradient(top, #ee5f5b, #bd362f);background-image:-webkit-gradient(linear, 0 0, 0 100%, from(#ee5f5b), to(#bd362f));background-image:-webkit-linear-gradient(top, #ee5f5b, #bd362f);background-image:-o-linear-gradient(top, #ee5f5b, #bd362f);background-image:linear-gradient(to bottom, #ee5f5b, #bd362f);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffee5f5b', endColorstr='#ffbd362f', GradientType=0);border-color:#bd362f #bd362f #802420;border-color:rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25);*background-color:#bd362f;filter:progid:DXImageTransform.Microsoft.gradient(enabled = false);}.btn-danger:hover,.btn-danger:focus,.btn-danger:active,.btn-danger.active,.btn-danger.disabled,.btn-danger[disabled]{color:#ffffff;background-color:#bd362f;*background-color:#a9302a;} +.btn-danger:active,.btn-danger.active{background-color:#942a25 \9;} +.btn-success{color:#ffffff;text-shadow:0 -1px 0 rgba(0, 0, 0, 0.25);background-color:#5bb75b;background-image:-moz-linear-gradient(top, #62c462, #51a351);background-image:-webkit-gradient(linear, 0 0, 0 100%, from(#62c462), to(#51a351));background-image:-webkit-linear-gradient(top, #62c462, #51a351);background-image:-o-linear-gradient(top, #62c462, #51a351);background-image:linear-gradient(to bottom, #62c462, #51a351);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff62c462', endColorstr='#ff51a351', GradientType=0);border-color:#51a351 #51a351 #387038;border-color:rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25);*background-color:#51a351;filter:progid:DXImageTransform.Microsoft.gradient(enabled = false);}.btn-success:hover,.btn-success:focus,.btn-success:active,.btn-success.active,.btn-success.disabled,.btn-success[disabled]{color:#ffffff;background-color:#51a351;*background-color:#499249;} +.btn-success:active,.btn-success.active{background-color:#408140 \9;} +.btn-info{color:#ffffff;text-shadow:0 -1px 0 rgba(0, 0, 0, 0.25);background-color:#49afcd;background-image:-moz-linear-gradient(top, #5bc0de, #2f96b4);background-image:-webkit-gradient(linear, 0 0, 0 100%, from(#5bc0de), to(#2f96b4));background-image:-webkit-linear-gradient(top, #5bc0de, #2f96b4);background-image:-o-linear-gradient(top, #5bc0de, #2f96b4);background-image:linear-gradient(to bottom, #5bc0de, #2f96b4);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5bc0de', endColorstr='#ff2f96b4', GradientType=0);border-color:#2f96b4 #2f96b4 #1f6377;border-color:rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25);*background-color:#2f96b4;filter:progid:DXImageTransform.Microsoft.gradient(enabled = false);}.btn-info:hover,.btn-info:focus,.btn-info:active,.btn-info.active,.btn-info.disabled,.btn-info[disabled]{color:#ffffff;background-color:#2f96b4;*background-color:#2a85a0;} +.btn-info:active,.btn-info.active{background-color:#24748c \9;} +.btn-inverse{color:#ffffff;text-shadow:0 -1px 0 rgba(0, 0, 0, 0.25);background-color:#363636;background-image:-moz-linear-gradient(top, #444444, #222222);background-image:-webkit-gradient(linear, 0 0, 0 100%, from(#444444), to(#222222));background-image:-webkit-linear-gradient(top, #444444, #222222);background-image:-o-linear-gradient(top, #444444, #222222);background-image:linear-gradient(to bottom, #444444, #222222);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff444444', endColorstr='#ff222222', GradientType=0);border-color:#222222 #222222 #000000;border-color:rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25);*background-color:#222222;filter:progid:DXImageTransform.Microsoft.gradient(enabled = false);}.btn-inverse:hover,.btn-inverse:focus,.btn-inverse:active,.btn-inverse.active,.btn-inverse.disabled,.btn-inverse[disabled]{color:#ffffff;background-color:#222222;*background-color:#151515;} +.btn-inverse:active,.btn-inverse.active{background-color:#080808 \9;} +button.btn,input[type="submit"].btn{*padding-top:3px;*padding-bottom:3px;}button.btn::-moz-focus-inner,input[type="submit"].btn::-moz-focus-inner{padding:0;border:0;} +button.btn.btn-large,input[type="submit"].btn.btn-large{*padding-top:7px;*padding-bottom:7px;} +button.btn.btn-small,input[type="submit"].btn.btn-small{*padding-top:3px;*padding-bottom:3px;} +button.btn.btn-mini,input[type="submit"].btn.btn-mini{*padding-top:1px;*padding-bottom:1px;} +.btn-link,.btn-link:active,.btn-link[disabled]{background-color:transparent;background-image:none;-webkit-box-shadow:none;-moz-box-shadow:none;box-shadow:none;} +.btn-link{border-color:transparent;cursor:pointer;color:#0088cc;-webkit-border-radius:0;-moz-border-radius:0;border-radius:0;} +.btn-link:hover,.btn-link:focus{color:#005580;text-decoration:underline;background-color:transparent;} +.btn-link[disabled]:hover,.btn-link[disabled]:focus{color:#333333;text-decoration:none;} +.btn-group{position:relative;display:inline-block;*display:inline;*zoom:1;font-size:0;vertical-align:middle;white-space:nowrap;*margin-left:.3em;}.btn-group:first-child{*margin-left:0;} +.btn-group+.btn-group{margin-left:5px;} +.btn-toolbar{font-size:0;margin-top:10px;margin-bottom:10px;}.btn-toolbar>.btn+.btn,.btn-toolbar>.btn-group+.btn,.btn-toolbar>.btn+.btn-group{margin-left:5px;} +.btn-group>.btn{position:relative;-webkit-border-radius:0;-moz-border-radius:0;border-radius:0;} +.btn-group>.btn+.btn{margin-left:-1px;} +.btn-group>.btn,.btn-group>.dropdown-menu,.btn-group>.popover{font-size:14px;} +.btn-group>.btn-mini{font-size:10.5px;} +.btn-group>.btn-small{font-size:11.9px;} +.btn-group>.btn-large{font-size:17.5px;} +.btn-group>.btn:first-child{margin-left:0;-webkit-border-top-left-radius:4px;-moz-border-radius-topleft:4px;border-top-left-radius:4px;-webkit-border-bottom-left-radius:4px;-moz-border-radius-bottomleft:4px;border-bottom-left-radius:4px;} +.btn-group>.btn:last-child,.btn-group>.dropdown-toggle{-webkit-border-top-right-radius:4px;-moz-border-radius-topright:4px;border-top-right-radius:4px;-webkit-border-bottom-right-radius:4px;-moz-border-radius-bottomright:4px;border-bottom-right-radius:4px;} +.btn-group>.btn.large:first-child{margin-left:0;-webkit-border-top-left-radius:6px;-moz-border-radius-topleft:6px;border-top-left-radius:6px;-webkit-border-bottom-left-radius:6px;-moz-border-radius-bottomleft:6px;border-bottom-left-radius:6px;} +.btn-group>.btn.large:last-child,.btn-group>.large.dropdown-toggle{-webkit-border-top-right-radius:6px;-moz-border-radius-topright:6px;border-top-right-radius:6px;-webkit-border-bottom-right-radius:6px;-moz-border-radius-bottomright:6px;border-bottom-right-radius:6px;} +.btn-group>.btn:hover,.btn-group>.btn:focus,.btn-group>.btn:active,.btn-group>.btn.active{z-index:2;} +.btn-group .dropdown-toggle:active,.btn-group.open .dropdown-toggle{outline:0;} +.btn-group>.btn+.dropdown-toggle{padding-left:8px;padding-right:8px;-webkit-box-shadow:inset 1px 0 0 rgba(255,255,255,.125), inset 0 1px 0 rgba(255,255,255,.2), 0 1px 2px rgba(0,0,0,.05);-moz-box-shadow:inset 1px 0 0 rgba(255,255,255,.125), inset 0 1px 0 rgba(255,255,255,.2), 0 1px 2px rgba(0,0,0,.05);box-shadow:inset 1px 0 0 rgba(255,255,255,.125), inset 0 1px 0 rgba(255,255,255,.2), 0 1px 2px rgba(0,0,0,.05);*padding-top:5px;*padding-bottom:5px;} +.btn-group>.btn-mini+.dropdown-toggle{padding-left:5px;padding-right:5px;*padding-top:2px;*padding-bottom:2px;} +.btn-group>.btn-small+.dropdown-toggle{*padding-top:5px;*padding-bottom:4px;} +.btn-group>.btn-large+.dropdown-toggle{padding-left:12px;padding-right:12px;*padding-top:7px;*padding-bottom:7px;} +.btn-group.open .dropdown-toggle{background-image:none;-webkit-box-shadow:inset 0 2px 4px rgba(0,0,0,.15), 0 1px 2px rgba(0,0,0,.05);-moz-box-shadow:inset 0 2px 4px rgba(0,0,0,.15), 0 1px 2px rgba(0,0,0,.05);box-shadow:inset 0 2px 4px rgba(0,0,0,.15), 0 1px 2px rgba(0,0,0,.05);} +.btn-group.open .btn.dropdown-toggle{background-color:#e6e6e6;} +.btn-group.open .btn-primary.dropdown-toggle{background-color:#0044cc;} +.btn-group.open .btn-warning.dropdown-toggle{background-color:#f89406;} +.btn-group.open .btn-danger.dropdown-toggle{background-color:#bd362f;} +.btn-group.open .btn-success.dropdown-toggle{background-color:#51a351;} +.btn-group.open .btn-info.dropdown-toggle{background-color:#2f96b4;} +.btn-group.open .btn-inverse.dropdown-toggle{background-color:#222222;} +.btn .caret{margin-top:8px;margin-left:0;} +.btn-large .caret{margin-top:6px;} +.btn-large .caret{border-left-width:5px;border-right-width:5px;border-top-width:5px;} +.btn-mini .caret,.btn-small .caret{margin-top:8px;} +.dropup .btn-large .caret{border-bottom-width:5px;} +.btn-primary .caret,.btn-warning .caret,.btn-danger .caret,.btn-info .caret,.btn-success .caret,.btn-inverse .caret{border-top-color:#ffffff;border-bottom-color:#ffffff;} +.btn-group-vertical{display:inline-block;*display:inline;*zoom:1;} +.btn-group-vertical>.btn{display:block;float:none;max-width:100%;-webkit-border-radius:0;-moz-border-radius:0;border-radius:0;} +.btn-group-vertical>.btn+.btn{margin-left:0;margin-top:-1px;} +.btn-group-vertical>.btn:first-child{-webkit-border-radius:4px 4px 0 0;-moz-border-radius:4px 4px 0 0;border-radius:4px 4px 0 0;} +.btn-group-vertical>.btn:last-child{-webkit-border-radius:0 0 4px 4px;-moz-border-radius:0 0 4px 4px;border-radius:0 0 4px 4px;} +.btn-group-vertical>.btn-large:first-child{-webkit-border-radius:6px 6px 0 0;-moz-border-radius:6px 6px 0 0;border-radius:6px 6px 0 0;} +.btn-group-vertical>.btn-large:last-child{-webkit-border-radius:0 0 6px 6px;-moz-border-radius:0 0 6px 6px;border-radius:0 0 6px 6px;} +.nav{margin-left:0;margin-bottom:20px;list-style:none;} +.nav>li>a{display:block;} +.nav>li>a:hover,.nav>li>a:focus{text-decoration:none;background-color:#eeeeee;} +.nav>li>a>img{max-width:none;} +.nav>.pull-right{float:right;} +.nav-header{display:block;padding:3px 15px;font-size:11px;font-weight:bold;line-height:20px;color:#999999;text-shadow:0 1px 0 rgba(255, 255, 255, 0.5);text-transform:uppercase;} +.nav li+.nav-header{margin-top:9px;} +.nav-list{padding-left:15px;padding-right:15px;margin-bottom:0;} +.nav-list>li>a,.nav-list .nav-header{margin-left:-15px;margin-right:-15px;text-shadow:0 1px 0 rgba(255, 255, 255, 0.5);} +.nav-list>li>a{padding:3px 15px;} +.nav-list>.active>a,.nav-list>.active>a:hover,.nav-list>.active>a:focus{color:#ffffff;text-shadow:0 -1px 0 rgba(0, 0, 0, 0.2);background-color:#0088cc;} +.nav-list [class^="icon-"],.nav-list [class*=" icon-"]{margin-right:2px;} +.nav-list .divider{*width:100%;height:1px;margin:9px 1px;*margin:-5px 0 5px;overflow:hidden;background-color:#e5e5e5;border-bottom:1px solid #ffffff;} +.nav-tabs,.nav-pills{*zoom:1;}.nav-tabs:before,.nav-pills:before,.nav-tabs:after,.nav-pills:after{display:table;content:"";line-height:0;} +.nav-tabs:after,.nav-pills:after{clear:both;} +.nav-tabs>li,.nav-pills>li{float:left;} +.nav-tabs>li>a,.nav-pills>li>a{padding-right:12px;padding-left:12px;margin-right:2px;line-height:14px;} +.nav-tabs{border-bottom:1px solid #ddd;} +.nav-tabs>li{margin-bottom:-1px;} +.nav-tabs>li>a{padding-top:8px;padding-bottom:8px;line-height:20px;border:1px solid transparent;-webkit-border-radius:4px 4px 0 0;-moz-border-radius:4px 4px 0 0;border-radius:4px 4px 0 0;}.nav-tabs>li>a:hover,.nav-tabs>li>a:focus{border-color:#eeeeee #eeeeee #dddddd;} +.nav-tabs>.active>a,.nav-tabs>.active>a:hover,.nav-tabs>.active>a:focus{color:#555555;background-color:#ffffff;border:1px solid #ddd;border-bottom-color:transparent;cursor:default;} +.nav-pills>li>a{padding-top:8px;padding-bottom:8px;margin-top:2px;margin-bottom:2px;-webkit-border-radius:5px;-moz-border-radius:5px;border-radius:5px;} +.nav-pills>.active>a,.nav-pills>.active>a:hover,.nav-pills>.active>a:focus{color:#ffffff;background-color:#0088cc;} +.nav-stacked>li{float:none;} +.nav-stacked>li>a{margin-right:0;} +.nav-tabs.nav-stacked{border-bottom:0;} +.nav-tabs.nav-stacked>li>a{border:1px solid #ddd;-webkit-border-radius:0;-moz-border-radius:0;border-radius:0;} +.nav-tabs.nav-stacked>li:first-child>a{-webkit-border-top-right-radius:4px;-moz-border-radius-topright:4px;border-top-right-radius:4px;-webkit-border-top-left-radius:4px;-moz-border-radius-topleft:4px;border-top-left-radius:4px;} +.nav-tabs.nav-stacked>li:last-child>a{-webkit-border-bottom-right-radius:4px;-moz-border-radius-bottomright:4px;border-bottom-right-radius:4px;-webkit-border-bottom-left-radius:4px;-moz-border-radius-bottomleft:4px;border-bottom-left-radius:4px;} +.nav-tabs.nav-stacked>li>a:hover,.nav-tabs.nav-stacked>li>a:focus{border-color:#ddd;z-index:2;} +.nav-pills.nav-stacked>li>a{margin-bottom:3px;} +.nav-pills.nav-stacked>li:last-child>a{margin-bottom:1px;} +.nav-tabs .dropdown-menu{-webkit-border-radius:0 0 6px 6px;-moz-border-radius:0 0 6px 6px;border-radius:0 0 6px 6px;} +.nav-pills .dropdown-menu{-webkit-border-radius:6px;-moz-border-radius:6px;border-radius:6px;} +.nav .dropdown-toggle .caret{border-top-color:#0088cc;border-bottom-color:#0088cc;margin-top:6px;} +.nav .dropdown-toggle:hover .caret,.nav .dropdown-toggle:focus .caret{border-top-color:#005580;border-bottom-color:#005580;} +.nav-tabs .dropdown-toggle .caret{margin-top:8px;} +.nav .active .dropdown-toggle .caret{border-top-color:#fff;border-bottom-color:#fff;} +.nav-tabs .active .dropdown-toggle .caret{border-top-color:#555555;border-bottom-color:#555555;} +.nav>.dropdown.active>a:hover,.nav>.dropdown.active>a:focus{cursor:pointer;} +.nav-tabs .open .dropdown-toggle,.nav-pills .open .dropdown-toggle,.nav>li.dropdown.open.active>a:hover,.nav>li.dropdown.open.active>a:focus{color:#ffffff;background-color:#999999;border-color:#999999;} +.nav li.dropdown.open .caret,.nav li.dropdown.open.active .caret,.nav li.dropdown.open a:hover .caret,.nav li.dropdown.open a:focus .caret{border-top-color:#ffffff;border-bottom-color:#ffffff;opacity:1;filter:alpha(opacity=100);} +.tabs-stacked .open>a:hover,.tabs-stacked .open>a:focus{border-color:#999999;} +.tabbable{*zoom:1;}.tabbable:before,.tabbable:after{display:table;content:"";line-height:0;} +.tabbable:after{clear:both;} +.tab-content{overflow:auto;} +.tabs-below>.nav-tabs,.tabs-right>.nav-tabs,.tabs-left>.nav-tabs{border-bottom:0;} +.tab-content>.tab-pane,.pill-content>.pill-pane{display:none;} +.tab-content>.active,.pill-content>.active{display:block;} +.tabs-below>.nav-tabs{border-top:1px solid #ddd;} +.tabs-below>.nav-tabs>li{margin-top:-1px;margin-bottom:0;} +.tabs-below>.nav-tabs>li>a{-webkit-border-radius:0 0 4px 4px;-moz-border-radius:0 0 4px 4px;border-radius:0 0 4px 4px;}.tabs-below>.nav-tabs>li>a:hover,.tabs-below>.nav-tabs>li>a:focus{border-bottom-color:transparent;border-top-color:#ddd;} +.tabs-below>.nav-tabs>.active>a,.tabs-below>.nav-tabs>.active>a:hover,.tabs-below>.nav-tabs>.active>a:focus{border-color:transparent #ddd #ddd #ddd;} +.tabs-left>.nav-tabs>li,.tabs-right>.nav-tabs>li{float:none;} +.tabs-left>.nav-tabs>li>a,.tabs-right>.nav-tabs>li>a{min-width:74px;margin-right:0;margin-bottom:3px;} +.tabs-left>.nav-tabs{float:left;margin-right:19px;border-right:1px solid #ddd;} +.tabs-left>.nav-tabs>li>a{margin-right:-1px;-webkit-border-radius:4px 0 0 4px;-moz-border-radius:4px 0 0 4px;border-radius:4px 0 0 4px;} +.tabs-left>.nav-tabs>li>a:hover,.tabs-left>.nav-tabs>li>a:focus{border-color:#eeeeee #dddddd #eeeeee #eeeeee;} +.tabs-left>.nav-tabs .active>a,.tabs-left>.nav-tabs .active>a:hover,.tabs-left>.nav-tabs .active>a:focus{border-color:#ddd transparent #ddd #ddd;*border-right-color:#ffffff;} +.tabs-right>.nav-tabs{float:right;margin-left:19px;border-left:1px solid #ddd;} +.tabs-right>.nav-tabs>li>a{margin-left:-1px;-webkit-border-radius:0 4px 4px 0;-moz-border-radius:0 4px 4px 0;border-radius:0 4px 4px 0;} +.tabs-right>.nav-tabs>li>a:hover,.tabs-right>.nav-tabs>li>a:focus{border-color:#eeeeee #eeeeee #eeeeee #dddddd;} +.tabs-right>.nav-tabs .active>a,.tabs-right>.nav-tabs .active>a:hover,.tabs-right>.nav-tabs .active>a:focus{border-color:#ddd #ddd #ddd transparent;*border-left-color:#ffffff;} +.nav>.disabled>a{color:#999999;} +.nav>.disabled>a:hover,.nav>.disabled>a:focus{text-decoration:none;background-color:transparent;cursor:default;} +.navbar{overflow:visible;margin-bottom:20px;*position:relative;*z-index:2;} +.navbar-inner{min-height:40px;padding-left:20px;padding-right:20px;background-color:#fafafa;background-image:-moz-linear-gradient(top, #ffffff, #f2f2f2);background-image:-webkit-gradient(linear, 0 0, 0 100%, from(#ffffff), to(#f2f2f2));background-image:-webkit-linear-gradient(top, #ffffff, #f2f2f2);background-image:-o-linear-gradient(top, #ffffff, #f2f2f2);background-image:linear-gradient(to bottom, #ffffff, #f2f2f2);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffffff', endColorstr='#fff2f2f2', GradientType=0);border:1px solid #d4d4d4;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;-webkit-box-shadow:0 1px 4px rgba(0, 0, 0, 0.065);-moz-box-shadow:0 1px 4px rgba(0, 0, 0, 0.065);box-shadow:0 1px 4px rgba(0, 0, 0, 0.065);*zoom:1;}.navbar-inner:before,.navbar-inner:after{display:table;content:"";line-height:0;} +.navbar-inner:after{clear:both;} +.navbar .container{width:auto;} +.nav-collapse.collapse{height:auto;overflow:visible;} +.navbar .brand{float:left;display:block;padding:10px 20px 10px;margin-left:-20px;font-size:20px;font-weight:200;color:#777777;text-shadow:0 1px 0 #ffffff;}.navbar .brand:hover,.navbar .brand:focus{text-decoration:none;} +.navbar-text{margin-bottom:0;line-height:40px;color:#777777;} +.navbar-link{color:#777777;}.navbar-link:hover,.navbar-link:focus{color:#333333;} +.navbar .divider-vertical{height:40px;margin:0 9px;border-left:1px solid #f2f2f2;border-right:1px solid #ffffff;} +.navbar .btn,.navbar .btn-group{margin-top:5px;} +.navbar .btn-group .btn,.navbar .input-prepend .btn,.navbar .input-append .btn,.navbar .input-prepend .btn-group,.navbar .input-append .btn-group{margin-top:0;} +.navbar-form{margin-bottom:0;*zoom:1;}.navbar-form:before,.navbar-form:after{display:table;content:"";line-height:0;} +.navbar-form:after{clear:both;} +.navbar-form input,.navbar-form select,.navbar-form .radio,.navbar-form .checkbox{margin-top:5px;} +.navbar-form input,.navbar-form select,.navbar-form .btn{display:inline-block;margin-bottom:0;} +.navbar-form input[type="image"],.navbar-form input[type="checkbox"],.navbar-form input[type="radio"]{margin-top:3px;} +.navbar-form .input-append,.navbar-form .input-prepend{margin-top:5px;white-space:nowrap;}.navbar-form .input-append input,.navbar-form .input-prepend input{margin-top:0;} +.navbar-search{position:relative;float:left;margin-top:5px;margin-bottom:0;}.navbar-search .search-query{margin-bottom:0;padding:4px 14px;font-family:"Helvetica Neue",Helvetica,Arial,sans-serif;font-size:13px;font-weight:normal;line-height:1;-webkit-border-radius:15px;-moz-border-radius:15px;border-radius:15px;} +.navbar-static-top{position:static;margin-bottom:0;}.navbar-static-top .navbar-inner{-webkit-border-radius:0;-moz-border-radius:0;border-radius:0;} +.navbar-fixed-top,.navbar-fixed-bottom{position:fixed;right:0;left:0;z-index:1030;margin-bottom:0;} +.navbar-fixed-top .navbar-inner,.navbar-static-top .navbar-inner{border-width:0 0 1px;} +.navbar-fixed-bottom .navbar-inner{border-width:1px 0 0;} +.navbar-fixed-top .navbar-inner,.navbar-fixed-bottom .navbar-inner{padding-left:0;padding-right:0;-webkit-border-radius:0;-moz-border-radius:0;border-radius:0;} +.navbar-static-top .container,.navbar-fixed-top .container,.navbar-fixed-bottom .container{width:940px;} +.navbar-fixed-top{top:0;} +.navbar-fixed-top .navbar-inner,.navbar-static-top .navbar-inner{-webkit-box-shadow:0 1px 10px rgba(0,0,0,.1);-moz-box-shadow:0 1px 10px rgba(0,0,0,.1);box-shadow:0 1px 10px rgba(0,0,0,.1);} +.navbar-fixed-bottom{bottom:0;}.navbar-fixed-bottom .navbar-inner{-webkit-box-shadow:0 -1px 10px rgba(0,0,0,.1);-moz-box-shadow:0 -1px 10px rgba(0,0,0,.1);box-shadow:0 -1px 10px rgba(0,0,0,.1);} +.navbar .nav{position:relative;left:0;display:block;float:left;margin:0 10px 0 0;} +.navbar .nav.pull-right{float:right;margin-right:0;} +.navbar .nav>li{float:left;} +.navbar .nav>li>a{float:none;padding:10px 15px 10px;color:#777777;text-decoration:none;text-shadow:0 1px 0 #ffffff;} +.navbar .nav .dropdown-toggle .caret{margin-top:8px;} +.navbar .nav>li>a:focus,.navbar .nav>li>a:hover{background-color:transparent;color:#333333;text-decoration:none;} +.navbar .nav>.active>a,.navbar .nav>.active>a:hover,.navbar .nav>.active>a:focus{color:#555555;text-decoration:none;background-color:#e5e5e5;-webkit-box-shadow:inset 0 3px 8px rgba(0, 0, 0, 0.125);-moz-box-shadow:inset 0 3px 8px rgba(0, 0, 0, 0.125);box-shadow:inset 0 3px 8px rgba(0, 0, 0, 0.125);} +.navbar .btn-navbar{display:none;float:right;padding:7px 10px;margin-left:5px;margin-right:5px;color:#ffffff;text-shadow:0 -1px 0 rgba(0, 0, 0, 0.25);background-color:#ededed;background-image:-moz-linear-gradient(top, #f2f2f2, #e5e5e5);background-image:-webkit-gradient(linear, 0 0, 0 100%, from(#f2f2f2), to(#e5e5e5));background-image:-webkit-linear-gradient(top, #f2f2f2, #e5e5e5);background-image:-o-linear-gradient(top, #f2f2f2, #e5e5e5);background-image:linear-gradient(to bottom, #f2f2f2, #e5e5e5);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff2f2f2', endColorstr='#ffe5e5e5', GradientType=0);border-color:#e5e5e5 #e5e5e5 #bfbfbf;border-color:rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25);*background-color:#e5e5e5;filter:progid:DXImageTransform.Microsoft.gradient(enabled = false);-webkit-box-shadow:inset 0 1px 0 rgba(255,255,255,.1), 0 1px 0 rgba(255,255,255,.075);-moz-box-shadow:inset 0 1px 0 rgba(255,255,255,.1), 0 1px 0 rgba(255,255,255,.075);box-shadow:inset 0 1px 0 rgba(255,255,255,.1), 0 1px 0 rgba(255,255,255,.075);}.navbar .btn-navbar:hover,.navbar .btn-navbar:focus,.navbar .btn-navbar:active,.navbar .btn-navbar.active,.navbar .btn-navbar.disabled,.navbar .btn-navbar[disabled]{color:#ffffff;background-color:#e5e5e5;*background-color:#d9d9d9;} +.navbar .btn-navbar:active,.navbar .btn-navbar.active{background-color:#cccccc \9;} +.navbar .btn-navbar .icon-bar{display:block;width:18px;height:2px;background-color:#f5f5f5;-webkit-border-radius:1px;-moz-border-radius:1px;border-radius:1px;-webkit-box-shadow:0 1px 0 rgba(0, 0, 0, 0.25);-moz-box-shadow:0 1px 0 rgba(0, 0, 0, 0.25);box-shadow:0 1px 0 rgba(0, 0, 0, 0.25);} +.btn-navbar .icon-bar+.icon-bar{margin-top:3px;} +.navbar .nav>li>.dropdown-menu:before{content:'';display:inline-block;border-left:7px solid transparent;border-right:7px solid transparent;border-bottom:7px solid #ccc;border-bottom-color:rgba(0, 0, 0, 0.2);position:absolute;top:-7px;left:9px;} +.navbar .nav>li>.dropdown-menu:after{content:'';display:inline-block;border-left:6px solid transparent;border-right:6px solid transparent;border-bottom:6px solid #ffffff;position:absolute;top:-6px;left:10px;} +.navbar-fixed-bottom .nav>li>.dropdown-menu:before{border-top:7px solid #ccc;border-top-color:rgba(0, 0, 0, 0.2);border-bottom:0;bottom:-7px;top:auto;} +.navbar-fixed-bottom .nav>li>.dropdown-menu:after{border-top:6px solid #ffffff;border-bottom:0;bottom:-6px;top:auto;} +.navbar .nav li.dropdown>a:hover .caret,.navbar .nav li.dropdown>a:focus .caret{border-top-color:#333333;border-bottom-color:#333333;} +.navbar .nav li.dropdown.open>.dropdown-toggle,.navbar .nav li.dropdown.active>.dropdown-toggle,.navbar .nav li.dropdown.open.active>.dropdown-toggle{background-color:#e5e5e5;color:#555555;} +.navbar .nav li.dropdown>.dropdown-toggle .caret{border-top-color:#777777;border-bottom-color:#777777;} +.navbar .nav li.dropdown.open>.dropdown-toggle .caret,.navbar .nav li.dropdown.active>.dropdown-toggle .caret,.navbar .nav li.dropdown.open.active>.dropdown-toggle .caret{border-top-color:#555555;border-bottom-color:#555555;} +.navbar .pull-right>li>.dropdown-menu,.navbar .nav>li>.dropdown-menu.pull-right{left:auto;right:0;}.navbar .pull-right>li>.dropdown-menu:before,.navbar .nav>li>.dropdown-menu.pull-right:before{left:auto;right:12px;} +.navbar .pull-right>li>.dropdown-menu:after,.navbar .nav>li>.dropdown-menu.pull-right:after{left:auto;right:13px;} +.navbar .pull-right>li>.dropdown-menu .dropdown-menu,.navbar .nav>li>.dropdown-menu.pull-right .dropdown-menu{left:auto;right:100%;margin-left:0;margin-right:-1px;-webkit-border-radius:6px 0 6px 6px;-moz-border-radius:6px 0 6px 6px;border-radius:6px 0 6px 6px;} +.navbar-inverse .navbar-inner{background-color:#1b1b1b;background-image:-moz-linear-gradient(top, #222222, #111111);background-image:-webkit-gradient(linear, 0 0, 0 100%, from(#222222), to(#111111));background-image:-webkit-linear-gradient(top, #222222, #111111);background-image:-o-linear-gradient(top, #222222, #111111);background-image:linear-gradient(to bottom, #222222, #111111);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff222222', endColorstr='#ff111111', GradientType=0);border-color:#252525;} +.navbar-inverse .brand,.navbar-inverse .nav>li>a{color:#999999;text-shadow:0 -1px 0 rgba(0, 0, 0, 0.25);}.navbar-inverse .brand:hover,.navbar-inverse .nav>li>a:hover,.navbar-inverse .brand:focus,.navbar-inverse .nav>li>a:focus{color:#ffffff;} +.navbar-inverse .brand{color:#999999;} +.navbar-inverse .navbar-text{color:#999999;} +.navbar-inverse .nav>li>a:focus,.navbar-inverse .nav>li>a:hover{background-color:transparent;color:#ffffff;} +.navbar-inverse .nav .active>a,.navbar-inverse .nav .active>a:hover,.navbar-inverse .nav .active>a:focus{color:#ffffff;background-color:#111111;} +.navbar-inverse .navbar-link{color:#999999;}.navbar-inverse .navbar-link:hover,.navbar-inverse .navbar-link:focus{color:#ffffff;} +.navbar-inverse .divider-vertical{border-left-color:#111111;border-right-color:#222222;} +.navbar-inverse .nav li.dropdown.open>.dropdown-toggle,.navbar-inverse .nav li.dropdown.active>.dropdown-toggle,.navbar-inverse .nav li.dropdown.open.active>.dropdown-toggle{background-color:#111111;color:#ffffff;} +.navbar-inverse .nav li.dropdown>a:hover .caret,.navbar-inverse .nav li.dropdown>a:focus .caret{border-top-color:#ffffff;border-bottom-color:#ffffff;} +.navbar-inverse .nav li.dropdown>.dropdown-toggle .caret{border-top-color:#999999;border-bottom-color:#999999;} +.navbar-inverse .nav li.dropdown.open>.dropdown-toggle .caret,.navbar-inverse .nav li.dropdown.active>.dropdown-toggle .caret,.navbar-inverse .nav li.dropdown.open.active>.dropdown-toggle .caret{border-top-color:#ffffff;border-bottom-color:#ffffff;} +.navbar-inverse .navbar-search .search-query{color:#ffffff;background-color:#515151;border-color:#111111;-webkit-box-shadow:inset 0 1px 2px rgba(0,0,0,.1), 0 1px 0 rgba(255,255,255,.15);-moz-box-shadow:inset 0 1px 2px rgba(0,0,0,.1), 0 1px 0 rgba(255,255,255,.15);box-shadow:inset 0 1px 2px rgba(0,0,0,.1), 0 1px 0 rgba(255,255,255,.15);-webkit-transition:none;-moz-transition:none;-o-transition:none;transition:none;}.navbar-inverse .navbar-search .search-query:-moz-placeholder{color:#cccccc;} +.navbar-inverse .navbar-search .search-query:-ms-input-placeholder{color:#cccccc;} +.navbar-inverse .navbar-search .search-query::-webkit-input-placeholder{color:#cccccc;} +.navbar-inverse .navbar-search .search-query:focus,.navbar-inverse .navbar-search .search-query.focused{padding:5px 15px;color:#333333;text-shadow:0 1px 0 #ffffff;background-color:#ffffff;border:0;-webkit-box-shadow:0 0 3px rgba(0, 0, 0, 0.15);-moz-box-shadow:0 0 3px rgba(0, 0, 0, 0.15);box-shadow:0 0 3px rgba(0, 0, 0, 0.15);outline:0;} +.navbar-inverse .btn-navbar{color:#ffffff;text-shadow:0 -1px 0 rgba(0, 0, 0, 0.25);background-color:#0e0e0e;background-image:-moz-linear-gradient(top, #151515, #040404);background-image:-webkit-gradient(linear, 0 0, 0 100%, from(#151515), to(#040404));background-image:-webkit-linear-gradient(top, #151515, #040404);background-image:-o-linear-gradient(top, #151515, #040404);background-image:linear-gradient(to bottom, #151515, #040404);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff151515', endColorstr='#ff040404', GradientType=0);border-color:#040404 #040404 #000000;border-color:rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25);*background-color:#040404;filter:progid:DXImageTransform.Microsoft.gradient(enabled = false);}.navbar-inverse .btn-navbar:hover,.navbar-inverse .btn-navbar:focus,.navbar-inverse .btn-navbar:active,.navbar-inverse .btn-navbar.active,.navbar-inverse .btn-navbar.disabled,.navbar-inverse .btn-navbar[disabled]{color:#ffffff;background-color:#040404;*background-color:#000000;} +.navbar-inverse .btn-navbar:active,.navbar-inverse .btn-navbar.active{background-color:#000000 \9;} +.breadcrumb{padding:8px 15px;margin:0 0 20px;list-style:none;background-color:#f5f5f5;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;}.breadcrumb>li{display:inline-block;*display:inline;*zoom:1;text-shadow:0 1px 0 #ffffff;}.breadcrumb>li>.divider{padding:0 5px;color:#ccc;} +.breadcrumb>.active{color:#999999;} +.pagination{margin:20px 0;} +.pagination ul{display:inline-block;*display:inline;*zoom:1;margin-left:0;margin-bottom:0;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;-webkit-box-shadow:0 1px 2px rgba(0, 0, 0, 0.05);-moz-box-shadow:0 1px 2px rgba(0, 0, 0, 0.05);box-shadow:0 1px 2px rgba(0, 0, 0, 0.05);} +.pagination ul>li{display:inline;} +.pagination ul>li>a,.pagination ul>li>span{float:left;padding:4px 12px;line-height:20px;text-decoration:none;background-color:#ffffff;border:1px solid #dddddd;border-left-width:0;} +.pagination ul>li>a:hover,.pagination ul>li>a:focus,.pagination ul>.active>a,.pagination ul>.active>span{background-color:#f5f5f5;} +.pagination ul>.active>a,.pagination ul>.active>span{color:#999999;cursor:default;} +.pagination ul>.disabled>span,.pagination ul>.disabled>a,.pagination ul>.disabled>a:hover,.pagination ul>.disabled>a:focus{color:#999999;background-color:transparent;cursor:default;} +.pagination ul>li:first-child>a,.pagination ul>li:first-child>span{border-left-width:1px;-webkit-border-top-left-radius:4px;-moz-border-radius-topleft:4px;border-top-left-radius:4px;-webkit-border-bottom-left-radius:4px;-moz-border-radius-bottomleft:4px;border-bottom-left-radius:4px;} +.pagination ul>li:last-child>a,.pagination ul>li:last-child>span{-webkit-border-top-right-radius:4px;-moz-border-radius-topright:4px;border-top-right-radius:4px;-webkit-border-bottom-right-radius:4px;-moz-border-radius-bottomright:4px;border-bottom-right-radius:4px;} +.pagination-centered{text-align:center;} +.pagination-right{text-align:right;} +.pagination-large ul>li>a,.pagination-large ul>li>span{padding:11px 19px;font-size:17.5px;} +.pagination-large ul>li:first-child>a,.pagination-large ul>li:first-child>span{-webkit-border-top-left-radius:6px;-moz-border-radius-topleft:6px;border-top-left-radius:6px;-webkit-border-bottom-left-radius:6px;-moz-border-radius-bottomleft:6px;border-bottom-left-radius:6px;} +.pagination-large ul>li:last-child>a,.pagination-large ul>li:last-child>span{-webkit-border-top-right-radius:6px;-moz-border-radius-topright:6px;border-top-right-radius:6px;-webkit-border-bottom-right-radius:6px;-moz-border-radius-bottomright:6px;border-bottom-right-radius:6px;} +.pagination-mini ul>li:first-child>a,.pagination-small ul>li:first-child>a,.pagination-mini ul>li:first-child>span,.pagination-small ul>li:first-child>span{-webkit-border-top-left-radius:3px;-moz-border-radius-topleft:3px;border-top-left-radius:3px;-webkit-border-bottom-left-radius:3px;-moz-border-radius-bottomleft:3px;border-bottom-left-radius:3px;} +.pagination-mini ul>li:last-child>a,.pagination-small ul>li:last-child>a,.pagination-mini ul>li:last-child>span,.pagination-small ul>li:last-child>span{-webkit-border-top-right-radius:3px;-moz-border-radius-topright:3px;border-top-right-radius:3px;-webkit-border-bottom-right-radius:3px;-moz-border-radius-bottomright:3px;border-bottom-right-radius:3px;} +.pagination-small ul>li>a,.pagination-small ul>li>span{padding:2px 10px;font-size:11.9px;} +.pagination-mini ul>li>a,.pagination-mini ul>li>span{padding:0 6px;font-size:10.5px;} +.pager{margin:20px 0;list-style:none;text-align:center;*zoom:1;}.pager:before,.pager:after{display:table;content:"";line-height:0;} +.pager:after{clear:both;} +.pager li{display:inline;} +.pager li>a,.pager li>span{display:inline-block;padding:5px 14px;background-color:#fff;border:1px solid #ddd;-webkit-border-radius:15px;-moz-border-radius:15px;border-radius:15px;} +.pager li>a:hover,.pager li>a:focus{text-decoration:none;background-color:#f5f5f5;} +.pager .next>a,.pager .next>span{float:right;} +.pager .previous>a,.pager .previous>span{float:left;} +.pager .disabled>a,.pager .disabled>a:hover,.pager .disabled>a:focus,.pager .disabled>span{color:#999999;background-color:#fff;cursor:default;} +.thumbnails{margin-left:-20px;list-style:none;*zoom:1;}.thumbnails:before,.thumbnails:after{display:table;content:"";line-height:0;} +.thumbnails:after{clear:both;} +.row-fluid .thumbnails{margin-left:0;} +.thumbnails>li{float:left;margin-bottom:20px;margin-left:20px;} +.thumbnail{display:block;padding:4px;line-height:20px;border:1px solid #ddd;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;-webkit-box-shadow:0 1px 3px rgba(0, 0, 0, 0.055);-moz-box-shadow:0 1px 3px rgba(0, 0, 0, 0.055);box-shadow:0 1px 3px rgba(0, 0, 0, 0.055);-webkit-transition:all 0.2s ease-in-out;-moz-transition:all 0.2s ease-in-out;-o-transition:all 0.2s ease-in-out;transition:all 0.2s ease-in-out;} +a.thumbnail:hover,a.thumbnail:focus{border-color:#0088cc;-webkit-box-shadow:0 1px 4px rgba(0, 105, 214, 0.25);-moz-box-shadow:0 1px 4px rgba(0, 105, 214, 0.25);box-shadow:0 1px 4px rgba(0, 105, 214, 0.25);} +.thumbnail>img{display:block;max-width:100%;margin-left:auto;margin-right:auto;} +.thumbnail .caption{padding:9px;color:#555555;} +.alert{padding:8px 35px 8px 14px;margin-bottom:20px;text-shadow:0 1px 0 rgba(255, 255, 255, 0.5);background-color:#fcf8e3;border:1px solid #fbeed5;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;} +.alert,.alert h4{color:#c09853;} +.alert h4{margin:0;} +.alert .close{position:relative;top:-2px;right:-21px;line-height:20px;} +.alert-success{background-color:#dff0d8;border-color:#d6e9c6;color:#468847;} +.alert-success h4{color:#468847;} +.alert-danger,.alert-error{background-color:#f2dede;border-color:#eed3d7;color:#b94a48;} +.alert-danger h4,.alert-error h4{color:#b94a48;} +.alert-info{background-color:#d9edf7;border-color:#bce8f1;color:#3a87ad;} +.alert-info h4{color:#3a87ad;} +.alert-block{padding-top:14px;padding-bottom:14px;} +.alert-block>p,.alert-block>ul{margin-bottom:0;} +.alert-block p+p{margin-top:5px;} +@-webkit-keyframes progress-bar-stripes{from{background-position:40px 0;} to{background-position:0 0;}}@-moz-keyframes progress-bar-stripes{from{background-position:40px 0;} to{background-position:0 0;}}@-ms-keyframes progress-bar-stripes{from{background-position:40px 0;} to{background-position:0 0;}}@-o-keyframes progress-bar-stripes{from{background-position:0 0;} to{background-position:40px 0;}}@keyframes progress-bar-stripes{from{background-position:40px 0;} to{background-position:0 0;}}.progress{overflow:hidden;height:20px;margin-bottom:20px;background-color:#f7f7f7;background-image:-moz-linear-gradient(top, #f5f5f5, #f9f9f9);background-image:-webkit-gradient(linear, 0 0, 0 100%, from(#f5f5f5), to(#f9f9f9));background-image:-webkit-linear-gradient(top, #f5f5f5, #f9f9f9);background-image:-o-linear-gradient(top, #f5f5f5, #f9f9f9);background-image:linear-gradient(to bottom, #f5f5f5, #f9f9f9);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff5f5f5', endColorstr='#fff9f9f9', GradientType=0);-webkit-box-shadow:inset 0 1px 2px rgba(0, 0, 0, 0.1);-moz-box-shadow:inset 0 1px 2px rgba(0, 0, 0, 0.1);box-shadow:inset 0 1px 2px rgba(0, 0, 0, 0.1);-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;} +.progress .bar{width:0%;height:100%;color:#ffffff;float:left;font-size:12px;text-align:center;text-shadow:0 -1px 0 rgba(0, 0, 0, 0.25);background-color:#0e90d2;background-image:-moz-linear-gradient(top, #149bdf, #0480be);background-image:-webkit-gradient(linear, 0 0, 0 100%, from(#149bdf), to(#0480be));background-image:-webkit-linear-gradient(top, #149bdf, #0480be);background-image:-o-linear-gradient(top, #149bdf, #0480be);background-image:linear-gradient(to bottom, #149bdf, #0480be);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff149bdf', endColorstr='#ff0480be', GradientType=0);-webkit-box-shadow:inset 0 -1px 0 rgba(0, 0, 0, 0.15);-moz-box-shadow:inset 0 -1px 0 rgba(0, 0, 0, 0.15);box-shadow:inset 0 -1px 0 rgba(0, 0, 0, 0.15);-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box;-webkit-transition:width 0.6s ease;-moz-transition:width 0.6s ease;-o-transition:width 0.6s ease;transition:width 0.6s ease;} +.progress .bar+.bar{-webkit-box-shadow:inset 1px 0 0 rgba(0,0,0,.15), inset 0 -1px 0 rgba(0,0,0,.15);-moz-box-shadow:inset 1px 0 0 rgba(0,0,0,.15), inset 0 -1px 0 rgba(0,0,0,.15);box-shadow:inset 1px 0 0 rgba(0,0,0,.15), inset 0 -1px 0 rgba(0,0,0,.15);} +.progress-striped .bar{background-color:#149bdf;background-image:-webkit-gradient(linear, 0 100%, 100% 0, color-stop(0.25, rgba(255, 255, 255, 0.15)), color-stop(0.25, transparent), color-stop(0.5, transparent), color-stop(0.5, rgba(255, 255, 255, 0.15)), color-stop(0.75, rgba(255, 255, 255, 0.15)), color-stop(0.75, transparent), to(transparent));background-image:-webkit-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);background-image:-moz-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);background-image:-o-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);background-image:linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);-webkit-background-size:40px 40px;-moz-background-size:40px 40px;-o-background-size:40px 40px;background-size:40px 40px;} +.progress.active .bar{-webkit-animation:progress-bar-stripes 2s linear infinite;-moz-animation:progress-bar-stripes 2s linear infinite;-ms-animation:progress-bar-stripes 2s linear infinite;-o-animation:progress-bar-stripes 2s linear infinite;animation:progress-bar-stripes 2s linear infinite;} +.progress-danger .bar,.progress .bar-danger{background-color:#dd514c;background-image:-moz-linear-gradient(top, #ee5f5b, #c43c35);background-image:-webkit-gradient(linear, 0 0, 0 100%, from(#ee5f5b), to(#c43c35));background-image:-webkit-linear-gradient(top, #ee5f5b, #c43c35);background-image:-o-linear-gradient(top, #ee5f5b, #c43c35);background-image:linear-gradient(to bottom, #ee5f5b, #c43c35);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffee5f5b', endColorstr='#ffc43c35', GradientType=0);} +.progress-danger.progress-striped .bar,.progress-striped .bar-danger{background-color:#ee5f5b;background-image:-webkit-gradient(linear, 0 100%, 100% 0, color-stop(0.25, rgba(255, 255, 255, 0.15)), color-stop(0.25, transparent), color-stop(0.5, transparent), color-stop(0.5, rgba(255, 255, 255, 0.15)), color-stop(0.75, rgba(255, 255, 255, 0.15)), color-stop(0.75, transparent), to(transparent));background-image:-webkit-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);background-image:-moz-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);background-image:-o-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);background-image:linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);} +.progress-success .bar,.progress .bar-success{background-color:#5eb95e;background-image:-moz-linear-gradient(top, #62c462, #57a957);background-image:-webkit-gradient(linear, 0 0, 0 100%, from(#62c462), to(#57a957));background-image:-webkit-linear-gradient(top, #62c462, #57a957);background-image:-o-linear-gradient(top, #62c462, #57a957);background-image:linear-gradient(to bottom, #62c462, #57a957);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff62c462', endColorstr='#ff57a957', GradientType=0);} +.progress-success.progress-striped .bar,.progress-striped .bar-success{background-color:#62c462;background-image:-webkit-gradient(linear, 0 100%, 100% 0, color-stop(0.25, rgba(255, 255, 255, 0.15)), color-stop(0.25, transparent), color-stop(0.5, transparent), color-stop(0.5, rgba(255, 255, 255, 0.15)), color-stop(0.75, rgba(255, 255, 255, 0.15)), color-stop(0.75, transparent), to(transparent));background-image:-webkit-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);background-image:-moz-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);background-image:-o-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);background-image:linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);} +.progress-info .bar,.progress .bar-info{background-color:#4bb1cf;background-image:-moz-linear-gradient(top, #5bc0de, #339bb9);background-image:-webkit-gradient(linear, 0 0, 0 100%, from(#5bc0de), to(#339bb9));background-image:-webkit-linear-gradient(top, #5bc0de, #339bb9);background-image:-o-linear-gradient(top, #5bc0de, #339bb9);background-image:linear-gradient(to bottom, #5bc0de, #339bb9);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5bc0de', endColorstr='#ff339bb9', GradientType=0);} +.progress-info.progress-striped .bar,.progress-striped .bar-info{background-color:#5bc0de;background-image:-webkit-gradient(linear, 0 100%, 100% 0, color-stop(0.25, rgba(255, 255, 255, 0.15)), color-stop(0.25, transparent), color-stop(0.5, transparent), color-stop(0.5, rgba(255, 255, 255, 0.15)), color-stop(0.75, rgba(255, 255, 255, 0.15)), color-stop(0.75, transparent), to(transparent));background-image:-webkit-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);background-image:-moz-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);background-image:-o-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);background-image:linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);} +.progress-warning .bar,.progress .bar-warning{background-color:#faa732;background-image:-moz-linear-gradient(top, #fbb450, #f89406);background-image:-webkit-gradient(linear, 0 0, 0 100%, from(#fbb450), to(#f89406));background-image:-webkit-linear-gradient(top, #fbb450, #f89406);background-image:-o-linear-gradient(top, #fbb450, #f89406);background-image:linear-gradient(to bottom, #fbb450, #f89406);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fffbb450', endColorstr='#fff89406', GradientType=0);} +.progress-warning.progress-striped .bar,.progress-striped .bar-warning{background-color:#fbb450;background-image:-webkit-gradient(linear, 0 100%, 100% 0, color-stop(0.25, rgba(255, 255, 255, 0.15)), color-stop(0.25, transparent), color-stop(0.5, transparent), color-stop(0.5, rgba(255, 255, 255, 0.15)), color-stop(0.75, rgba(255, 255, 255, 0.15)), color-stop(0.75, transparent), to(transparent));background-image:-webkit-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);background-image:-moz-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);background-image:-o-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);background-image:linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);} +.hero-unit{padding:60px;margin-bottom:30px;font-size:18px;font-weight:200;line-height:30px;color:inherit;background-color:#eeeeee;-webkit-border-radius:6px;-moz-border-radius:6px;border-radius:6px;}.hero-unit h1{margin-bottom:0;font-size:60px;line-height:1;color:inherit;letter-spacing:-1px;} +.hero-unit li{line-height:30px;} +.media,.media-body{overflow:hidden;*overflow:visible;zoom:1;} +.media,.media .media{margin-top:15px;} +.media:first-child{margin-top:0;} +.media-object{display:block;} +.media-heading{margin:0 0 5px;} +.media>.pull-left{margin-right:10px;} +.media>.pull-right{margin-left:10px;} +.media-list{margin-left:0;list-style:none;} +.tooltip{position:absolute;z-index:1030;display:block;visibility:visible;font-size:11px;line-height:1.4;opacity:0;filter:alpha(opacity=0);}.tooltip.in{opacity:0.8;filter:alpha(opacity=80);} +.tooltip.top{margin-top:-3px;padding:5px 0;} +.tooltip.right{margin-left:3px;padding:0 5px;} +.tooltip.bottom{margin-top:3px;padding:5px 0;} +.tooltip.left{margin-left:-3px;padding:0 5px;} +.tooltip-inner{max-width:200px;padding:8px;color:#ffffff;text-align:center;text-decoration:none;background-color:#000000;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;} +.tooltip-arrow{position:absolute;width:0;height:0;border-color:transparent;border-style:solid;} +.tooltip.top .tooltip-arrow{bottom:0;left:50%;margin-left:-5px;border-width:5px 5px 0;border-top-color:#000000;} +.tooltip.right .tooltip-arrow{top:50%;left:0;margin-top:-5px;border-width:5px 5px 5px 0;border-right-color:#000000;} +.tooltip.left .tooltip-arrow{top:50%;right:0;margin-top:-5px;border-width:5px 0 5px 5px;border-left-color:#000000;} +.tooltip.bottom .tooltip-arrow{top:0;left:50%;margin-left:-5px;border-width:0 5px 5px;border-bottom-color:#000000;} +.popover{position:absolute;top:0;left:0;z-index:1010;display:none;max-width:276px;padding:1px;text-align:left;background-color:#ffffff;-webkit-background-clip:padding-box;-moz-background-clip:padding;background-clip:padding-box;border:1px solid #ccc;border:1px solid rgba(0, 0, 0, 0.2);-webkit-border-radius:6px;-moz-border-radius:6px;border-radius:6px;-webkit-box-shadow:0 5px 10px rgba(0, 0, 0, 0.2);-moz-box-shadow:0 5px 10px rgba(0, 0, 0, 0.2);box-shadow:0 5px 10px rgba(0, 0, 0, 0.2);white-space:normal;}.popover.top{margin-top:-10px;} +.popover.right{margin-left:10px;} +.popover.bottom{margin-top:10px;} +.popover.left{margin-left:-10px;} +.popover-title{margin:0;padding:8px 14px;font-size:14px;font-weight:normal;line-height:18px;background-color:#f7f7f7;border-bottom:1px solid #ebebeb;-webkit-border-radius:5px 5px 0 0;-moz-border-radius:5px 5px 0 0;border-radius:5px 5px 0 0;}.popover-title:empty{display:none;} +.popover-content{padding:9px 14px;} +.popover .arrow,.popover .arrow:after{position:absolute;display:block;width:0;height:0;border-color:transparent;border-style:solid;} +.popover .arrow{border-width:11px;} +.popover .arrow:after{border-width:10px;content:"";} +.popover.top .arrow{left:50%;margin-left:-11px;border-bottom-width:0;border-top-color:#999;border-top-color:rgba(0, 0, 0, 0.25);bottom:-11px;}.popover.top .arrow:after{bottom:1px;margin-left:-10px;border-bottom-width:0;border-top-color:#ffffff;} +.popover.right .arrow{top:50%;left:-11px;margin-top:-11px;border-left-width:0;border-right-color:#999;border-right-color:rgba(0, 0, 0, 0.25);}.popover.right .arrow:after{left:1px;bottom:-10px;border-left-width:0;border-right-color:#ffffff;} +.popover.bottom .arrow{left:50%;margin-left:-11px;border-top-width:0;border-bottom-color:#999;border-bottom-color:rgba(0, 0, 0, 0.25);top:-11px;}.popover.bottom .arrow:after{top:1px;margin-left:-10px;border-top-width:0;border-bottom-color:#ffffff;} +.popover.left .arrow{top:50%;right:-11px;margin-top:-11px;border-right-width:0;border-left-color:#999;border-left-color:rgba(0, 0, 0, 0.25);}.popover.left .arrow:after{right:1px;border-right-width:0;border-left-color:#ffffff;bottom:-10px;} +.modal-backdrop{position:fixed;top:0;right:0;bottom:0;left:0;z-index:1040;background-color:#000000;}.modal-backdrop.fade{opacity:0;} +.modal-backdrop,.modal-backdrop.fade.in{opacity:0.8;filter:alpha(opacity=80);} +.modal{position:fixed;top:10%;left:50%;z-index:1050;width:560px;margin-left:-280px;background-color:#ffffff;border:1px solid #999;border:1px solid rgba(0, 0, 0, 0.3);*border:1px solid #999;-webkit-border-radius:6px;-moz-border-radius:6px;border-radius:6px;-webkit-box-shadow:0 3px 7px rgba(0, 0, 0, 0.3);-moz-box-shadow:0 3px 7px rgba(0, 0, 0, 0.3);box-shadow:0 3px 7px rgba(0, 0, 0, 0.3);-webkit-background-clip:padding-box;-moz-background-clip:padding-box;background-clip:padding-box;outline:none;}.modal.fade{-webkit-transition:opacity .3s linear, top .3s ease-out;-moz-transition:opacity .3s linear, top .3s ease-out;-o-transition:opacity .3s linear, top .3s ease-out;transition:opacity .3s linear, top .3s ease-out;top:-25%;} +.modal.fade.in{top:10%;} +.modal-header{padding:9px 15px;border-bottom:1px solid #eee;}.modal-header .close{margin-top:2px;} +.modal-header h3{margin:0;line-height:30px;} +.modal-body{position:relative;overflow-y:auto;max-height:400px;padding:15px;} +.modal-form{margin-bottom:0;} +.modal-footer{padding:14px 15px 15px;margin-bottom:0;text-align:right;background-color:#f5f5f5;border-top:1px solid #ddd;-webkit-border-radius:0 0 6px 6px;-moz-border-radius:0 0 6px 6px;border-radius:0 0 6px 6px;-webkit-box-shadow:inset 0 1px 0 #ffffff;-moz-box-shadow:inset 0 1px 0 #ffffff;box-shadow:inset 0 1px 0 #ffffff;*zoom:1;}.modal-footer:before,.modal-footer:after{display:table;content:"";line-height:0;} +.modal-footer:after{clear:both;} +.modal-footer .btn+.btn{margin-left:5px;margin-bottom:0;} +.modal-footer .btn-group .btn+.btn{margin-left:-1px;} +.modal-footer .btn-block+.btn-block{margin-left:0;} +.dropup,.dropdown{position:relative;} +.dropdown-toggle{*margin-bottom:-3px;} +.dropdown-toggle:active,.open .dropdown-toggle{outline:0;} +.caret{display:inline-block;width:0;height:0;vertical-align:top;border-top:4px solid #000000;border-right:4px solid transparent;border-left:4px solid transparent;content:"";} +.dropdown .caret{margin-top:8px;margin-left:2px;} +.dropdown-menu{position:absolute;top:100%;left:0;z-index:1000;display:none;float:left;min-width:160px;padding:5px 0;margin:2px 0 0;list-style:none;background-color:#ffffff;border:1px solid #ccc;border:1px solid rgba(0, 0, 0, 0.2);*border-right-width:2px;*border-bottom-width:2px;-webkit-border-radius:6px;-moz-border-radius:6px;border-radius:6px;-webkit-box-shadow:0 5px 10px rgba(0, 0, 0, 0.2);-moz-box-shadow:0 5px 10px rgba(0, 0, 0, 0.2);box-shadow:0 5px 10px rgba(0, 0, 0, 0.2);-webkit-background-clip:padding-box;-moz-background-clip:padding;background-clip:padding-box;}.dropdown-menu.pull-right{right:0;left:auto;} +.dropdown-menu .divider{*width:100%;height:1px;margin:9px 1px;*margin:-5px 0 5px;overflow:hidden;background-color:#e5e5e5;border-bottom:1px solid #ffffff;} +.dropdown-menu>li>a{display:block;padding:3px 20px;clear:both;font-weight:normal;line-height:20px;color:#333333;white-space:nowrap;} +.dropdown-menu>li>a:hover,.dropdown-menu>li>a:focus,.dropdown-submenu:hover>a,.dropdown-submenu:focus>a{text-decoration:none;color:#ffffff;background-color:#0081c2;background-image:-moz-linear-gradient(top, #0088cc, #0077b3);background-image:-webkit-gradient(linear, 0 0, 0 100%, from(#0088cc), to(#0077b3));background-image:-webkit-linear-gradient(top, #0088cc, #0077b3);background-image:-o-linear-gradient(top, #0088cc, #0077b3);background-image:linear-gradient(to bottom, #0088cc, #0077b3);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff0088cc', endColorstr='#ff0077b3', GradientType=0);} +.dropdown-menu>.active>a,.dropdown-menu>.active>a:hover,.dropdown-menu>.active>a:focus{color:#ffffff;text-decoration:none;outline:0;background-color:#0081c2;background-image:-moz-linear-gradient(top, #0088cc, #0077b3);background-image:-webkit-gradient(linear, 0 0, 0 100%, from(#0088cc), to(#0077b3));background-image:-webkit-linear-gradient(top, #0088cc, #0077b3);background-image:-o-linear-gradient(top, #0088cc, #0077b3);background-image:linear-gradient(to bottom, #0088cc, #0077b3);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff0088cc', endColorstr='#ff0077b3', GradientType=0);} +.dropdown-menu>.disabled>a,.dropdown-menu>.disabled>a:hover,.dropdown-menu>.disabled>a:focus{color:#999999;} +.dropdown-menu>.disabled>a:hover,.dropdown-menu>.disabled>a:focus{text-decoration:none;background-color:transparent;background-image:none;filter:progid:DXImageTransform.Microsoft.gradient(enabled = false);cursor:default;} +.open{*z-index:1000;}.open>.dropdown-menu{display:block;} +.dropdown-backdrop{position:fixed;left:0;right:0;bottom:0;top:0;z-index:990;} +.pull-right>.dropdown-menu{right:0;left:auto;} +.dropup .caret,.navbar-fixed-bottom .dropdown .caret{border-top:0;border-bottom:4px solid #000000;content:"";} +.dropup .dropdown-menu,.navbar-fixed-bottom .dropdown .dropdown-menu{top:auto;bottom:100%;margin-bottom:1px;} +.dropdown-submenu{position:relative;} +.dropdown-submenu>.dropdown-menu{top:0;left:100%;margin-top:-6px;margin-left:-1px;-webkit-border-radius:0 6px 6px 6px;-moz-border-radius:0 6px 6px 6px;border-radius:0 6px 6px 6px;} +.dropdown-submenu:hover>.dropdown-menu{display:block;} +.dropup .dropdown-submenu>.dropdown-menu{top:auto;bottom:0;margin-top:0;margin-bottom:-2px;-webkit-border-radius:5px 5px 5px 0;-moz-border-radius:5px 5px 5px 0;border-radius:5px 5px 5px 0;} +.dropdown-submenu>a:after{display:block;content:" ";float:right;width:0;height:0;border-color:transparent;border-style:solid;border-width:5px 0 5px 5px;border-left-color:#cccccc;margin-top:5px;margin-right:-10px;} +.dropdown-submenu:hover>a:after{border-left-color:#ffffff;} +.dropdown-submenu.pull-left{float:none;}.dropdown-submenu.pull-left>.dropdown-menu{left:-100%;margin-left:10px;-webkit-border-radius:6px 0 6px 6px;-moz-border-radius:6px 0 6px 6px;border-radius:6px 0 6px 6px;} +.dropdown .dropdown-menu .nav-header{padding-left:20px;padding-right:20px;} +.typeahead{z-index:1051;margin-top:2px;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;} +.accordion{margin-bottom:20px;} +.accordion-group{margin-bottom:2px;border:1px solid #e5e5e5;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;} +.accordion-heading{border-bottom:0;} +.accordion-heading .accordion-toggle{display:block;padding:8px 15px;} +.accordion-toggle{cursor:pointer;} +.accordion-inner{padding:9px 15px;border-top:1px solid #e5e5e5;} +.carousel{position:relative;margin-bottom:20px;line-height:1;} +.carousel-inner{overflow:hidden;width:100%;position:relative;} +.carousel-inner>.item{display:none;position:relative;-webkit-transition:0.6s ease-in-out left;-moz-transition:0.6s ease-in-out left;-o-transition:0.6s ease-in-out left;transition:0.6s ease-in-out left;}.carousel-inner>.item>img,.carousel-inner>.item>a>img{display:block;line-height:1;} +.carousel-inner>.active,.carousel-inner>.next,.carousel-inner>.prev{display:block;} +.carousel-inner>.active{left:0;} +.carousel-inner>.next,.carousel-inner>.prev{position:absolute;top:0;width:100%;} +.carousel-inner>.next{left:100%;} +.carousel-inner>.prev{left:-100%;} +.carousel-inner>.next.left,.carousel-inner>.prev.right{left:0;} +.carousel-inner>.active.left{left:-100%;} +.carousel-inner>.active.right{left:100%;} +.carousel-control{position:absolute;top:40%;left:15px;width:40px;height:40px;margin-top:-20px;font-size:60px;font-weight:100;line-height:30px;color:#ffffff;text-align:center;background:#222222;border:3px solid #ffffff;-webkit-border-radius:23px;-moz-border-radius:23px;border-radius:23px;opacity:0.5;filter:alpha(opacity=50);}.carousel-control.right{left:auto;right:15px;} +.carousel-control:hover,.carousel-control:focus{color:#ffffff;text-decoration:none;opacity:0.9;filter:alpha(opacity=90);} +.carousel-indicators{position:absolute;top:15px;right:15px;z-index:5;margin:0;list-style:none;}.carousel-indicators li{display:block;float:left;width:10px;height:10px;margin-left:5px;text-indent:-999px;background-color:#ccc;background-color:rgba(255, 255, 255, 0.25);border-radius:5px;} +.carousel-indicators .active{background-color:#fff;} +.carousel-caption{position:absolute;left:0;right:0;bottom:0;padding:15px;background:#333333;background:rgba(0, 0, 0, 0.75);} +.carousel-caption h4,.carousel-caption p{color:#ffffff;line-height:20px;} +.carousel-caption h4{margin:0 0 5px;} +.carousel-caption p{margin-bottom:0;} +.well{min-height:20px;padding:19px;margin-bottom:20px;background-color:#f5f5f5;border:1px solid #e3e3e3;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;-webkit-box-shadow:inset 0 1px 1px rgba(0, 0, 0, 0.05);-moz-box-shadow:inset 0 1px 1px rgba(0, 0, 0, 0.05);box-shadow:inset 0 1px 1px rgba(0, 0, 0, 0.05);}.well blockquote{border-color:#ddd;border-color:rgba(0, 0, 0, 0.15);} +.well-large{padding:24px;-webkit-border-radius:6px;-moz-border-radius:6px;border-radius:6px;} +.well-small{padding:9px;-webkit-border-radius:3px;-moz-border-radius:3px;border-radius:3px;} +.close{float:right;font-size:20px;font-weight:bold;line-height:20px;color:#000000;text-shadow:0 1px 0 #ffffff;opacity:0.2;filter:alpha(opacity=20);}.close:hover,.close:focus{color:#000000;text-decoration:none;cursor:pointer;opacity:0.4;filter:alpha(opacity=40);} +button.close{padding:0;cursor:pointer;background:transparent;border:0;-webkit-appearance:none;} +.pull-right{float:right;} +.pull-left{float:left;} +.hide{display:none;} +.show{display:block;} +.invisible{visibility:hidden;} +.affix{position:fixed;} +.fade{opacity:0;-webkit-transition:opacity 0.15s linear;-moz-transition:opacity 0.15s linear;-o-transition:opacity 0.15s linear;transition:opacity 0.15s linear;}.fade.in{opacity:1;} +.collapse{position:relative;height:0;overflow:hidden;-webkit-transition:height 0.35s ease;-moz-transition:height 0.35s ease;-o-transition:height 0.35s ease;transition:height 0.35s ease;}.collapse.in{height:auto;} +@-ms-viewport{width:device-width;}.hidden{display:none;visibility:hidden;} +.visible-phone{display:none !important;} +.visible-tablet{display:none !important;} +.hidden-desktop{display:none !important;} +.visible-desktop{display:inherit !important;} +@media (min-width:768px) and (max-width:979px){.hidden-desktop{display:inherit !important;} .visible-desktop{display:none !important ;} .visible-tablet{display:inherit !important;} .hidden-tablet{display:none !important;}}@media (max-width:767px){.hidden-desktop{display:inherit !important;} .visible-desktop{display:none !important;} .visible-phone{display:inherit !important;} .hidden-phone{display:none !important;}}.visible-print{display:none !important;} +@media print{.visible-print{display:inherit !important;} .hidden-print{display:none !important;}}@media (max-width:767px){body{padding-left:20px;padding-right:20px;} .navbar-fixed-top,.navbar-fixed-bottom,.navbar-static-top{margin-left:-20px;margin-right:-20px;} .container-fluid{padding:0;} .dl-horizontal dt{float:none;clear:none;width:auto;text-align:left;} .dl-horizontal dd{margin-left:0;} .container{width:auto;} .row-fluid{width:100%;} .row,.thumbnails{margin-left:0;} .thumbnails>li{float:none;margin-left:0;} [class*="span"],.uneditable-input[class*="span"],.row-fluid [class*="span"]{float:none;display:block;width:100%;margin-left:0;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box;} .span12,.row-fluid .span12{width:100%;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box;} .row-fluid [class*="offset"]:first-child{margin-left:0;} .input-large,.input-xlarge,.input-xxlarge,input[class*="span"],select[class*="span"],textarea[class*="span"],.uneditable-input{display:block;width:100%;min-height:30px;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box;} .input-prepend input,.input-append input,.input-prepend input[class*="span"],.input-append input[class*="span"]{display:inline-block;width:auto;} .controls-row [class*="span"]+[class*="span"]{margin-left:0;} .modal{position:fixed;top:20px;left:20px;right:20px;width:auto;margin:0;}.modal.fade{top:-100px;} .modal.fade.in{top:20px;}}@media (max-width:480px){.nav-collapse{-webkit-transform:translate3d(0, 0, 0);} .page-header h1 small{display:block;line-height:20px;} input[type="checkbox"],input[type="radio"]{border:1px solid #ccc;} .form-horizontal .control-label{float:none;width:auto;padding-top:0;text-align:left;} .form-horizontal .controls{margin-left:0;} .form-horizontal .control-list{padding-top:0;} .form-horizontal .form-actions{padding-left:10px;padding-right:10px;} .media .pull-left,.media .pull-right{float:none;display:block;margin-bottom:10px;} .media-object{margin-right:0;margin-left:0;} .modal{top:10px;left:10px;right:10px;} .modal-header .close{padding:10px;margin:-10px;} .carousel-caption{position:static;}}@media (min-width:768px) and (max-width:979px){.row{margin-left:-20px;*zoom:1;}.row:before,.row:after{display:table;content:"";line-height:0;} .row:after{clear:both;} [class*="span"]{float:left;min-height:1px;margin-left:20px;} .container,.navbar-static-top .container,.navbar-fixed-top .container,.navbar-fixed-bottom .container{width:724px;} .span12{width:724px;} .span11{width:662px;} .span10{width:600px;} .span9{width:538px;} .span8{width:476px;} .span7{width:414px;} .span6{width:352px;} .span5{width:290px;} .span4{width:228px;} .span3{width:166px;} .span2{width:104px;} .span1{width:42px;} .offset12{margin-left:764px;} .offset11{margin-left:702px;} .offset10{margin-left:640px;} .offset9{margin-left:578px;} .offset8{margin-left:516px;} .offset7{margin-left:454px;} .offset6{margin-left:392px;} .offset5{margin-left:330px;} .offset4{margin-left:268px;} .offset3{margin-left:206px;} .offset2{margin-left:144px;} .offset1{margin-left:82px;} .row-fluid{width:100%;*zoom:1;}.row-fluid:before,.row-fluid:after{display:table;content:"";line-height:0;} .row-fluid:after{clear:both;} .row-fluid [class*="span"]{display:block;width:100%;min-height:30px;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box;float:left;margin-left:2.7624309392265194%;*margin-left:2.709239449864817%;} .row-fluid [class*="span"]:first-child{margin-left:0;} .row-fluid .controls-row [class*="span"]+[class*="span"]{margin-left:2.7624309392265194%;} .row-fluid .span12{width:100%;*width:99.94680851063829%;} .row-fluid .span11{width:91.43646408839778%;*width:91.38327259903608%;} .row-fluid .span10{width:82.87292817679558%;*width:82.81973668743387%;} .row-fluid .span9{width:74.30939226519337%;*width:74.25620077583166%;} .row-fluid .span8{width:65.74585635359117%;*width:65.69266486422946%;} .row-fluid .span7{width:57.18232044198895%;*width:57.12912895262725%;} .row-fluid .span6{width:48.61878453038674%;*width:48.56559304102504%;} .row-fluid .span5{width:40.05524861878453%;*width:40.00205712942283%;} .row-fluid .span4{width:31.491712707182323%;*width:31.43852121782062%;} .row-fluid .span3{width:22.92817679558011%;*width:22.87498530621841%;} .row-fluid .span2{width:14.3646408839779%;*width:14.311449394616199%;} .row-fluid .span1{width:5.801104972375691%;*width:5.747913483013988%;} .row-fluid .offset12{margin-left:105.52486187845304%;*margin-left:105.41847889972962%;} .row-fluid .offset12:first-child{margin-left:102.76243093922652%;*margin-left:102.6560479605031%;} .row-fluid .offset11{margin-left:96.96132596685082%;*margin-left:96.8549429881274%;} .row-fluid .offset11:first-child{margin-left:94.1988950276243%;*margin-left:94.09251204890089%;} .row-fluid .offset10{margin-left:88.39779005524862%;*margin-left:88.2914070765252%;} .row-fluid .offset10:first-child{margin-left:85.6353591160221%;*margin-left:85.52897613729868%;} .row-fluid .offset9{margin-left:79.8342541436464%;*margin-left:79.72787116492299%;} .row-fluid .offset9:first-child{margin-left:77.07182320441989%;*margin-left:76.96544022569647%;} .row-fluid .offset8{margin-left:71.2707182320442%;*margin-left:71.16433525332079%;} .row-fluid .offset8:first-child{margin-left:68.50828729281768%;*margin-left:68.40190431409427%;} .row-fluid .offset7{margin-left:62.70718232044199%;*margin-left:62.600799341718584%;} .row-fluid .offset7:first-child{margin-left:59.94475138121547%;*margin-left:59.838368402492065%;} .row-fluid .offset6{margin-left:54.14364640883978%;*margin-left:54.037263430116376%;} .row-fluid .offset6:first-child{margin-left:51.38121546961326%;*margin-left:51.27483249088986%;} .row-fluid .offset5{margin-left:45.58011049723757%;*margin-left:45.47372751851417%;} .row-fluid .offset5:first-child{margin-left:42.81767955801105%;*margin-left:42.71129657928765%;} .row-fluid .offset4{margin-left:37.01657458563536%;*margin-left:36.91019160691196%;} .row-fluid .offset4:first-child{margin-left:34.25414364640884%;*margin-left:34.14776066768544%;} .row-fluid .offset3{margin-left:28.45303867403315%;*margin-left:28.346655695309746%;} .row-fluid .offset3:first-child{margin-left:25.69060773480663%;*margin-left:25.584224756083227%;} .row-fluid .offset2{margin-left:19.88950276243094%;*margin-left:19.783119783707537%;} .row-fluid .offset2:first-child{margin-left:17.12707182320442%;*margin-left:17.02068884448102%;} .row-fluid .offset1{margin-left:11.32596685082873%;*margin-left:11.219583872105325%;} .row-fluid .offset1:first-child{margin-left:8.56353591160221%;*margin-left:8.457152932878806%;} input,textarea,.uneditable-input{margin-left:0;} .controls-row [class*="span"]+[class*="span"]{margin-left:20px;} input.span12,textarea.span12,.uneditable-input.span12{width:710px;} input.span11,textarea.span11,.uneditable-input.span11{width:648px;} input.span10,textarea.span10,.uneditable-input.span10{width:586px;} input.span9,textarea.span9,.uneditable-input.span9{width:524px;} input.span8,textarea.span8,.uneditable-input.span8{width:462px;} input.span7,textarea.span7,.uneditable-input.span7{width:400px;} input.span6,textarea.span6,.uneditable-input.span6{width:338px;} input.span5,textarea.span5,.uneditable-input.span5{width:276px;} input.span4,textarea.span4,.uneditable-input.span4{width:214px;} input.span3,textarea.span3,.uneditable-input.span3{width:152px;} input.span2,textarea.span2,.uneditable-input.span2{width:90px;} input.span1,textarea.span1,.uneditable-input.span1{width:28px;}}@media (min-width:1200px){.row{margin-left:-30px;*zoom:1;}.row:before,.row:after{display:table;content:"";line-height:0;} .row:after{clear:both;} [class*="span"]{float:left;min-height:1px;margin-left:30px;} .container,.navbar-static-top .container,.navbar-fixed-top .container,.navbar-fixed-bottom .container{width:1170px;} .span12{width:1170px;} .span11{width:1070px;} .span10{width:970px;} .span9{width:870px;} .span8{width:770px;} .span7{width:670px;} .span6{width:570px;} .span5{width:470px;} .span4{width:370px;} .span3{width:270px;} .span2{width:170px;} .span1{width:70px;} .offset12{margin-left:1230px;} .offset11{margin-left:1130px;} .offset10{margin-left:1030px;} .offset9{margin-left:930px;} .offset8{margin-left:830px;} .offset7{margin-left:730px;} .offset6{margin-left:630px;} .offset5{margin-left:530px;} .offset4{margin-left:430px;} .offset3{margin-left:330px;} .offset2{margin-left:230px;} .offset1{margin-left:130px;} .row-fluid{width:100%;*zoom:1;}.row-fluid:before,.row-fluid:after{display:table;content:"";line-height:0;} .row-fluid:after{clear:both;} .row-fluid [class*="span"]{display:block;width:100%;min-height:30px;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box;float:left;margin-left:2.564102564102564%;*margin-left:2.5109110747408616%;} .row-fluid [class*="span"]:first-child{margin-left:0;} .row-fluid .controls-row [class*="span"]+[class*="span"]{margin-left:2.564102564102564%;} .row-fluid .span12{width:100%;*width:99.94680851063829%;} .row-fluid .span11{width:91.45299145299145%;*width:91.39979996362975%;} .row-fluid .span10{width:82.90598290598291%;*width:82.8527914166212%;} .row-fluid .span9{width:74.35897435897436%;*width:74.30578286961266%;} .row-fluid .span8{width:65.81196581196582%;*width:65.75877432260411%;} .row-fluid .span7{width:57.26495726495726%;*width:57.21176577559556%;} .row-fluid .span6{width:48.717948717948715%;*width:48.664757228587014%;} .row-fluid .span5{width:40.17094017094017%;*width:40.11774868157847%;} .row-fluid .span4{width:31.623931623931625%;*width:31.570740134569924%;} .row-fluid .span3{width:23.076923076923077%;*width:23.023731587561375%;} .row-fluid .span2{width:14.52991452991453%;*width:14.476723040552828%;} .row-fluid .span1{width:5.982905982905983%;*width:5.929714493544281%;} .row-fluid .offset12{margin-left:105.12820512820512%;*margin-left:105.02182214948171%;} .row-fluid .offset12:first-child{margin-left:102.56410256410257%;*margin-left:102.45771958537915%;} .row-fluid .offset11{margin-left:96.58119658119658%;*margin-left:96.47481360247316%;} .row-fluid .offset11:first-child{margin-left:94.01709401709402%;*margin-left:93.91071103837061%;} .row-fluid .offset10{margin-left:88.03418803418803%;*margin-left:87.92780505546462%;} .row-fluid .offset10:first-child{margin-left:85.47008547008548%;*margin-left:85.36370249136206%;} .row-fluid .offset9{margin-left:79.48717948717949%;*margin-left:79.38079650845607%;} .row-fluid .offset9:first-child{margin-left:76.92307692307693%;*margin-left:76.81669394435352%;} .row-fluid .offset8{margin-left:70.94017094017094%;*margin-left:70.83378796144753%;} .row-fluid .offset8:first-child{margin-left:68.37606837606839%;*margin-left:68.26968539734497%;} .row-fluid .offset7{margin-left:62.393162393162385%;*margin-left:62.28677941443899%;} .row-fluid .offset7:first-child{margin-left:59.82905982905982%;*margin-left:59.72267685033642%;} .row-fluid .offset6{margin-left:53.84615384615384%;*margin-left:53.739770867430444%;} .row-fluid .offset6:first-child{margin-left:51.28205128205128%;*margin-left:51.175668303327875%;} .row-fluid .offset5{margin-left:45.299145299145295%;*margin-left:45.1927623204219%;} .row-fluid .offset5:first-child{margin-left:42.73504273504273%;*margin-left:42.62865975631933%;} .row-fluid .offset4{margin-left:36.75213675213675%;*margin-left:36.645753773413354%;} .row-fluid .offset4:first-child{margin-left:34.18803418803419%;*margin-left:34.081651209310785%;} .row-fluid .offset3{margin-left:28.205128205128204%;*margin-left:28.0987452264048%;} .row-fluid .offset3:first-child{margin-left:25.641025641025642%;*margin-left:25.53464266230224%;} .row-fluid .offset2{margin-left:19.65811965811966%;*margin-left:19.551736679396257%;} .row-fluid .offset2:first-child{margin-left:17.094017094017094%;*margin-left:16.98763411529369%;} .row-fluid .offset1{margin-left:11.11111111111111%;*margin-left:11.004728132387708%;} .row-fluid .offset1:first-child{margin-left:8.547008547008547%;*margin-left:8.440625568285142%;} input,textarea,.uneditable-input{margin-left:0;} .controls-row [class*="span"]+[class*="span"]{margin-left:30px;} input.span12,textarea.span12,.uneditable-input.span12{width:1156px;} input.span11,textarea.span11,.uneditable-input.span11{width:1056px;} input.span10,textarea.span10,.uneditable-input.span10{width:956px;} input.span9,textarea.span9,.uneditable-input.span9{width:856px;} input.span8,textarea.span8,.uneditable-input.span8{width:756px;} input.span7,textarea.span7,.uneditable-input.span7{width:656px;} input.span6,textarea.span6,.uneditable-input.span6{width:556px;} input.span5,textarea.span5,.uneditable-input.span5{width:456px;} input.span4,textarea.span4,.uneditable-input.span4{width:356px;} input.span3,textarea.span3,.uneditable-input.span3{width:256px;} input.span2,textarea.span2,.uneditable-input.span2{width:156px;} input.span1,textarea.span1,.uneditable-input.span1{width:56px;} .thumbnails{margin-left:-30px;} .thumbnails>li{margin-left:30px;} .row-fluid .thumbnails{margin-left:0;}}@media (max-width:979px){body{padding-top:0;} .navbar-fixed-top,.navbar-fixed-bottom{position:static;} .navbar-fixed-top{margin-bottom:20px;} .navbar-fixed-bottom{margin-top:20px;} .navbar-fixed-top .navbar-inner,.navbar-fixed-bottom .navbar-inner{padding:5px;} .navbar .container{width:auto;padding:0;} .navbar .brand{padding-left:10px;padding-right:10px;margin:0 0 0 -5px;} .nav-collapse{clear:both;} .nav-collapse .nav{float:none;margin:0 0 10px;} .nav-collapse .nav>li{float:none;} .nav-collapse .nav>li>a{margin-bottom:2px;} .nav-collapse .nav>.divider-vertical{display:none;} .nav-collapse .nav .nav-header{color:#777777;text-shadow:none;} .nav-collapse .nav>li>a,.nav-collapse .dropdown-menu a{padding:9px 15px;font-weight:bold;color:#777777;-webkit-border-radius:3px;-moz-border-radius:3px;border-radius:3px;} .nav-collapse .btn{padding:4px 10px 4px;font-weight:normal;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;} .nav-collapse .dropdown-menu li+li a{margin-bottom:2px;} .nav-collapse .nav>li>a:hover,.nav-collapse .nav>li>a:focus,.nav-collapse .dropdown-menu a:hover,.nav-collapse .dropdown-menu a:focus{background-color:#f2f2f2;} .navbar-inverse .nav-collapse .nav>li>a,.navbar-inverse .nav-collapse .dropdown-menu a{color:#999999;} .navbar-inverse .nav-collapse .nav>li>a:hover,.navbar-inverse .nav-collapse .nav>li>a:focus,.navbar-inverse .nav-collapse .dropdown-menu a:hover,.navbar-inverse .nav-collapse .dropdown-menu a:focus{background-color:#111111;} .nav-collapse.in .btn-group{margin-top:5px;padding:0;} .nav-collapse .dropdown-menu{position:static;top:auto;left:auto;float:none;display:none;max-width:none;margin:0 15px;padding:0;background-color:transparent;border:none;-webkit-border-radius:0;-moz-border-radius:0;border-radius:0;-webkit-box-shadow:none;-moz-box-shadow:none;box-shadow:none;} .nav-collapse .open>.dropdown-menu{display:block;} .nav-collapse .dropdown-menu:before,.nav-collapse .dropdown-menu:after{display:none;} .nav-collapse .dropdown-menu .divider{display:none;} .nav-collapse .nav>li>.dropdown-menu:before,.nav-collapse .nav>li>.dropdown-menu:after{display:none;} .nav-collapse .navbar-form,.nav-collapse .navbar-search{float:none;padding:10px 15px;margin:10px 0;border-top:1px solid #f2f2f2;border-bottom:1px solid #f2f2f2;-webkit-box-shadow:inset 0 1px 0 rgba(255,255,255,.1), 0 1px 0 rgba(255,255,255,.1);-moz-box-shadow:inset 0 1px 0 rgba(255,255,255,.1), 0 1px 0 rgba(255,255,255,.1);box-shadow:inset 0 1px 0 rgba(255,255,255,.1), 0 1px 0 rgba(255,255,255,.1);} .navbar-inverse .nav-collapse .navbar-form,.navbar-inverse .nav-collapse .navbar-search{border-top-color:#111111;border-bottom-color:#111111;} .navbar .nav-collapse .nav.pull-right{float:none;margin-left:0;} .nav-collapse,.nav-collapse.collapse{overflow:hidden;height:0;} .navbar .btn-navbar{display:block;} .navbar-static .navbar-inner{padding-left:10px;padding-right:10px;}}@media (min-width:980px){.nav-collapse.collapse{height:auto !important;overflow:visible !important;}} diff --git a/docs/ModuleApi/css/font-awesome.min.css b/docs/ModuleApi/css/font-awesome.min.css new file mode 100644 index 0000000..866437f --- /dev/null +++ b/docs/ModuleApi/css/font-awesome.min.css @@ -0,0 +1,403 @@ +@font-face{font-family:'FontAwesome';src:url('../font/fontawesome-webfont.eot?v=3.2.1');src:url('../font/fontawesome-webfont.eot?#iefix&v=3.2.1') format('embedded-opentype'),url('../font/fontawesome-webfont.woff?v=3.2.1') format('woff'),url('../font/fontawesome-webfont.ttf?v=3.2.1') format('truetype'),url('../font/fontawesome-webfont.svg#fontawesomeregular?v=3.2.1') format('svg');font-weight:normal;font-style:normal;}[class^="icon-"],[class*=" icon-"]{font-family:FontAwesome;font-weight:normal;font-style:normal;text-decoration:inherit;-webkit-font-smoothing:antialiased;*margin-right:.3em;} +[class^="icon-"]:before,[class*=" icon-"]:before{text-decoration:inherit;display:inline-block;speak:none;} +.icon-large:before{vertical-align:-10%;font-size:1.3333333333333333em;} +a [class^="icon-"],a [class*=" icon-"]{display:inline;} +[class^="icon-"].icon-fixed-width,[class*=" icon-"].icon-fixed-width{display:inline-block;width:1.1428571428571428em;text-align:right;padding-right:0.2857142857142857em;}[class^="icon-"].icon-fixed-width.icon-large,[class*=" icon-"].icon-fixed-width.icon-large{width:1.4285714285714286em;} +.icons-ul{margin-left:2.142857142857143em;list-style-type:none;}.icons-ul>li{position:relative;} +.icons-ul .icon-li{position:absolute;left:-2.142857142857143em;width:2.142857142857143em;text-align:center;line-height:inherit;} +[class^="icon-"].hide,[class*=" icon-"].hide{display:none;} +.icon-muted{color:#eeeeee;} +.icon-light{color:#ffffff;} +.icon-dark{color:#333333;} +.icon-border{border:solid 1px #eeeeee;padding:.2em .25em .15em;-webkit-border-radius:3px;-moz-border-radius:3px;border-radius:3px;} +.icon-2x{font-size:2em;}.icon-2x.icon-border{border-width:2px;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;} +.icon-3x{font-size:3em;}.icon-3x.icon-border{border-width:3px;-webkit-border-radius:5px;-moz-border-radius:5px;border-radius:5px;} +.icon-4x{font-size:4em;}.icon-4x.icon-border{border-width:4px;-webkit-border-radius:6px;-moz-border-radius:6px;border-radius:6px;} +.icon-5x{font-size:5em;}.icon-5x.icon-border{border-width:5px;-webkit-border-radius:7px;-moz-border-radius:7px;border-radius:7px;} +.pull-right{float:right;} +.pull-left{float:left;} +[class^="icon-"].pull-left,[class*=" icon-"].pull-left{margin-right:.3em;} +[class^="icon-"].pull-right,[class*=" icon-"].pull-right{margin-left:.3em;} +[class^="icon-"],[class*=" icon-"]{display:inline;width:auto;height:auto;line-height:normal;vertical-align:baseline;background-image:none;background-position:0% 0%;background-repeat:repeat;margin-top:0;} +.icon-white,.nav-pills>.active>a>[class^="icon-"],.nav-pills>.active>a>[class*=" icon-"],.nav-list>.active>a>[class^="icon-"],.nav-list>.active>a>[class*=" icon-"],.navbar-inverse .nav>.active>a>[class^="icon-"],.navbar-inverse .nav>.active>a>[class*=" icon-"],.dropdown-menu>li>a:hover>[class^="icon-"],.dropdown-menu>li>a:hover>[class*=" icon-"],.dropdown-menu>.active>a>[class^="icon-"],.dropdown-menu>.active>a>[class*=" icon-"],.dropdown-submenu:hover>a>[class^="icon-"],.dropdown-submenu:hover>a>[class*=" icon-"]{background-image:none;} +.btn [class^="icon-"].icon-large,.nav [class^="icon-"].icon-large,.btn [class*=" icon-"].icon-large,.nav [class*=" icon-"].icon-large{line-height:.9em;} +.btn [class^="icon-"].icon-spin,.nav [class^="icon-"].icon-spin,.btn [class*=" icon-"].icon-spin,.nav [class*=" icon-"].icon-spin{display:inline-block;} +.nav-tabs [class^="icon-"],.nav-pills [class^="icon-"],.nav-tabs [class*=" icon-"],.nav-pills [class*=" icon-"],.nav-tabs [class^="icon-"].icon-large,.nav-pills [class^="icon-"].icon-large,.nav-tabs [class*=" icon-"].icon-large,.nav-pills [class*=" icon-"].icon-large{line-height:.9em;} +.btn [class^="icon-"].pull-left.icon-2x,.btn [class*=" icon-"].pull-left.icon-2x,.btn [class^="icon-"].pull-right.icon-2x,.btn [class*=" icon-"].pull-right.icon-2x{margin-top:.18em;} +.btn [class^="icon-"].icon-spin.icon-large,.btn [class*=" icon-"].icon-spin.icon-large{line-height:.8em;} +.btn.btn-small [class^="icon-"].pull-left.icon-2x,.btn.btn-small [class*=" icon-"].pull-left.icon-2x,.btn.btn-small [class^="icon-"].pull-right.icon-2x,.btn.btn-small [class*=" icon-"].pull-right.icon-2x{margin-top:.25em;} +.btn.btn-large [class^="icon-"],.btn.btn-large [class*=" icon-"]{margin-top:0;}.btn.btn-large [class^="icon-"].pull-left.icon-2x,.btn.btn-large [class*=" icon-"].pull-left.icon-2x,.btn.btn-large [class^="icon-"].pull-right.icon-2x,.btn.btn-large [class*=" icon-"].pull-right.icon-2x{margin-top:.05em;} +.btn.btn-large [class^="icon-"].pull-left.icon-2x,.btn.btn-large [class*=" icon-"].pull-left.icon-2x{margin-right:.2em;} +.btn.btn-large [class^="icon-"].pull-right.icon-2x,.btn.btn-large [class*=" icon-"].pull-right.icon-2x{margin-left:.2em;} +.nav-list [class^="icon-"],.nav-list [class*=" icon-"]{line-height:inherit;} +.icon-stack{position:relative;display:inline-block;width:2em;height:2em;line-height:2em;vertical-align:-35%;}.icon-stack [class^="icon-"],.icon-stack [class*=" icon-"]{display:block;text-align:center;position:absolute;width:100%;height:100%;font-size:1em;line-height:inherit;*line-height:2em;} +.icon-stack .icon-stack-base{font-size:2em;*line-height:1em;} +.icon-spin{display:inline-block;-moz-animation:spin 2s infinite linear;-o-animation:spin 2s infinite linear;-webkit-animation:spin 2s infinite linear;animation:spin 2s infinite linear;} +a .icon-stack,a .icon-spin{display:inline-block;text-decoration:none;} +@-moz-keyframes spin{0%{-moz-transform:rotate(0deg);} 100%{-moz-transform:rotate(359deg);}}@-webkit-keyframes spin{0%{-webkit-transform:rotate(0deg);} 100%{-webkit-transform:rotate(359deg);}}@-o-keyframes spin{0%{-o-transform:rotate(0deg);} 100%{-o-transform:rotate(359deg);}}@-ms-keyframes spin{0%{-ms-transform:rotate(0deg);} 100%{-ms-transform:rotate(359deg);}}@keyframes spin{0%{transform:rotate(0deg);} 100%{transform:rotate(359deg);}}.icon-rotate-90:before{-webkit-transform:rotate(90deg);-moz-transform:rotate(90deg);-ms-transform:rotate(90deg);-o-transform:rotate(90deg);transform:rotate(90deg);filter:progid:DXImageTransform.Microsoft.BasicImage(rotation=1);} +.icon-rotate-180:before{-webkit-transform:rotate(180deg);-moz-transform:rotate(180deg);-ms-transform:rotate(180deg);-o-transform:rotate(180deg);transform:rotate(180deg);filter:progid:DXImageTransform.Microsoft.BasicImage(rotation=2);} +.icon-rotate-270:before{-webkit-transform:rotate(270deg);-moz-transform:rotate(270deg);-ms-transform:rotate(270deg);-o-transform:rotate(270deg);transform:rotate(270deg);filter:progid:DXImageTransform.Microsoft.BasicImage(rotation=3);} +.icon-flip-horizontal:before{-webkit-transform:scale(-1, 1);-moz-transform:scale(-1, 1);-ms-transform:scale(-1, 1);-o-transform:scale(-1, 1);transform:scale(-1, 1);} +.icon-flip-vertical:before{-webkit-transform:scale(1, -1);-moz-transform:scale(1, -1);-ms-transform:scale(1, -1);-o-transform:scale(1, -1);transform:scale(1, -1);} +a .icon-rotate-90:before,a .icon-rotate-180:before,a .icon-rotate-270:before,a .icon-flip-horizontal:before,a .icon-flip-vertical:before{display:inline-block;} +.icon-glass:before{content:"\f000";} +.icon-music:before{content:"\f001";} +.icon-search:before{content:"\f002";} +.icon-envelope-alt:before{content:"\f003";} +.icon-heart:before{content:"\f004";} +.icon-star:before{content:"\f005";} +.icon-star-empty:before{content:"\f006";} +.icon-user:before{content:"\f007";} +.icon-film:before{content:"\f008";} +.icon-th-large:before{content:"\f009";} +.icon-th:before{content:"\f00a";} +.icon-th-list:before{content:"\f00b";} +.icon-ok:before{content:"\f00c";} +.icon-remove:before{content:"\f00d";} +.icon-zoom-in:before{content:"\f00e";} +.icon-zoom-out:before{content:"\f010";} +.icon-power-off:before,.icon-off:before{content:"\f011";} +.icon-signal:before{content:"\f012";} +.icon-gear:before,.icon-cog:before{content:"\f013";} +.icon-trash:before{content:"\f014";} +.icon-home:before{content:"\f015";} +.icon-file-alt:before{content:"\f016";} +.icon-time:before{content:"\f017";} +.icon-road:before{content:"\f018";} +.icon-download-alt:before{content:"\f019";} +.icon-download:before{content:"\f01a";} +.icon-upload:before{content:"\f01b";} +.icon-inbox:before{content:"\f01c";} +.icon-play-circle:before{content:"\f01d";} +.icon-rotate-right:before,.icon-repeat:before{content:"\f01e";} +.icon-refresh:before{content:"\f021";} +.icon-list-alt:before{content:"\f022";} +.icon-lock:before{content:"\f023";} +.icon-flag:before{content:"\f024";} +.icon-headphones:before{content:"\f025";} +.icon-volume-off:before{content:"\f026";} +.icon-volume-down:before{content:"\f027";} +.icon-volume-up:before{content:"\f028";} +.icon-qrcode:before{content:"\f029";} +.icon-barcode:before{content:"\f02a";} +.icon-tag:before{content:"\f02b";} +.icon-tags:before{content:"\f02c";} +.icon-book:before{content:"\f02d";} +.icon-bookmark:before{content:"\f02e";} +.icon-print:before{content:"\f02f";} +.icon-camera:before{content:"\f030";} +.icon-font:before{content:"\f031";} +.icon-bold:before{content:"\f032";} +.icon-italic:before{content:"\f033";} +.icon-text-height:before{content:"\f034";} +.icon-text-width:before{content:"\f035";} +.icon-align-left:before{content:"\f036";} +.icon-align-center:before{content:"\f037";} +.icon-align-right:before{content:"\f038";} +.icon-align-justify:before{content:"\f039";} +.icon-list:before{content:"\f03a";} +.icon-indent-left:before{content:"\f03b";} +.icon-indent-right:before{content:"\f03c";} +.icon-facetime-video:before{content:"\f03d";} +.icon-picture:before{content:"\f03e";} +.icon-pencil:before{content:"\f040";} +.icon-map-marker:before{content:"\f041";} +.icon-adjust:before{content:"\f042";} +.icon-tint:before{content:"\f043";} +.icon-edit:before{content:"\f044";} +.icon-share:before{content:"\f045";} +.icon-check:before{content:"\f046";} +.icon-move:before{content:"\f047";} +.icon-step-backward:before{content:"\f048";} +.icon-fast-backward:before{content:"\f049";} +.icon-backward:before{content:"\f04a";} +.icon-play:before{content:"\f04b";} +.icon-pause:before{content:"\f04c";} +.icon-stop:before{content:"\f04d";} +.icon-forward:before{content:"\f04e";} +.icon-fast-forward:before{content:"\f050";} +.icon-step-forward:before{content:"\f051";} +.icon-eject:before{content:"\f052";} +.icon-chevron-left:before{content:"\f053";} +.icon-chevron-right:before{content:"\f054";} +.icon-plus-sign:before{content:"\f055";} +.icon-minus-sign:before{content:"\f056";} +.icon-remove-sign:before{content:"\f057";} +.icon-ok-sign:before{content:"\f058";} +.icon-question-sign:before{content:"\f059";} +.icon-info-sign:before{content:"\f05a";} +.icon-screenshot:before{content:"\f05b";} +.icon-remove-circle:before{content:"\f05c";} +.icon-ok-circle:before{content:"\f05d";} +.icon-ban-circle:before{content:"\f05e";} +.icon-arrow-left:before{content:"\f060";} +.icon-arrow-right:before{content:"\f061";} +.icon-arrow-up:before{content:"\f062";} +.icon-arrow-down:before{content:"\f063";} +.icon-mail-forward:before,.icon-share-alt:before{content:"\f064";} +.icon-resize-full:before{content:"\f065";} +.icon-resize-small:before{content:"\f066";} +.icon-plus:before{content:"\f067";} +.icon-minus:before{content:"\f068";} +.icon-asterisk:before{content:"\f069";} +.icon-exclamation-sign:before{content:"\f06a";} +.icon-gift:before{content:"\f06b";} +.icon-leaf:before{content:"\f06c";} +.icon-fire:before{content:"\f06d";} +.icon-eye-open:before{content:"\f06e";} +.icon-eye-close:before{content:"\f070";} +.icon-warning-sign:before{content:"\f071";} +.icon-plane:before{content:"\f072";} +.icon-calendar:before{content:"\f073";} +.icon-random:before{content:"\f074";} +.icon-comment:before{content:"\f075";} +.icon-magnet:before{content:"\f076";} +.icon-chevron-up:before{content:"\f077";} +.icon-chevron-down:before{content:"\f078";} +.icon-retweet:before{content:"\f079";} +.icon-shopping-cart:before{content:"\f07a";} +.icon-folder-close:before{content:"\f07b";} +.icon-folder-open:before{content:"\f07c";} +.icon-resize-vertical:before{content:"\f07d";} +.icon-resize-horizontal:before{content:"\f07e";} +.icon-bar-chart:before{content:"\f080";} +.icon-twitter-sign:before{content:"\f081";} +.icon-facebook-sign:before{content:"\f082";} +.icon-camera-retro:before{content:"\f083";} +.icon-key:before{content:"\f084";} +.icon-gears:before,.icon-cogs:before{content:"\f085";} +.icon-comments:before{content:"\f086";} +.icon-thumbs-up-alt:before{content:"\f087";} +.icon-thumbs-down-alt:before{content:"\f088";} +.icon-star-half:before{content:"\f089";} +.icon-heart-empty:before{content:"\f08a";} +.icon-signout:before{content:"\f08b";} +.icon-linkedin-sign:before{content:"\f08c";} +.icon-pushpin:before{content:"\f08d";} +.icon-external-link:before{content:"\f08e";} +.icon-signin:before{content:"\f090";} +.icon-trophy:before{content:"\f091";} +.icon-github-sign:before{content:"\f092";} +.icon-upload-alt:before{content:"\f093";} +.icon-lemon:before{content:"\f094";} +.icon-phone:before{content:"\f095";} +.icon-unchecked:before,.icon-check-empty:before{content:"\f096";} +.icon-bookmark-empty:before{content:"\f097";} +.icon-phone-sign:before{content:"\f098";} +.icon-twitter:before{content:"\f099";} +.icon-facebook:before{content:"\f09a";} +.icon-github:before{content:"\f09b";} +.icon-unlock:before{content:"\f09c";} +.icon-credit-card:before{content:"\f09d";} +.icon-rss:before{content:"\f09e";} +.icon-hdd:before{content:"\f0a0";} +.icon-bullhorn:before{content:"\f0a1";} +.icon-bell:before{content:"\f0a2";} +.icon-certificate:before{content:"\f0a3";} +.icon-hand-right:before{content:"\f0a4";} +.icon-hand-left:before{content:"\f0a5";} +.icon-hand-up:before{content:"\f0a6";} +.icon-hand-down:before{content:"\f0a7";} +.icon-circle-arrow-left:before{content:"\f0a8";} +.icon-circle-arrow-right:before{content:"\f0a9";} +.icon-circle-arrow-up:before{content:"\f0aa";} +.icon-circle-arrow-down:before{content:"\f0ab";} +.icon-globe:before{content:"\f0ac";} +.icon-wrench:before{content:"\f0ad";} +.icon-tasks:before{content:"\f0ae";} +.icon-filter:before{content:"\f0b0";} +.icon-briefcase:before{content:"\f0b1";} +.icon-fullscreen:before{content:"\f0b2";} +.icon-group:before{content:"\f0c0";} +.icon-link:before{content:"\f0c1";} +.icon-cloud:before{content:"\f0c2";} +.icon-beaker:before{content:"\f0c3";} +.icon-cut:before{content:"\f0c4";} +.icon-copy:before{content:"\f0c5";} +.icon-paperclip:before,.icon-paper-clip:before{content:"\f0c6";} +.icon-save:before{content:"\f0c7";} +.icon-sign-blank:before{content:"\f0c8";} +.icon-reorder:before{content:"\f0c9";} +.icon-list-ul:before{content:"\f0ca";} +.icon-list-ol:before{content:"\f0cb";} +.icon-strikethrough:before{content:"\f0cc";} +.icon-underline:before{content:"\f0cd";} +.icon-table:before{content:"\f0ce";} +.icon-magic:before{content:"\f0d0";} +.icon-truck:before{content:"\f0d1";} +.icon-pinterest:before{content:"\f0d2";} +.icon-pinterest-sign:before{content:"\f0d3";} +.icon-google-plus-sign:before{content:"\f0d4";} +.icon-google-plus:before{content:"\f0d5";} +.icon-money:before{content:"\f0d6";} +.icon-caret-down:before{content:"\f0d7";} +.icon-caret-up:before{content:"\f0d8";} +.icon-caret-left:before{content:"\f0d9";} +.icon-caret-right:before{content:"\f0da";} +.icon-columns:before{content:"\f0db";} +.icon-sort:before{content:"\f0dc";} +.icon-sort-down:before{content:"\f0dd";} +.icon-sort-up:before{content:"\f0de";} +.icon-envelope:before{content:"\f0e0";} +.icon-linkedin:before{content:"\f0e1";} +.icon-rotate-left:before,.icon-undo:before{content:"\f0e2";} +.icon-legal:before{content:"\f0e3";} +.icon-dashboard:before{content:"\f0e4";} +.icon-comment-alt:before{content:"\f0e5";} +.icon-comments-alt:before{content:"\f0e6";} +.icon-bolt:before{content:"\f0e7";} +.icon-sitemap:before{content:"\f0e8";} +.icon-umbrella:before{content:"\f0e9";} +.icon-paste:before{content:"\f0ea";} +.icon-lightbulb:before{content:"\f0eb";} +.icon-exchange:before{content:"\f0ec";} +.icon-cloud-download:before{content:"\f0ed";} +.icon-cloud-upload:before{content:"\f0ee";} +.icon-user-md:before{content:"\f0f0";} +.icon-stethoscope:before{content:"\f0f1";} +.icon-suitcase:before{content:"\f0f2";} +.icon-bell-alt:before{content:"\f0f3";} +.icon-coffee:before{content:"\f0f4";} +.icon-food:before{content:"\f0f5";} +.icon-file-text-alt:before{content:"\f0f6";} +.icon-building:before{content:"\f0f7";} +.icon-hospital:before{content:"\f0f8";} +.icon-ambulance:before{content:"\f0f9";} +.icon-medkit:before{content:"\f0fa";} +.icon-fighter-jet:before{content:"\f0fb";} +.icon-beer:before{content:"\f0fc";} +.icon-h-sign:before{content:"\f0fd";} +.icon-plus-sign-alt:before{content:"\f0fe";} +.icon-double-angle-left:before{content:"\f100";} +.icon-double-angle-right:before{content:"\f101";} +.icon-double-angle-up:before{content:"\f102";} +.icon-double-angle-down:before{content:"\f103";} +.icon-angle-left:before{content:"\f104";} +.icon-angle-right:before{content:"\f105";} +.icon-angle-up:before{content:"\f106";} +.icon-angle-down:before{content:"\f107";} +.icon-desktop:before{content:"\f108";} +.icon-laptop:before{content:"\f109";} +.icon-tablet:before{content:"\f10a";} +.icon-mobile-phone:before{content:"\f10b";} +.icon-circle-blank:before{content:"\f10c";} +.icon-quote-left:before{content:"\f10d";} +.icon-quote-right:before{content:"\f10e";} +.icon-spinner:before{content:"\f110";} +.icon-circle:before{content:"\f111";} +.icon-mail-reply:before,.icon-reply:before{content:"\f112";} +.icon-github-alt:before{content:"\f113";} +.icon-folder-close-alt:before{content:"\f114";} +.icon-folder-open-alt:before{content:"\f115";} +.icon-expand-alt:before{content:"\f116";} +.icon-collapse-alt:before{content:"\f117";} +.icon-smile:before{content:"\f118";} +.icon-frown:before{content:"\f119";} +.icon-meh:before{content:"\f11a";} +.icon-gamepad:before{content:"\f11b";} +.icon-keyboard:before{content:"\f11c";} +.icon-flag-alt:before{content:"\f11d";} +.icon-flag-checkered:before{content:"\f11e";} +.icon-terminal:before{content:"\f120";} +.icon-code:before{content:"\f121";} +.icon-reply-all:before{content:"\f122";} +.icon-mail-reply-all:before{content:"\f122";} +.icon-star-half-full:before,.icon-star-half-empty:before{content:"\f123";} +.icon-location-arrow:before{content:"\f124";} +.icon-crop:before{content:"\f125";} +.icon-code-fork:before{content:"\f126";} +.icon-unlink:before{content:"\f127";} +.icon-question:before{content:"\f128";} +.icon-info:before{content:"\f129";} +.icon-exclamation:before{content:"\f12a";} +.icon-superscript:before{content:"\f12b";} +.icon-subscript:before{content:"\f12c";} +.icon-eraser:before{content:"\f12d";} +.icon-puzzle-piece:before{content:"\f12e";} +.icon-microphone:before{content:"\f130";} +.icon-microphone-off:before{content:"\f131";} +.icon-shield:before{content:"\f132";} +.icon-calendar-empty:before{content:"\f133";} +.icon-fire-extinguisher:before{content:"\f134";} +.icon-rocket:before{content:"\f135";} +.icon-maxcdn:before{content:"\f136";} +.icon-chevron-sign-left:before{content:"\f137";} +.icon-chevron-sign-right:before{content:"\f138";} +.icon-chevron-sign-up:before{content:"\f139";} +.icon-chevron-sign-down:before{content:"\f13a";} +.icon-html5:before{content:"\f13b";} +.icon-css3:before{content:"\f13c";} +.icon-anchor:before{content:"\f13d";} +.icon-unlock-alt:before{content:"\f13e";} +.icon-bullseye:before{content:"\f140";} +.icon-ellipsis-horizontal:before{content:"\f141";} +.icon-ellipsis-vertical:before{content:"\f142";} +.icon-rss-sign:before{content:"\f143";} +.icon-play-sign:before{content:"\f144";} +.icon-ticket:before{content:"\f145";} +.icon-minus-sign-alt:before{content:"\f146";} +.icon-check-minus:before{content:"\f147";} +.icon-level-up:before{content:"\f148";} +.icon-level-down:before{content:"\f149";} +.icon-check-sign:before{content:"\f14a";} +.icon-edit-sign:before{content:"\f14b";} +.icon-external-link-sign:before{content:"\f14c";} +.icon-share-sign:before{content:"\f14d";} +.icon-compass:before{content:"\f14e";} +.icon-collapse:before{content:"\f150";} +.icon-collapse-top:before{content:"\f151";} +.icon-expand:before{content:"\f152";} +.icon-euro:before,.icon-eur:before{content:"\f153";} +.icon-gbp:before{content:"\f154";} +.icon-dollar:before,.icon-usd:before{content:"\f155";} +.icon-rupee:before,.icon-inr:before{content:"\f156";} +.icon-yen:before,.icon-jpy:before{content:"\f157";} +.icon-renminbi:before,.icon-cny:before{content:"\f158";} +.icon-won:before,.icon-krw:before{content:"\f159";} +.icon-bitcoin:before,.icon-btc:before{content:"\f15a";} +.icon-file:before{content:"\f15b";} +.icon-file-text:before{content:"\f15c";} +.icon-sort-by-alphabet:before{content:"\f15d";} +.icon-sort-by-alphabet-alt:before{content:"\f15e";} +.icon-sort-by-attributes:before{content:"\f160";} +.icon-sort-by-attributes-alt:before{content:"\f161";} +.icon-sort-by-order:before{content:"\f162";} +.icon-sort-by-order-alt:before{content:"\f163";} +.icon-thumbs-up:before{content:"\f164";} +.icon-thumbs-down:before{content:"\f165";} +.icon-youtube-sign:before{content:"\f166";} +.icon-youtube:before{content:"\f167";} +.icon-xing:before{content:"\f168";} +.icon-xing-sign:before{content:"\f169";} +.icon-youtube-play:before{content:"\f16a";} +.icon-dropbox:before{content:"\f16b";} +.icon-stackexchange:before{content:"\f16c";} +.icon-instagram:before{content:"\f16d";} +.icon-flickr:before{content:"\f16e";} +.icon-adn:before{content:"\f170";} +.icon-bitbucket:before{content:"\f171";} +.icon-bitbucket-sign:before{content:"\f172";} +.icon-tumblr:before{content:"\f173";} +.icon-tumblr-sign:before{content:"\f174";} +.icon-long-arrow-down:before{content:"\f175";} +.icon-long-arrow-up:before{content:"\f176";} +.icon-long-arrow-left:before{content:"\f177";} +.icon-long-arrow-right:before{content:"\f178";} +.icon-apple:before{content:"\f179";} +.icon-windows:before{content:"\f17a";} +.icon-android:before{content:"\f17b";} +.icon-linux:before{content:"\f17c";} +.icon-dribbble:before{content:"\f17d";} +.icon-skype:before{content:"\f17e";} +.icon-foursquare:before{content:"\f180";} +.icon-trello:before{content:"\f181";} +.icon-female:before{content:"\f182";} +.icon-male:before{content:"\f183";} +.icon-gittip:before{content:"\f184";} +.icon-sun:before{content:"\f185";} +.icon-moon:before{content:"\f186";} +.icon-archive:before{content:"\f187";} +.icon-bug:before{content:"\f188";} +.icon-vk:before{content:"\f189";} +.icon-weibo:before{content:"\f18a";} +.icon-renren:before{content:"\f18b";} diff --git a/docs/ModuleApi/css/jquery.iviewer.css b/docs/ModuleApi/css/jquery.iviewer.css new file mode 100644 index 0000000..11f5f09 --- /dev/null +++ b/docs/ModuleApi/css/jquery.iviewer.css @@ -0,0 +1,65 @@ +.viewer { + -ms-touch-action: none; +} + +.iviewer_common { + position:absolute; + bottom:10px; + border: 1px solid #000; + height: 28px; + z-index: 5000; +} + +.iviewer_cursor { + cursor: url(../images/iviewer/hand.cur) 6 8, pointer; +} + +.iviewer_drag_cursor { + cursor: url(../images/iviewer/grab.cur) 6 8, pointer; +} + +.iviewer_button { + width: 28px; + cursor: pointer; + background-position: center center; + background-repeat: no-repeat; +} + +.iviewer_zoom_in { + left: 20px; + background: url(../images/iviewer/iviewer.zoom_in.png); +} + +.iviewer_zoom_out { + left: 55px; + background: url(../images/iviewer/iviewer.zoom_out.png); +} + +.iviewer_zoom_zero { + left: 90px; + background: url(../images/iviewer/iviewer.zoom_zero.png); +} + +.iviewer_zoom_fit { + left: 125px; + background: url(../images/iviewer/iviewer.zoom_fit.png); +} + +.iviewer_zoom_status { + left: 160px; + font: 1em/28px Sans; + color: #000; + background-color: #fff; + text-align: center; + width: 60px; +} + +.iviewer_rotate_left { + left: 227px; + background: #fff url(../images/iviewer/iviewer.rotate_left.png) center center no-repeat; +} + +.iviewer_rotate_right { + left: 262px; + background: #fff url(../images/iviewer/iviewer.rotate_right.png) center center no-repeat; +} diff --git a/docs/ModuleApi/css/phpdocumentor-clean-icons/Read Me.txt b/docs/ModuleApi/css/phpdocumentor-clean-icons/Read Me.txt new file mode 100644 index 0000000..9d2b9e5 --- /dev/null +++ b/docs/ModuleApi/css/phpdocumentor-clean-icons/Read Me.txt @@ -0,0 +1,3 @@ +To modify your generated font, use the *dev.svg* file, located in the *fonts* folder in this package. You can import this dev.svg file to the IcoMoon app. All the tags (class names) and the Unicode points of your glyphs are saved in this file. + +See the documentation for more info on how to use this package: http://icomoon.io/#docs/font-face \ No newline at end of file diff --git a/docs/ModuleApi/css/phpdocumentor-clean-icons/fonts/phpdocumentor-clean-icons.dev.svg b/docs/ModuleApi/css/phpdocumentor-clean-icons/fonts/phpdocumentor-clean-icons.dev.svg new file mode 100644 index 0000000..8b543c1 --- /dev/null +++ b/docs/ModuleApi/css/phpdocumentor-clean-icons/fonts/phpdocumentor-clean-icons.dev.svg @@ -0,0 +1,17 @@ + + + + +This is a custom SVG font generated by IcoMoon. + + + + + + + + + + + + \ No newline at end of file diff --git a/docs/ModuleApi/css/phpdocumentor-clean-icons/fonts/phpdocumentor-clean-icons.eot b/docs/ModuleApi/css/phpdocumentor-clean-icons/fonts/phpdocumentor-clean-icons.eot new file mode 100644 index 0000000..ef43f26 Binary files /dev/null and b/docs/ModuleApi/css/phpdocumentor-clean-icons/fonts/phpdocumentor-clean-icons.eot differ diff --git a/docs/ModuleApi/css/phpdocumentor-clean-icons/fonts/phpdocumentor-clean-icons.svg b/docs/ModuleApi/css/phpdocumentor-clean-icons/fonts/phpdocumentor-clean-icons.svg new file mode 100644 index 0000000..cf0548b --- /dev/null +++ b/docs/ModuleApi/css/phpdocumentor-clean-icons/fonts/phpdocumentor-clean-icons.svg @@ -0,0 +1,17 @@ + + + + +This is a custom SVG font generated by IcoMoon. + + + + + + + + + + + + \ No newline at end of file diff --git a/docs/ModuleApi/css/phpdocumentor-clean-icons/fonts/phpdocumentor-clean-icons.ttf b/docs/ModuleApi/css/phpdocumentor-clean-icons/fonts/phpdocumentor-clean-icons.ttf new file mode 100644 index 0000000..1937c7a Binary files /dev/null and b/docs/ModuleApi/css/phpdocumentor-clean-icons/fonts/phpdocumentor-clean-icons.ttf differ diff --git a/docs/ModuleApi/css/phpdocumentor-clean-icons/fonts/phpdocumentor-clean-icons.woff b/docs/ModuleApi/css/phpdocumentor-clean-icons/fonts/phpdocumentor-clean-icons.woff new file mode 100644 index 0000000..32fe30d Binary files /dev/null and b/docs/ModuleApi/css/phpdocumentor-clean-icons/fonts/phpdocumentor-clean-icons.woff differ diff --git a/docs/ModuleApi/css/phpdocumentor-clean-icons/lte-ie7.js b/docs/ModuleApi/css/phpdocumentor-clean-icons/lte-ie7.js new file mode 100644 index 0000000..881c16e --- /dev/null +++ b/docs/ModuleApi/css/phpdocumentor-clean-icons/lte-ie7.js @@ -0,0 +1,30 @@ +/* Load this script using conditional IE comments if you need to support IE 7 and IE 6. */ + +window.onload = function() { + function addIcon(el, entity) { + var html = el.innerHTML; + el.innerHTML = '' + entity + '' + html; + } + var icons = { + 'icon-trait' : '', + 'icon-interface' : '', + 'icon-class' : '' + }, + els = document.getElementsByTagName('*'), + i, attr, html, c, el; + for (i = 0; ; i += 1) { + el = els[i]; + if(!el) { + break; + } + attr = el.getAttribute('data-icon'); + if (attr) { + addIcon(el, attr); + } + c = el.className; + c = c.match(/icon-[^\s'"]+/); + if (c && icons[c[0]]) { + addIcon(el, icons[c[0]]); + } + } +}; \ No newline at end of file diff --git a/docs/ModuleApi/css/phpdocumentor-clean-icons/style.css b/docs/ModuleApi/css/phpdocumentor-clean-icons/style.css new file mode 100644 index 0000000..f069ec1 --- /dev/null +++ b/docs/ModuleApi/css/phpdocumentor-clean-icons/style.css @@ -0,0 +1,48 @@ +@font-face { + font-family: 'phpdocumentor-clean-icons'; + src:url('fonts/phpdocumentor-clean-icons.eot'); + src:url('fonts/phpdocumentor-clean-icons.eot?#iefix') format('embedded-opentype'), + url('fonts/phpdocumentor-clean-icons.woff') format('woff'), + url('fonts/phpdocumentor-clean-icons.ttf') format('truetype'), + url('fonts/phpdocumentor-clean-icons.svg#phpdocumentor-clean-icons') format('svg'); + font-weight: normal; + font-style: normal; +} + +/* Use the following CSS code if you want to use data attributes for inserting your icons */ +[data-icon]:before { + font-family: 'phpdocumentor-clean-icons'; + content: attr(data-icon); + speak: none; + font-weight: normal; + font-variant: normal; + text-transform: none; + line-height: 1; + -webkit-font-smoothing: antialiased; +} + +/* Use the following CSS code if you want to have a class per icon */ +/* +Instead of a list of all class selectors, +you can use the generic selector below, but it's slower: +[class*="icon-"] { +*/ +.icon-trait, .icon-interface, .icon-class { + font-family: 'phpdocumentor-clean-icons'; + speak: none; + font-style: normal; + font-weight: normal; + font-variant: normal; + text-transform: none; + line-height: 1; + -webkit-font-smoothing: antialiased; +} +.icon-trait:before { + content: "\e000"; +} +.icon-interface:before { + content: "\e001"; +} +.icon-class:before { + content: "\e002"; +} diff --git a/docs/ModuleApi/css/prism.css b/docs/ModuleApi/css/prism.css new file mode 100644 index 0000000..17876af --- /dev/null +++ b/docs/ModuleApi/css/prism.css @@ -0,0 +1,204 @@ +/** + * prism.js default theme for JavaScript, CSS and HTML + * Based on dabblet (http://dabblet.com) + * @author Lea Verou + */ + +code[class*="language-"], +pre[class*="language-"] { + color: black; + text-shadow: 0 1px white; + font-family: Consolas, Monaco, 'Andale Mono', monospace; + direction: ltr; + text-align: left; + white-space: pre; + word-spacing: normal; + + -moz-tab-size: 4; + -o-tab-size: 4; + tab-size: 4; + + -webkit-hyphens: none; + -moz-hyphens: none; + -ms-hyphens: none; + hyphens: none; +} + +::-moz-selection { + text-shadow: none; + background: #b3d4fc; +} + +::selection { + text-shadow: none; + background: #b3d4fc; +} + +@media print { + code[class*="language-"], + pre[class*="language-"] { + text-shadow: none; + } +} + +/* Code blocks */ +pre[class*="language-"] { + padding: 1em; + margin: .5em 0; + overflow: auto; +} + +:not(pre) > code[class*="language-"], +pre[class*="language-"] { + background: #f5f2f0; +} + +/* Inline code */ +:not(pre) > code[class*="language-"] { + padding: .1em; + border-radius: .3em; +} + +.token.comment, +.token.prolog, +.token.doctype, +.token.cdata { + color: slategray; +} + +.token.punctuation { + color: #999; +} + +.namespace { + opacity: .7; +} + +.token.property, +.token.tag, +.token.boolean, +.token.number { + color: #905; +} + +.token.selector, +.token.attr-name, +.token.string { + color: #690; +} + +.token.operator, +.token.entity, +.token.url, +.language-css .token.string, +.style .token.string { + color: #a67f59; + background: hsla(0,0%,100%,.5); +} + +.token.atrule, +.token.attr-value, +.token.keyword { + color: #07a; +} + + +.token.regex, +.token.important { + color: #e90; +} + +.token.important { + font-weight: bold; +} + +.token.entity { + cursor: help; +} +pre[data-line] { + position: relative; + padding: 1em 0 1em 3em; +} + +.line-highlight { + position: absolute; + left: 0; + right: 0; + padding: inherit 0; + margin-top: 1em; /* Same as .prism’s padding-top */ + + background: hsla(24, 20%, 50%,.08); + background: -moz-linear-gradient(left, hsla(24, 20%, 50%,.1) 70%, hsla(24, 20%, 50%,0)); + background: -webkit-linear-gradient(left, hsla(24, 20%, 50%,.1) 70%, hsla(24, 20%, 50%,0)); + background: -o-linear-gradient(left, hsla(24, 20%, 50%,.1) 70%, hsla(24, 20%, 50%,0)); + background: linear-gradient(left, hsla(24, 20%, 50%,.1) 70%, hsla(24, 20%, 50%,0)); + + pointer-events: none; + + line-height: inherit; + white-space: pre; +} + + .line-highlight:before, + .line-highlight[data-end]:after { + content: attr(data-start); + position: absolute; + top: .4em; + left: .6em; + min-width: 1em; + padding: 0 .5em; + background-color: hsla(24, 20%, 50%,.4); + color: hsl(24, 20%, 95%); + font: bold 65%/1.5 sans-serif; + text-align: center; + vertical-align: .3em; + border-radius: 999px; + text-shadow: none; + box-shadow: 0 1px white; + } + + .line-highlight[data-end]:after { + content: attr(data-end); + top: auto; + bottom: .4em; + } +pre.line-numbers { + position: relative; + padding-left: 3.8em; + counter-reset: linenumber; +} + +pre.line-numbers > code { + position: relative; +} + +.line-numbers .line-numbers-rows { + position: absolute; + pointer-events: none; + top: 0; + font-size: 100%; + left: -3.8em; + width: 3em; /* works for line-numbers below 1000 lines */ + letter-spacing: -1px; + border-right: 1px solid #999; + + -webkit-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + user-select: none; + +} + + .line-numbers-rows > span { + pointer-events: none; + display: block; + counter-increment: linenumber; + } + + .line-numbers-rows > span:before { + content: counter(linenumber); + color: #999; + display: block; + padding-right: 0.8em; + text-align: right; + } diff --git a/docs/ModuleApi/css/template.css b/docs/ModuleApi/css/template.css new file mode 100644 index 0000000..9edf5ee --- /dev/null +++ b/docs/ModuleApi/css/template.css @@ -0,0 +1,429 @@ +@import url(https://fonts.googleapis.com/css?family=Source+Sans+Pro); +@import url('phpdocumentor-clean-icons/style.css'); + +body { + padding-top: 40px; + background-color: #333333; +} + +a { + color: #6495ed; +} +a.anchor { + height: 40px; + margin-top: -40px; + display: block; +} + +h1, h2, h3, h4, h5, h6, .brand { + font-family: 'Source Sans Pro', sans-serif; + font-weight: normal; + letter-spacing: 0.05em; +} + +h2, h3, .detailsbar h1 { + overflow: hidden; + white-space: nowrap; + margin: 30px 0 20px 0; +} + +h2:after, h3:after, .detailsbar h1:after { + content: ''; + display: inline-block; + vertical-align: middle; + width: 100%; + height: 2px; + margin-left: 1em; + background: silver; +} + +h3 { + margin: 10px 0 20px 0; +} + +h4 { + margin: 20px 0 10px 0; + color: gray; + font-size: 18.5px; +} + +h3.public, h3.protected, h3.private { + padding-left: 10px; + text-overflow: ellipsis; +} + +.table tr:first-of-type th, .table tr:first-of-type td { + border-top: none; +} +.detailsbar { + color: #eeeeee; + background-color: #333333; + font-size: 0.9em; + overflow: hidden; + border-left: 2px solid gray; +} + +.detailsbar h1 { + font-size: 1.5em; + margin-bottom: 20px; + margin-top: 0; +} + +.detailsbar h2 { + font-size: 1.2em; + margin: 0; + padding: 0; +} + +.detailsbar h1:after { + background: gray; +} +.detailsbar h2:after, .detailsbar h3:after { + background: transparent; +} + +.detailsbar dt { + font-variant: small-caps; + text-transform: lowercase; + font-size: 1.1em; + letter-spacing: 0.1em; + color: silver; +} + +.hierarchy div:nth-of-type(2) { margin-left: 11px; } +.hierarchy div:nth-of-type(3) { margin-left: 22px; } +.hierarchy div:nth-of-type(4) { margin-left: 33px; } +.hierarchy div:nth-of-type(5) { margin-left: 44px; } +.hierarchy div:nth-of-type(6) { margin-left: 55px; } +.hierarchy div:nth-of-type(7) { margin-left: 66px; } +.hierarchy div:nth-of-type(8) { margin-left: 77px; } +.hierarchy div:nth-of-type(9) { margin-left: 88px; } +.hierarchy div:before { + content: "\f0da"; + font-family: FontAwesome; + margin-right: 5px; +} + +.row-fluid { + background-color: white; + overflow: hidden; +} + +footer.row-fluid, footer.row-fluid * { + background-color: #333333; + color: white; +} + +footer.row-fluid { + border-top: 2px dashed #555; + margin-top: 2px; +} + +.footer-sections .span4 { + border: 2px solid #555; + text-align: center; + border-radius: 10px; + margin-top: 70px; + margin-bottom: 20px; + background: #373737; +} + +.footer-sections .span4 h1 { + background: transparent; + margin-top: -30px; + margin-bottom: 20px; + font-size: 5em; +} + +.footer-sections .span4 h1 * { + background: transparent; +} + +.footer-sections .span4 div { + border-bottom-right-radius: 6px; + border-bottom-left-radius: 6px; + padding: 10px; + min-height: 40px; +} +.footer-sections .span4 div, .footer-sections .span4 div * { + background-color: #555; +} +.footer-sections .span4 ul { + text-align: left; + list-style: none; + margin: 0; + padding: 0; +} + +.content { + background-color: white; + padding-right: 20px; +} + +.content nav { + text-align: center; + border-bottom: 1px solid silver; + margin: 5px 0 20px 0; + padding-bottom: 5px; +} + +.content > h1 { + padding-bottom: 15px; +} + +.content > h1 small { + display: block; + padding-bottom: 8px; + font-size: 0.6em; +} + +.deprecated { + text-decoration: line-through; +} + +.method { + margin-bottom: 20px; +} + +.method .signature .argument { + color: maroon; + font-weight: bold; +} + +.class #summary section.row-fluid { + overflow: hidden +} + +.class #summary .heading { + font-weight: bold; + text-align: center; +} + +.class #summary section .span4 { + padding: 3px; + overflow: hidden; + margin-bottom: -9999px; + padding-bottom: 9999px; + white-space: nowrap; + text-overflow: ellipsis; + border-left: 5px solid transparent; +} + +.class #summary section.public .span4:first-of-type:before, +.class #summary section.public .span6:first-of-type:before, +h3.public:before { + font-family: FontAwesome; + content: "\f046"; + color: green; + display: inline-block; + width: 1.2em; +} + +.class #summary section .span4:first-of-type, +.class #summary section .span6:first-of-type { + padding-left: 21px; +} +.class #summary section .span4:first-of-type:before, +.class #summary section .span6:first-of-type:before { + margin-left: -21px; +} +.class #summary section.protected .span4:first-of-type:before, +.class #summary section.protected .span6:first-of-type:before, +h3.protected:before { + font-family: FontAwesome; + content: "\f132"; + color: orange; + display: inline-block; + width: 1.2em; +} + +.class #summary section.private .span4:first-of-type:before, +.class #summary section.private .span6:first-of-type:before, +h3.private:before { + font-family: FontAwesome; + content: "\f023"; + color: red; + display: inline-block; + width: 1.2em; +} + +.class #summary section em { + font-size: 0.9em; + color: silver; +} +.class #summary .inherited { + color: gray; + font-style: italic; +} + +.accordion-group { + border: none; +} + +.accordion { + margin-bottom: 0; +} + +.accordion a:hover { + text-decoration: none; + background: #333333; + color: #eeeeee; +} + +.accordion-heading .accordion-toggle:before { + content: "\f078"; + font-family: FontAwesome; + margin-right: 5px; +} + +.accordion-heading .accordion-toggle.collapsed:before { + content: "\f054"; +} +.accordion-heading .accordion-toggle { + float: left; + width: 16px; + height: 16px; + padding: 4px 2px 4px 12px; +} +.accordion-heading a { + display: block; + padding: 4px 12px; +} + +.accordion-inner a { + display: block; + padding: 4px 12px; +} + +.accordion-inner > ul a:before { + font-family: 'phpdocumentor-clean-icons'; + content: "\e001"; + margin-right: 5px; +} + +.accordion-inner li.class a:before { + content: "\e002"; +} + +.accordion-inner li.interface a:before { + content: "\e001"; +} + +.accordion-inner li.trait a:before { + content: "\e000"; +} + +.accordion-inner { + padding: 4px 0 4px 12px; +} +.accordion-inner ul { + list-style: none; + padding: 0; + margin: 0; +} + +.row-fluid .span2 { + width: 16.5%; +} + +body .modal { + width: 90%; /* desired relative width */ + left: 5%; /* (100%-width)/2 */ + /* place center */ + margin-left:auto; + margin-right:auto; +} + +.side-nav.nav-list li a { + overflow: hidden; + white-space: nowrap; + text-overflow: ellipsis; +} + +@media (min-width: 767px) { + .sidebar { + position: fixed; + top: 40px; + bottom: 0; + background-color: #f3f3f3; + left: 0; + border-right: 1px solid #e9e9e9; + overflow-y: scroll; + overflow-x: hidden; + padding-top: 10px; + } + + .sidebar::-webkit-scrollbar { + width: 10px; + } + + .sidebar::-webkit-scrollbar-thumb { + background: #cccccc; + background-clip: padding-box; + border: 3px solid #f3f3f3; + border-radius: 5px; + } + + .sidebar::-webkit-scrollbar-button { + display: none; + } + + .sidebar::-webkit-scrollbar-track { + background: #f3f3f3; + } +} + +@media (max-width: 979px) { + body { + padding-top: 0; + } +} + +@media (max-width: 767px) { + .class #summary .heading { + display: none; + } + + .detailsbar h1 { + display: none; + } + + body { + background-color: white; + } + + footer.row-fluid, footer.row-fluid * { + background-color: white; + } + + .footer-sections .span4 h1 { + color: #ccccd9; + margin-top: 0; + } + + .detailsbar { + background-color: white; + color: #333; + border: none; + } + + .row-fluid .span2 { + width: 100%; + } +} + +@media (min-width: 767px) { + .detailsbar { + min-height: 100%; + margin-bottom: -99999px; + padding-bottom: 99999px; + padding-left: 20px; + padding-top: 10px; + } +} + +@media (min-width: 1200px) { + .row-fluid .span2 { + width: 16.5%; + } +} diff --git a/docs/ModuleApi/files/core.html b/docs/ModuleApi/files/core.html new file mode 100644 index 0000000..5e09779 --- /dev/null +++ b/docs/ModuleApi/files/core.html @@ -0,0 +1,2449 @@ + + + + + + Pepperminty Wiki Module API + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ +
+
+
+
+
+ + + +

core.php

+

The Pepperminty Wiki core

+ + + + +

Classes

+ + + + + + + + + +
idsProvides an interface to interact with page ids.
page_rendererRenders the HTML page that is sent to the client.
+
+ + +
+ + +
+
+

Functions

+
+ +
+ +
+
+ +
+

url_origin()

+ +
url_origin(array  $s = false, boolean  $use_forwarded_host = false) : string
+

Get the actual absolute origin of the request sent by the user.

+ + +

Parameters

+ + + + + + + + + + + +
array$s

The $_SERVER variable contents. Defaults to $_SERVER.

boolean$use_forwarded_host

Whether to utilise the X-Forwarded-Host header when calculating the actual origin.

+ + +

Returns

+ string + —

The actual origin of the user's request.

+ +
+
+ +
+ +
+
+ +
+

full_url()

+ +
full_url(array  $s = false, boolean  $use_forwarded_host = false) : string
+

Get the full url, as requested by the client.

+ + +

Parameters

+ + + + + + + + + + + +
array$s

The $_SERVER variable. Defaults to $_SERVER.

boolean$use_forwarded_host

Whether to take the X-Forwarded-Host header into account.

+ + +

Returns

+ string + —

The full url, as requested by the client.

+ +
+
+ +
+ +
+
+ +
+

human_filesize()

+ +
human_filesize(\number  $bytes, \number  $decimals = 2) : string
+

Converts a filesize into a human-readable string.

+ + +

Parameters

+ + + + + + + + + + + +
\number$bytes

The number of bytes to convert.

\number$decimals

The number of decimal places to preserve.

+ + +

Returns

+ string + —

A human-readable filesize.

+ +
+
+ +
+ +
+
+ +
+

human_time_since()

+ +
human_time_since(integer  $time) : string
+

Calculates the time since a particular timestamp and returns a +human-readable result.

+ + +

Parameters

+ + + + + + +
integer$time

The timestamp to convert.

+ + +

Returns

+ string + —

The time since the given timestamp as +a human-readable string.

+ +
+
+ +
+ +
+
+ +
+

human_time()

+ +
human_time(integer  $seconds) : string
+

Renders a given number of seconds as something that humans can understand more easily.

+ + +

Parameters

+ + + + + + +
integer$seconds

The number of seconds to render.

+ + +

Returns

+ string + —

The rendered time.

+ +
+
+ +
+ +
+
+ +
+

glob_recursive()

+ +
glob_recursive(string  $pattern, integer  $flags) : array
+

A recursive glob() function.

+ + +

Parameters

+ + + + + + + + + + + +
string$pattern

The glob pattern to use to find filenames.

integer$flags

The glob flags to use when finding filenames.

+ + +

Returns

+ array + —

An array of the filepaths that match the given glob.

+ +
+
+ +
+ +
+
+ +
+

get_page_parent()

+ +
get_page_parent(string  $pagename) : string|boolean
+

Gets the name of the parent page to the specified page.

+ + +

Parameters

+ + + + + + +
string$pagename

The child page to get the parent +page name for.

+ + +

Returns

+ string|boolean + +
+
+ +
+ +
+
+ +
+

get_subpages()

+ +
get_subpages(object  $pageindex, string  $pagename) : object
+

Gets a list of all the sub pages of the current page.

+ + +

Parameters

+ + + + + + + + + + + +
object$pageindex

The pageindex to use to search.

string$pagename

The name of the page to list the sub pages of.

+ + +

Returns

+ object + —

An object containing all the subpages and their +respective distances from the given page name in the pageindex tree.

+ +
+
+ +
+ +
+
+ +
+

check_subpage_parents()

+ +
check_subpage_parents(  $pagename) 
+

Makes sure that a subpage's parents exist.

+

Note this doesn't check the pagename itself.

+ +

Parameters

+ + + + + + +
$pagename

The pagename to check.

+ + + +
+
+ +
+ +
+
+ +
+

makepathsafe()

+ +
makepathsafe(string  $string) : string
+

Makes a path safe.

+

Paths may only contain alphanumeric characters, spaces, underscores, and +dashes.

+ +

Parameters

+ + + + + + +
string$string

The string to make safe.

+ + +

Returns

+ string + —

A safe version of the given string.

+ +
+
+ +
+ +
+
+ +
+

hide_email()

+ +
hide_email(string  $str) : string
+

Hides an email address from bots by adding random html entities.

+ + +

Parameters

+ + + + + + +
string$str

The original email address

+ + +

Returns

+ string + —

The mangled email address.

+ +
+
+ +
+ +
+
+ +
+

starts_with()

+ +
starts_with(string  $haystack, string  $needle) : boolean
+

Checks to see if $haystack starts with $needle.

+ + +

Parameters

+ + + + + + + + + + + +
string$haystack

The string to search.

string$needle

The string to search for at the beginning +of $haystack.

+ + +

Returns

+ boolean + —

Whether $needle can be found at the beginning +of $haystack.

+ +
+
+ +
+ +
+
+ +
+

mb_stripos_all()

+ +
mb_stripos_all(string  $haystack, string  $needle) : array|false
+

Case-insensitively finds all occurrences of $needle in $haystack. Handles +UTF-8 characters correctly.

+ + +

Parameters

+ + + + + + + + + + + +
string$haystack

The string to search.

string$needle

The string to find.

+ + +

Returns

+ array|false + —

An array of match indices, or false if +nothing was found.

+ +
+
+ +
+ +
+
+ +
+

startsWith()

+ +
startsWith(string  $haystack, string  $needle) : boolean
+

Tests whether a string starts with a specified substring.

+ + +

Parameters

+ + + + + + + + + + + +
string$haystack

The string to check against.

string$needle

The substring to look for.

+ + +

Returns

+ boolean + —

Whether the string starts with the specified substring.

+ +
+
+ +
+ +
+
+ +
+

endsWith()

+ +
endsWith(string  $whole, string  $end) : boolean
+

Tests whether a string ends with a given substring.

+ + +

Parameters

+ + + + + + + + + + + +
string$whole

The string to test against.

string$end

The substring test for.

+ + +

Returns

+ boolean + —

Whether $whole ends in $end.

+ +
+
+ +
+ +
+
+ +
+

str_replace_once()

+ +
str_replace_once(string  $find, string  $replace, string  $subject) : string
+

Replaces the first occurrence of $find with $replace.

+ + +

Parameters

+ + + + + + + + + + + + + + + + +
string$find

The string to search for.

string$replace

The string to replace the search string with.

string$subject

The string ot perform the search and replace on.

+ + +

Returns

+ string + —

The source string after the find and replace has been performed.

+ +
+
+ +
+ +
+
+ +
+

system_mime_type_extensions()

+ +
system_mime_type_extensions() : array
+

Returns the system's mime type mappings, considering the first extension +listed to be cacnonical.

+ + + + +

Returns

+ array + —

An array of mime type mappings.

+ +
+
+ +
+ +
+
+ +
+

system_mime_type_extension()

+ +
system_mime_type_extension(string  $type) : string
+

Converts a given mime type to it's associated file extension.

+ + +

Parameters

+ + + + + + +
string$type

The mime type to convert.

+ + +

Returns

+ string + —

The extension for the given mime type.

+ +
+
+ +
+ +
+
+ +
+

system_extension_mime_types()

+ +
system_extension_mime_types() : array
+

Returns the system MIME type mapping of extensions to MIME types.

+ + + + +

Returns

+ array + —

An array mapping file extensions to their associated mime types.

+ +
+
+ +
+ +
+
+ +
+

system_extension_mime_type()

+ +
system_extension_mime_type(string  $ext) : string
+

Converts a given file extension to it's associated mime type.

+ + +

Parameters

+ + + + + + +
string$ext

The extension to convert.

+ + +

Returns

+ string + —

The mime type associated with the given extension.

+ +
+
+ +
+ +
+
+ +
+

accept_contains_mime()

+ +
accept_contains_mime(string  $accept_header, string  $mime_type) : boolean
+

Figures out whether a given http accepts header contains a +specified mime type.

+ + +

Parameters

+ + + + + + + + + + + +
string$accept_header

The accept header to search.

string$mime_type

The mime type to search for.

+ + +

Returns

+ boolean + —

Whether the specified mime type was found +in the specified accepts header.

+ +
+
+ +
+ +
+
+ +
+

stack_trace()

+ +
stack_trace(boolean  $log_trace = true, boolean  $full = false) : string
+

Generates a stack trace.

+ + +

Parameters

+ + + + + + + + + + + +
boolean$log_trace

Whether to send the stack trace to the error log.

boolean$full

Whether to output a full description of all the variables involved.

+ + +

Returns

+ string + —

A string prepresentation of a stack trace.

+ +
+
+ +
+ +
+
+ +
+

var_dump_ret()

+ +
var_dump_ret(mixed  $var) : string
+

Calls var_dump() and returns the output.

+ + +

Parameters

+ + + + + + +
mixed$var

The thing to pass to var_dump().

+ + +

Returns

+ string + —

The output captured from var_dump().

+ +
+
+ +
+ +
+
+ +
+

var_dump_short()

+ +
var_dump_short(mixed  $var) : string
+

Calls var_dump(), shortening the output for various types.

+ + +

Parameters

+ + + + + + +
mixed$var

The thing to pass to var_dump().

+ + +

Returns

+ string + —

A shortened version of the var_dump() output.

+ +
+
+ +
+ +
+
+ +
+

getallheaders()

+ +
getallheaders() 
+

Polyfill for PHP's native getallheaders() function on platforms that +don't have it.

+ + + + + +
+
+ +
+ +
+
+ +
+

render_timestamp()

+ +
render_timestamp(integer  $timestamp) : string
+

Renders a timestamp in HTML.

+ + +

Parameters

+ + + + + + +
integer$timestamp

The timestamp to render.

+ + +

Returns

+ string + —

HTML representing the given timestamp.

+ +
+
+ +
+ +
+
+ +
+

render_pagename()

+ +
render_pagename(object  $rchange) : string
+

Renders a page name in HTML.

+ + +

Parameters

+ + + + + + +
object$rchange

The recent change to render as a page name

+ + +

Returns

+ string + —

HTML representing the name of the given page.

+ +
+
+ +
+ +
+
+ +
+

render_editor()

+ +
render_editor(string  $editorName) : string
+

Renders an editor's or a group of editors name(s) in HTML.

+ + +

Parameters

+ + + + + + +
string$editorName

The name of the editor to render.

+ + +

Returns

+ string + —

HTML representing the given editor's name.

+ +
+
+ +
+ +
+
+ +
+

save_userdata()

+ +
save_userdata() : boolean
+

Saves the currently logged in uesr's data back to peppermint.json.

+ + + + +

Returns

+ boolean + —

Whether the user's data was saved successfully. Returns false if the user isn't logged in.

+ +
+
+ +
+ +
+
+ +
+

get_user_pagename()

+ +
get_user_pagename(string  $username) : string
+

Figures out the path to the user page for a given username.

+

Does not check to make sure the user acutally exists.

+ +

Parameters

+ + + + + + +
string$username

The username to get the path to their user page for.

+ + +

Returns

+ string + —

The path to the given user's page.

+ +
+
+ +
+ +
+
+ +
+

extract_user_from_userpage()

+ +
extract_user_from_userpage(string  $userPagename) : string
+

Extracts a username from a user page path.

+ + +

Parameters

+ + + + + + +
string$userPagename

The suer page path to extract from.

+ + +

Returns

+ string + —

The name of the user that the user page belongs to.

+ +
+
+ +
+ +
+
+ +
+

email_user()

+ +
email_user(string  $username, string  $subject, string  $body) : boolean
+

Sends a plain text email to a user, replacing {username} with the specified username.

+ + +

Parameters

+ + + + + + + + + + + + + + + + +
string$username

The username to send the email to.

string$subject

The subject of the email.

string$body

The body of the email.

+ + +

Returns

+ boolean + —

Whether the email was sent successfully or not. Currently, this may fail if the user doesn't have a registered email address.

+ +
+
+ +
+ +
+
+ +
+

email_users()

+ +
email_users(array<mixed,string>  $usernames, string  $subject, string  $body) : integer
+

Sends a plain text email to a list of users, replacing {username} with each user's name.

+ + +

Parameters

+ + + + + + + + + + + + + + + + +
array<mixed,string>$usernames

A list of usernames to email.

string$subject

The subject of the email.

string$body

The body of the email.

+ + +

Returns

+ integer + —

The number of emails sent successfully.

+ +
+
+ +
+ +
+
+ +
+

register_module()

+ +
register_module(array  $moduledata) 
+

Registers a module.

+ + +

Parameters

+ + + + + + +
array$moduledata

The module data to register.

+ + + +
+
+ +
+ +
+
+ +
+

module_exists()

+ +
module_exists(string  $id) : boolean
+

Checks to see whether a module with the given id exists.

+ + +

Parameters

+ + + + + + +
string$id

The id to search for.

+ + +

Returns

+ boolean + —

Whether a module is currently loaded with the given id.

+ +
+
+ +
+ +
+
+ +
+

add_action()

+ +
add_action(string  $action_name, \function  $func) 
+

Registers a new action handler.

+ + +

Parameters

+ + + + + + + + + + + +
string$action_name

The action to register.

\function$func

The function to call when the specified +action is requested.

+ + + +
+
+ +
+ +
+
+ +
+

has_action()

+ +
has_action(string  $action_name) : boolean
+

Figures out whether a given action is currently registered.

+

Only guaranteed to be accurate in inside an existing action function

+ +

Parameters

+ + + + + + +
string$action_name

The name of the action to search for

+ + +

Returns

+ boolean + —

Whether an action with the specified name exists.

+ +
+
+ +
+ +
+
+ +
+

add_parser()

+ +
add_parser(string  $name, \function  $parser_code) 
+

Registers a new parser.

+ + +

Parameters

+ + + + + + + + + + + +
string$name

The name of the new parser to register.

\function$parser_code

The function to register as a new parser.

+ + + +
+
+ +
+ +
+
+ +
+

parse_page_source()

+ +
parse_page_source(string  $source) : string
+

Parses the specified page source using the parser specified in the settings +into HTML.

+

The specified parser may (though it's unilkely) render it to other things.

+ +

Parameters

+ + + + + + +
string$source

The source to render.

+ + +

Returns

+ string + —

The source rendered to HTML.

+ +
+
+ +
+ +
+
+ +
+

register_save_preprocessor()

+ +
register_save_preprocessor(\function  $func) 
+

Register a new proprocessor that will be executed just before +an edit is saved.

+ + +

Parameters

+ + + + + + +
\function$func

The function to register.

+ + + +
+
+ +
+ +
+
+ +
+

add_help_section()

+ +
add_help_section(string  $index, string  $title, string  $content) 
+

Adds a new help section to the help page.

+ + +

Parameters

+ + + + + + + + + + + + + + + + +
string$index

The string to index the new section under.

string$title

The title to display above the section.

string$content

The content to display.

+ + + +
+
+ +
+ +
+
+ +
+

statistic_add()

+ +
statistic_add(array  $stat_data) 
+

Registers a statistic calculator against the system.

+ + +

Parameters

+ + + + + + +
array$stat_data

The statistic object to register.

+ + + +
+
+ +
+ +
+
+ +
+

has_statistic()

+ +
has_statistic(string  $stat_id) : boolean
+

Checks whether a specified statistic has been registered.

+ + +

Parameters

+ + + + + + +
string$stat_id

The id of the statistic to check the existence of.

+ + +

Returns

+ boolean + —

Whether the specified statistic has been registered.

+ +
+
+ +
+ + +
+
+ + + + +
+ + + diff --git a/docs/ModuleApi/files/core.php.txt b/docs/ModuleApi/files/core.php.txt new file mode 100644 index 0000000..270a9df --- /dev/null +++ b/docs/ModuleApi/files/core.php.txt @@ -0,0 +1,1772 @@ +action = $settings->defaultaction; +/** The page name requested by the remote client. */ +$env->page = ""; +/** The filename that the page is stored in. */ +$env->page_filename = ""; +/** Whether we are looking at a history revision. */ +$env->is_history_revision = false; +/** An object holding history revision information for the current request */ +$env->history = new stdClass(); +/** The revision number requested of the current page */ +$env->history->revision_number = -1; +/** The revision data object from the page index for the requested revision */ +$env->history->revision_data = false; +/** The user's name if they are logged in. Defaults to `$settings->anonymous_user_name` if the user isn't currently logged in. @var string */ +$env->user = $settings->anonymous_user_name; +/** Whether the user is logged in */ +$env->is_logged_in = false; +/** Whether the user is an admin (moderator) @todo Refactor this to is_moderator, so that is_admin can be for the server owner. */ +$env->is_admin = false; +/** The currently logged in user's data. Please see $settings->users->username if you need to edit this - this is here for convenience :-) */ +$env->user_data = new stdClass(); +/** The data storage directory. Page filenames should be prefixed with this if you want their content. */ +$env->storage_prefix = $settings->data_storage_dir . DIRECTORY_SEPARATOR; +/** Contains performance data statistics for the current request. */ +$env->perfdata = new stdClass(); +/// Paths /// +/** + * Contains a bunch of useful paths to various important files. + * None of these need to be prefixed with `$env->storage_prefix`. + */ +$paths = new stdClass(); +/** The pageindex. Contains extensive information about all pages currently in this wiki. Individual entries for pages may be extended with arbitrary properties. */ +$paths->pageindex = "pageindex.json"; +/** The inverted index used for searching. Use the `search` class to interact with this - otherwise your brain might explode :P */ +$paths->searchindex = "invindex.json"; +/** The index that maps ids to page names. Use the `ids` class to interact with it :-) */ +$paths->idindex = "idindex.json"; +/** The cache of the most recently calculated statistics. */ +$paths->statsindex = "statsindex.json"; + +// Prepend the storage data directory to all the defined paths. +foreach ($paths as &$path) { + $path = $env->storage_prefix . $path; +} + +/** The master settings file @var string */ +$paths->settings_file = $settingsFilename; +/** The prefix to add to uploaded files */ +$paths->upload_file_prefix = "Files/"; + +session_start(); +// Make sure that the login cookie lasts beyond the end of the user's session +setcookie(session_name(), session_id(), time() + $settings->sessionlifetime, "", "", false, true); +///////// Login System ///////// +// Clear expired sessions +if(isset($_SESSION[$settings->sessionprefix . "-expiretime"]) and + $_SESSION[$settings->sessionprefix . "-expiretime"] < time()) +{ + // Clear the session variables + $_SESSION = []; + session_destroy(); +} + +if(isset($_SESSION[$settings->sessionprefix . "-user"]) and + isset($_SESSION[$settings->sessionprefix . "-pass"])) +{ + // Grab the session variables + // Note that the 'pass' field here is actually a hash of the password set + // by the login action + $env->user = $_SESSION[$settings->sessionprefix . "-user"]; + $env->pass = $_SESSION[$settings->sessionprefix . "-pass"]; + + if($settings->users->{$env->user}->password == $env->pass) + { + // The user is logged in + $env->is_logged_in = true; + $env->user_data = $settings->users->{$env->user}; + } + else + { + // The user's login details are invalid (what is going on here?) + // Unset the session variables, treat them as an anonymous user, + // and get out of here + $env->is_logged_in = false; + $env->user = $settings->anonymous_user_name; + $env->pass = ""; + // Clear the session data + $_SESSION = []; // Delete all the variables + session_destroy(); // Destroy the session + } +} + +// Check to see if the currently logged in user is an admin +$env->is_admin = false; +if($env->is_logged_in) +{ + foreach($settings->admins as $admin_username) + { + if($admin_username == $env->user) + { + $env->is_admin = true; + break; + } + } +} +/////// Login System End /////// + +//////////////////// +// APIDoc strings // +//////////////////// +/** + * @apiDefine Moderator Only users loggged with a moderator account may use this call. + */ +/** +* @apiDefine User Only users loggged in may use this call. +*/ +/** +* @apiDefine Anonymous Anybody may use this call. +*/ +/** + * @apiDefine UserNotLoggedInError + * @apiError UserNotLoggedInError You didn't log in before sending this request. + */ +/** +* @apiDefine UserNotModeratorError +* @apiError UserNotModeratorError You weren't loggged in as a moderator before sending this request. +*/ +/** +* @apiDefine PageParameter +* @apiParam {string} page The page to operate on. +*/ +//////////////////// + +/////////////////////////////////////////////////////////////////////////////// +////////////////////////////////// Functions ////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +/** + * Get the actual absolute origin of the request sent by the user. + * @package core + * @param array $s The $_SERVER variable contents. Defaults to $_SERVER. + * @param bool $use_forwarded_host Whether to utilise the X-Forwarded-Host header when calculating the actual origin. + * @return string The actual origin of the user's request. + */ +function url_origin( $s = false, $use_forwarded_host = false ) +{ + if($s === false) $s = $_SERVER; + $ssl = ( ! empty( $s['HTTPS'] ) && $s['HTTPS'] == 'on' ); + $sp = strtolower( $s['SERVER_PROTOCOL'] ); + $protocol = substr( $sp, 0, strpos( $sp, '/' ) ) . ( ( $ssl ) ? 's' : '' ); + $port = $s['SERVER_PORT']; + $port = ( ( ! $ssl && $port=='80' ) || ( $ssl && $port=='443' ) ) ? '' : ':'.$port; + $host = ( $use_forwarded_host && isset( $s['HTTP_X_FORWARDED_HOST'] ) ) ? $s['HTTP_X_FORWARDED_HOST'] : ( isset( $s['HTTP_HOST'] ) ? $s['HTTP_HOST'] : null ); + $host = isset( $host ) ? $host : $s['SERVER_NAME'] . $port; + return $protocol . '://' . $host; +} + +/** + * Get the full url, as requested by the client. + * @package core + * @see http://stackoverflow.com/a/8891890/1460422 This Stackoverflow answer. + * @param array $s The $_SERVER variable. Defaults to $_SERVER. + * @param bool $use_forwarded_host Whether to take the X-Forwarded-Host header into account. + * @return string The full url, as requested by the client. + */ +function full_url( $s = false, $use_forwarded_host = false ) +{ + return url_origin( $s, $use_forwarded_host ) . $s['REQUEST_URI']; +} + +/** + * Converts a filesize into a human-readable string. + * @package core + * @see http://php.net/manual/en/function.filesize.php#106569 The original source + * @author rommel + * @author Edited by Starbeamrainbowlabs + * @param number $bytes The number of bytes to convert. + * @param number $decimals The number of decimal places to preserve. + * @return string A human-readable filesize. + */ +function human_filesize($bytes, $decimals = 2) +{ + $sz = ["b", "kb", "mb", "gb", "tb", "pb", "eb", "yb", "zb"]; + $factor = floor((strlen($bytes) - 1) / 3); + $result = round($bytes / pow(1024, $factor), $decimals); + return $result . @$sz[$factor]; +} + +/** + * Calculates the time since a particular timestamp and returns a + * human-readable result. + * @package core + * @see http://goo.gl/zpgLgq The original source. No longer exists, maybe the wayback machine caught it :-( + * @param integer $time The timestamp to convert. + * @return string The time since the given timestamp as + * a human-readable string. + */ +function human_time_since($time) +{ + return human_time(time() - $time); +} +/** + * Renders a given number of seconds as something that humans can understand more easily. + * @package core + * @param int $seconds The number of seconds to render. + * @return string The rendered time. + */ +function human_time($seconds) +{ + $tokens = array ( + 31536000 => 'year', + 2592000 => 'month', + 604800 => 'week', + 86400 => 'day', + 3600 => 'hour', + 60 => 'minute', + 1 => 'second' + ); + foreach ($tokens as $unit => $text) { + if ($seconds < $unit) continue; + $numberOfUnits = floor($seconds / $unit); + return $numberOfUnits.' '.$text.(($numberOfUnits>1)?'s':'').' ago'; + } +} + +/** + * A recursive glob() function. + * @package core + * @see http://in.php.net/manual/en/function.glob.php#106595 The original source + * @author Mike + * @param string $pattern The glob pattern to use to find filenames. + * @param integer $flags The glob flags to use when finding filenames. + * @return array An array of the filepaths that match the given glob. + */ +function glob_recursive($pattern, $flags = 0) +{ + $files = glob($pattern, $flags); + foreach (glob(dirname($pattern).'/*', GLOB_ONLYDIR|GLOB_NOSORT) as $dir) + { + $prefix = "$dir/"; + // Remove the "./" from the beginning if it exists + if(substr($prefix, 0, 2) == "./") $prefix = substr($prefix, 2); + $files = array_merge($files, glob_recursive($prefix . basename($pattern), $flags)); + } + return $files; +} + +/** + * Gets the name of the parent page to the specified page. + * @since 0.15 + * @package core + * @param string $pagename The child page to get the parent + * page name for. + * @return string|bool + */ +function get_page_parent($pagename) { + if(mb_strpos($pagename, "/") === false) + return false; + return mb_substr($pagename, 0, mb_strrpos($pagename, "/")); +} + +/** + * Gets a list of all the sub pages of the current page. + * @package core + * @param object $pageindex The pageindex to use to search. + * @param string $pagename The name of the page to list the sub pages of. + * @return object An object containing all the subpages and their + * respective distances from the given page name in the pageindex tree. + */ +function get_subpages($pageindex, $pagename) +{ + $pagenames = get_object_vars($pageindex); + $result = new stdClass(); + + $stem = "$pagename/"; + $stem_length = strlen($stem); + foreach($pagenames as $entry => $value) + { + if(substr($entry, 0, $stem_length) == $stem) + { + // We found a subpage + + // Extract the subpage's key relative to the page that we are searching for + $subpage_relative_key = substr($entry, $stem_length, -3); + // Calculate how many times removed the current subpage is from the current page. 0 = direct descendant. + $times_removed = substr_count($subpage_relative_key, "/"); + // Store the name of the subpage we found + $result->$entry = $times_removed; + } + } + + unset($pagenames); + return $result; +} + +/** + * Makes sure that a subpage's parents exist. + * Note this doesn't check the pagename itself. + * @package core + * @param $pagename The pagename to check. + */ +function check_subpage_parents($pagename) +{ + global $pageindex, $paths, $env; + // Save the new pageindex and return if there aren't any more parent pages to check + if(strpos($pagename, "/") === false) + { + file_put_contents($paths->pageindex, json_encode($pageindex, JSON_PRETTY_PRINT)); + return; + } + + $parent_pagename = substr($pagename, 0, strrpos($pagename, "/")); + $parent_page_filename = "$parent_pagename.md"; + if(!file_exists($env->storage_prefix . $parent_page_filename)) + { + // This parent page doesn't exist! Create it and add it to the page index. + touch($env->storage_prefix . $parent_page_filename, 0); + + $newentry = new stdClass(); + $newentry->filename = $parent_page_filename; + $newentry->size = 0; + $newentry->lastmodified = 0; + $newentry->lasteditor = "none"; + $pageindex->$parent_pagename = $newentry; + } + + check_subpage_parents($parent_pagename); +} + +/** + * Makes a path safe. + * Paths may only contain alphanumeric characters, spaces, underscores, and + * dashes. + * @package core + * @param string $string The string to make safe. + * @return string A safe version of the given string. + */ +function makepathsafe($string) +{ + // Old restrictive system + //$string = preg_replace("/[^0-9a-zA-Z\_\-\ \/\.]/i", "", $string); + // Remove reserved characters + $string = preg_replace("/[?%*:|\"><()\\[\\]]/i", "", $string); + // Collapse multiple dots into a single dot + $string = preg_replace("/\.+/", ".", $string); + // Don't allow slashes at the beginning + $string = ltrim($string, "\\/"); + return $string; +} + +/** + * Hides an email address from bots by adding random html entities. + * @todo Make this moree clevererer :D + * @package core + * @param string $str The original email address + * @return string The mangled email address. + */ +function hide_email($str) +{ + $hidden_email = ""; + for($i = 0; $i < strlen($str); $i++) + { + if($str[$i] == "@") + { + $hidden_email .= "&#" . ord("@") . ";"; + continue; + } + if(rand(0, 1) == 0) + $hidden_email .= $str[$i]; + else + $hidden_email .= "&#" . ord($str[$i]) . ";"; + } + + return $hidden_email; +} +/** + * Checks to see if $haystack starts with $needle. + * @package core + * @param string $haystack The string to search. + * @param string $needle The string to search for at the beginning + * of $haystack. + * @return boolean Whether $needle can be found at the beginning + * of $haystack. + */ +function starts_with($haystack, $needle) +{ + $length = strlen($needle); + return (substr($haystack, 0, $length) === $needle); +} + +/** + * Case-insensitively finds all occurrences of $needle in $haystack. Handles + * UTF-8 characters correctly. + * @package core + * @see http://www.pontikis.net/tip/?id=16 the source + * @see http://www.php.net/manual/en/function.strpos.php#87061 the source that the above was based on + * @param string $haystack The string to search. + * @param string $needle The string to find. + * @return array|false An array of match indices, or false if + * nothing was found. + */ +function mb_stripos_all($haystack, $needle) { + $s = 0; $i = 0; + while(is_integer($i)) { + $i = function_exists("mb_stripos") ? mb_stripos($haystack, $needle, $s) : stripos($haystack, $needle, $s); + if(is_integer($i)) { + $aStrPos[] = $i; + $s = $i + (function_exists("mb_strlen") ? mb_strlen($needle) : strlen($needle)); + } + } + if(isset($aStrPos)) + return $aStrPos; + else + return false; +} + +/** + * Tests whether a string starts with a specified substring. + * @package core + * @param string $haystack The string to check against. + * @param string $needle The substring to look for. + * @return bool Whether the string starts with the specified substring. + */ +function startsWith($haystack, $needle) { + return $needle === "" || strrpos($haystack, $needle, -strlen($haystack)) !== false; +} +/** + * Tests whether a string ends with a given substring. + * @package core + * @param string $whole The string to test against. + * @param string $end The substring test for. + * @return bool Whether $whole ends in $end. + */ +function endsWith($whole, $end) +{ + return (strpos($whole, $end, strlen($whole) - strlen($end)) !== false); +} +/** + * Replaces the first occurrence of $find with $replace. + * @package core + * @param string $find The string to search for. + * @param string $replace The string to replace the search string with. + * @param string $subject The string ot perform the search and replace on. + * @return string The source string after the find and replace has been performed. + */ +function str_replace_once($find, $replace, $subject) +{ + $index = strpos($subject, $find); + if($index !== false) + return substr_replace($subject, $replace, $index, strlen($find)); + return $subject; +} + +/** + * Returns the system's mime type mappings, considering the first extension + * listed to be cacnonical. + * @package core + * @see http://stackoverflow.com/a/1147952/1460422 From this stackoverflow answer + * @author chaos + * @author Edited by Starbeamrainbowlabs + * @return array An array of mime type mappings. + */ +function system_mime_type_extensions() +{ + global $settings; + $out = array(); + $file = fopen($settings->mime_extension_mappings_location, 'r'); + while(($line = fgets($file)) !== false) { + $line = trim(preg_replace('/#.*/', '', $line)); + if(!$line) + continue; + $parts = preg_split('/\s+/', $line); + if(count($parts) == 1) + continue; + $type = array_shift($parts); + if(!isset($out[$type])) + $out[$type] = array_shift($parts); + } + fclose($file); + return $out; +} + +/** + * Converts a given mime type to it's associated file extension. + * @package core + * @see http://stackoverflow.com/a/1147952/1460422 From this stackoverflow answer + * @author chaos + * @author Edited by Starbeamrainbowlabs + * @param string $type The mime type to convert. + * @return string The extension for the given mime type. + */ +function system_mime_type_extension($type) +{ + static $exts; + if(!isset($exts)) + $exts = system_mime_type_extensions(); + return isset($exts[$type]) ? $exts[$type] : null; +} + +/** + * Returns the system MIME type mapping of extensions to MIME types. + * @package core + * @see http://stackoverflow.com/a/1147952/1460422 From this stackoverflow answer + * @author chaos + * @author Edited by Starbeamrainbowlabs + * @return array An array mapping file extensions to their associated mime types. + */ +function system_extension_mime_types() +{ + global $settings; + $out = array(); + $file = fopen($settings->mime_extension_mappings_location, 'r'); + while(($line = fgets($file)) !== false) { + $line = trim(preg_replace('/#.*/', '', $line)); + if(!$line) + continue; + $parts = preg_split('/\s+/', $line); + if(count($parts) == 1) + continue; + $type = array_shift($parts); + foreach($parts as $part) + $out[$part] = $type; + } + fclose($file); + return $out; +} +/** + * Converts a given file extension to it's associated mime type. + * @package core + * @see http://stackoverflow.com/a/1147952/1460422 From this stackoverflow answer + * @author chaos + * @author Edited by Starbeamrainbowlabs + * @param string $ext The extension to convert. + * @return string The mime type associated with the given extension. + */ +function system_extension_mime_type($ext) { + static $types; + if(!isset($types)) + $types = system_extension_mime_types(); + $ext = strtolower($ext); + return isset($types[$ext]) ? $types[$ext] : null; +} +/** + * Figures out whether a given http accepts header contains a + * specified mime type. + * @package core + * @param string $accept_header The accept header to search. + * @param string $mime_type The mime type to search for. + * @return bool Whether the specified mime type was found + * in the specified accepts header. + */ +function accept_contains_mime($accept_header, $mime_type) +{ + $accepted_mimes = explode(",", $accept_header); + foreach($accepted_mimes as $accepted_mime) { + if(explode(";", $accepted_mime)[0] == $mime_type) + return true; + } + return false; +} + +/** + * Generates a stack trace. + * @package core + * @param bool $log_trace Whether to send the stack trace to the error log. + * @param bool $full Whether to output a full description of all the variables involved. + * @return string A string prepresentation of a stack trace. + */ +function stack_trace($log_trace = true, $full = false) +{ + $result = ""; + $stackTrace = debug_backtrace(); + $stackHeight = count($stackTrace); + foreach ($stackTrace as $i => $stackEntry) + { + $result .= "#" . ($stackHeight - $i) . ": "; + $result .= (isset($stackEntry["file"]) ? $stackEntry["file"] : "(unknown file)") . ":" . (isset($stackEntry["line"]) ? $stackEntry["line"] : "(unknown line)") . " - "; + if(isset($stackEntry["function"])) + { + $result .= "(calling " . $stackEntry["function"]; + if(isset($stackEntry["args"]) && count($stackEntry["args"])) + { + $result .= ": "; + $result .= implode(", ", array_map($full ? "var_dump_ret" : "var_dump_short", $stackEntry["args"])); + } + } + $result .= ")\n"; + } + if($log_trace) + error_log($result); + return $result; +} +/** + * Calls var_dump() and returns the output. + * @package core + * @param mixed $var The thing to pass to var_dump(). + * @return string The output captured from var_dump(). + */ +function var_dump_ret($var) +{ + ob_start(); + var_dump($var); + return ob_get_clean(); +} + +/** + * Calls var_dump(), shortening the output for various types. + * @package core + * @param mixed $var The thing to pass to var_dump(). + * @return string A shortened version of the var_dump() output. + */ +function var_dump_short($var) +{ + $result = trim(var_dump_ret($var)); + if(substr($result, 0, 6) === "object" || substr($result, 0, 5) === "array") + { + $result = substr($result, 0, strpos($result, " ")) . " { ... }"; + } + return $result; +} + +if (!function_exists('getallheaders')) { + /** + * Polyfill for PHP's native getallheaders() function on platforms that + * don't have it. + * @package core + * @todo Identify which platforms don't have it and whether we still need this + */ + function getallheaders() + { + if (!is_array($_SERVER)) + return []; + + $headers = array(); + foreach ($_SERVER as $name => $value) { + if (substr($name, 0, 5) == 'HTTP_') { + $headers[str_replace(' ', '-', ucwords(strtolower(str_replace('_', ' ', substr($name, 5)))))] = $value; + } + } + return $headers; + } +} +/** + * Renders a timestamp in HTML. + * @package core + * @param int $timestamp The timestamp to render. + * @return string HTML representing the given timestamp. + */ +function render_timestamp($timestamp) +{ + return ""; +} +/** + * Renders a page name in HTML. + * @package core + * @param object $rchange The recent change to render as a page name + * @return string HTML representing the name of the given page. + */ +function render_pagename($rchange) +{ + global $pageindex; + $pageDisplayName = $rchange->page; + if(isset($pageindex->$pageDisplayName) and !empty($pageindex->$pageDisplayName->redirect)) + $pageDisplayName = "$pageDisplayName"; + $pageDisplayLink = "$pageDisplayName"; + return $pageDisplayName; +} +/** + * Renders an editor's or a group of editors name(s) in HTML. + * @package core + * @param string $editorName The name of the editor to render. + * @return string HTML representing the given editor's name. + */ +function render_editor($editorName) +{ + return "✎ $editorName"; +} + +/** + * Saves the currently logged in uesr's data back to peppermint.json. + * @package core + * @return bool Whether the user's data was saved successfully. Returns false if the user isn't logged in. + */ +function save_userdata() +{ + global $env, $settings, $paths; + + if(!$env->is_logged_in) + return false; + + $settings->users->{$env->user} = $env->user_data; + file_put_contents($paths->settings_file, json_encode($settings, JSON_PRETTY_PRINT)); + + return true; +} + +/** + * Figures out the path to the user page for a given username. + * Does not check to make sure the user acutally exists. + * @package core + * @param string $username The username to get the path to their user page for. + * @return string The path to the given user's page. + */ +function get_user_pagename($username) { + global $settings; + return "$settings->user_page_prefix/$username"; +} +/** + * Extracts a username from a user page path. + * @package core + * @param string $userPagename The suer page path to extract from. + * @return string The name of the user that the user page belongs to. + */ +function extract_user_from_userpage($userPagename) { + global $settings; + $matches = []; + preg_match("/$settings->user_page_prefix\\/([^\\/]+)\\/?/", $userPagename, $matches); + + return $matches[1]; +} + +/** + * Sends a plain text email to a user, replacing {username} with the specified username. + * @package core + * @param string $username The username to send the email to. + * @param string $subject The subject of the email. + * @param string $body The body of the email. + * @return boolean Whether the email was sent successfully or not. Currently, this may fail if the user doesn't have a registered email address. + */ +function email_user($username, $subject, $body) +{ + global $version, $settings; + + // If the user doesn't have an email address, then we can't email them :P + if(empty($settings->users->{$username}->emailAddress)) + return false; + + $subject = str_replace("{username}", $username, $subject); + $body = str_replace("{username}", $username, $body); + + $headers = [ + "content-type" => "text/plain", + "x-mailer" => "$settings->sitename Pepperminty-Wiki/$version PHP/" . phpversion(), + "reply-to" => "$settings->admindetails_name <$settings->admindetails_email>" + ]; + $compiled_headers = ""; + foreach($headers as $header => $value) + $compiled_headers .= "$header: $value\r\n"; + + mail($settings->users->{$username}->emailAddress, $subject, $body, $compiled_headers, "-t"); + return true; +} +/** + * Sends a plain text email to a list of users, replacing {username} with each user's name. + * @package core + * @param string[] $usernames A list of usernames to email. + * @param string $subject The subject of the email. + * @param string $body The body of the email. + * @return integer The number of emails sent successfully. + */ +function email_users($usernames, $subject, $body) +{ + $emailsSent = 0; + foreach($usernames as $username) + { + $emailsSent += email_user($username, $subject, $body) ? 1 : 0; + } + return $emailsSent; +} + +/////////////////////////////////////////////////////////////////////////////// + +/////////////////////////////////////////////////////////////////////////////// +////////////////////// Security and Consistency Measures ////////////////////// +/////////////////////////////////////////////////////////////////////////////// + +/* + * Sort out the pageindex. Create it if it doesn't exist, and load + parse it + * if it does. + */ +if(!file_exists($paths->pageindex)) +{ + $glob_str = $env->storage_prefix . "*.md"; + $existingpages = glob_recursive($glob_str); + // Debug statements. Uncomment when debugging the pageindex regenerator. + // var_dump($env->storage_prefix); + // var_dump($glob_str); + // var_dump($existingpages); + $pageindex = new stdClass(); + // We use a for loop here because foreach doesn't loop over new values inserted + // while we were looping + for($i = 0; $i < count($existingpages); $i++) + { + $pagefilename = $existingpages[$i]; + + // Create a new entry + $newentry = new stdClass(); + $newentry->filename = utf8_encode(substr( // Store the filename, whilst trimming the storage prefix + $pagefilename, + strlen(preg_replace("/^\.\//i", "", $env->storage_prefix)) // glob_recursive trim the ./ from returned filenames , so we need to as well + )); + // Remove the `./` from the beginning if it's still hanging around + if(substr($newentry->filename, 0, 2) == "./") + $newentry->filename = substr($newentry->filename, 2); + $newentry->size = filesize($pagefilename); // Store the page size + $newentry->lastmodified = filemtime($pagefilename); // Store the date last modified + // Todo find a way to keep the last editor independent of the page index + $newentry->lasteditor = utf8_encode("unknown"); // Set the editor to "unknown" + // Extract the name of the (sub)page without the ".md" + $pagekey = utf8_encode(substr($newentry->filename, 0, -3)); + + if(file_exists($env->storage_prefix . $pagekey) && // If it exists... + !is_dir($env->storage_prefix . $pagekey)) // ...and isn't a directory + { + // This page (potentially) has an associated file! + // Let's investigate. + + // Blindly add the file to the pageindex for now. + // Future We might want to do a security check on the file later on. + // File a bug if you think we should do this. + $newentry->uploadedfile = true; // Yes this page does have an uploaded file associated with it + $newentry->uploadedfilepath = $pagekey; // It's stored here + + // Work out what kind of file it really is + $mimechecker = finfo_open(FILEINFO_MIME_TYPE); + $newentry->uploadedfilemime = finfo_file($mimechecker, $env->storage_prefix . $pagekey); + } + + // Debug statements. Uncomment when debugging the pageindex regenerator. + // echo("pagekey: "); + // var_dump($pagekey); + // echo("newentry: "); + // var_dump($newentry); + + // Subpage parent checker + if(strpos($pagekey, "/") !== false) + { + // We have a sub page people + // Work out what our direct parent's key must be in order to check to + // make sure that it actually exists. If it doesn't, then we need to + // create it. + $subpage_parent_key = substr($pagekey, 0, strrpos($pagekey, "/")); + $subpage_parent_filename = "$env->storage_prefix$subpage_parent_key.md"; + if(array_search($subpage_parent_filename, $existingpages) === false) + { + // Our parent page doesn't actually exist - create it + touch($subpage_parent_filename, 0); + // Furthermore, we should add this page to the list of existing pages + // in order for it to be indexed + $existingpages[] = $subpage_parent_filename; + } + } + + // Store the new entry in the new page index + $pageindex->$pagekey = $newentry; + } + file_put_contents($paths->pageindex, json_encode($pageindex, JSON_PRETTY_PRINT)); + unset($existingpages); +} +else +{ + $pageindex_read_start = microtime(true); + $pageindex = json_decode(file_get_contents($paths->pageindex)); + $env->perfdata->pageindex_decode_time = round((microtime(true) - $pageindex_read_start)*1000, 3); + header("x-pageindex-decode-time: " . $env->perfdata->pageindex_decode_time . "ms"); +} + +////////////////////////// +///// Page id system ///// +////////////////////////// +if(!file_exists($paths->idindex)) + file_put_contents($paths->idindex, "{}"); +$idindex_decode_start = microtime(true); +$idindex = json_decode(file_get_contents($paths->idindex)); +$env->perfdata->idindex_decode_time = round((microtime(true) - $idindex_decode_start)*1000, 3); +/** + * Provides an interface to interact with page ids. + * @package core + */ +class ids +{ + /** + * Gets the page id associated with the given page name. + * If it doesn't exist in the id index, it will be added. + * @package core + * @param string $pagename The name of the page to fetch the id for. + * @return integer The id for the specified page name. + */ + public static function getid($pagename) + { + global $idindex; + + foreach ($idindex as $id => $entry) + { + if($entry == $pagename) + return $id; + } + + // This pagename doesn't have an id - assign it one quick! + return self::assign($pagename); + } + + /** + * Gets the page name associated with the given page id. + * Be warned that if the id index is cleared (e.g. when the search index is + * rebuilt from scratch), the id associated with a page name may change! + * @package core + * @param int $id The id to fetch the page name for. + * @return string The page name currently associated with the specified id. + */ + public static function getpagename($id) + { + global $idindex; + + if(!isset($idindex->$id)) + return false; + else + return $idindex->$id; + } + + /** + * Moves a page in the id index from $oldpagename to $newpagename. + * Note that this function doesn't perform any special checks to make sure + * that the destination name doesn't already exist. + * @package core + * @param string $oldpagename The old page name to move. + * @param string $newpagename The new pagee name to move the old page name to. + */ + public static function movepagename($oldpagename, $newpagename) + { + global $idindex, $paths; + + $pageid = self::getid($oldpagename); + $idindex->$pageid = $newpagename; + + file_put_contents($paths->idindex, json_encode($idindex)); + } + + /** + * Removes the given page name from the id index. + * Note that this function doesn't handle multiple entries with the same + * name. Also note that it may get re-added during a search reindex if the + * page still exists. + * @package core + * @param string $pagename The page name to delete from the id index. + */ + public static function deletepagename($pagename) + { + global $idindex, $paths; + + // Get the id of the specified page + $pageid = self::getid($pagename); + // Remove it from the pageindex + unset($idindex->$pageid); + // Save the id index + file_put_contents($paths->idindex, json_encode($idindex)); + } + + /** + * Clears the id index completely. + * Will break the inverted search index! Make sure you rebuild the search + * index (if the search module is installed, of course) if you want search + * to still work. Of course, note that will re-add all the pages to the id + * index. + * @package core + */ + public static function clear() + { + global $paths, $idindex; + // Delete the old id index + unlink($paths->idindex); + // Create the new id index + file_put_contents($paths->idindex, "{}"); + // Reset the in-memory id index + $idindex = new stdClass(); + } + + /** + * Assigns an id to a pagename. Doesn't check to make sure that + * pagename doesn't already exist in the id index. + * @package core + * @param string $pagename The page name to assign an id to. + * @return integer The id assigned to the specified page name. + */ + protected static function assign($pagename) + { + global $idindex, $paths; + + $nextid = count(array_keys(get_object_vars($idindex))); + // Increment the generated id until it's unique + while(isset($idindex->nextid)) + $nextid++; + + // Update the id index + $idindex->$nextid = utf8_encode($pagename); + + // Save the id index + file_put_contents($paths->idindex, json_encode($idindex)); + + return $nextid; + } +} +////////////////////////// +////////////////////////// + +// Work around an Opera + Syntaxtic bug where there is no margin at the left +// hand side if there isn't a query string when accessing a .php file. +if(!isset($_GET["action"]) and !isset($_GET["page"]) and basename(parse_url($_SERVER['REQUEST_URI'], PHP_URL_PATH)) == "index.php") +{ + http_response_code(302); + header("location: " . dirname(parse_url($_SERVER['REQUEST_URI'], PHP_URL_PATH))); + exit(); +} + +// Make sure that the action is set +if(empty($_GET["action"])) + $_GET["action"] = $settings->defaultaction; +// Make sure that the page is set +if(empty($_GET["page"]) or strlen($_GET["page"]) === 0) + $_GET["page"] = $settings->defaultpage; + +// Redirect the user to the safe version of the path if they entered an unsafe character +if(makepathsafe($_GET["page"]) !== $_GET["page"]) +{ + http_response_code(301); + header("location: index.php?action=" . rawurlencode($_GET["action"]) . "&page=" . makepathsafe($_GET["page"])); + header("x-requested-page: " . $_GET["page"]); + header("x-actual-page: " . makepathsafe($_GET["page"])); + exit(); +} + + +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// + +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////// HTML fragments //////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +/** + * Renders the HTML page that is sent to the client. + * @package core + */ +class page_renderer +{ + /** + * The root HTML template that all pages are built from. + * @var string + * @package core + */ + public static $html_template = " + + + + {title} + + + + {header-html} + + + {body} + + + +"; + /** + * The main content template that is used to render normal wiki pages. + * @var string + * @package core + */ + public static $main_content_template = "{navigation-bar} +

{sitename}

+
+ {content} +
+ {extra} + + {navigation-bar-bottom} + {all-pages-datalist}"; + /** + * A specially minified content template that doesn't include the navbar and + * other elements not suiltable for printing. + * @var string + * @package core + */ + public static $minimal_content_template = "
{content}
+ "; + + /** + * An array of functions that have been registered to process the + * find / replace array before the page is rendered. Note that the function + * should take a *reference* to an array as its only argument. + * @var array + * @package core + */ + protected static $part_processors = []; + + /** + * Registers a function as a part post processor. + * @package core + * @param function $function The part preprocessor to register. + */ + public static function register_part_preprocessor($function) + { + global $settings; + + // Make sure that the function we are about to register is valid + if(!is_callable($function)) + { + http_response_code(500); + $admin_name = $settings->admindetails_name; + $admin_email = hide_email($settings->admindetails_email); + exit(page_renderer::render("$settings->sitename - Module Error", "

$settings->sitename has got a misbehaving module installed that tried to register an invalid HTML handler with the page renderer. Please contact $settings->sitename's administrator $admin_name at $admin_email.")); + } + + self::$part_processors[] = $function; + + return true; + } + + /** + * Renders a HTML page with the content specified. + * @package core + * @param string $title The title of the page. + * @param string $content The (HTML) content of the page. + * @param boolean $body_template The HTML content template to use. + * @return string The rendered HTML, ready to send to the client :-) + */ + public static function render($title, $content, $body_template = false) + { + global $settings, $start_time, $version; + + if($body_template === false) + $body_template = self::$main_content_template; + + if(strlen($settings->logo_url) > 0) + { + // A logo url has been specified + $logo_html = ""; + switch($settings->logo_position) + { + case "left": + $logo_html = "$logo_html $settings->sitename"; + break; + case "right": + $logo_html .= " $settings->sitename"; + break; + default: + throw new Exception("Invalid logo_position '$settings->logo_position'. Valid values are either \"left\" or \"right\" and are case sensitive."); + } + } + + $parts = [ + "{body}" => $body_template, + + "{sitename}" => $logo_html, + "{version}" => $version, + "{favicon-url}" => $settings->favicon, + "{header-html}" => self::get_header_html(), + + "{navigation-bar}" => self::render_navigation_bar($settings->nav_links, $settings->nav_links_extra, "top"), + "{navigation-bar-bottom}" => self::render_navigation_bar($settings->nav_links_bottom, [], "bottom"), + + "{admin-details-name}" => $settings->admindetails_name, + "{admin-details-email}" => $settings->admindetails_email, + + "{admins-name-list}" => implode(", ", array_map(function($username) { return page_renderer::render_username($username); }, $settings->admins)), + + "{generation-date}" => date("l jS \of F Y \a\\t h:ia T"), + + "{all-pages-datalist}" => self::generate_all_pages_datalist(), + + "{footer-message}" => $settings->footer_message, + + /// Secondary Parts /// + + "{content}" => $content, + "{extra}" => "", + "{title}" => $title, + ]; + + // Pass the parts through the part processors + foreach(self::$part_processors as $function) + { + $function($parts); + } + + $result = self::$html_template; + + $result = str_replace(array_keys($parts), array_values($parts), $result); + + $result = str_replace("{generation-time-taken}", round((microtime(true) - $start_time)*1000, 2), $result); + return $result; + } + /** + * Renders a normal HTML page. + * @package core + * @param string $title The title of the page. + * @param string $content The content of the page. + * @return string The rendered page. + */ + public static function render_main($title, $content) + { + return self::render($title, $content, self::$main_content_template); + } + /** + * Renders a minimal HTML page. Useful for printable pages. + * @package core + * @param string $title The title of the page. + * @param string $content The content of the page. + * @return string The rendered page. + */ + public static function render_minimal($title, $content) + { + return self::render($title, $content, self::$minimal_content_template); + } + + /** + * Renders the header HTML. + * @package core + * @return string The rendered HTML that goes in the header. + */ + public static function get_header_html() + { + global $settings; + $result = self::get_css_as_html(); + $result .= self::getJS(); + + // We can't use module_exists here because sometimes global $modules + // hasn't populated yet when we get called O.o + if(class_exists("search")) + $result .= "\t\t\n"; + + if(!empty($settings->enable_math_rendering)) + { + $result .= ""; + } + + return $result; + } + /** + * Renders all the CSS as HTML. + * @package core + * @return string The css as HTML, ready to be included in the HTML header. + */ + public static function get_css_as_html() + { + global $settings; + + if(preg_match("/^[^\/]*\/\/|^\//", $settings->css)) + return "\n"; + else + { + $css = $settings->css; + if(!empty($settings->optimize_pages)) + { + // CSS Minification ideas by Jean from catswhocode.com + // Link: http://www.catswhocode.com/blog/3-ways-to-compress-css-files-using-php + // Remove comments + $css = preg_replace('!/\*[^*]*\*+([^/][^*]*\*+)*/!', "", $css); + // Cut down whitespace + $css = preg_replace('/\s+/', " ", $css); + // Remove whitespace after colons and semicolons + $css = str_replace([ + " :", + ": ", + "; ", + " { ", + " } " + ], [ + ":", + ":", + ";", + "{", + "}" + ], $css); + + } + return "\n"; + } + } + /** + * The javascript snippets that will be included in the page. + * @var string[] + * @package core + */ + private static $jsSnippets = []; + /** + * The urls of the external javascript files that should be referenced + * by the page. + * @var string[] + * @package core + */ + private static $jsLinks = []; + /** + * Adds the specified url to a javascript file as a reference to the page. + * @package core + * @param string $scriptUrl The url of the javascript file to reference. + */ + public function AddJSLink(string $scriptUrl) + { + static::$jsLinks[] = $scriptUrl; + } + /** + * Adds a javascript snippet to the page. + * @package core + * @param string $script The snippet of javascript to add. + */ + public function AddJSSnippet(string $script) + { + static::$jsSnippets[] = $script; + } + /** + * Renders the included javascript header for inclusion in the final + * rendered page. + * @package core + * @return string The rendered javascript ready for inclusion in the page. + */ + private static function getJS() + { + $result = "\n"; + foreach(static::$jsSnippets as $snippet) + $result .= "\n"; + foreach(static::$jsLinks as $link) + $result .= "\n"; + return $result; + } + + // ~ + + /** + * The navigation bar divider. + * @package core + * @var string + */ + public static $nav_divider = " | "; + + /** + * Renders a navigation bar from an array of links. See + * $settings->nav_links for format information. + * @package core + * @param array $nav_links The links to add to the navigation bar. + * @param array $nav_links_extra The extra nav links to add to + * the "More..." menu. + * @param string $class The class(es) to assign to the rendered + * navigation bar. + */ + public static function render_navigation_bar($nav_links, $nav_links_extra, $class = "") + { + global $settings, $env; + $result = "

"; + return $result; + } + /** + * Renders a username for inclusion in a page. + * @package core + * @param string $name The username to render. + * @return string The username rendered in HTML. + */ + public static function render_username($name) + { + global $settings; + $result = ""; + $result .= ""; + if($settings->avatars_show) + $result .= " "; + if(in_array($name, $settings->admins)) + $result .= $settings->admindisplaychar; + $result .= htmlentities($name); + $result .= ""; + + return $result; + } + + // ~ + + /** + * Renders the datalist for the search box as HTML. + * @package core + * @return string The search box datalist as HTML. + */ + public static function generate_all_pages_datalist() + { + global $settings, $pageindex; + $arrayPageIndex = get_object_vars($pageindex); + ksort($arrayPageIndex); + $result = "\n"; + + // If dynamic page sugggestions are enabled, then we should send a loading message instead. + if($settings->dynamic_page_suggestion_count > 0) + { + $result .= ""; + + return $result; + } +} + +// Math rendering support +if(!empty($settings->enable_math_rendering)) +{ + page_renderer::AddJSLink("https://cdnjs.cloudflare.com/ajax/libs/mathjax/2.7.1/MathJax.js?config=TeX-MML-AM_CHTML"); +} +// alt+enter support in the search box +page_renderer::AddJSSnippet('// Alt + Enter support in the top search box +window.addEventListener("load", function(event) { + document.querySelector("input[type=search]").addEventListener("keyup", function(event) { + // Listen for Alt + Enter + if(event.keyCode == 13 && event.altKey) { + event.stopPropagation(); + event.preventDefault(); + event.cancelBubble = true; + event.target.form.setAttribute("target", "_blank"); + event.target.form.submit(); + event.target.form.removeAttribute("target"); + } + }); +}); +'); + +/// Finish setting up the environment object /// +$env->page = $_GET["page"]; +if(isset($_GET["revision"]) and is_numeric($_GET["revision"])) +{ + // We have a revision number! + $env->is_history_revision = true; + $env->history->revision_number = intval($_GET["revision"]); + + // Make sure that the revision exists for later on + if(!isset($pageindex->{$env->page}->history[$env->history->revision_number])) + { + http_response_code(404); + exit(page_renderer::render_main("404: Revision Not Found - $env->page - $settings->sitename", "

Revision #{$env->history->revision_number} of $env->page doesn't appear to exist. Try viewing the list of revisions for $env->page, or viewing the latest revision instead.

")); + } + + $env->history->revision_data = $pageindex->{$env->page}->history[$env->history->revision_number]; +} +// Construct the page's filename +$env->page_filename = $env->storage_prefix; +if($env->is_history_revision) + $env->page_filename .= $pageindex->{$env->page}->history[$env->history->revision_number]->filename; +else if(isset($pageindex->{$env->page})) + $env->page_filename .= $pageindex->{$env->page}->filename; + +$env->action = strtolower($_GET["action"]); + +//////////////////////////////////////////////// + +////////////////////////////////////// +///// Extra consistency measures ///// +////////////////////////////////////// +// Redirect to the search page if there isn't a page with the requested name +if(!isset($pageindex->{$env->page}) and isset($_GET["search-redirect"])) +{ + http_response_code(307); + $url = "?action=search&query=" . rawurlencode($env->page); + header("location: $url"); + exit(page_renderer::render("Non existent page - $settings->sitename", "

There isn't a page on $settings->sitename with that name. However, you could search for this page name in other pages.

+

Alternatively, you could create this page.

")); +} + +// Redirect the user to the login page if: +// - A login is required to view this wiki +// - The user isn't already requesting the login page +// Note we use $_GET here because $env->action isn't populated at this point +if($settings->require_login_view === true && // If this site requires a login in order to view pages + !$env->is_logged_in && // And the user isn't logged in + !in_array($_GET["action"], [ "login", "checklogin", "opensearch-description", "invindex-rebuild", "stats-update" ])) // And the user isn't trying to login, or get the opensearch description, or access actions that apply their own access rules +{ + // Redirect the user to the login page + http_response_code(307); + $url = "?action=login&returnto=" . rawurlencode($_SERVER["REQUEST_URI"]) . "&required=true"; + header("location: $url"); + exit(page_renderer::render("Login required - $settings->sitename", "

$settings->sitename requires that you login before you are able to access it.

+

Login.

")); +} +////////////////////////////////////// +////////////////////////////////////// + +////////////////////////// +/// Module functions /// +////////////////////////// +// These functions are // +// used by modules to // +// register themselves // +// or new pages. // +////////////////////////// +/** A list of all the currentlyloaded modules. Not guaranteed to be populated until an action is executed. */ +$modules = []; +/** + * Registers a module. + * @package core + * @param array $moduledata The module data to register. + */ +function register_module($moduledata) +{ + global $modules; + //echo("registering module\n"); + //var_dump($moduledata); + $modules[] = $moduledata; +} +/** + * Checks to see whether a module with the given id exists. + * @package core + * @param string $id The id to search for. + * @return bool Whether a module is currently loaded with the given id. + */ +function module_exists($id) +{ + global $modules; + foreach($modules as $module) + { + if($module["id"] == $id) + return true; + } + return false; +} + +$actions = new stdClass(); +/** + * Registers a new action handler. + * @package core + * @param string $action_name The action to register. + * @param function $func The function to call when the specified + * action is requested. + */ +function add_action($action_name, $func) +{ + global $actions; + $actions->$action_name = $func; +} +/** + * Figures out whether a given action is currently registered. + * Only guaranteed to be accurate in inside an existing action function + * @package core + * @param string $action_name The name of the action to search for + * @return boolean Whether an action with the specified name exists. + */ +function has_action($action_name) +{ + global $actions; + return !empty($actions->$action_name); +} + +$parsers = [ + "none" => function() { + throw new Exception("No parser registered!"); + } +]; +/** + * Registers a new parser. + * @package core + * @param string $name The name of the new parser to register. + * @param function $parser_code The function to register as a new parser. + */ +function add_parser($name, $parser_code) +{ + global $parsers; + if(isset($parsers[$name])) + throw new Exception("Can't register parser with name '$name' because a parser with that name already exists."); + + $parsers[$name] = $parser_code; +} +/** + * Parses the specified page source using the parser specified in the settings + * into HTML. + * The specified parser may (though it's unilkely) render it to other things. + * @package core + * @param string $source The source to render. + * @return string The source rendered to HTML. + */ +function parse_page_source($source) +{ + global $settings, $parsers; + if(!isset($parsers[$settings->parser])) + exit(page_renderer::render_main("Parsing error - $settings->sitename", "

Parsing some page source data failed. This is most likely because $settings->sitename has the parser setting set incorrectly. Please contact " . $settings->admindetails_name . ", your $settings->sitename Administrator.")); + +/* Not needed atm because escaping happens when saving, not when rendering * + if($settings->clean_raw_html) + $source = htmlentities($source, ENT_QUOTES | ENT_HTML5); +*/ + return $parsers[$settings->parser]($source); +} + +// Function to +$save_preprocessors = []; +/** + * Register a new proprocessor that will be executed just before + * an edit is saved. + * @package core + * @param function $func The function to register. + */ +function register_save_preprocessor($func) +{ + global $save_preprocessors; + $save_preprocessors[] = $func; +} + +$help_sections = []; +/** + * Adds a new help section to the help page. + * @package core + * @param string $index The string to index the new section under. + * @param string $title The title to display above the section. + * @param string $content The content to display. + */ +function add_help_section($index, $title, $content) +{ + global $help_sections; + + $help_sections[$index] = [ + "title" => $title, + "content" => $content + ]; +} + +if(!empty($settings->enable_math_rendering)) + add_help_section("22-mathematical-mxpressions", "Mathematical Expressions", "

$settings->sitename supports rendering of mathematical expressions. Mathematical expressions can be included practically anywhere in your page. Expressions should be written in LaTeX and enclosed in dollar signs like this: $x^2$.

+

Note that expression parsing is done on the viewer's computer with javascript (specifically MathJax) and not by $settings->sitename directly (also called client side rendering).

"); + +/** An array of the currently registerd statistic calculators. Not guaranteed to be populated until the requested action function is called. */ +$statistic_calculators = []; +/** + * Registers a statistic calculator against the system. + * @package core + * @param array $stat_data The statistic object to register. + */ +function statistic_add($stat_data) { + global $statistic_calculators; + $statistic_calculators[$stat_data["id"]] = $stat_data; +} +/** + * Checks whether a specified statistic has been registered. + * @package core + * @param string $stat_id The id of the statistic to check the existence of. + * @return boolean Whether the specified statistic has been registered. + */ +function has_statistic($stat_id) { + global $statistic_calculators; + return !empty($statistic_calculators[$stat_id]); +} + +////////////////////////////////////////////////////////////////// + +// %next_module% // + +////////////////////////////////////////////////////////////////// + +// Execute each module's code +foreach($modules as $moduledata) +{ + $moduledata["code"](); +} +// Make sure that the credits page exists +if(!isset($actions->credits)) +{ + exit(page_renderer::render_main("Error - $settings->$sitename", "

No credits page detected. The credits page is a required module!

")); +} + +// Perform the appropriate action +$action_name = $env->action; +if(isset($actions->$action_name)) +{ + $req_action_data = $actions->$action_name; + $req_action_data(); +} +else +{ + exit(page_renderer::render_main("Error - $settings->sitename", "

No action called " . strtolower($_GET["action"]) ." has been registered. Perhaps you are missing a module?

")); +} + +?> + diff --git a/docs/ModuleApi/files/download.html b/docs/ModuleApi/files/download.html new file mode 100644 index 0000000..2a6949f --- /dev/null +++ b/docs/ModuleApi/files/download.html @@ -0,0 +1,250 @@ + + + + + + Pepperminty Wiki Module API + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ +
+
+
+
+
+ + + +

download.php

+

+ + + + +
+ + +
+ + + +
+
+ + + + +
+ + + diff --git a/docs/ModuleApi/files/download.php.txt b/docs/ModuleApi/files/download.php.txt new file mode 100644 index 0000000..e131f71 --- /dev/null +++ b/docs/ModuleApi/files/download.php.txt @@ -0,0 +1,94 @@ + + + + + Pepperminty Wiki Download + + +

Pepperminty Wiki Downloader

+ + +

Module selector

+

Choose the modules that you want to include in your installation of Pepperminty Wiki .

+ +

+ + +

+ + + + + + + + + + + optional) && $module->optional === true) ? "" : " checked"; + echo(" + + + + + + + "); + + } + ?> +
NameDescriptionAuthorVersionLast Updated
$module->description$module->author$module->version" . date("D jS M Y", $module->lastupdate) . "
+ +
+
+ + + +
+ +

+ Pepperminty Wiki was built by Starbeamrainbowlabs. The code is available on GitHub. +

+

+ Other contributors: @ikisler +

+ + + + + + + + + + diff --git a/docs/ModuleApi/files/modules.action-hash.html b/docs/ModuleApi/files/modules.action-hash.html new file mode 100644 index 0000000..50a849d --- /dev/null +++ b/docs/ModuleApi/files/modules.action-hash.html @@ -0,0 +1,250 @@ + + + + + + Pepperminty Wiki Module API + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ +
+
+
+
+
+ + + +

modulesaction-hash.php

+

+ + + + +
+ + +
+ + + +
+
+ + + + +
+ + + diff --git a/docs/ModuleApi/files/modules.action-protect.html b/docs/ModuleApi/files/modules.action-protect.html new file mode 100644 index 0000000..6f2f921 --- /dev/null +++ b/docs/ModuleApi/files/modules.action-protect.html @@ -0,0 +1,250 @@ + + + + + + Pepperminty Wiki Module API + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ +
+
+
+
+
+ + + +

modulesaction-protect.php

+

+ + + + +
+ + +
+ + + +
+
+ + + + +
+ + + diff --git a/docs/ModuleApi/files/modules.action-random.html b/docs/ModuleApi/files/modules.action-random.html new file mode 100644 index 0000000..a33d158 --- /dev/null +++ b/docs/ModuleApi/files/modules.action-random.html @@ -0,0 +1,250 @@ + + + + + + Pepperminty Wiki Module API + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ +
+
+
+
+
+ + + +

modulesaction-random.php

+

+ + + + +
+ + +
+ + + +
+
+ + + + +
+ + + diff --git a/docs/ModuleApi/files/modules.action-raw.html b/docs/ModuleApi/files/modules.action-raw.html new file mode 100644 index 0000000..38b187f --- /dev/null +++ b/docs/ModuleApi/files/modules.action-raw.html @@ -0,0 +1,250 @@ + + + + + + Pepperminty Wiki Module API + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ +
+
+
+
+
+ + + +

modulesaction-raw.php

+

+ + + + +
+ + +
+ + + +
+
+ + + + +
+ + + diff --git a/docs/ModuleApi/files/modules.api-status.html b/docs/ModuleApi/files/modules.api-status.html new file mode 100644 index 0000000..e562527 --- /dev/null +++ b/docs/ModuleApi/files/modules.api-status.html @@ -0,0 +1,250 @@ + + + + + + Pepperminty Wiki Module API + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ +
+
+
+
+
+ + + +

modulesapi-status.php

+

+ + + + +
+ + +
+ + + +
+
+ + + + +
+ + + diff --git a/docs/ModuleApi/files/modules.extra-sidebar.html b/docs/ModuleApi/files/modules.extra-sidebar.html new file mode 100644 index 0000000..877d7f6 --- /dev/null +++ b/docs/ModuleApi/files/modules.extra-sidebar.html @@ -0,0 +1,309 @@ + + + + + + Pepperminty Wiki Module API + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ +
+
+
+
+
+ + + +

modulesextra-sidebar.php

+

+ + + + +
+ + +
+ + +
+
+

Functions

+
+ +
+ +
+
+ +
+

render_sidebar()

+ +
render_sidebar(array  $pageindex, string  $root_pagename = "") : string
+

Renders the sidebar for a given pageindex.

+ + +

Parameters

+ + + + + + + + + + + +
array$pageindex

The pageindex to render the sidebar for

string$root_pagename

The pagename that should be considered the root of the rendering. You don't usually need to use this, it is used by the algorithm itself since it is recursive.

+ + +

Returns

+ string + —

A HTML rendering of the sidebar for the given pageindex.

+ +
+
+ +
+ + +
+
+ + + + +
+ + + diff --git a/docs/ModuleApi/files/modules.feature-comments.html b/docs/ModuleApi/files/modules.feature-comments.html new file mode 100644 index 0000000..f9b4674 --- /dev/null +++ b/docs/ModuleApi/files/modules.feature-comments.html @@ -0,0 +1,504 @@ + + + + + + Pepperminty Wiki Module API + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ +
+
+
+
+
+ + + +

modulesfeature-comments.php

+

+ + + + +
+ + +
+ + +
+
+

Functions

+
+ +
+ +
+
+ +
+

get_comment_filename()

+ +
get_comment_filename(string  $pagename) : string
+

Given a page name, returns the absolute file path in which that page's +comments are stored.

+ + +

Parameters

+ + + + + + +
string$pagename

The name pf the page to fetch the comments filename for.

+ + +

Returns

+ string + —

The path to the file that the

+ +
+
+ +
+ +
+
+ +
+

generate_comment_id()

+ +
generate_comment_id() : string
+

Generates a new random comment id.

+ + + + +

Returns

+ string + —

A new random comment id.

+ +
+
+ +
+ +
+
+ +
+

find_comment()

+ +
find_comment(array  $comment_data, string  $comment_id) : object
+

Finds the comment with specified id by way of an almost-breadth-first search.

+ + +

Parameters

+ + + + + + + + + + + +
array$comment_data

The comment data to search.

string$comment_id

The id of the comment to find.

+ + +

Returns

+ object + —

The comment data with the specified id, or +false if it wasn't found.

+ +
+
+ +
+ +
+
+ +
+

fetch_comment_thread()

+ +
fetch_comment_thread(array  $comment_data, string  $comment_id) : array<mixed,object>
+

Fetches all the parent comments of the specified comment id, including the +comment itself at the end.

+

Useful for figuring out who needs notifying when a new comment is posted.

+ +

Parameters

+ + + + + + + + + + + +
array$comment_data

The comment data to search.

string$comment_id

The comment id to fetch the thread for.

+ + +

Returns

+ array<mixed,object> + —

A list of the comments in the thread, with the deepest +one at the end.

+ +
+
+ +
+ +
+
+ +
+

render_comments()

+ +
render_comments(array<mixed,object>  $comments_data, integer  $depth) : string
+

Renders a given comments tree to html.

+ + +

Parameters

+ + + + + + + + + + + +
array<mixed,object>$comments_data

The comments tree to render.

integer$depth

For internal use only. Specifies the depth +at which the comments are being rendered.

+ + +

Returns

+ string + —

The given comments tree as html.

+ +
+
+ +
+ + +
+
+ + + + +
+ + + diff --git a/docs/ModuleApi/files/modules.feature-guiconfig.html b/docs/ModuleApi/files/modules.feature-guiconfig.html new file mode 100644 index 0000000..3812fbf --- /dev/null +++ b/docs/ModuleApi/files/modules.feature-guiconfig.html @@ -0,0 +1,250 @@ + + + + + + Pepperminty Wiki Module API + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ +
+
+
+
+
+ + + +

modulesfeature-guiconfig.php

+

+ + + + +
+ + +
+ + + +
+
+ + + + +
+ + + diff --git a/docs/ModuleApi/files/modules.feature-history.html b/docs/ModuleApi/files/modules.feature-history.html new file mode 100644 index 0000000..4810602 --- /dev/null +++ b/docs/ModuleApi/files/modules.feature-history.html @@ -0,0 +1,315 @@ + + + + + + Pepperminty Wiki Module API + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ +
+
+
+
+
+ + + +

modulesfeature-history.php

+

+ + + + +
+ + +
+ + +
+
+

Functions

+
+ +
+ +
+
+ +
+

history_add_revision()

+ +
history_add_revision(  $pageinfo,   $newsource,   $oldsource,   $save_pageindex = true) 
+

+ + +

Parameters

+ + + + + + + + + + + + + + + + + + + + + +
$pageinfo
$newsource
$oldsource
$save_pageindex
+ + + +
+
+ +
+ + +
+
+ + + + +
+ + + diff --git a/docs/ModuleApi/files/modules.feature-recent-changes.html b/docs/ModuleApi/files/modules.feature-recent-changes.html new file mode 100644 index 0000000..8947dda --- /dev/null +++ b/docs/ModuleApi/files/modules.feature-recent-changes.html @@ -0,0 +1,395 @@ + + + + + + Pepperminty Wiki Module API + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ +
+
+
+
+
+ + + +

modulesfeature-recent-changes.php

+

+ + + + +
+ + +
+ + +
+
+

Functions

+
+ +
+ +
+
+ +
+

add_recent_change()

+ +
add_recent_change(array  $rchange) 
+

Adds a new recent change to the recent changes file.

+ + +

Parameters

+ + + + + + +
array$rchange

The new change to add.

+ + + +
+
+ +
+ +
+
+ +
+

render_recent_changes()

+ +
render_recent_changes(array  $recent_changes) : string
+

Renders a list of recent changes to HTML.

+ + +

Parameters

+ + + + + + +
array$recent_changes

The recent changes to render.

+ + +

Returns

+ string + —

The given recent changes as HTML.

+ +
+
+ +
+ +
+
+ +
+

render_recent_change()

+ +
render_recent_change(object  $rchange) : string
+

Renders a single recent change

+ + +

Parameters

+ + + + + + +
object$rchange

The recent change to render.

+ + +

Returns

+ string + —

The recent change, rendered to HTML.

+ +
+
+ +
+ + +
+
+ + + + +
+ + + diff --git a/docs/ModuleApi/files/modules.feature-redirect.html b/docs/ModuleApi/files/modules.feature-redirect.html new file mode 100644 index 0000000..29cc324 --- /dev/null +++ b/docs/ModuleApi/files/modules.feature-redirect.html @@ -0,0 +1,250 @@ + + + + + + Pepperminty Wiki Module API + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ +
+
+
+
+
+ + + +

modulesfeature-redirect.php

+

+ + + + +
+ + +
+ + + +
+
+ + + + +
+ + + diff --git a/docs/ModuleApi/files/modules.feature-search.html b/docs/ModuleApi/files/modules.feature-search.html new file mode 100644 index 0000000..a8537af --- /dev/null +++ b/docs/ModuleApi/files/modules.feature-search.html @@ -0,0 +1,257 @@ + + + + + + Pepperminty Wiki Module API + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ +
+
+
+
+
+ + + +

modulesfeature-search.php

+

+ + + + +

Classes

+ + + + + +
search
+
+ + +
+ + + +
+
+ + + + +
+ + + diff --git a/docs/ModuleApi/files/modules.feature-stats.html b/docs/ModuleApi/files/modules.feature-stats.html new file mode 100644 index 0000000..6e4687e --- /dev/null +++ b/docs/ModuleApi/files/modules.feature-stats.html @@ -0,0 +1,386 @@ + + + + + + Pepperminty Wiki Module API + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ +
+
+
+
+
+ + + +

modulesfeature-stats.php

+

+ + + + +
+ + +
+ + +
+
+

Functions

+
+ +
+ +
+
+ +
+

update_statistics()

+ +
update_statistics(  $update_all = false) 
+

+ + +

Parameters

+ + + + + + +
$update_all
+ + + +
+
+ +
+ +
+
+ +
+

stats_load()

+ +
stats_load() : object
+

Loads and returns the statistics cache file.

+ + + + +

Returns

+ object + —

The loaded & decoded statistics.

+ +
+
+ +
+ +
+
+ +
+

stats_save()

+ +
stats_save(  $stats) : boolean
+

Saves the statistics back to disk.

+ + +

Parameters

+ + + + + + +
$stats
+ + +

Returns

+ boolean + —

Whether saving succeeded or not.

+ +
+
+ +
+ + +
+
+ + + + +
+ + + diff --git a/docs/ModuleApi/files/modules.feature-upload.html b/docs/ModuleApi/files/modules.feature-upload.html new file mode 100644 index 0000000..7f1aa57 --- /dev/null +++ b/docs/ModuleApi/files/modules.feature-upload.html @@ -0,0 +1,514 @@ + + + + + + Pepperminty Wiki Module API + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ +
+
+
+
+
+ + + +

modulesfeature-upload.php

+

+ + + + +
+ + +
+ + +
+
+

Functions

+
+ +
+ +
+
+ +
+

get_max_upload_size()

+ +
get_max_upload_size() : integer
+

Calculates the actual maximum upload size supported by the server +Returns a file size limit in bytes based on the PHP upload_max_filesize and +post_max_size

+ + + + +

Returns

+ integer + —

The maximum upload size supported bythe server, in bytes.

+ +
+
+ +
+ +
+
+ +
+

parse_size()

+ +
parse_size(string  $size) : integer
+

Parses a PHP size to an integer

+ + +

Parameters

+ + + + + + +
string$size

The size to parse.

+ + +

Returns

+ integer + —

The number of bytees represented by the specified +size string.

+ +
+
+ +
+ +
+
+ +
+

upload_check_svg()

+ +
upload_check_svg(string  $temp_filename) : array<mixed,integer>
+

Checks an uploaded SVG file to make sure it's (at least somewhat) safe.

+

Sends an error to the client if a problem is found.

+ +

Parameters

+ + + + + + +
string$temp_filename

The filename of the SVG file to check.

+ + +

Returns

+ array<mixed,integer> + —

The size of the SVG image.

+ +
+
+ +
+ +
+
+ +
+

getsvgsize()

+ +
getsvgsize(string  $svgFilename) : array<mixed,integer>
+

Calculates the size of the specified SVG file.

+ + +

Parameters

+ + + + + + +
string$svgFilename

The filename to calculate the size of.

+ + +

Returns

+ array<mixed,integer> + —

The width and height respectively of the +specified SVG file.

+ +
+
+ +
+ +
+
+ +
+

errorimage()

+ +
errorimage(string  $text, integer  $target_size = null) : \image
+

Creates an images containing the specified text.

+

Useful for sending errors back to the client.

+ +

Parameters

+ + + + + + + + + + + +
string$text

The text to include in the image.

integer$target_size

The target width to aim for when creating +the image.

+ + +

Returns

+ \image + —

The handle to the generated GD image.

+ +
+
+ +
+ + +
+
+ + + + +
+ + + diff --git a/docs/ModuleApi/files/modules.feature-user-preferences.html b/docs/ModuleApi/files/modules.feature-user-preferences.html new file mode 100644 index 0000000..16f6494 --- /dev/null +++ b/docs/ModuleApi/files/modules.feature-user-preferences.html @@ -0,0 +1,250 @@ + + + + + + Pepperminty Wiki Module API + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ +
+
+
+
+
+ + + +

modulesfeature-user-preferences.php

+

+ + + + +
+ + +
+ + + +
+
+ + + + +
+ + + diff --git a/docs/ModuleApi/files/modules.page-credits.html b/docs/ModuleApi/files/modules.page-credits.html new file mode 100644 index 0000000..b32216f --- /dev/null +++ b/docs/ModuleApi/files/modules.page-credits.html @@ -0,0 +1,250 @@ + + + + + + Pepperminty Wiki Module API + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ +
+
+
+
+
+ + + +

modulespage-credits.php

+

+ + + + +
+ + +
+ + + +
+
+ + + + +
+ + + diff --git a/docs/ModuleApi/files/modules.page-debug-info.html b/docs/ModuleApi/files/modules.page-debug-info.html new file mode 100644 index 0000000..d82ab75 --- /dev/null +++ b/docs/ModuleApi/files/modules.page-debug-info.html @@ -0,0 +1,250 @@ + + + + + + Pepperminty Wiki Module API + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ +
+
+
+
+
+ + + +

modulespage-debug-info.php

+

+ + + + +
+ + +
+ + + +
+
+ + + + +
+ + + diff --git a/docs/ModuleApi/files/modules.page-delete.html b/docs/ModuleApi/files/modules.page-delete.html new file mode 100644 index 0000000..3b42a90 --- /dev/null +++ b/docs/ModuleApi/files/modules.page-delete.html @@ -0,0 +1,250 @@ + + + + + + Pepperminty Wiki Module API + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ +
+
+
+
+
+ + + +

modulespage-delete.php

+

+ + + + +
+ + +
+ + + +
+
+ + + + +
+ + + diff --git a/docs/ModuleApi/files/modules.page-edit.html b/docs/ModuleApi/files/modules.page-edit.html new file mode 100644 index 0000000..83d2abd --- /dev/null +++ b/docs/ModuleApi/files/modules.page-edit.html @@ -0,0 +1,250 @@ + + + + + + Pepperminty Wiki Module API + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ +
+
+
+
+
+ + + +

modulespage-edit.php

+

+ + + + +
+ + +
+ + + +
+
+ + + + +
+ + + diff --git a/docs/ModuleApi/files/modules.page-export.html b/docs/ModuleApi/files/modules.page-export.html new file mode 100644 index 0000000..a41583c --- /dev/null +++ b/docs/ModuleApi/files/modules.page-export.html @@ -0,0 +1,250 @@ + + + + + + Pepperminty Wiki Module API + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ +
+
+
+
+
+ + + +

modulespage-export.php

+

+ + + + +
+ + +
+ + + +
+
+ + + + +
+ + + diff --git a/docs/ModuleApi/files/modules.page-help.html b/docs/ModuleApi/files/modules.page-help.html new file mode 100644 index 0000000..8f7ff3f --- /dev/null +++ b/docs/ModuleApi/files/modules.page-help.html @@ -0,0 +1,250 @@ + + + + + + Pepperminty Wiki Module API + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ +
+
+
+
+
+ + + +

modulespage-help.php

+

+ + + + +
+ + +
+ + + +
+
+ + + + +
+ + + diff --git a/docs/ModuleApi/files/modules.page-list.html b/docs/ModuleApi/files/modules.page-list.html new file mode 100644 index 0000000..046a538 --- /dev/null +++ b/docs/ModuleApi/files/modules.page-list.html @@ -0,0 +1,304 @@ + + + + + + Pepperminty Wiki Module API + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ +
+
+
+
+
+ + + +

modulespage-list.php

+

+ + + + +
+ + +
+ + +
+
+

Functions

+
+ +
+ +
+
+ +
+

generate_page_list()

+ +
generate_page_list(array<mixed,string>  $pagelist) : string
+

Renders a list of pages as HTML.

+ + +

Parameters

+ + + + + + +
array<mixed,string>$pagelist

A list of page names to include in the list.

+ + +

Returns

+ string + —

The specified list of pages as HTML.

+ +
+
+ +
+ + +
+
+ + + + +
+ + + diff --git a/docs/ModuleApi/files/modules.page-login.html b/docs/ModuleApi/files/modules.page-login.html new file mode 100644 index 0000000..1da943f --- /dev/null +++ b/docs/ModuleApi/files/modules.page-login.html @@ -0,0 +1,306 @@ + + + + + + Pepperminty Wiki Module API + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ +
+
+
+
+
+ + + +

modulespage-login.php

+

+ + + + +
+ + +
+ + +
+
+

Functions

+
+ +
+ +
+
+ +
+

hash_password()

+ +
hash_password(string  $pass) : string
+

Hashes the given password according to the current settings defined +in $settings.

+ + +

Parameters

+ + + + + + +
string$pass

The password to hash.

+ + +

Returns

+ string + —

The hashed password. Uses sha3 if $settings->use_sha3 is +enabled, or sha256 otherwise.

+ +
+
+ +
+ + +
+
+ + + + +
+ + + diff --git a/docs/ModuleApi/files/modules.page-logout.html b/docs/ModuleApi/files/modules.page-logout.html new file mode 100644 index 0000000..838f424 --- /dev/null +++ b/docs/ModuleApi/files/modules.page-logout.html @@ -0,0 +1,250 @@ + + + + + + Pepperminty Wiki Module API + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ +
+
+
+
+
+ + + +

modulespage-logout.php

+

+ + + + +
+ + +
+ + + +
+
+ + + + +
+ + + diff --git a/docs/ModuleApi/files/modules.page-move.html b/docs/ModuleApi/files/modules.page-move.html new file mode 100644 index 0000000..5d79900 --- /dev/null +++ b/docs/ModuleApi/files/modules.page-move.html @@ -0,0 +1,250 @@ + + + + + + Pepperminty Wiki Module API + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ +
+
+
+
+
+ + + +

modulespage-move.php

+

+ + + + +
+ + +
+ + + +
+
+ + + + +
+ + + diff --git a/docs/ModuleApi/files/modules.page-update.html b/docs/ModuleApi/files/modules.page-update.html new file mode 100644 index 0000000..72eda38 --- /dev/null +++ b/docs/ModuleApi/files/modules.page-update.html @@ -0,0 +1,250 @@ + + + + + + Pepperminty Wiki Module API + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ +
+
+
+
+
+ + + +

modulespage-update.php

+

+ + + + +
+ + +
+ + + +
+
+ + + + +
+ + + diff --git a/docs/ModuleApi/files/modules.page-user-list.html b/docs/ModuleApi/files/modules.page-user-list.html new file mode 100644 index 0000000..5b8e84f --- /dev/null +++ b/docs/ModuleApi/files/modules.page-user-list.html @@ -0,0 +1,250 @@ + + + + + + Pepperminty Wiki Module API + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ +
+
+
+
+
+ + + +

modulespage-user-list.php

+

+ + + + +
+ + +
+ + + +
+
+ + + + +
+ + + diff --git a/docs/ModuleApi/files/modules.page-view.html b/docs/ModuleApi/files/modules.page-view.html new file mode 100644 index 0000000..b47d08c --- /dev/null +++ b/docs/ModuleApi/files/modules.page-view.html @@ -0,0 +1,250 @@ + + + + + + Pepperminty Wiki Module API + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ +
+
+
+
+
+ + + +

modulespage-view.php

+

+ + + + +
+ + +
+ + + +
+
+ + + + +
+ + + diff --git a/docs/ModuleApi/files/modules.parser-default-old.html b/docs/ModuleApi/files/modules.parser-default-old.html new file mode 100644 index 0000000..854f024 --- /dev/null +++ b/docs/ModuleApi/files/modules.parser-default-old.html @@ -0,0 +1,257 @@ + + + + + + Pepperminty Wiki Module API + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ +
+
+
+
+
+ + + +

modulesparser-default-old.php

+

+ + + + +

Classes

+ + + + + +
SlimdownModified by Starbeamrainbowlabs (starbeamrainbowlabs)
+
+ + +
+ + + +
+
+ + + + +
+ + + diff --git a/docs/ModuleApi/files/modules.parser-parsedown.html b/docs/ModuleApi/files/modules.parser-parsedown.html new file mode 100644 index 0000000..89569cc --- /dev/null +++ b/docs/ModuleApi/files/modules.parser-parsedown.html @@ -0,0 +1,257 @@ + + + + + + Pepperminty Wiki Module API + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ +
+
+
+
+
+ + + +

modulesparser-parsedown.php

+

+ + + + +

Classes

+ + + + + +
PeppermintParsedown
+
+ + +
+ + + +
+
+ + + + +
+ + + diff --git a/docs/ModuleApi/files/modules/action-hash.php.txt b/docs/ModuleApi/files/modules/action-hash.php.txt new file mode 100644 index 0000000..d7dcf56 --- /dev/null +++ b/docs/ModuleApi/files/modules/action-hash.php.txt @@ -0,0 +1,51 @@ + "Password hashing action", + "version" => "0.6", + "author" => "Starbeamrainbowlabs", + "description" => "Adds a utility action (that anyone can use) called hash that hashes a given string. Useful when changing a user's password.", + "id" => "action-hash", + "code" => function() { + /** + * @api {get} ?action=hash&string={text} Hash a password + * @apiName Hash + * @apiGroup Utility + * @apiPermission Anonymous + * + * @apiParam {string} string The string to hash. + * @apiParam {boolean} raw Whether to return the hashed password as a raw string instead of as part of an HTML page. + * + * @apiError ParamNotFound The string parameter was not specified. + */ + + /* + * ██ ██ █████ ███████ ██ ██ + * ██ ██ ██ ██ ██ ██ ██ + * ███████ ███████ ███████ ███████ + * ██ ██ ██ ██ ██ ██ ██ + * ██ ██ ██ ██ ███████ ██ ██ + */ + add_action("hash", function() { + global $settings; + + if(!isset($_GET["string"])) + { + http_response_code(422); + exit(page_renderer::render_main("Missing parameter", "

The GET parameter string must be specified.

+

It is strongly recommended that you utilise this page via a private or incognito window in order to prevent your password from appearing in your browser history.

")); + } + else if(!empty($_GET["raw"])) + { + header("content-type: text/plain"); + exit(hash_password($_GET["string"])); + } + else + { + exit(page_renderer::render_main("Hashed string", "

Algorithm: " . ($settings->use_sha3 ? "sha3" : "sha256") . "

\n

" . $_GET["string"] . " → " . hash_password($_GET["string"]) . "

")); + } + }); + } +]); + +?> + diff --git a/docs/ModuleApi/files/modules/action-protect.php.txt b/docs/ModuleApi/files/modules/action-protect.php.txt new file mode 100644 index 0000000..28bf1c8 --- /dev/null +++ b/docs/ModuleApi/files/modules/action-protect.php.txt @@ -0,0 +1,63 @@ + "Page protection", + "version" => "0.2", + "author" => "Starbeamrainbowlabs", + "description" => "Exposes Pepperminty Wiki's new page protection mechanism and makes the protect button in the 'More...' menu on the top bar work.", + "id" => "action-protect", + "code" => function() { + /** + * @api {get} ?action=protect&page={pageName} Toggle the protection of a page. + * @apiName Protect + * @apiGroup Page + * @apiPermission Moderator + * + * @apiParam {string} page The page name to toggle the protection of. + */ + + /* + * ██████ ██████ ██████ ████████ ███████ ██████ ████████ + * ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ + * ██████ ██████ ██ ██ ██ █████ ██ ██ + * ██ ██ ██ ██ ██ ██ ██ ██ ██ + * ██ ██ ██ ██████ ██ ███████ ██████ ██ + */ + add_action("protect", function() { + global $env, $pageindex, $paths, $settings; + + // Make sure that the user is logged in as an admin / mod. + if($env->is_admin) + { + // They check out ok, toggle the page's protection. + $page = $env->page; + + if(!isset($pageindex->$page->protect)) + { + $pageindex->$page->protect = true; + } + else if($pageindex->$page->protect === true) + { + $pageindex->$page->protect = false; + } + else if($pageindex->$page->protect === false) + { + $pageindex->$page->protect = true; + } + + // Save the pageindex + file_put_contents($paths->pageindex, json_encode($pageindex, JSON_PRETTY_PRINT)); + + $state = ($pageindex->$page->protect ? "enabled" : "disabled"); + $title = "Page protection $state."; + exit(page_renderer::render_main($title, "

Page protection for $env->page has been $state.

Go back.")); + } + else + { + exit(page_renderer::render_main("Error protecting page", "

You are not allowed to protect pages because you are not logged in as a mod or admin. Please try logging out if you are logged in and then try logging in as an administrator.

")); + } + }); + } +]); + +?> + diff --git a/docs/ModuleApi/files/modules/action-random.php.txt b/docs/ModuleApi/files/modules/action-random.php.txt new file mode 100644 index 0000000..f7c2b9c --- /dev/null +++ b/docs/ModuleApi/files/modules/action-random.php.txt @@ -0,0 +1,32 @@ + "Random Page", + "version" => "0.1", + "author" => "Starbeamrainbowlabs", + "description" => "Adds an action called 'random' that redirects you to a random page.", + "id" => "action-random", + "code" => function() { + global $settings; + /** + * @api {get} ?action=random Redirects to a random page. + * @apiName RawSource + * @apiGroup Page + * @apiPermission Anonymous + */ + + add_action("random", function() { + global $pageindex; + + $pageNames = array_keys(get_object_vars($pageindex)); + $randomPageName = $pageNames[array_rand($pageNames)]; + + http_response_code(307); + header("location: ?page=" . rawurlencode($randomPageName)); + }); + + add_help_section("26-random-redirect", "Jumping to a random page", "

$settings->sitename has a function that can send you to a random page. To use it, click here. $settings->admindetails_name ($settings->sitename's adminstrator) may have added it to one of the menus.

"); + } +]); + +?> + diff --git a/docs/ModuleApi/files/modules/action-raw.php.txt b/docs/ModuleApi/files/modules/action-raw.php.txt new file mode 100644 index 0000000..3e3f5ce --- /dev/null +++ b/docs/ModuleApi/files/modules/action-raw.php.txt @@ -0,0 +1,39 @@ + "Raw page source", + "version" => "0.7", + "author" => "Starbeamrainbowlabs", + "description" => "Adds a 'raw' action that shows you the raw source of a page.", + "id" => "action-raw", + "code" => function() { + global $settings; + /** + * @api {get} ?action=raw&page={pageName} Get the raw source code of a page + * @apiName RawSource + * @apiGroup Page + * @apiPermission Anonymous + * + * @apiParam {string} page The page to return the source of. + */ + + /* + * ██████ █████ ██ ██ + * ██ ██ ██ ██ ██ ██ + * ██████ ███████ ██ █ ██ + * ██ ██ ██ ██ ██ ███ ██ + * ██ ██ ██ ██ ███ ███ + */ + add_action("raw", function() { + global $env; + + header("content-type: text/markdown"); + exit(file_get_contents($env->page_filename)); + }); + + add_help_section("800-raw-page-content", "Viewing Raw Page Content", "

Although you can use the edit page to view a page's source, you can also ask $settings->sitename to send you the raw page source and nothing else. This feature is intented for those who want to automate their interaction with $settings->sitename.

+

To use this feature, navigate to the page for which you want to see the source, and then alter the action parameter in the url's query string to be raw. If the action parameter doesn't exist, add it. Note that when used on an file's page this action will return the source of the description and not the file itself.

"); + } +]); + +?> + diff --git a/docs/ModuleApi/files/modules/api-status.php.txt b/docs/ModuleApi/files/modules/api-status.php.txt new file mode 100644 index 0000000..03059c4 --- /dev/null +++ b/docs/ModuleApi/files/modules/api-status.php.txt @@ -0,0 +1,50 @@ + "API status", + "version" => "0.1", + "author" => "Starbeamrainbowlabs", + "description" => "Provides a basic JSON status action that provices a few useful bits of information for API consumption.", + "id" => "api-status", + "code" => function() { + global $settings; + /** + * @api {get} ?action=raw&page={pageName} Get the raw source code of a page + * @apiName RawSource + * @apiGroup Page + * @apiPermission Anonymous + * + * @apiParam {string} page The page to return the source of. + */ + + + add_action("status", function() { + global $version, $env, $settings, $actions; + + // Make sure the client can accept JSON + if(!accept_contains_mime($_SERVER["HTTP_ACCEPT"] ?? "application/json", "application/json")) { + http_response_code(406); + header("content-type: text/plain"); + + exit("Unfortunately, this API is currently only available in application/json at the moment, which you haven't indicated you accept in your http accept header. You said this in your accept header:\n" . $_SERVER["HTTP_ACCEPT"]); + } + + $action_names = array_keys(get_object_vars($actions)); + sort($action_names); + + $result = new stdClass(); + $result->status = "ok"; + $result->version = $version; + $result->available_actions = $action_names; + $result->wiki_name = $settings->sitename; + $result->logo_url = $settings->favicon; + + header("content-type: application/json"); + exit(json_encode($result, JSON_PRETTY_PRINT) . "\n"); + }); + + add_help_section("960-api-status", "Wiki Status API", "

"); + } +]); + +?> + diff --git a/docs/ModuleApi/files/modules/extra-sidebar.php.txt b/docs/ModuleApi/files/modules/extra-sidebar.php.txt new file mode 100644 index 0000000..cebfb36 --- /dev/null +++ b/docs/ModuleApi/files/modules/extra-sidebar.php.txt @@ -0,0 +1,122 @@ + "Sidebar", + "version" => "0.3.1", + "author" => "Starbeamrainbowlabs", + "description" => "Adds a sidebar to the left hand side of every page. Add '\$settings->sidebar_show = true;' to your configuration, or append '&sidebar=yes' to the url to enable. Adding to the url sets a cookie to remember your setting.", + "id" => "extra-sidebar", + "code" => function() { + global $settings; + + $show_sidebar = false; + + // Show the sidebar if it is enabled in the settings + if(isset($settings->sidebar_show) && $settings->sidebar_show === true) + $show_sidebar = true; + + // Also show and persist the sidebar if the special GET parameter + // sidebar is seet + if(!$show_sidebar && isset($_GET["sidebar"])) + { + $show_sidebar = true; + // Set a cookie to persist the display of the sidebar + setcookie("sidebar_show", "true", time() + (60 * 60 * 24 * 30)); + } + + // Show the sidebar if the cookie is set + if(!$show_sidebar && isset($_COOKIE["sidebar_show"])) + $show_sidebar = true; + + // Delete the cookie and hide the sidebar if the special GET paramter + // nosidebar is set + if(isset($_GET["nosidebar"])) + { + $show_sidebar = false; + unset($_COOKIE["sidebar_show"]); + setcookie("sidebar_show", null, time() - 3600); + } + + page_renderer::register_part_preprocessor(function(&$parts) use ($show_sidebar) { + global $settings, $pageindex, $env; + + // Don't render a sidebar if the user is logging in and a login is + // required in order to view pages. + if($settings->require_login_view && in_array($env->action, [ "login", "checklogin" ])) + return false; + + if($show_sidebar && !isset($_GET["printable"])) + { + // Show the sidebar + $exec_start = microtime(true); + + // Sort the pageindex + $sorted_pageindex = get_object_vars($pageindex); + ksort($sorted_pageindex, SORT_NATURAL); + + $sidebar_contents = ""; + $sidebar_contents .= render_sidebar($sorted_pageindex); + + $parts["{body}"] = " +
" . $parts["{body}"] . "
+ + "; + } + }); + + add_help_section("50-sidebar", "Sidebar", "

$settings->sitename has an optional sidebar which displays a list of all the current pages (but not subpages) that it is currently hosting. It may or may not be enabled.

+

If it isn't enabled, it can be enabled for your current browser only by appending sidebar=yes to the current page's query string.

"); + } +]); + +/** + * Renders the sidebar for a given pageindex. + * @package extra-sidebar + * @param array $pageindex The pageindex to render the sidebar for + * @param string $root_pagename The pagename that should be considered the root of the rendering. You don't usually need to use this, it is used by the algorithm itself since it is recursive. + * @return string A HTML rendering of the sidebar for the given pageindex. + */ +function render_sidebar($pageindex, $root_pagename = "") +{ + global $settings; + + $result = " $details) + { + // If we have a valid root pagename, and it isn't present at the + // beginning of the current pagename, skip it + if($root_pagename !== "" && strpos($pagename, $root_pagename) !== 0) + continue; + + // The current page is the same as the root page, skip it + if($pagename == $root_pagename) + continue; + + // If the page already appears on the sidebar, skip it + if(preg_match("/>$pagename<\a>/m", $result) === 1) + continue; + + // If the part of the current pagename that comes after the root + // pagename has a slash in it, skip it as it is a sub-sub page. + if(strpos(substr($pagename, strlen($root_pagename)), "/") !== false) + continue; + + $result .= "
  • $pagename\n"; + $result .= render_sidebar($pageindex, $pagename); + $result .= "
  • \n"; + } + $result .= "\n"; + + return $result == "\n" ? "" : $result; +} + +?> + diff --git a/docs/ModuleApi/files/modules/feature-comments.php.txt b/docs/ModuleApi/files/modules/feature-comments.php.txt new file mode 100644 index 0000000..34c264a --- /dev/null +++ b/docs/ModuleApi/files/modules/feature-comments.php.txt @@ -0,0 +1,341 @@ + "Page Comments", + "version" => "0.2.3", + "author" => "Starbeamrainbowlabs", + "description" => "Adds threaded comments to the bottom of every page.", + "id" => "feature-comments", + "code" => function() { + global $env, $settings; + + /** + * @api {post} ?action=comment Comment on a page + * @apiName Comment + * @apiGroup Comment + * @apiPermission User + * @apiDescription Posts a comment on a page, optionally in reply to another comment. Currently, comments must be made by a logged-in user. + * + * @apiParam {string} message The comment text. Supports the same syntax that the renderer of the main page supports. The default is extended markdown - see the help page of the specific wiki for more information. + * @apiParam {string} replyto Optional. If specified the comment will be posted in reply to the comment with the specified id. + * + * + * @apiError CommentNotFound The comment to reply to was not found. + */ + + /* + * ██████ ██████ ███ ███ ███ ███ ███████ ███ ██ ████████ + * ██ ██ ██ ████ ████ ████ ████ ██ ████ ██ ██ + * ██ ██ ██ ██ ████ ██ ██ ████ ██ █████ ██ ██ ██ ██ + * ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ + * ██████ ██████ ██ ██ ██ ██ ███████ ██ ████ ██ + */ + add_action("comment", function() { + global $settings, $env; + + $reply_to = $_POST["replyto"] ?? null; + $message = $_POST["message"] ?? ""; + + if(!$env->is_logged_in) { + http_response_code(401); + exit(page_renderer::render_main("Error posting comment - $settings->sitename", "

    Your comment couldn't be posted because you're not logged in. You can login here. Here's the comment you tried to post:

    + ")); + } + + $message_length = strlen($message); + if($message_length < $settings->comment_min_length) { + http_response_code(422); + exit(page_renderer::render_main("Error posting comment - $settings->sitename", "

    Your comment couldn't be posted because it was too short. $settings->sitename needs at $settings->comment_min_length characters in a comment in order to post it.

    ")); + } + if($message_length > $settings->comment_max_length) { + http_response_code(422); + exit(page_renderer::renderer_main("Error posting comment - $settings->sitename", "

    Your comment couldn't be posted because it was too long. $settings->sitenamae can only post comments that are up to $settings->comment_max_length characters in length, and yours was $message_length characters. Try splitting it up into multiple comments! Here's the comment you tried to post:

    + ")); + } + + // Figure out where the comments are stored + $comment_filename = get_comment_filename($env->page); + if(!file_exists($comment_filename)) { + if(file_put_contents($comment_filename, "[]\n") === false) { + http_response_code(503); + exit(page_renderer::renderer_main("Error posting comment - $settings->sitename", "

    $settings->sitename ran into a problem whilst creating a file to save your comment to! Please contact $settings->admindetails_name, $settings->sitename's administrator and tell them about this problem.

    ")); + } + } + + $comment_data = json_decode(file_get_contents($comment_filename)); + + $new_comment = new stdClass(); + $new_comment->id = generate_comment_id(); + $new_comment->timestamp = date("c"); + $new_comment->username = $env->user; + $new_comment->logged_in = $env->is_logged_in; + $new_comment->message = $message; + $new_comment->replies = []; + + if($reply_to == null) + $comment_data[] = $new_comment; + else { + $parent_comment = find_comment($comment_data, $reply_to); + if($parent_comment === false) { + http_response_code(422); + exit(page_renderer::render_main("Error posting comment - $settings->sitename", "

    $settings->sitename couldn't post your comment because it couldn't find the parent comment you replied to. It's possible that $settings->admindetails_name, $settings->sitename's administrator, deleted the comment. Here's the comment you tried to post:

    + ")); + } + + $parent_comment->replies[] = $new_comment; + + // Get an array of all the parent comments we need to notify + $comment_thread = fetch_comment_thread($comment_data, $parent_comment->id); + + $email_subject = "[Notification] $env->user replied to your comment on $env->page - $settings->sitename"; + + foreach($comment_thread as $thread_comment) { + $email_body = "Hello, {username}!\n" . + "It's $settings->sitename here, letting you know that " . + "someone replied to your comment (or a reply to your comment) on $env->page.\n" . + "\n" . + "They said:\n" . + "\n" . + "$new_comment->message" . + "\n" . + "You said on " . date("c", strtotime($thread_comment->timestamp)) . ":\n" . + "\n" . + "$thread_comment->message\n" . + "\n"; + + email_user($thread_comment->username, $email_subject, $email_body); + } + } + + // Save the comments back to disk + if(file_put_contents($comment_filename, json_encode($comment_data, JSON_PRETTY_PRINT)) === false) { + http_response_code(503); + exit(page_renderer::renderer_main("Error posting comment - $settings->sitename", "

    $settings->sitename ran into a problem whilst saving your comment to disk! Please contact $settings->admindetails_name, $settings->sitename's administrator and tell them about this problem.

    ")); + } + + // Add a recent change if the recent changes module is installed + if(module_exists("feature-recent-changes")) { + add_recent_change([ + "type" => "comment", + "timestamp" => time(), + "page" => $env->page, + "user" => $env->user, + "reply_depth" => count($comment_thread), + "comment_id" => $new_comment->id + ]); + } + + http_response_code(307); + header("location: ?action=view&page=" . rawurlencode($env->page) . "&commentsuccess=yes#comment-$new_comment->id"); + exit(page_renderer::render_main("Comment posted successfully - $settings->sitename", "

    Your comment on $env->page was posted successfully. If your browser doesn't redirect you automagically, please click here to go to the comment you posted on the page you were viewing.

    ")); + }); + + if($env->action == "view") { + page_renderer::register_part_preprocessor(function(&$parts) { + global $env; + $comments_filename = get_comment_filename($env->page); + $comments_data = file_exists($comments_filename) ? json_decode(file_get_contents($comments_filename)) : []; + + + $comments_html = "\n"; + + $to_comments_link = "
    Jump to comments
    "; + + $parts["{extra}"] = $comments_html . $parts["{extra}"]; + + $parts["{content}"] = str_replace_once("", "\n$to_comments_link", $parts["{content}"]); + }); + + $reply_js_snippet = <<<'REPLYJS' +/////////////////////////////////// +///////// Commenting Form ///////// +/////////////////////////////////// +window.addEventListener("load", function(event) { + var replyButtons = document.querySelectorAll(".reply-button"); + for(let i = 0; i < replyButtons.length; i++) { + replyButtons[i].addEventListener("click", display_reply_form); + replyButtons[i].addEventListener("touchend", display_reply_form); + } +}); + +function display_reply_form(event) +{ + // Deep-clone the comment form + var replyForm = document.querySelector(".comment-reply-form").cloneNode(true); + replyForm.classList.add("nested"); + // Set the comment we're replying to + replyForm.querySelector("[name=replyto]").value = event.target.parentElement.parentElement.parentElement.dataset.commentId; + // Display the newly-cloned commenting form + var replyBoxContiner = event.target.parentElement.parentElement.parentElement.querySelector(".reply-box-container"); + replyBoxContiner.classList.add("active"); + replyBoxContiner.appendChild(replyForm); + // Hide the reply button so it can't be pressed more than once - that could + // be awkward :P + event.target.parentElement.removeChild(event.target); +} + +REPLYJS; + page_renderer::AddJSSnippet($reply_js_snippet); + + } + + add_help_section("29-commenting", "Commenting", "

    $settings->sitename has a threaded commenting system on every page. You can find it below each page's content, and can either leave a new comment, or reply to an existing one. If you reply to an existing one, then the authors of all the comments above yours will get notified by email of your reply - so long as they have an email address registered in their preferences.

    "); + } +]); + +/** + * Given a page name, returns the absolute file path in which that page's + * comments are stored. + * @package feature-comments + * @param string $pagename The name pf the page to fetch the comments filename for. + * @return string The path to the file that the + */ +function get_comment_filename($pagename) +{ + global $env; + $pagename = makepathsafe($pagename); + return "$env->storage_prefix$pagename.comments.json"; +} + +/** + * Generates a new random comment id. + * @package feature-comments + * @return string A new random comment id. + */ +function generate_comment_id() +{ + $result = base64_encode(random_bytes(16)); + $result = str_replace(["+", "/", "="], ["-", "_"], $result); + return $result; +} + +/** + * Finds the comment with specified id by way of an almost-breadth-first search. + * @package feature-comments + * @param array $comment_data The comment data to search. + * @param string $comment_id The id of the comment to find. + * @return object The comment data with the specified id, or + * false if it wasn't found. + */ +function find_comment($comment_data, $comment_id) +{ + $subtrees = []; + foreach($comment_data as $comment) + { + if($comment->id === $comment_id) + return $comment; + + if(count($comment->replies) > 0) { + $subtrees[] = $comment->replies; + } + } + + foreach($subtrees as $subtree) + { + $subtree_result = find_comment($subtree, $comment_id); + if($subtree_result !== false) + return $subtree_result; + } + + return false; +} + +/** + * Fetches all the parent comments of the specified comment id, including the + * comment itself at the end. + * Useful for figuring out who needs notifying when a new comment is posted. + * @package feature-comments + * @param array $comment_data The comment data to search. + * @param string $comment_id The comment id to fetch the thread for. + * @return object[] A list of the comments in the thread, with the deepest + * one at the end. + */ +function fetch_comment_thread($comment_data, $comment_id) +{ + foreach($comment_data as $comment) + { + // If we're the comment they're looking for, then return ourselves as + // the beginning of a thread + if($comment->id === $comment_id) + return [ $comment ]; + + if(count($comment->replies) > 0) { + $subtree_result = fetch_comment_thread($comment->replies, $comment_id); + if($subtree_result !== false) { + // Prepend ourselves to the result + array_unshift($subtree_result, $comment); + return $subtree_result; // Return the comment thread + } + } + } + + return false; +} + +/** + * Renders a given comments tree to html. + * @package feature-comments + * @param object[] $comments_data The comments tree to render. + * @param integer $depth For internal use only. Specifies the depth + * at which the comments are being rendered. + * @return string The given comments tree as html. + */ +function render_comments($comments_data, $depth = 0) +{ + global $settings; + + if(count($comments_data) == 0) { + if($depth == 0) + return "

    No comments here! Start the conversation above.

    "; + else + return ""; + } + + $result = "
    "; + + //$comments_data = array_reverse($comments_data); + for($i = count($comments_data) - 1; $i >= 0; $i--) { + $comment = $comments_data[$i]; + + $result .= "\t
    \n"; + $result .= "\t

    " . page_renderer::render_username($comment->username) . " said:

    "; + $result .= "\t
    \n"; + $result .= "\t\t" . parse_page_source($comment->message); + $result .= "\t
    \n"; + $result .= "\t
    \n"; + $result .= "\t\n"; + $result .= "\t" . render_comments($comment->replies, $depth + 1) . "\n"; + $result .= "\t
    "; + } + $result .= "
    "; + + return $result; +} + +?> + diff --git a/docs/ModuleApi/files/modules/feature-guiconfig.php.txt b/docs/ModuleApi/files/modules/feature-guiconfig.php.txt new file mode 100644 index 0000000..035c24d --- /dev/null +++ b/docs/ModuleApi/files/modules/feature-guiconfig.php.txt @@ -0,0 +1,200 @@ + "Settings GUI", + "version" => "0.1.1", + "author" => "Starbeamrainbowlabs", + "description" => "The module everyone has been waiting for! Adds a web based gui that lets mods change the wiki settings.", + "id" => "feature-guiconfig", + "code" => function() { + global $settings; + /** + * @api {get} ?action=configure Get a page to change the global wiki settings + * @apiName ConfigureSettings + * @apiGroup Utility + * @apiPermission Moderator + */ + + /* + * ██████ ██████ ███ ██ ███████ ██ ██████ ██ ██ ██████ ███████ + * ██ ██ ██ ████ ██ ██ ██ ██ ██ ██ ██ ██ ██ + * ██ ██ ██ ██ ██ ██ █████ ██ ██ ███ ██ ██ ██████ █████ + * ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ + * ██████ ██████ ██ ████ ██ ██ ██████ ██████ ██ ██ ███████ + */ + add_action("configure", function() { + global $settings, $env, $guiConfig; + + if(!$env->is_admin) + { + $errorMessage = "

    You don't have permission to change $settings->sitename's master settings.

    \n"; + if(!$env->is_logged_in) + $errorMessage .= "

    You could try logging in.

    "; + else + $errorMessage .= "

    You could try logging out and then logging in again with a different account that has the appropriate privileges..

    "; + exit(page_renderer::render_main("Error - $settings->sitename", $errorMessage)); + } + + $content = "

    Master Control Panel

    \n"; + $content .= "

    This page lets you configure $settings->sitename's master settings. Please be careful - you can break things easily on this page if you're not careful!

    \n"; + $content .= "

    Actions

    "; + + $content .= "
    \n"; + $content .= "
    \n"; + $content .= "
    \n"; + + $invindex_rebuild_script = <<")); + + break; + + case "POST": + // Recieve file + + // Make sure uploads are enabled + if(!$settings->upload_enabled) + { + if(!empty($_FILES["file"])) + unlink($_FILES["file"]["tmp_name"]); + http_response_code(412); + exit(page_renderer::render("Upload failed - $settings->sitename", "

    Your upload couldn't be processed because uploads are currently disabled on $settings->sitename. Go back to the main page.

    ")); + } + + // Make sure that the user is logged in + if(!$env->is_logged_in) + { + if(!empty($_FILES["file"])) + unlink($_FILES["file"]["tmp_name"]); + http_response_code(401); + exit(page_renderer::render("Upload failed - $settings->sitename", "

    Your upload couldn't be processed because you are not logged in.

    Try logging in first.")); + } + + // Check for php upload errors + if($_FILES["file"]["error"] > 0) + { + if(!empty($_FILES["file"])) + unlink($_FILES["file"]["tmp_name"]); + if($_FILES["file"]["error"] == 1 || $_FILES["file"]["error"] == 2) + http_response_code(413); // file is too large + else + http_response_code(500); // something else went wrong + exit(page_renderer::render("Upload failed - $settings->sitename", "

    Your upload couldn't be processed because " . (($_FILES["file"]["error"] == 1 || $_FILES["file"]["error"] == 2) ? "the file is too large" : "an error occurred") . ".

    Please contact $settings->admindetails_name, $settings->sitename's administrator for help.

    ")); + + } + + // Calculate the target name, removing any characters we + // are unsure about. + $target_name = makepathsafe($_POST["name"] ?? "Users/$env->user/Avatar"); + $temp_filename = $_FILES["file"]["tmp_name"]; + + $mimechecker = finfo_open(FILEINFO_MIME_TYPE); + $mime_type = finfo_file($mimechecker, $temp_filename); + finfo_close($mimechecker); + + if(!in_array($mime_type, $settings->upload_allowed_file_types)) + { + http_response_code(415); + exit(page_renderer::render("Unknown file type - Upload error - $settings->sitename", "

    $settings->sitename recieved the file you tried to upload successfully, but detected that the type of file you uploaded is not in the allowed file types list. The file has been discarded.

    +

    The file you tried to upload appeared to be of type $mime_type, but $settings->sitename currently only allows the uploading of the following file types: " . implode(", ", $settings->upload_allowed_file_types) . ".

    +

    Go back to the Main Page.

    ")); + } + + // Perform appropriate checks based on the *real* filetype + if($is_avatar && substr($mime_type, 0, strpos($mime_type, "/")) !== "image") { + http_response_code(415); + exit(page_renderer::render_main("Error uploading avatar - $settings->sitename", "

    That file appears to be unsuitable as an avatar, as $settings->sitename has detected it to be of type $mime_type, which doesn't appear to be an image. Please try uploading a different file to use as your avatar.

    ")); + } + + switch(substr($mime_type, 0, strpos($mime_type, "/"))) + { + case "image": + $extra_data = []; + // Check SVG uploads with a special function + $imagesize = $mime_type !== "image/svg+xml" ? getimagesize($temp_filename, $extra_data) : upload_check_svg($temp_filename); + + // Make sure that the image size is defined + if(!is_int($imagesize[0]) or !is_int($imagesize[1])) + { + http_response_code(415); + exit(page_renderer::render("Upload Error - $settings->sitename", "

    Although the file that you uploaded appears to be an image, $settings->sitename has been unable to determine it's dimensions. The uploaded file has been discarded. Go back to try again.

    +

    You may wish to consider opening an issue against Pepperminty Wiki (the software that powers $settings->sitename) if this isn't the first time that you have seen this message.

    ")); + } + break; + } + + $file_extension = system_mime_type_extension($mime_type); + + // Override the detected file extension if a file extension + // is explicitly specified in the settings + if(isset($settings->mime_mappings_overrides->$mime_type)) + $file_extension = $settings->mime_mappings_overrides->$mime_type; + + if(in_array($file_extension, [ "php", ".htaccess", "asp", "aspx" ])) + { + http_response_code(415); + exit(page_renderer::render("Upload Error - $settings->sitename", "

    The file you uploaded appears to be dangerous and has been discarded. Please contact $settings->sitename's administrator for assistance.

    +

    Additional information: The file uploaded appeared to be of type $mime_type, which mapped onto the extension $file_extension. This file extension has the potential to be executed accidentally by the web server.

    ")); + } + + // Rewrite the name to include the _actual_ file extension we've cleverly calculated :D + + // The path to the place (relative to the wiki data root) + // that we're actually going to store the uploaded file itself + $new_filename = "$paths->upload_file_prefix$target_name.$file_extension"; + // The path (relative, as before) to the description file + $new_description_filename = "$new_filename.md"; + + // The page path that the new file will be stored under + $new_pagepath = $new_filename; + + // Rewrite the paths to store avatars in the right place + if($is_avatar) { + $new_pagepath = $target_name; + $new_filename = "$target_name.$file_extension"; + } + + if(isset($pageindex->$new_pagepath) && !$is_avatar) + exit(page_renderer::render("Upload Error - $settings->sitename", "

    A page or file has already been uploaded with the name '$new_filename'. Try deleting it first. If you do not have permission to delete things, try contacting one of the moderators.

    ")); + + // Delete the previously uploaded avatar, if it exists + // In the future we _may_ not need this once we have + // file history online. + if($is_avatar && isset($pageindex->$new_pagepath) && $pageindex->$new_pagepath->uploadedfile) + unlink($pageindex->$new_pagepath->uploadedfilepath); + + // Make sure that the palce we're uploading to exists + if(!file_exists(dirname($env->storage_prefix . $new_filename))) + mkdir(dirname($env->storage_prefix . $new_filename), 0775, true); + + if(!move_uploaded_file($temp_filename, $env->storage_prefix . $new_filename)) + { + http_response_code(409); + exit(page_renderer::render("Upload Error - $settings->sitename", "

    The file you uploaded was valid, but $settings->sitename couldn't verify that it was tampered with during the upload process. This probably means that either is a configuration error, or that $settings->sitename has been attacked. Please contact " . $settings->admindetails_name . ", your $settings->sitename Administrator.

    ")); + } + + $description = $_POST["description"] ?? "_(No description provided)_\n"; + + // Escape the raw html in the provided description if the setting is enabled + if($settings->clean_raw_html) + $description = htmlentities($description, ENT_QUOTES); + + file_put_contents($env->storage_prefix . $new_description_filename, $description); + + // Construct a new entry for the pageindex + $entry = new stdClass(); + // Point to the description's filepath since this property + // should point to a markdown file + $entry->filename = $new_description_filename; + $entry->size = strlen($description ?? "(No description provided)"); + $entry->lastmodified = time(); + $entry->lasteditor = $env->user; + $entry->uploadedfile = true; + $entry->uploadedfilepath = $new_filename; + $entry->uploadedfilemime = $mime_type; + // Add the new entry to the pageindex + // Assign the new entry to the image's filepath as that + // should be the page name. + $pageindex->$new_pagepath = $entry; + + // Generate a revision to keep the page history up to date + if(module_exists("feature-history")) + { + $oldsource = ""; // Only variables can be passed by reference, not literals + history_add_revision($entry, $description, $oldsource, false); + } + + // Save the pageindex + file_put_contents($paths->pageindex, json_encode($pageindex, JSON_PRETTY_PRINT)); + + if(module_exists("feature-recent-changes")) + { + add_recent_change([ + "type" => "upload", + "timestamp" => time(), + "page" => $new_pagepath, + "user" => $env->user, + "filesize" => filesize($env->storage_prefix . $entry->uploadedfilepath) + ]); + } + + header("location: ?action=view&page=$new_pagepath&upload=success"); + + break; + } + }); + + /** + * @api {get} ?action=preview&page={pageName}[&size={someSize}] Get a preview of a file + * @apiName PreviewFile + * @apiGroup Upload + * @apiPermission Anonymous + * + * @apiParam {string} page The name of the file to preview. + * @apiParam {number} size Optional. The size fo the resulting preview. Will be clamped to fit within the bounds specified in the wiki's settings. May also be set to the keyword 'original', which will cause the original file to be returned with it's appropriate mime type instead. + * + * @apiError PreviewNoFileError No file was found associated with the specified page. + * @apiError PreviewUnknownFileTypeError Pepperminty Wiki was unable to generate a preview for the requested file's type. + */ + + /* + * ██████ ██████ ███████ ██ ██ ██ ███████ ██ ██ + * ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ + * ██████ ██████ █████ ██ ██ ██ █████ ██ █ ██ + * ██ ██ ██ ██ ██ ██ ██ ██ ██ ███ ██ + * ██ ██ ██ ███████ ████ ██ ███████ ███ ███ + */ + add_action("preview", function() { + global $settings, $env, $pageindex, $start_time; + + if(empty($pageindex->{$env->page}->uploadedfilepath)) + { + $im = errorimage("The page '$env->page' doesn't have an associated file."); + header("content-type: image/png"); + imagepng($im); + exit(); + } + + $filepath = realpath($env->storage_prefix . $pageindex->{$env->page}->uploadedfilepath); + $mime_type = $pageindex->{$env->page}->uploadedfilemime; + $shortFilename = substr($filepath, 1 + (strrpos($filepath, '/') !== false ? strrpos($filepath, '/') : -1)); + + header("content-disposition: inline; filename=\"$shortFilename\""); + header("last-modified: " . gmdate('D, d M Y H:i:s T', $pageindex->{$env->page}->lastmodified)); + + // If the size is set or original, then send (or redirect to) the original image + // Also do the same for SVGs if svg rendering is disabled. + if(isset($_GET["size"]) and $_GET["size"] == "original" or + (empty($settings->render_svg_previews) && $mime_type == "image/svg+xml")) + { + // Get the file size + $filesize = filesize($filepath); + + // Send some headers + header("content-length: $filesize"); + header("content-type: $mime_type"); + + // Open the file and send it to the user + $handle = fopen($filepath, "rb"); + fpassthru($handle); + fclose($handle); + exit(); + } + + // Determine the target size of the image + $target_size = 512; + if(isset($_GET["size"])) + $target_size = intval($_GET["size"]); + if($target_size < $settings->min_preview_size) + $target_size = $settings->min_preview_size; + if($target_size > $settings->max_preview_size) + $target_size = $settings->max_preview_size; + + // Determine the output file type + $output_mime = $settings->preview_file_type; + if(isset($_GET["type"]) and in_array($_GET["type"], [ "image/png", "image/jpeg", "image/webp" ])) + $output_mime = $_GET["type"]; + + /// ETag handling /// + // Generate the etag and send it to the client + $preview_etag = sha1("$output_mime|$target_size|$filepath|$mime_type"); + $allheaders = getallheaders(); + $allheaders = array_change_key_case($allheaders, CASE_LOWER); + if(!isset($allheaders["if-none-match"])) + { + header("etag: $preview_etag"); + } + else + { + if($allheaders["if-none-match"] === $preview_etag) + { + http_response_code(304); + header("x-generation-time: " . (microtime(true) - $start_time)); + exit(); + } + } + /// ETag handling end /// + + /* Disabled until we work out what to do about caching previews * + $previewFilename = "$filepath.preview.$outputFormat"; + if($target_size === $settings->default_preview_size) + { + // The request is for the default preview size + // Check to see if we have a preview pre-rendered + + } + */ + + $preview = new Imagick(); + switch(substr($mime_type, 0, strpos($mime_type, "/"))) + { + case "image": + $preview->readImage($filepath); + break; + + case "application": + if($mime_type == "application/pdf") + { + $preview = new imagick(); + $preview->readImage("{$filepath}[0]"); + $preview->setResolution(300,300); + $preview->setImageColorspace(255); + break; + } + + case "video": + case "audio": + if($settings->data_storage_dir == ".") + { + // The data storage directory is the current directory + // Redirect to the file isntead + http_response_code(307); + header("location: " . $pageindex->{$env->page}->uploadedfilepath); + exit(); + } + // TODO: Add support for ranges here. + // Get the file size + $filesize = filesize($filepath); + + // Send some headers + header("content-length: $filesize"); + header("content-type: $mime_type"); + + // Open the file and send it to the user + $handle = fopen($filepath, "rb"); + fpassthru($handle); + fclose($handle); + exit(); + break; + + default: + http_response_code(501); + $preview = errorimage("Unrecognised file type '$mime_type'.", $target_size); + header("content-type: image/png"); + imagepng($preview); + exit(); + } + + // Scale the image down to the target size + $preview->resizeImage($target_size, $target_size, imagick::FILTER_LANCZOS, 1, true); + + // Send the completed preview image to the user + header("content-type: $output_mime"); + header("x-generation-time: " . (microtime(true) - $start_time) . "s"); + $outputFormat = substr($output_mime, strpos($output_mime, "/") + 1); + $preview->setImageFormat($outputFormat); + echo($preview->getImageBlob()); + /* Disabled while we work out what to do about caching previews * + // Save a preview file if there isn't one alreaddy + if(!file_exists($previewFilename)) + file_put_contents($previewFilename, $preview->getImageBlob()); + */ + }); + + /* + * ██████ ██████ ███████ ██ ██ ██ ███████ ██ ██ + * ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ + * ██████ ██████ █████ ██ ██ ██ █████ ██ █ ██ + * ██ ██ ██ ██ ██ ██ ██ ██ ██ ███ ██ + * ██ ██ ██ ███████ ████ ██ ███████ ███ ███ + * + * ██████ ██ ███████ ██████ ██ █████ ██ ██ ███████ ██████ + * ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ + * ██ ██ ██ ███████ ██████ ██ ███████ ████ █████ ██████ + * ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ + * ██████ ██ ███████ ██ ███████ ██ ██ ██ ███████ ██ ██ + */ + page_renderer::register_part_preprocessor(function(&$parts) { + global $pageindex, $env, $settings; + // Don't do anything if the action isn't view + if($env->action !== "view") + return; + + if(isset($pageindex->{$env->page}->uploadedfile) and $pageindex->{$env->page}->uploadedfile == true) + { + // We are looking at a page that is paired with an uploaded file + $filepath = $pageindex->{$env->page}->uploadedfilepath; + $mime_type = $pageindex->{$env->page}->uploadedfilemime; + $dimensions = $mime_type !== "image/svg+xml" ? getimagesize($env->storage_prefix . $filepath) : getsvgsize($env->storage_prefix . $filepath); + $fileTypeDisplay = substr($mime_type, 0, strpos($mime_type, "/")); + $previewUrl = "?action=preview&size=$settings->default_preview_size&page=" . rawurlencode($env->page); + + $preview_html = ""; + switch($fileTypeDisplay) + { + case "application": + case "image": + if($mime_type == "application/pdf") + $fileTypeDisplay = "file"; + + $preview_sizes = [ 256, 512, 768, 1024, 1440 ]; + $preview_html .= "\t\t\t
    + + \n\t\t\t
    "; + break; + + case "video": + $preview_html .= "\t\t\t
    + +
    "; + break; + + case "audio": + $preview_html .= "\t\t\t
    + +
    "; + } + + $fileInfo = []; + $fileInfo["Name"] = str_replace("File/", "", $filepath); + $fileInfo["Type"] = $mime_type; + $fileInfo["Size"] = human_filesize(filesize($env->storage_prefix . $filepath)); + switch($fileTypeDisplay) + { + case "image": + $dimensionsKey = $mime_type !== "image/svg+xml" ? "Original dimensions" : "Native size"; + $fileInfo[$dimensionsKey] = "$dimensions[0] x $dimensions[1]"; + break; + } + $fileInfo["Uploaded by"] = $pageindex->{$env->page}->lasteditor; + + $preview_html .= "\t\t\t

    File Information

    + "; + foreach ($fileInfo as $displayName => $displayValue) + { + $preview_html .= "\n"; + } + $preview_html .= "
    $displayName$displayValue
    "; + + $parts["{content}"] = str_replace("", "\n$preview_html", $parts["{content}"]); + } + }); + + // Register a section on the help page on uploading files + add_help_section("28-uploading-files", "Uploading Files", "

    $settings->sitename supports the uploading of files, though it is up to " . $settings->admindetails_name . ", $settings->sitename's administrator as to whether it is enabled or not (uploads are currently " . (($settings->upload_enabled) ? "enabled" : "disabled") . ").

    +

    Currently Pepperminty Wiki (the software that $settings->sitename uses) only supports the uploading of images, although more file types should be supported in the future (open an issue on GitHub if you are interested in support for more file types).

    +

    Uploading a file is actually quite simple. Click the "Upload" option in the "More..." menu to go to the upload page. The upload page will tell you what types of file $settings->sitename allows, and the maximum supported filesize for files that you upload (this is usually set by the web server that the wiki is running on).

    +

    Use the file chooser to select the file that you want to upload, and then decide on a name for it. Note that the name that you choose should not include the file extension, as this will be determined automatically. Enter a description that will appear on the file's page, and then click upload.

    "); + } +]); + +/** + * Calculates the actual maximum upload size supported by the server + * Returns a file size limit in bytes based on the PHP upload_max_filesize and + * post_max_size + * @package feature-upload + * @author Lifted from Drupal by @meustrus from Stackoverflow + * @see http://stackoverflow.com/a/25370978/1460422 Source Stackoverflow answer + * @return integer The maximum upload size supported bythe server, in bytes. + */ +function get_max_upload_size() +{ + static $max_size = -1; + if ($max_size < 0) { + // Start with post_max_size. + $max_size = parse_size(ini_get('post_max_size')); + // If upload_max_size is less, then reduce. Except if upload_max_size is + // zero, which indicates no limit. + $upload_max = parse_size(ini_get('upload_max_filesize')); + if ($upload_max > 0 && $upload_max < $max_size) { + $max_size = $upload_max; + } + } + return $max_size; +} +/** + * Parses a PHP size to an integer + * @package feature-upload + * @author Lifted from Drupal by @meustrus from Stackoverflow + * @see http://stackoverflow.com/a/25370978/1460422 Source Stackoverflow answer + * @param string $size The size to parse. + * @return integer The number of bytees represented by the specified + * size string. + */ +function parse_size($size) { + $unit = preg_replace('/[^bkmgtpezy]/i', '', $size); // Remove the non-unit characters from the size. + $size = preg_replace('/[^0-9\.]/', '', $size); // Remove the non-numeric characters from the size. + if ($unit) { + // Find the position of the unit in the ordered string which is the power of magnitude to multiply a kilobyte by. + return round($size * pow(1024, stripos('bkmgtpezy', $unit[0]))); + } else { + return round($size); + } +} +/** + * Checks an uploaded SVG file to make sure it's (at least somewhat) safe. + * Sends an error to the client if a problem is found. + * @package feature-upload + * @param string $temp_filename The filename of the SVG file to check. + * @return int[] The size of the SVG image. + */ +function upload_check_svg($temp_filename) +{ + global $settings; + // Check for script tags + if(strpos(file_get_contents($temp_filename), "sitename", "

    $settings->sitename detected that you uploaded an SVG image and performed some extra security checks on your file. Whilst performing these checks it was discovered that the file you uploaded contains some Javascript, which could be dangerous. The uploaded file has been discarded. Go back to try again.

    +

    You may wish to consider opening an issue against Pepperminty Wiki (the software that powers $settings->sitename) if this isn't the first time that you have seen this message.

    ")); + } + + // Find and return the size of the SVG image + return getsvgsize($temp_filename); +} + +/** + * Calculates the size of the specified SVG file. + * @package feature-upload + * @param string $svgFilename The filename to calculate the size of. + * @return int[] The width and height respectively of the + * specified SVG file. + */ +function getsvgsize($svgFilename) +{ + $svg = simplexml_load_file($svgFilename); // Load it as XML + if($svg === false) + { + http_response_code(415); + exit(page_renderer::render("Upload Error - $settings->sitename", "

    When $settings->sitename tried to open your SVG file for checking, it found some invalid syntax. The uploaded file has been discarded. Go back to try again.

    ")); + } + $rootAttrs = $svg->attributes(); + $imageSize = false; + if(isset($rootAttrs->width) and isset($rootAttrs->height)) + $imageSize = [ intval($rootAttrs->width), intval($rootAttrs->height) ]; + else if(isset($rootAttrs->viewBox)) + $imageSize = array_map("intval", array_slice(explode(" ", $rootAttrs->viewBox), -2, 2)); + + return $imageSize; +} + +/** + * Creates an images containing the specified text. + * Useful for sending errors back to the client. + * @package feature-upload + * @param string $text The text to include in the image. + * @param integer $target_size The target width to aim for when creating + * the image. + * @return image The handle to the generated GD image. + */ +function errorimage($text, $target_size = null) +{ + $width = 640; + $height = 480; + + if(!empty($target_size)) + { + $width = $target_size; + $height = $target_size * (2 / 3); + } + + $image = imagecreatetruecolor($width, $height); + imagefill($image, 0, 0, imagecolorallocate($image, 238, 232, 242)); // Set the background to #eee8f2 + $fontwidth = imagefontwidth(3); + imagestring($image, 3, + ($width / 2) - (($fontwidth * strlen($text)) / 2), + ($height / 2) - (imagefontheight(3) / 2), + $text, + imagecolorallocate($image, 17, 17, 17) // #111111 + ); + + return $image; +} + +?> + diff --git a/docs/ModuleApi/files/modules/feature-user-preferences.php.txt b/docs/ModuleApi/files/modules/feature-user-preferences.php.txt new file mode 100644 index 0000000..9dd84e8 --- /dev/null +++ b/docs/ModuleApi/files/modules/feature-user-preferences.php.txt @@ -0,0 +1,233 @@ + "User Preferences", + "version" => "0.3.2", + "author" => "Starbeamrainbowlabs", + "description" => "Adds a user preferences page, letting pople do things like change their email address and password.", + "id" => "feature-user-preferences", + "code" => function() { + global $env, $settings; + /** + * @api {get} ?action=user-preferences Get a user preferences configuration page + * @apiName UserPreferences + * @apiGroup Settings + * @apiPermission User + */ + + /* + * ██ ██ ███████ ███████ ██████ + * ██ ██ ██ ██ ██ ██ + * ██ ██ ███████ █████ ██████ █████ + * ██ ██ ██ ██ ██ ██ + * ██████ ███████ ███████ ██ ██ + * + * ██████ ██████ ███████ ███████ ███████ + * ██ ██ ██ ██ ██ ██ ██ + * ██████ ██████ █████ █████ ███████ + * ██ ██ ██ ██ ██ ██ + * ██ ██ ██ ███████ ██ ███████ + */ + add_action("user-preferences", function() { + global $env, $settings; + + if(!$env->is_logged_in) + { + exit(page_renderer::render_main("Error - $settings->sitename", "

    Since you aren't logged in, you can't change your preferences. This is because stored preferences are tied to each registered user account. You can login here.

    ")); + } + + $statusMessages = [ + "change-password" => "Password changed successfully!" + ]; + + if(!isset($env->user_data->emailAddress)) { + $env->user_data->emailAddress = ""; + save_userdata(); + } + + $content = "

    User Preferences

    \n"; + if(isset($_GET["success"]) && $_GET["success"] === "yes") + { + $content .= "

    " . $statusMessages[$_GET["operation"]] . "

    \n"; + } + // If avatar support is present, allow the user to upload a new avatar + if(has_action("avatar") && module_exists("feature-upload")) { + $content .= "
    \n"; + $content .= "\t\n"; + $content .= "
    Upload a new avatar
    \n"; + $content .= "

    \n"; + } + $content .= "\n"; + $content .= "\n"; + $content .= "
    \n"; + $content .= " \n"; + $content .= " \n"; + $content .= "

    Used to send you notifications etc. Never shared with anyone except $settings->admindetails_name, $settings->sitename's administrator.

    \n"; + $content .= " \n"; + $content .= "
    \n"; + $content .= "

    Change Password"; + $content .= "
    \n"; + $content .= " \n"; + $content .= " \n"; + $content .= "
    \n"; + $content .= " \n"; + $content .= " \n"; + $content .= "
    \n"; + $content .= " \n"; + $content .= " \n"; + $content .= "
    \n"; + $content .= " \n"; + $content .= "
    \n"; + + if($env->is_admin) + $content .= "

    As an admin, you can also edit $settings->sitename's master settings.

    \n"; + + exit(page_renderer::render_main("User Preferences - $settings->sitename", $content)); + }); + + /** + * @api {post} ?action=save-preferences Save your user preferences + * @apiName UserPreferencesSave + * @apiGroup Settings + * @apiPermission User + */ + add_action("save-preferences", function() { + global $env, $settings; + + if(!$env->is_logged_in) + { + http_response_code(400); + exit(page_renderer::render_main("Error Saving Preferences - $settings->sitename", "

    You aren't logged in, so you can't save your preferences. Try logging in first.

    ")); + } + + if(isset($_POST["email-address"])) + { + if(mb_strlen($_POST["email-address"]) > 320) + { + http_response_code(413); + exit(page_renderer::render_main("Error Saving Email Address - $settings->sitename", "

    The email address you supplied ({$_POST['email-address']}) is too long. Email addresses can only be 320 characters long. Go back.")); + } + + if(mb_strpos($_POST["email-address"], "@") === false) + { + http_response_code(422); + exit(page_renderer::render_main("Error Saving Email Address - $settings->sitename", "

    The email address you supplied ({$_POST['email-address']}) doesn't appear to be valid. Go back.")); + } + + $env->user_data->emailAddress = $_POST["email-address"]; + } + + // Save the user's preferences + if(!save_userdata()) + { + http_response_code(503); + exit(page_renderer::render_main("Error Saving Preferences - $settings->sitename", "

    $settings->sitename had some trouble saving your preferences! Please contact $settings->admindetails_name, $settings->sitename's administrator and tell them about this error if it still occurs in 5 minutes. They can be contacted by email at this address: " . hide_email($settings->admindetails_email) . ".

    ")); + } + + exit(page_renderer::render_main("Preferences Saved Successfully - $settings->sitename", "

    Your preferences have been saved successfully! You could go back your preferences page, or on to the $settings->defaultpage.

    ")); + }); + + /** + * @api {post} ?action=change-password Change your password + * @apiName ChangePassword + * @apiGroup Settings + * @apiPermission User + * + * @apiParam {string} current-pass Your current password. + * @apiParam {string} new-pass Your new password. + * @apiParam {string} new-pass-confirm Your new password again, to make sure you've typed it correctly. + * + * @apiError PasswordMismatchError The new password fields don't match. + */ + add_action("change-password", function() { + global $env, $settings; + + // Make sure the new password was typed correctly + // This comes before the current password check since that's more intensive + if($_POST["new-pass"] !== $_POST["new-pass-confirm"]) { + exit(page_renderer::render_main("Password mismatch - $settings->sitename", "

    The new password you typed twice didn't match! Go back.

    ")); + } + // Check the current password + if(hash_password($_POST["current-pass"]) !== $env->user_data->password) { + exit(page_renderer::render_main("Password mismatch - $settings->sitename", "

    Error: You typed your current password incorrectly! Go back.

    ")); + } + + // All's good! Go ahead and change the password. + $env->user_data->password = hash_password($_POST["new-pass"]); + // Save the userdata back to disk + save_userdata(); + + http_response_code(307); + header("location: ?action=user-preferences&success=yes&operation=change-password"); + exit(page_renderer::render_main("Password Changed Successfully", "

    You password was changed successfully. Go back to the user preferences page.

    ")); + }); + + + /* + * █████ ██ ██ █████ ████████ █████ ██████ + * ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ + * ███████ ██ ██ ███████ ██ ███████ ██████ + * ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ + * ██ ██ ████ ██ ██ ██ ██ ██ ██ ██ + */ + + /** + * @api {get} ?action=avatar&user={username}[&size={size}] Get a user's avatar + * @apiName Avatar + * @apiGroup Upload + * @apiPermission Anonymous + * + * @apiParam {string} user The username to fetch the avatar for + * @apiParam {string} size The preferred size of the avatar + */ + add_action("avatar", function() { + global $settings; + + $size = intval($_GET["size"] ?? 32); + + /// Use gravatar if there's some issue with the requested user + + // No user specified + if(empty($_GET["user"])) { + http_response_code(307); + header("x-reason: no-user-specified"); + header("location: https://gravatar.com/avatar/?default=blank"); + } + + $requested_username = $_GET["user"]; + + // The user hasn't uploaded an avatar + if(empty($pageindex->{"User/$requested_username/Avatar"}) || !$pageindex->{"User/$requested_username/Avatar"}->uploadedfile) { + $user_fragment = !empty($settings->users->$requested_username->emailAddress) ? $settings->users->$requested_username->emailAddress : $requested_username; + + http_response_code(307); + header("x-reason: no-avatar-found"); + header("x-hash-method: " . ($user_fragment === $requested_username ? "username" : "email_address")); + header("location: https://gravatar.com/avatar/" . md5($user_fragment) . "?default=identicon&rating=g&size=$size"); + exit(); + } + + // The user has uploaded an avatar, so we can redirec to the regular previewer :D + + http_response_code(307); + header("x-reason: found-local-avatar"); + header("location: ?action=preview&size=$size&page=" . urlencode("Users/$requested_username/Avatar")); + exit("This user's avatar can be found at Files/$requested_username/Avatar"); + }); + + // Display a help section on the user preferences, but only if the user + // is logged in and so able to access them + if($env->is_logged_in) + { + add_help_section("910-user-preferences", "User Preferences", "

    As you are logged in, $settings->sitename lets you configure a selection of personal preferences. These can be viewed and tweaked to you liking over on the preferences page, which can be accessed at any time by clicking the cog icon (it looks something like this: $settings->user_preferences_button_text), though the administrator of $settings->sitename ($settings->admindetails_name) may have changed its appearance.

    "); + } + + if($settings->avatars_show) + { + add_help_section("915-avatars", "Avatars", "

    $settings->sitename allows you to upload an avatar and have it displayed next to your name. If you don't have an avatar uploaded yet, then $settings->sitename will take a hash of your email address and ask Gravatar for for your Gravatar instead. If you haven't told $settings->sitename what your email address is either, a hash of your username is used instead. If you don't have a gravatar, then $settings->sitename asks Gravatar for an identicon instead.

    +

    Your avatar on $settings->sitename currently looks like this: " . ($settings->upload_enabled ? " - you can upload a new one by going to your preferences, or clicking here." : ", but $settings->sitename currently has uploads disabled, so you can't upload a new one directly to $settings->sitename. You can, however, set your email address in your preferences and create a Gravatar, and then it should show up here on $settings->sitename shortly.") . "

    "); + } + } +]); + +?> + diff --git a/docs/ModuleApi/files/modules/page-credits.php.txt b/docs/ModuleApi/files/modules/page-credits.php.txt new file mode 100644 index 0000000..71597c2 --- /dev/null +++ b/docs/ModuleApi/files/modules/page-credits.php.txt @@ -0,0 +1,146 @@ + "Credits", + "version" => "0.7.7", + "author" => "Starbeamrainbowlabs", + "description" => "Adds the credits page. You *must* have this module :D", + "id" => "page-credits", + "code" => function() { + /** + * @api {get} ?action=credits Get the credits page + * @apiName Credits + * @apiGroup Utility + * @apiPermission Anonymous + */ + + /* + * ██████ ██████ ███████ ██████ ██ ████████ ███████ + * ██ ██ ██ ██ ██ ██ ██ ██ ██ + * ██ ██████ █████ ██ ██ ██ ██ ███████ + * ██ ██ ██ ██ ██ ██ ██ ██ ██ + * ██████ ██ ██ ███████ ██████ ██ ██ ███████ + */ + add_action("credits", function() { + global $settings, $version, $pageindex, $modules; + + $credits = [ + "Code" => [ + "author" => "Starbeamrainbowlabs", + "author_url" => "https://starbeamrmainbowlabs.com/", + "thing_url" => "https://github.com/sbrl/Pepprminty-Wiki", + "icon" => "https://avatars0.githubusercontent.com/u/9929737?v=3&s=24" + ], + "Mime type to file extension mapper" => [ + "author" => "Chaos", + "author_url" => "https://stackoverflow.com/users/47529/chaos", + "thing_url" => "https://stackoverflow.com/a/1147952/1460422", + "icon" => "https://www.gravatar.com/avatar/aaee40db39ad6b164cfb89cb6ad4d176?s=328&d=identicon&s=24" + ], + "Parsedown" => [ + "author" => "Emanuil Rusev and others", + "author_url" => "https://github.com/erusev/", + "thing_url" => "https://github.com/erusev/parsedown/", + "icon" => "https://avatars1.githubusercontent.com/u/184170?v=3&s=24" + ], + "CSS Minification Code" => [ + "author" => "Jean", + "author_url" => "http://www.catswhocode.com/", + "thing_url" => "http://www.catswhocode.com/blog/3-ways-to-compress-css-files-using-php" + ], + "Slightly modified version of Slimdown" => [ + "author" => "Johnny Broadway", + "author_url" => "https://github.com/jbroadway", + "thing_url" => "https://gist.github.com/jbroadway/2836900", + "icon" => "https://avatars2.githubusercontent.com/u/87886?v=3&s=24" + ], + "Insert tab characters into textareas" => [ + "author" => "Unknown", + "author_url" => "http://stackoverflow.com/q/6140632/1460422", + "thing_url" => "https://jsfiddle.net/2wAzx/13/", + ], + "Default Favicon" => [ + "author" => "bluefrog23", + "author_url" => "https://openclipart.org/user-detail/bluefrog23/", + "thing_url" => "https://openclipart.org/detail/19571/peppermint-candy-by-bluefrog23" + ], + "Bug Reports" => [ + "author" => "nibreh", + "author_url" => "https://github.com/nibreh/", + "thing_url" => "", + "icon" => "https://avatars2.githubusercontent.com/u/7314006?v=3&s=24" + ], + "PR #135: Fix repeated page names on sidebar" => [ + "author" => "ikisler", + "author_url" => "https://github.com/ikisler", + "thing_url" => "https://github.com/sbrl/Pepperminty-Wiki/pull/135", + "icon" => "https://avatars3.githubusercontent.com/u/12506147?v=3&s=24" + ], + "PR #136: Fix issue where bottom nav is cut off" => [ + "author" => "ikisler", + "author_url" => "https://github.com/ikisler", + "thing_url" => "https://github.com/sbrl/Pepperminty-Wiki/pull/136", + "icon" => "https://avatars3.githubusercontent.com/u/12506147?v=3&s=24" + ], + "PR #140: Edit Previewing" => [ + "author" => "ikisler", + "author_url" => "https://github.com/ikisler", + "thing_url" => "https://github.com/sbrl/Pepperminty-Wiki/pull/140", + "icon" => "https://avatars3.githubusercontent.com/u/12506147?v=3&s=24" + ] + ]; + + //// Credits html renderer //// + $credits_html = "
      \n"; + foreach($credits as $thing => $author_details) + { + $credits_html .= "
    • "; + $credits_html .= "$thing by "; + if(isset($author_details["icon"])) + $credits_html .= " "; + $credits_html .= "" . $author_details["author"] . ""; + $credits_html .= "
    • \n"; + } + $credits_html .= "
    "; + /////////////////////////////// + + //// Module html renderer //// + $modules_html = " + + + + + + "; + foreach($modules as $module) + { + $modules_html .= " + + + + + \n"; + } + $modules_html .= "
    NameVersionAuthorDescription
    " . $module["name"] . "" . $module["version"] . "" . $module["author"] . "" . $module["description"] . "
    "; + ////////////////////////////// + + $title = "Credits - $settings->sitename"; + $content = "

    $settings->sitename credits

    +

    $settings->sitename is powered by Pepperminty Wiki - an entire wiki packed inside a single file, which was built by Starbeamrainbowlabs, and can be found on GitHub (contributors will also be listed here in the future). Pepperminty Wiki is licensed under the Mozilla Public License 2.0 (simple version).

    +

    Main Credits

    + $credits_html +

    Site status

    + + + + + +
    Site name:$settings->sitename ({$settings->admindisplaychar}Update, {$settings->admindisplaychar}Edit master settings, Export as zip - Check for permission first)
    Pepperminty Wiki version:$version
    Number of pages:" . count(get_object_vars($pageindex)) . "
    Number of modules:" . count($modules) . "
    +

    Installed Modules

    + $modules_html"; + exit(page_renderer::render_main($title, $content)); + }); + } +]); + +?> + diff --git a/docs/ModuleApi/files/modules/page-debug-info.php.txt b/docs/ModuleApi/files/modules/page-debug-info.php.txt new file mode 100644 index 0000000..2ad746a --- /dev/null +++ b/docs/ModuleApi/files/modules/page-debug-info.php.txt @@ -0,0 +1,77 @@ + "Debug Information", + "version" => "0.1.1", + "author" => "Starbeamrainbowlabs", + "description" => "Adds a debug action for administrator use only that collects a load of useful information to make reporting bugs easier.", + "id" => "page-debug-info", + "code" => function() { + global $settings, $env; + /** + * @api {get} ?action=debug Get a debug dump + * @apiName Debug + * @apiGroup Utility + * @apiPermission Moderator + * + * @apiUse UserNotModeratorError + */ + + /* + * ██████ ███████ ██████ ██ ██ ██████ + * ██ ██ ██ ██ ██ ██ ██ ██ + * ██ ██ █████ ██████ ██ ██ ██ ███ + * ██ ██ ██ ██ ██ ██ ██ ██ ██ + * ██████ ███████ ██████ ██████ ██████ + */ + add_action("debug", function() { + global $settings, $env, $paths, $version; + header("content-type: text/plain"); + + if(!$env->is_admin) + { + exit("You must be logged in as an moderator in order to generate debugging information."); + } + + $title = "$settings->sitename debug report"; + echo("$title\n"); + echo(str_repeat("=", strlen($title)) . "\n"); + echo("Powered by Pepperminty Wiki version $version.\n"); + echo("This report may contain personal information.\n\n"); + echo("Environment: "); + echo(var_export($env, true)); + echo("\nPaths: "); + var_dump(var_export($paths, true)); + echo("\nServer information:\n"); + echo("uname -a: " . php_uname() . "\n"); + echo("Path: " . getenv("PATH") . "\n"); + echo("Temporary directory: " . sys_get_temp_dir() . "\n"); + echo("Server: " . $_SERVER["SERVER_SOFTWARE"] . "\n"); + echo("Web root: " . $_SERVER["DOCUMENT_ROOT"] . "\n"); + echo("Web server user: " . exec("whoami") . "\n"); + echo("PHP version: " . phpversion() . "\n"); + echo("index.php location: " . __FILE__ . "\n"); + echo("index.php file permissions: " . substr(sprintf('%o', fileperms("./index.php")), -4) . "\n"); + echo("Current folder permissions: " . substr(sprintf('%o', fileperms(".")), -4) . "\n"); + echo("Storage directory permissions: " . substr(sprintf('%o', fileperms($env->storage_prefix)), -4) . "\n"); + echo("Loaded extensions: " . implode(", ", get_loaded_extensions()) . "\n"); + echo("Settings:\n-----\n"); + $settings_export = explode("\n", var_export($settings, true)); + foreach ($settings_export as &$row) + { + if(preg_match("/(sitesecret|email)/i", $row)) $row = "********* secret *********"; + } + echo(implode("\n", $settings_export)); + echo("\n-----\n"); + exit(); + }); + + if($env->is_admin) + { + add_help_section("950-debug-information", "Gathering debug information", "

    As a moderator, $settings->sitename gives you the ability to generate a report on $settings->sitename's installation of Pepperminty Wiki for debugging purposes.

    +

    To generate such a report, visit the debug action or click here.

    "); + } + } +]); + +?> + diff --git a/docs/ModuleApi/files/modules/page-delete.php.txt b/docs/ModuleApi/files/modules/page-delete.php.txt new file mode 100644 index 0000000..e84379d --- /dev/null +++ b/docs/ModuleApi/files/modules/page-delete.php.txt @@ -0,0 +1,120 @@ + "Page deleter", + "version" => "0.10", + "author" => "Starbeamrainbowlabs", + "description" => "Adds an action to allow administrators to delete pages.", + "id" => "page-delete", + "code" => function() { + global $settings; + /** + * @api {post} ?action=delete Delete a page + * @apiDescription Delete a page and all its associated data. + * @apiName DeletePage + * @apiGroup Page + * @apiPermission Moderator + * + * @apiParam {string} page The name of the page to delete. + * @apiParam {string} delete Set to 'yes' to actually delete the page. + * + * @apiUse UserNotModeratorError + * @apiError PageNonExistentError The specified page doesn't exist + */ + + /* + * ██████ ███████ ██ ███████ ████████ ███████ + * ██ ██ ██ ██ ██ ██ ██ + * ██ ██ █████ ██ █████ ██ █████ + * ██ ██ ██ ██ ██ ██ ██ + * ██████ ███████ ███████ ███████ ██ ███████ + */ + add_action("delete", function() { + global $pageindex, $settings, $env, $paths, $modules; + if(!$settings->editing) + { + exit(page_renderer::render_main("Error: Editing disabled - Deleting $env->page", "

    You tried to delete $env->page, but editing is disabled on this wiki.

    +

    If you wish to delete this page, please re-enable editing on this wiki first.

    +

    Go back to $env->page.

    +

    Nothing has been changed.

    ")); + } + if(!$env->is_admin) + { + exit(page_renderer::render_main("Error: Insufficient permissions - Deleting $env->page", "

    You tried to delete $env->page, but you as aren't a moderator you don't have permission to do that.

    +

    You could try logging in as an admin, or asking one of $settings->sitename's friendly moderators (find their names at the bottom of every page!) to delete it for you.

    ")); + } + if(!isset($pageindex->{$env->page})) + { + exit(page_renderer::render_main("Error: Non-existent page - Deleting $env->page", "

    You tried to delete $env->page, but that page doesn't appear to exist in the first page. Go back to the $settings->defaultpage.

    ")); + } + + if(!isset($_GET["delete"]) or $_GET["delete"] !== "yes") + { + exit(page_renderer::render_main("Deleting $env->page", "

    You are about to delete $env->page" . (module_exists("feature-history")?" and all its revisions":"") . (module_exists("feature-comments")?" and all its comments":"") . ". You can't undo this!

    +

    Click here to delete $env->page.

    +

    Click here to go back.")); + } + $page = $env->page; + // Delete the associated file if it exists + if(!empty($pageindex->$page->uploadedfile)) + { + unlink($env->storage_prefix . $pageindex->$page->uploadedfilepath); + } + + // While we're at it, we should delete all the revisions too + foreach($pageindex->{$env->page}->history as $revisionData) + { + unlink($env->storage_prefix . $revisionData->filename); + } + + // If the commenting module is installed and the page has comments, + // delete those too + if(module_exists("feature-comments") and + file_exists(get_comment_filename($env->page))) + { + unlink(get_comment_filename($env->page)); + } + + // Delete the page from the page index + unset($pageindex->$page); + + // Save the new page index + file_put_contents($paths->pageindex, json_encode($pageindex, JSON_PRETTY_PRINT)); + + // Remove the page's name from the id index + ids::deletepagename($env->page); + + // Delete the page from the search index, if that module is installed + if(module_exists("feature-search")) + { + $pageid = ids::getid($env->page); + $invindex = search::load_invindex($paths->searchindex); + search::delete_entry($invindex, $pageid); + search::save_invindex($paths->searchindex, $invindex); + } + + // Delete the page from the disk + unlink("$env->storage_prefix$env->page.md"); + + // Add a recent change announcing the deletion if the recent changes + // module is installed + if(module_exists("feature-recent-changes")) + { + add_recent_change([ + "type" => "deletion", + "timestamp" => time(), + "page" => $env->page, + "user" => $env->user, + ]); + } + + exit(page_renderer::render_main("Deleting $env->page - $settings->sitename", "

    $env->page has been deleted. Go back to the main page.

    ")); + }); + + // Register a help section + add_help_section("60-delete", "Deleting Pages", "

    If you are logged in as an adminitrator, then you have the power to delete pages. To do this, click "Delete" in the "More..." menu when browsing the pge you wish to delete. When you are sure that you want to delete the page, click the given link.

    +

    Warning: Once a page has been deleted, you can't bring it back! You will need to recover it from your backup, if you have one (which you really should).

    "); + } +]); + +?> + diff --git a/docs/ModuleApi/files/modules/page-edit.php.txt b/docs/ModuleApi/files/modules/page-edit.php.txt new file mode 100644 index 0000000..52ee6c2 --- /dev/null +++ b/docs/ModuleApi/files/modules/page-edit.php.txt @@ -0,0 +1,459 @@ + "Page editor", + "version" => "0.16", + "author" => "Starbeamrainbowlabs", + "description" => "Allows you to edit pages by adding the edit and save actions. You should probably include this one.", + "id" => "page-edit", + + "code" => function() { + global $settings, $env; + + /** + * @api {get} ?action=edit&page={pageName}[&newpage=yes] Get an editing page + * @apiDescription Gets an editing page for a given page. If you don't have permission to edit the page in question, a view source pagee is returned instead. + * @apiName EditPage + * @apiGroup Page + * @apiPermission Anonymous + * + * @apiUse PageParameter + * @apiParam {string} newpage Set to 'yes' if a new page is being created. Only affects a few bits of text here and there, and the HTTP response code recieved on success from the `save` action. + */ + + /* + * _ _ _ + * ___ __| (_) |_ + * / _ \/ _` | | __| + * | __/ (_| | | |_ + * \___|\__,_|_|\__| + * %edit% + */ + add_action("edit", function() { + global $pageindex, $settings, $env; + + $filename = "$env->storage_prefix$env->page.md"; + $page = $env->page; + $creatingpage = !isset($pageindex->$page); + if((isset($_GET["newpage"]) and $_GET["newpage"] == "true") or $creatingpage) + { + $title = "Creating $env->page"; + } + else if(isset($_POST['preview-edit']) && isset($_POST['content'])) + { + $title = "Preview Edits for $env->page"; + } + else + { + $title = "Editing $env->page"; + } + + $pagetext = ""; + if(isset($pageindex->$page)) + { + $pagetext = file_get_contents($filename); + } + + $isOtherUsersPage = false; + if( + $settings->user_page_prefix == mb_substr($env->page, 0, mb_strlen($settings->user_page_prefix)) and // The current page is a user page of some sort + ( + !$env->is_logged_in or // the user isn't logged in..... + extract_user_from_userpage($env->page) !== $env->user // ...or it's not under this user's own name + ) + ) { + $isOtherUsersPage = true; + } + + if((!$env->is_logged_in and !$settings->anonedits) or // if we aren't logged in and anonymous edits are disabled + !$settings->editing or // or editing is disabled + ( + isset($pageindex->$page) and // or if the page exists + isset($pageindex->$page->protect) and // the protect property exists + $pageindex->$page->protect and // the protect property is true + !$env->is_admin // the user isn't an admin + ) or + $isOtherUsersPage // this page actually belongs to another user + ) + { + if(!$creatingpage) + { + // The page already exists - let the user view the page source + $sourceViewContent = "

    $settings->sitename does not allow anonymous users to make edits. You can view the source of $env->page below, but you can't edit it. You could, however, try logging in.

    \n"; + + if($env->is_logged_in) + $sourceViewContent = "

    $env->page is protected, and you aren't an administrator or moderator. You can view the source of $env->page below, but you can't edit it.

    \n"; + + if($isOtherUsersPage) + $sourceViewContent = "

    $env->page is a special user page which acutally belongs to " . extract_user_from_userpage($env->page) . ", another user on $settings->sitename. Because of this, you are not allowed to edit it (though you can always edit your own page and any pages under it if you're logged in). You can, however, vieww it's source below.

    "; + + // Append a view of the page's source + $sourceViewContent .= ""; + + exit(page_renderer::render_main("Viewing source for $env->page", $sourceViewContent)); + } + else + { + $errorMessage = "

    The page $env->page does not exist, but you do not have permission to create it.

    If you haven't already, perhaps you should try logging in.

    \n"; + + if($isOtherUsersPage) { + $errorMessage = "

    The page " . htmlentities($env->page) . " doesn't exist, but you can't create it because it's a page belonging to another user.

    \n"; + if(!$env->is_logged_in) + $errorMessage .= "

    You could try logging in.

    \n"; + } + + http_response_code(404); + exit(page_renderer::render_main("404 - $env->page", $errorMessage)); + } + } + + $content = "

    $title

    "; + $page_tags = implode(", ", (!empty($pageindex->{$env->page}->tags)) ? $pageindex->{$env->page}->tags : []); + if(!$env->is_logged_in and $settings->anonedits) + { + $content .= "

    Warning: You are not logged in! Your IP address may be recorded.

    "; + } + + // Include preview, if set + if(isset($_POST['preview-edit']) && isset($_POST['content'])) { + // Need this for the prev-content-hash to prevent the conflict page from appearing + $old_pagetext = $pagetext; + + // set the page content to the newly edited content + $pagetext = $_POST['content']; + + // Set the tags to the new tags, if needed + if(isset($_POST['tags'])) + $page_tags = $_POST['tags']; + + // Insert the "view" part of the page we're editing + $content .= "

    This is only a preview, so your edits haven't been saved! Scroll down to continue editing.

    " . parse_page_source($pagetext); + + } + + $content .= "
    + + +
    
    +					
    +					

    $settings->editing_message

    + + +
    "; + // Allow tab characters in the page editor + page_renderer::AddJSSnippet("window.addEventListener('load', function(event) { + // Adapted from https://jsfiddle.net/2wAzx/13/ + document.querySelector(\"[name=content]\").addEventListener(\"keydown\", (event) => { + if(event.keyCode !== 9) return true; + var currentValue = event.target.value, startPos = event.target.selectionStart, endPos = event.target.selectionEnd; + event.target.value = currentValue.substring(0, startPos) + \"\\t\" + currentValue.substring(endPos); + event.target.selectionStart = event.target.selectionEnd = startPos + 1; + event.stopPropagation(); event.preventDefault(); + return false; + }); +});"); + + // Utilise the mirror to automatically resize the textarea to fit it's content + page_renderer::AddJSSnippet('function updateTextSize(textarea, mirror, event) { + let textareaFontSize = parseFloat(getComputedStyle(textarea).fontSize); + + let textareaWidth = textarea.getBoundingClientRect().width;// - parseInt(textarea.style.padding); + mirror.style.width = `${textareaWidth}px`; + mirror.innerText = textarea.value; + textarea.style.height = `${mirror.offsetHeight + (textareaFontSize * 5)}px`; +} +function trackTextSize(textarea) { + let mirror = textarea.nextElementSibling; + textarea.addEventListener("input", updateTextSize.bind(null, textarea, mirror)); + updateTextSize(textarea, mirror, null); +} +window.addEventListener("load", function(event) { + trackTextSize(document.querySelector("textarea[name=content]")); +}); +'); + + // ~ + + /// ~~~ Smart saving ~~~ /// + // TODO: Add a button to press that restores the content that you were working on before. + page_renderer::AddJSSnippet('document.addEventListener("load", function(event) { + // Smart saving + function getSmartSaveKey() { return document.querySelector("main h1").innerHTML.replace("Creating ", "").replace("Editing ", "").trim(); } + // Saving + document.querySelector("textarea[name=content]").addEventListener("keyup", function(event) { window.localStorage.setItem(getSmartSaveKey(), event.target.value) }); + // Loading + window.addEventListener("load", function(event) { + var editor = document.querySelector("textarea[name=content]"); + if(editor.value.length > 0) return; // Don\'t restore if there\'s data in the editor already + editor.value = localStorage.getItem(getSmartSaveKey()); + }); +});'); + + exit(page_renderer::render_main("$title - $settings->sitename", $content)); + }); + + /** + * @api {post} ?action=preview-edit&page={pageName}[&newpage=yes] Get a preview of the page + * @apiDescription Gets a preview of the current edit state of a given page + * @apiName PreviewPage + * @apiPermission Anonymous + * + * @apiUse PageParameter + * @apiParam {string} newpage Set to 'yes' if a new page is being created. + * @apiParam {string} preview-edit Set to a value to preview an edit of a page. + */ + + /* + * + * ██████ ██████ ███████ ██ ██ ██ ███████ ██ ██ + * ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ + * ██████ ██████ █████ ██ ██ ██ █████ ██ █ ██ + * ██ ██ ██ ██ ██ ██ ██ ██ ██ ███ ██ + * ██ ██ ██ ███████ ████ ██ ███████ ███ ███ + * + * ███████ ██████ ██ ████████ + * ██ ██ ██ ██ ██ + * █████ ██ ██ ██ ██ + * ██ ██ ██ ██ ██ + * ███████ ██████ ██ ██ + * + */ + add_action("preview-edit", function() { + global $pageindex, $settings, $env, $actions; + + if(isset($_POST['preview-edit']) && isset($_POST['content'])) { + // preview changes + get_object_vars($actions)['edit'](); + } + else { + // save page + get_object_vars($actions)['save'](); + } + + + }); + + /** + * @api {post} ?action=save&page={pageName} Save an edit to a page. + * @apiDescription Saves an edit to a page. If an edit conflict is encountered, then a conflict resolution page is returned instead. + * @apiName EditPage + * @apiGroup Page + * @apiPermission Anonymous + * + * @apiUse PageParameter + * @apiParam {string} newpage GET only. Set to 'yes' to indicate that this is a new page that is being saved. Only affects the HTTP response code you recieve upon success. + * @apiParam {string} content POST only. The new content to save to the given filename. + * @apiParam {string} tags POST only. A comma-separated list of tags to assign to the current page. Will replace the existing list of tags, if any are present. + * @apiParam {string} prev-content-hash POST only. The hash of the original content before editing. If this hash is found to be different to a hash computed of the currentl saved content, a conflict resolution page will be returned instead of saving the provided content. + * + * @apiError UnsufficientPermissionError You don't currently have sufficient permissions to save an edit. + */ + + /* + * + * ___ __ ___ _____ + * / __|/ _` \ \ / / _ \ + * \__ \ (_| |\ V / __/ + * |___/\__,_| \_/ \___| + * %save% + */ + add_action("save", function() { + global $pageindex, $settings, $env, $save_preprocessors, $paths; + + if(!$settings->editing) + { + header("location: index.php?page=$env->page"); + exit(page_renderer::render_main("Error saving edit", "

    Editing is currently disabled on this wiki.

    ")); + } + if(!$env->is_logged_in and !$settings->anonedits) + { + http_response_code(403); + header("refresh: 5; url=index.php?page=$env->page"); + exit("You are not logged in, so you are not allowed to save pages on $settings->sitename. Redirecting in 5 seconds...."); + } + $page = $env->page; + if(( + isset($pageindex->$page) and + isset($pageindex->page->protect) and + $pageindex->$page->protect + ) and !$env->is_admin) + { + http_response_code(403); + header("refresh: 5; url=index.php?page=$env->page"); + exit("$env->page is protected, and you aren't logged in as an administrator or moderator. Your edit was not saved. Redirecting in 5 seconds..."); + } + if(!isset($_POST["content"])) + { + http_response_code(400); + header("refresh: 5; url=index.php?page=$env->page"); + exit("Bad request: No content specified."); + } + + // Make sure that the directory in which the page needs to be saved exists + if(!is_dir(dirname("$env->storage_prefix$env->page.md"))) + { + // Recursively create the directory if needed + mkdir(dirname("$env->storage_prefix$env->page.md"), 0775, true); + } + + // Read in the new page content + $pagedata = $_POST["content"]; + // We don't need to santise the input here as Parsedown has an + // option that does this for us, and is _way_ more intelligent about + // it. + + // Read in the new page tags, so long as there are actually some + // tags to read in + $page_tags = []; + if(strlen(trim($_POST["tags"])) > 0) + { + $page_tags = explode(",", $_POST["tags"]); + // Trim off all the whitespace + foreach($page_tags as &$tag) + $tag = trim($tag); + } + + // Check for edit conflicts + if(!empty($pageindex->{$env->page}) && file_exists($env->storage_prefix . $pageindex->{$env->page}->filename)) + { + $existing_content_hash = sha1_file($env->storage_prefix . $pageindex->{$env->page}->filename); + if(isset($_POST["prev-content-hash"]) and + $existing_content_hash != $_POST["prev-content-hash"]) + { + $existingPageData = htmlentities(file_get_contents($env->storage_prefix . $env->storage_prefix . $pageindex->{$env->page}->filename)); + // An edit conflict has occurred! We should get the user to fix it. + $content = "

    Resolving edit conflict - $env->page

    "; + if(!$env->is_logged_in and $settings->anonedits) + { + $content .= "

    Warning: You are not logged in! Your IP address may be recorded.

    "; + } + $content .= "

    An edit conflict has arisen because someone else has saved an edit to $env->page since you started editing it. Both texts are shown below, along the differences between the 2 conflicting revisions. To continue, please merge your changes with the existing content. Note that only the text in the existing content box will be kept when you press the \"Resolve Conflict\" button at the bottom of the page.

    + +
    +

    Existing content

    + + +

    Differences

    +
    + + +

    Your content

    + + +

    $settings->editing_message

    + +
    "; + + // Insert a reference to jsdiff to generate the diffs + $diffScript = <<<'DIFFSCRIPT' +window.addEventListener("load", function(event) { + var destination = document.getElementById("highlighted-diff"), + diff = JsDiff.diffWords(document.getElementById("original-content").value, document.getElementById("new-content").value), + output = ""; + diff.forEach(function(change) { + var classes = "token"; + if(change.added) classes += " diff-added"; + if(change.removed) classes += " diff-removed"; + output += `${change.value}`; + }); + destination.innerHTML = output; +}); +DIFFSCRIPT; + + $content .= "\n + \n"; + + exit(page_renderer::render_main("Edit Conflict - $env->page - $settings->sitename", $content)); + } + } + + // -----~~~==~~~----- + + // Update the inverted search index + + // Construct an index for the old and new page content + $oldindex = []; + $oldpagedata = ""; // We need the old page data in order to pass it to the preprocessor + if(file_exists("$env->storage_prefix$env->page.md")) + { + $oldpagedata = file_get_contents("$env->storage_prefix$env->page.md"); + $oldindex = search::index($oldpagedata); + } + $newindex = search::index($pagedata); + + // Compare the indexes of the old and new content + $additions = []; + $removals = []; + search::compare_indexes($oldindex, $newindex, $additions, $removals); + // Load in the inverted index + $invindex = search::load_invindex($env->storage_prefix . "invindex.json"); + // Merge the changes into the inverted index + search::merge_into_invindex($invindex, ids::getid($env->page), $additions, $removals); + // Save the inverted index back to disk + search::save_invindex($env->storage_prefix . "invindex.json", $invindex); + + // -----~~~==~~~----- + + if(file_put_contents("$env->storage_prefix$env->page.md", $pagedata) !== false) + { + $page = $env->page; + // Make sure that this page's parents exist + check_subpage_parents($page); + + // Update the page index + if(!isset($pageindex->$page)) + { + $pageindex->$page = new stdClass(); + $pageindex->$page->filename = "$env->page.md"; + } + $pageindex->$page->size = strlen($_POST["content"]); + $pageindex->$page->lastmodified = time(); + if($env->is_logged_in) + $pageindex->$page->lasteditor = utf8_encode($env->user); + else // TODO: Add an option to record the user's IP here instead + $pageindex->$page->lasteditor = utf8_encode("anonymous"); + $pageindex->$page->tags = $page_tags; + + // A hack to resave the pagedata if the preprocessors have + // changed it. We need this because the preprocessors *must* + // run _after_ the pageindex has been updated. + $pagedata_orig = $pagedata; + + // Execute all the preprocessors + foreach($save_preprocessors as $func) + { + $func($pageindex->$page, $pagedata, $oldpagedata); + } + + if($pagedata !== $pagedata_orig) + file_put_contents("$env->storage_prefix$env->page.md", $pagedata); + + + file_put_contents($paths->pageindex, json_encode($pageindex, JSON_PRETTY_PRINT)); + + if(isset($_GET["newpage"])) + http_response_code(201); + else + http_response_code(200); + +// header("content-type: text/plain"); + header("location: index.php?page=$env->page&edit_status=success&redirect=no"); + exit(); + } + else + { + http_response_code(507); + exit(page_renderer::render_main("Error saving page - $settings->sitename", "

    $settings->sitename failed to write your changes to the server's disk. Your changes have not been saved, but you might be able to recover your edit by pressing the back button in your browser.

    +

    Please tell the administrator of this wiki (" . $settings->admindetails_name . ") about this problem.

    ")); + } + }); + + add_help_section("15-editing", "Editing", "

    To edit a page on $settings->sitename, click the edit button on the top bar. Note that you will probably need to be logged in. If you do not already have an account you will need to ask $settings->sitename's administrator for an account since there is no registration form. Note that the $settings->sitename's administrator may have changed these settings to allow anonymous edits.

    +

    Editing is simple. The edit page has a sizeable box that contains a page's current contents. Once you are done altering it, add or change the comma separated list of tags in the field below the editor and then click save page.

    +

    A reference to the syntax that $settings->sitename supports can be found below.

    "); + + add_help_section("17-user-pages", "User Pages", "

    If you are logged in, $settings->sitename allocates you your own user page that only you can edit. On $settings->sitename, user pages are sub-pages of the " . htmlentities($settings->user_page_prefix) . " page, and each user page can have a nested structure of pages underneath it, just like a normal page. Your user page is located at " . htmlentities(get_user_pagename($env->user)) . ".

    "); + } +]); + +?> + diff --git a/docs/ModuleApi/files/modules/page-export.php.txt b/docs/ModuleApi/files/modules/page-export.php.txt new file mode 100644 index 0000000..ad03430 --- /dev/null +++ b/docs/ModuleApi/files/modules/page-export.php.txt @@ -0,0 +1,77 @@ + "Export", + "version" => "0.4", + "author" => "Starbeamrainbowlabs", + "description" => "Adds a page that you can use to export your wiki as a .zip file. Uses \$settings->export_only_allow_admins, which controls whether only admins are allowed to export the wiki.", + "id" => "page-export", + "code" => function() { + global $settings; + + /** + * @api {get} ?action=export Export the all the wiki's content + * @apiDescription Export all the wiki's content. Please ask for permission before making a request to this URI. Note that some wikis may only allow moderators to export content. + * @apiName Export + * @apiGroup Utility + * @apiPermission Anonymous + * + * @apiError InsufficientExportPermissionsError The wiki has the export_allow_only_admins option turned on, and you aren't logged into a moderator account. + * @apiError CouldntOpenTempFileError Pepperminty Wiki couldn't open a temporary file to send the compressed archive to. + * @apiError CouldntCloseTempFileError Pepperminty Wiki couldn't close the temporary file to finish creating the zip archive ready for downloading. + */ + + /* + * ███████ ██ ██ ██████ ██████ ██████ ████████ + * ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ + * █████ ███ ██████ ██ ██ ██████ ██ + * ██ ██ ██ ██ ██ ██ ██ ██ ██ + * ███████ ██ ██ ██ ██████ ██ ██ ██ + */ + add_action("export", function() { + global $settings, $pageindex, $env; + + if($settings->export_allow_only_admins && !$env->is_admin) + { + http_response_code(401); + exit(page_renderer::render("Export error - $settings->sitename", "Only administrators of $settings->sitename are allowed to export the wiki as a zip. Return to the $settings->defaultpage.")); + } + + $tmpfilename = tempnam(sys_get_temp_dir(), "pepperminty-wiki-"); + + $zip = new ZipArchive(); + + if($zip->open($tmpfilename, ZipArchive::CREATE) !== true) + { + http_response_code(507); + exit(page_renderer::render("Export error - $settings->sitename", "Pepperminty Wiki was unable to open a temporary file to store the exported data in. Please contact $settings->sitename's administrator (" . $settings->admindetails_name . " at " . hide_email($settings->admindetails_email) . ") for assistance.")); + } + + foreach($pageindex as $entry) + { + $zip->addFile("$env->storage_prefix$entry->filename", $entry->filename); + } + + if($zip->close() !== true) + { + http_response_code(500); + exit(page_renderer::render("Export error - $settings->sitename", "Pepperminty wiki was unable to close the temporary zip file after creating it. Please contact $settings->sitename's administrator (" . $settings->admindetails_name . " at " . hide_email($settings->admindetails_email) . ") for assistance.")); + } + + header("content-type: application/zip"); + header("content-disposition: attachment; filename=$settings->sitename-export.zip"); + header("content-length: " . filesize($tmpfilename)); + + $zip_handle = fopen($tmpfilename, "rb"); + fpassthru($zip_handle); + fclose($zip_handle); + unlink($tmpfilename); + }); + + // Add a section to the help page + add_help_section("50-export", "Exporting", "

    $settings->sitename supports exporting the entire wiki's content as a zip. Note that you may need to be a moderator in order to do this. Also note that you should check for permission before doing so, even if you are able to export without asking.

    +

    To perform an export, go to the credits page and click "Export as zip - Check for permission first".

    "); + } +]); + +?> + diff --git a/docs/ModuleApi/files/modules/page-help.php.txt b/docs/ModuleApi/files/modules/page-help.php.txt new file mode 100644 index 0000000..af52333 --- /dev/null +++ b/docs/ModuleApi/files/modules/page-help.php.txt @@ -0,0 +1,154 @@ + "Help page", + "version" => "0.9.3", + "author" => "Starbeamrainbowlabs", + "description" => "Adds a rather useful help page. Access through the 'help' action. This module also exposes help content added to Pepperminty Wiki's inbuilt invisible help section system.", + "id" => "page-help", + "code" => function() { + global $settings; + + /** + * @api {get} ?action=help[&dev=yes] Get a help page + * @apiDescription Get a customised help page. This page will be slightly different for every wiki, depending on their name, settings, and installed modules. + * @apiName Help + * @apiGroup Utility + * @apiPermission Anonymous + * + * @apiParam {string} dev Set to 'yes' to get a developer help page instead. The developer help page gives some general information about which modules and help page sections are registered, and other various (non-sensitive) settings. + */ + + /* + * ██ ██ ███████ ██ ██████ + * ██ ██ ██ ██ ██ ██ + * ███████ █████ ██ ██████ + * ██ ██ ██ ██ ██ + * ██ ██ ███████ ███████ ██ + */ + add_action("help", function() { + global $env, $paths, $settings, $version, $help_sections, $actions; + + // Sort the help sections by key + ksort($help_sections, SORT_NATURAL); + + if(isset($_GET["dev"]) and $_GET["dev"] == "yes") + { + $title = "Developers Help - $settings->sitename"; + $content = "

    $settings->sitename runs on Pepperminty Wiki, an entire wiki packed into a single file. This page contains some information that developers may find useful.

    +

    A full guide to developing a Pepperminty Wiki module can be found on GitHub.

    +

    Registered Help Sections

    +

    The following help sections are currently registered:

    + \n"; + $totalSize = 0; + foreach($help_sections as $index => $section) + { + $sectionLength = strlen($section["content"]); + $totalSize += $sectionLength; + + $content .= "\t\t\t\n"; + } + $content .= "\t\t\t\n"; + $content .= "\t\t
    IndexTitleLength
    $index" . $section["title"] . "" . human_filesize($sectionLength) . "
    Total:" . human_filesize($totalSize) . "
    \n"; + $content .= "

    Registered Actions

    "; + $registeredActions = array_keys(get_object_vars($actions)); + sort($registeredActions); + $content .= "

    The following actions are currently registered:

    \n"; + $content .= "

    " . implode(", ", $registeredActions) . "

    "; + $content .= "

    Environment

    \n"; + $content .= "\n"; + + $content .= "

    Data

    \n"; + + $wikiSize = new stdClass(); + $wikiSize->all = 0; + $wikiSize->images = 0; + $wikiSize->audio = 0; + $wikiSize->videos = 0; + $wikiSize->pages = 0; + $wikiSize->history = 0; + $wikiSize->indexes = 0; + $wikiSize->other = 0; + $wikiFiles = glob_recursive($env->storage_prefix . "*"); + foreach($wikiFiles as $filename) + { + $extension = strtolower(substr($filename, strrpos($filename, ".") + 1)); + if($extension === "php") continue; // Skip php files + + $nextFilesize = filesize($filename); + $wikiSize->all += $nextFilesize; + if($extension[0] === "r") // It's a revision of a page + $wikiSize->history += $nextFilesize; + else if($extension == "md") // It's a page + $wikiSize->pages += $nextFilesize; + else if($extension == "json") // It's an index + $wikiSize->indexes += $nextFilesize; + else if(in_array($extension, [ // It's an uploaded image + "jpg", "jpeg", "png", "gif", "webp", "svg" + ])) + $wikiSize->images += $nextFilesize; + else if(in_array($extension, [ "mp3", "ogg", "wav", "aac", "m4a" ])) // It's an audio file + $wikiSize->audio += $nextFilesize; + else if(in_array($extension, [ "avi", "mp4", "m4v", "webm" ])) // It's a video file + $wikiSize->videos += $nextFilesize; + else + $wikiSize->other += $nextFilesize; + } + + $content .= "

    $settings->sitename is currently " . human_filesize($wikiSize->all) . " in size.

    \n"; + $content .= "
    +
    Indexes: " . human_filesize($wikiSize->indexes) . "
    +
    Pages: " . human_filesize($wikiSize->pages) . "
    +
    Page History: " . human_filesize($wikiSize->history) . "
    +
    Images: " . human_filesize($wikiSize->images) . "
    \n"; + if($wikiSize->audio > 0) + $content .= "
    Audio: " . human_filesize($wikiSize->audio) . "
    \n"; + if($wikiSize->videos > 0) + $content .= "
    Videos: " . human_filesize($wikiSize->videos) . "
    \n"; + if($wikiSize->other > 0) + $content .= "
    Other: " . human_filesize($wikiSize->other) . "
    \n"; + $content .= "
    "; + } + else + { + $title = "Help - $settings->sitename"; + + $content = "

    $settings->sitename Help

    +

    Welcome to $settings->sitename!

    +

    $settings->sitename is powered by Pepperminty Wiki, a complete wiki in a box you can drop into your server and expect it to just work.

    "; + + // Todo Insert a table of contents here? + + foreach($help_sections as $index => $section) + { + // Todo add a button that you can click to get a permanent link + // to this section. + $content .= "

    " . $section["title"] . "

    \n"; + $content .= $section["content"] . "\n"; + } + } + + exit(page_renderer::render_main($title, $content)); + }); + + // Register a help section on general navigation + add_help_section("5-navigation", "Navigating", "

    All the navigation links can be found on the top bar, along with a search box (if your site administrator has enabled it). There is also a "More..." menu in the top right that contains some additional links that you may fine useful.

    +

    This page, along with the credits page, can be found on the bar at the bottom of every page.

    "); + + add_help_section("1-extra", "Extra Information", "

    You can find out whch version of Pepperminty Wiki $settings->sitename is using by visiting the credits page.

    +

    Information for developers can be found on this page.

    "); + } +]); + +?> + diff --git a/docs/ModuleApi/files/modules/page-list.php.txt b/docs/ModuleApi/files/modules/page-list.php.txt new file mode 100644 index 0000000..17a85e6 --- /dev/null +++ b/docs/ModuleApi/files/modules/page-list.php.txt @@ -0,0 +1,154 @@ + "Page list", + "version" => "0.10.3", + "author" => "Starbeamrainbowlabs", + "description" => "Adds a page that lists all the pages in the index along with their metadata.", + "id" => "page-list", + "code" => function() { + global $settings; + + /** + * @api {get} ?action=list List all pages + * @apiDescription Gets a list of all the pages currently stored on the wiki. + * @apiName ListPages + * @apiGroup Page + * @apiPermission Anonymous + */ + + /* + * ██ ██ ███████ ████████ + * ██ ██ ██ ██ + * ██ ██ ███████ ██ + * ██ ██ ██ ██ + * ███████ ██ ███████ ██ + */ + add_action("list", function() { + global $pageindex, $settings; + + $title = "All Pages"; + $content = "

    $title on $settings->sitename

    "; + + $sorted_pageindex = get_object_vars($pageindex); + ksort($sorted_pageindex, SORT_NATURAL); + + $content .= generate_page_list(array_keys($sorted_pageindex)); + exit(page_renderer::render_main("$title - $settings->sitename", $content)); + }); + + /** + * @api {get} ?action=list-tags[&tag=] Get a list of tags or pages with a certain tag + * @apiDescription Gets a list of all tags on the wiki. Adding the `tag` parameter causes a list of pages with the given tag to be returned instead. + * @apiName ListTags + * @apiGroup Utility + * @apiPermission Anonymous + * + * @apiParam {string} tag Optional. If provided a list of all the pages with that tag is returned instead. + */ + + /* + * ██ ██ ███████ ████████ ████████ █████ ██████ ███████ + * ██ ██ ██ ██ ██ ██ ██ ██ ██ + * ██ ██ ███████ ██ █████ ██ ███████ ██ ███ ███████ + * ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ + * ███████ ██ ███████ ██ ██ ██ ██ ██████ ███████ + */ + add_action("list-tags", function() { + global $pageindex, $settings; + + if(!isset($_GET["tag"])) + { + // Render a list of all tags + $all_tags = []; + foreach($pageindex as $entry) + { + if(!isset($entry->tags)) continue; + foreach($entry->tags as $tag) + { + if(!in_array($tag, $all_tags)) $all_tags[] = $tag; + } + } + + sort($all_tags, SORT_NATURAL); + + $content = "

    All tags

    + \n"; + + exit(page_renderer::render("All tags - $settings->sitename", $content)); + } + $tag = $_GET["tag"]; + + + $sorted_pageindex = get_object_vars($pageindex); + ksort($sorted_pageindex, SORT_NATURAL); + + $pagelist = []; + foreach($pageindex as $pagename => $pagedetails) + { + if(empty($pagedetails->tags)) continue; + if(in_array($tag, $pagedetails->tags)) + $pagelist[] = $pagename; + } + + $content = "

    Tag List: $tag

    \n"; + $content .= generate_page_list($pagelist); + + $content .= "

    (All tags)

    \n"; + + exit(page_renderer::render("$tag - Tag List - $settings->sitename", $content)); + }); + + add_help_section("30-all-pages-tags", "Listing pages and tags", "

    All the pages and tags on $settings->sitename are listed on a pair of pages to aid navigation. The list of all pages on $settings->sitename can be found by clicking "All Pages" on the top bar. The list of all the tags currently in use can be found by clicking "All Tags" in the "More..." menu in the top right.

    +

    Each tag on either page can be clicked, and leads to a list of all pages that possess that particular tag.

    +

    Redirect pages are shown in italics. A page's last known editor is also shown next to each entry on a list of pages, along with the last known size (which should correct, unless it was changed outside of $settings->sitename) and the time since the last modification (hovering over this will show the exact time that the last modification was made in a tooltip).

    "); + } +]); + +/** + * Renders a list of pages as HTML. + * @package page-list + * @param string[] $pagelist A list of page names to include in the list. + * @return string The specified list of pages as HTML. + */ +function generate_page_list($pagelist) +{ + global $pageindex; + // ✎ ✎ 🕒 🕒 + $result = "\n"; + + return $result; +} + +?> + diff --git a/docs/ModuleApi/files/modules/page-login.php.txt b/docs/ModuleApi/files/modules/page-login.php.txt new file mode 100644 index 0000000..9a620dd --- /dev/null +++ b/docs/ModuleApi/files/modules/page-login.php.txt @@ -0,0 +1,160 @@ + "Login", + "version" => "0.8.5", + "author" => "Starbeamrainbowlabs", + "description" => "Adds a pair of actions (login and checklogin) that allow users to login. You need this one if you want your users to be able to login.", + "id" => "page-login", + "code" => function() { + global $settings; + + /** + * @api {get} ?action=login[&failed=yes][&returnto={someUrl}] Get the login page + * @apiName Login + * @apiGroup Authorisation + * @apiPermission Anonymous + * + * @apiParam {string} failed Setting to yes causes a login failure message to be displayed above the login form. + * @apiParam {string} returnto Set to the url to redirect to upon a successful login. + */ + + /* + * ██ ██████ ██████ ██ ███ ██ + * ██ ██ ██ ██ ██ ████ ██ + * ██ ██ ██ ██ ███ ██ ██ ██ ██ + * ██ ██ ██ ██ ██ ██ ██ ██ ██ + * ███████ ██████ ██████ ██ ██ ████ + */ + add_action("login", function() { + global $settings, $env; + + // Build the action url that will actually perform the login + $login_form_action_url = "index.php?action=checklogin"; + if(isset($_GET["returnto"])) + $login_form_action_url .= "&returnto=" . rawurlencode($_GET["returnto"]); + + if($env->is_logged_in && !empty($_GET["returnto"])) + { + http_response_code(307); + header("location: " . $_GET["returnto"]); + } + + $title = "Login to $settings->sitename"; + $content = "

    Login to $settings->sitename

    \n"; + if(isset($_GET["failed"])) + $content .= "\t\t

    Login failed.

    \n"; + if(isset($_GET["required"])) + $content .= "\t\t

    $settings->sitename requires that you login before continuing.

    \n"; + $content .= "\t\t
    + + +
    + + +
    + +
    \n"; + exit(page_renderer::render_main($title, $content)); + }); + + /** + * @api {post} ?action=checklogin Perform a login + * @apiName CheckLogin + * @apiGroup Authorisation + * @apiPermission Anonymous + * + * @apiParam {string} user The user name to login with. + * @apiParam {string} password The password to login with. + * @apiParam {string} returnto The URL to redirect to upon a successful login. + * + * @apiError InvalidCredentialsError The supplied credentials were invalid. Note that this error is actually a redirect to ?action=login&failed=yes (with the returnto parameter appended if you supplied one) + */ + + /* + * ██████ ██ ██ ███████ ██████ ██ ██ + * ██ ██ ██ ██ ██ ██ ██ + * ██ ███████ █████ ██ █████ + * ██ ██ ██ ██ ██ ██ ██ + * ██████ ██ ██ ███████ ██████ ██ ██ + * + * ██ ██████ ██████ ██ ███ ██ + * ██ ██ ██ ██ ██ ████ ██ + * ██ ██ ██ ██ ███ ██ ██ ██ ██ + * ██ ██ ██ ██ ██ ██ ██ ██ ██ + * ███████ ██████ ██████ ██ ██ ████ + */ + add_action("checklogin", function() { + global $settings, $env; + + //actually do the login + if(isset($_POST["user"]) and isset($_POST["pass"])) + { + //the user wants to log in + $user = $_POST["user"]; + $pass = $_POST["pass"]; + if($settings->users->$user->password == hash_password($pass)) + { + $env->is_logged_in = true; + $expiretime = time() + 60*60*24*30; //30 days from now + $_SESSION["$settings->sessionprefix-user"] = $user; + $_SESSION["$settings->sessionprefix-pass"] = hash_password($pass); + $_SESSION["$settings->sessionprefix-expiretime"] = $expiretime; + //redirect to wherever the user was going + http_response_code(302); + if(isset($_GET["returnto"])) + header("location: " . $_GET["returnto"]); + else + header("location: index.php"); + exit(); + } + else + { + http_response_code(302); + $nextUrl = "index.php?action=login&failed=yes"; + if(!empty($_GET["returnto"])) + $nextUrl .= "&returnto=" . rawurlencode($_GET["returnto"]); + header("location: $nextUrl"); + exit(); + } + } + else + { + http_response_code(302); + $nextUrl = "index.php?action=login&failed=yes&badrequest=yes"; + if(!empty($_GET["returnto"])) + $nextUrl .= "&returnto=" . rawurlencode($_GET["returnto"]); + header("location: $nextUrl"); + exit(); + } + }); + + // Register a section on logging in on the help page. + add_help_section("30-login", "Logging in", "

    In order to edit $settings->sitename and have your edit attributed to you, you need to be logged in. Depending on the settings, logging in may be a required step if you want to edit at all. Thankfully, loggging in is not hard. Simply click the "Login" link in the top left, type your username and password, and then click login.

    +

    If you do not have an account yet and would like one, try contacting $settings->admindetails_name, $settings->sitename's administrator and ask them nicely to see if they can create you an account.

    "); + } +]); + +/** + * Hashes the given password according to the current settings defined + * in $settings. + * @package page-login + * @param string $pass The password to hash. + * + * @return string The hashed password. Uses sha3 if $settings->use_sha3 is + * enabled, or sha256 otherwise. + */ +function hash_password($pass) +{ + global $settings; + if($settings->use_sha3) + { + return sha3($pass, 256); + } + else + { + return hash("sha256", $pass); + } +} + +?> + diff --git a/docs/ModuleApi/files/modules/page-logout.php.txt b/docs/ModuleApi/files/modules/page-logout.php.txt new file mode 100644 index 0000000..a64870b --- /dev/null +++ b/docs/ModuleApi/files/modules/page-logout.php.txt @@ -0,0 +1,41 @@ + "Logout", + "version" => "0.6", + "author" => "Starbeamrainbowlabs", + "description" => "Adds an action to let users user out. For security reasons it is wise to add this module since logging in automatically opens a session that is valid for 30 days.", + "id" => "page-logout", + "code" => function() { + + /** + * @api {post} ?action=logout Logout + * @apiDescription Logout. Make sure that your bot requests this URL when it is finished - this call not only clears your cookies but also clears the server's session file as well. Note that you can request this when you are already logged out and it will completely wipe your session on the server. + * @apiName Logout + * @apiGroup Authorisation + * @apiPermission Anonymous + */ + + /* + * ██ ██████ ██████ ██████ ██ ██ ████████ + * ██ ██ ██ ██ ██ ██ ██ ██ ██ + * ██ ██ ██ ██ ███ ██ ██ ██ ██ ██ + * ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ + * ███████ ██████ ██████ ██████ ██████ ██ + */ + add_action("logout", function() { + global $env; + $env->is_logged_in = false; + unset($env->user); + unset($env->pass); + //clear the session variables + $_SESSION = []; + session_destroy(); + + exit(page_renderer::render_main("Logout Successful", "

    Logout Successful

    +

    Logout Successful. You can login again here.

    ")); + }); + } +]); + +?> + diff --git a/docs/ModuleApi/files/modules/page-move.php.txt b/docs/ModuleApi/files/modules/page-move.php.txt new file mode 100644 index 0000000..8aca5ec --- /dev/null +++ b/docs/ModuleApi/files/modules/page-move.php.txt @@ -0,0 +1,132 @@ + "Page mover", + "version" => "0.9.2", + "author" => "Starbeamrainbowlabs", + "description" => "Adds an action to allow administrators to move pages.", + "id" => "page-move", + "code" => function() { + global $settings; + + /** + * @api {get} ?action=move[&new_name={newPageName}] Move a page + * @apiName Move + * @apiGroup Page + * @apiPermission Moderator + * + * @apiParam {string} new_name The new name to move the page to. If not set a page will be returned containing a move page form. + * + * @apiUse UserNotModeratorError + * @apiError EditingDisabledError Editing is disabled on this wiki, so pages can't be moved. + * @apiError PageExistsAtDestinationError A page already exists with the specified new name. + * @apiError NonExistentPageError The page you're trying to move doesn't exist in the first place. + * @apiError PreExistingFileError A pre-existing file on the server's file system was detected. + */ + + /* + * ███ ███ ██████ ██ ██ ███████ + * ████ ████ ██ ██ ██ ██ ██ + * ██ ████ ██ ██ ██ ██ ██ █████ + * ██ ██ ██ ██ ██ ██ ██ ██ + * ██ ██ ██████ ████ ███████ + */ + add_action("move", function() { + global $pageindex, $settings, $env, $paths; + if(!$settings->editing) + { + exit(page_renderer::render_main("Moving $env->page - error", "

    You tried to move $env->page, but editing is disabled on this wiki.

    +

    If you wish to move this page, please re-enable editing on this wiki first.

    +

    Go back to $env->page.

    +

    Nothing has been changed.

    ")); + } + if(!$env->is_admin) + { + exit(page_renderer::render_main("Moving $env->page - Error", "

    You tried to move $env->page, but you do not have permission to do that.

    +

    You should try logging in as an admin.

    ")); + } + + if(!isset($_GET["new_name"]) or strlen($_GET["new_name"]) == 0) + exit(page_renderer::render_main("Moving $env->page", "

    Moving $env->page

    +
    + + + +
    + + +
    + +
    ")); + + $new_name = makepathsafe($_GET["new_name"]); + + $page = $env->page; + if(!isset($pageindex->$page)) + exit(page_renderer::render_main("Moving $env->page - Error", "

    You tried to move $env->page to $new_name, but the page with the name $env->page does not exist in the first place.

    +

    Nothing has been changed.

    ")); + + if($env->page == $new_name) + exit(page_renderer::render_main("Moving $env->page - Error", "

    You tried to move $page, but the new name you gave is the same as it's current name.

    +

    It is possible that you tried to use some characters in the new name that are not allowed and were removed.

    +

    Page names may not contain any of these characters: ?%*:|\"><()[]

    ")); + + if(isset($pageindex->$page->uploadedfile) and + file_exists($new_name)) + exit(page_renderer::render_main("Moving $env->page - Error - $settings->sitename", "

    Whilst moving the file associated with $env->page, $settings->sitename detected a pre-existing file on the server's file system. Because $settings->sitename can't determine whether the existing file is important to another component of $settings->sitename or it's host web server, the move have been aborted - just in case.

    +

    If you know that this move is actually safe, please get your site administrator (" . $settings->admindetails_name . ") to perform the move manually. Their contact address can be found at the bottom of every page (including this one).

    ")); + + // Move the page in the page index + $pageindex->$new_name = new stdClass(); + foreach($pageindex->$page as $key => $value) + { + $pageindex->$new_name->$key = $value; + } + unset($pageindex->$page); + $pageindex->$new_name->filename = "$new_name.md"; + + // If this page has an associated file, then we should move that too + if(!empty($pageindex->$new_name->uploadedfile)) + { + // Update the filepath to point to the description and not the image + $pageindex->$new_name->filename = $pageindex->$new_name->filename . ".md"; + // Move the file in the pageindex + $pageindex->$new_name->uploadedfilepath = $new_name; + // Move the file on disk + rename($env->storage_prefix . $env->page, $env->storage_prefix . $new_name); + } + + // Come to think about it, we should probably move the history while we're at it + foreach($pageindex->$new_name->history as &$revisionData) + { + // We're only interested in edits + if($revisionData->type !== "edit") continue; + $newRevisionName = $pageindex->$new_name->filename . ".r$revisionData->rid"; + // Move the revision to it's new name + rename( + $env->storage_prefix . $revisionData->filename, + $env->storage_prefix . $newRevisionName + ); + // Update the pageindex entry + $revisionData->filename = $newRevisionName; + } + + // Save the updated pageindex + file_put_contents($paths->pageindex, json_encode($pageindex, JSON_PRETTY_PRINT)); + + // Move the page on the disk + rename("$env->storage_prefix$env->page.md", "$env->storage_prefix$new_name.md"); + + // Move the page in the id index + ids::movepagename($page, $new_name); + + // Exit with a nice message + exit(page_renderer::render_main("Moving $env->page", "

    $env->page has been moved to $new_name successfully.

    ")); + }); + + // Register a help section + add_help_section("60-move", "Moving Pages", "

    If you are logged in as an administrator, then you have the power to move pages. To do this, click "Delete" in the "More..." menu when browsing the pge you wish to move. Type in the new name of the page, and then click "Move Page".

    "); + } +]); + +?> + diff --git a/docs/ModuleApi/files/modules/page-update.php.txt b/docs/ModuleApi/files/modules/page-update.php.txt new file mode 100644 index 0000000..56ea649 --- /dev/null +++ b/docs/ModuleApi/files/modules/page-update.php.txt @@ -0,0 +1,89 @@ + "Update", + "version" => "0.6.2", + "author" => "Starbeamrainbowlabs", + "description" => "Adds an update page that downloads the latest stable version of Pepperminty Wiki. This module is currently outdated as it doesn't save your module preferences.", + "id" => "page-update", + "code" => function() { + + /** + * @api {get} ?action=update[do=yes] Update the wiki + * @apiDescription Update the wiki by downloading a new version of Pepperminty Wiki from the URL specified in the settings. Note that unless you change the url from it's default, all custom modules installed will be removed. **Note also that this plugin is currently out of date. Use with extreme caution!** + * @apiName Update + * @apiGroup Utility + * @apiPermission Moderator + * + * @apiParam {string} do Set to 'yes' to actually do the upgrade. Omission causes a page asking whether an update is desired instead. + * @apiParam {string} secret The wiki's secret string that's stored in the settings. + * + * @apiUse UserNotModeratorError + * @apiParam InvalidSecretError The supplied secret doesn't match up with the secret stored in the wiki's settings. + */ + + /* + * ██ ██ ██████ ██████ █████ ████████ ███████ + * ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ + * ██ ██ ██████ ██ ██ ███████ ██ █████ + * ██ ██ ██ ██ ██ ██ ██ ██ ██ + * ██████ ██ ██████ ██ ██ ██ ███████ + */ + add_action("update", function() { + global $settings, $env; + + if(!$env->is_admin) + { + http_response_code(401); + exit(page_renderer::render_main("Update - Error", "

    You must be an administrator to do that.

    ")); + } + + if(!isset($_GET["do"]) or $_GET["do"] !== "true" or $_GET["do"] !== "yes") + { + exit(page_renderer::render_main("Update $settings->sitename", "

    This page allows you to update $settings->sitename.

    +

    Currently, $settings->sitename is using $settings->version of Pepperminty Wiki.

    +

    This script will automatically download and install the latest version of Pepperminty Wiki from the url of your choice (see settings), regardless of whether an update is actually needed (version checking isn't implemented yet).

    +

    To update $settings->sitename, fill out the form below and click click the update button.

    +

    Note that a backup system has not been implemented yet! If this script fails you will loose your wiki's code and have to re-build it.

    +
    + + + + + +
    ")); + } + + if(!isset($_GET["secret"]) or $_GET["secret"] !== $settings->sitesecret) + { + exit(page_renderer::render_main("Update $settings->sitename - Error", "

    You forgot to enter $settings->sitename's secret code or entered it incorrectly. $settings->sitename's secret can be found in the settings portion of index.php.

    ")); + } + + $settings_separator = "/////////////// Do not edit below this line unless you know what you are doing! ///////////////"; + + $log = "Beginning update...\n"; + + $log .= "I am " . __FILE__ . ".\n"; + $oldcode = file_get_contents(__FILE__); + $log .= "Fetching new code..."; + $newcode = file_get_contents($settings->updateurl); + $log .= "done.\n"; + + $log .= "Rewriting " . __FILE__ . "..."; + $settings = substr($oldcode, 0, strpos($oldcode, $settings_separator)); + $code = substr($newcode, strpos($newcode, $settings_separator)); + $result = $settings . $code; + $log .= "done.\n"; + + $log .= "Saving..."; + file_put_contents(__FILE__, $result); + $log .= "done.\n"; + + $log .= "Update complete. I am now running on the latest version of Pepperminty Wiki."; + $log .= "The version number that I have updated to can be found on the credits or help ages."; + + exit(page_renderer::render_main("Update - Success", "")); + }); + } +]); +?> + diff --git a/docs/ModuleApi/files/modules/page-user-list.php.txt b/docs/ModuleApi/files/modules/page-user-list.php.txt new file mode 100644 index 0000000..eea1f6c --- /dev/null +++ b/docs/ModuleApi/files/modules/page-user-list.php.txt @@ -0,0 +1,49 @@ + "User list", + "version" => "0.1", + "author" => "Starbeamrainbowlabs", + "description" => "Adds a 'user-list' action that generates a list of users. Supports json output with 'format=json' in the queyr string.", + "id" => "page-user-list", + "code" => function() { + global $settings; + /** + * @api {get} ?action=user-list[format=json] List all users + * @apiName UserList + * @apiGroup Utility + * @apiPermission Anonymous + */ + + /* + * ██ ██ ███████ ███████ ██████ ██ ██ ███████ ████████ + * ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ + * ██ ██ ███████ █████ ██████ █████ ██ ██ ███████ ██ + * ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ + * ██████ ███████ ███████ ██ ██ ███████ ██ ███████ ██ + */ + add_action("user-list", function() { + global $env, $settings; + + $userList = array_keys(get_object_vars($settings->users)); + if(!empty($_GET["format"]) && $_GET["format"] === "json") + { + header("content-type: application/json"); + exit(json_encode($userList)); + } + + $content = "

    User List

    \n"; + $content .= "\n"; + + exit(page_renderer::render_main("User List - $settings->sitename", $content)); + }); + + add_help_section("800-raw-page-content", "Viewing Raw Page Content", "

    Although you can use the edit page to view a page's source, you can also ask $settings->sitename to send you the raw page source and nothing else. This feature is intented for those who want to automate their interaction with $settings->sitename.

    +

    To use this feature, navigate to the page for which you want to see the source, and then alter the action parameter in the url's query string to be raw. If the action parameter doesn't exist, add it. Note that when used on an file's page this action will return the source of the description and not the file itself.

    "); + } +]); + +?> + diff --git a/docs/ModuleApi/files/modules/page-view.php.txt b/docs/ModuleApi/files/modules/page-view.php.txt new file mode 100644 index 0000000..a086e15 --- /dev/null +++ b/docs/ModuleApi/files/modules/page-view.php.txt @@ -0,0 +1,181 @@ + "Page viewer", + "version" => "0.16.7", + "author" => "Starbeamrainbowlabs", + "description" => "Allows you to view pages. You really should include this one.", + "id" => "page-view", + "code" => function() { + /** + * @api {get} ?action=view[&page={pageName}][&revision=rid][&printable=yes] View a page + * @apiName View + * @apiGroup Page + * @apiPermission Anonymous + * + * @apiUse PageParameter + * @apiParam {number} revision The revision number to display. + * @apiParam {string} mode Optional. The display mode to use. Can hld the following values: 'normal' - The default. Sends a normal page. 'printable' - Sends a printable version of the page. 'contentonly' - Sends only the content of the page, not the extra stuff around it. 'parsedsourceonly' - Sends only the raw rendered source of the page, as it appears just after it has come out of the page parser. Useful for writing external tools (see also the `raw` action). + * + * @apiError NonExistentPageError The page doesn't exist and editing is disabled in the wiki's settings. If editing isn't disabled, you will be redirected to the edit page instead. + * @apiError NonExistentRevisionError The specified revision was not found. + */ + + /* + * ██ ██ ██ ███████ ██ ██ + * ██ ██ ██ ██ ██ ██ + * ██ ██ ██ █████ ██ █ ██ + * ██ ██ ██ ██ ██ ███ ██ + * ████ ██ ███████ ███ ███ + */ + add_action("view", function() { + global $pageindex, $settings, $env; + + // Check to make sure that the page exists + $page = $env->page; + if(!isset($pageindex->$page)) + { + // todo make this intelligent so we only redirect if the user is actually able to create the page + if($settings->editing) + { + // Editing is enabled, redirect to the editing page + http_response_code(307); // Temporary redirect + header("location: index.php?action=edit&newpage=yes&page=" . rawurlencode($env->page)); + exit(); + } + else + { + // Editing is disabled, show an error message + http_response_code(404); + exit(page_renderer::render_main("404: Page not found - $env->page - $settings->sitename", "

    $env->page does not exist.

    Since editing is currently disabled on this wiki, you may not create this page. If you feel that this page should exist, try contacting this wiki's Administrator.

    ")); + } + } + + header("last-modified: " . gmdate('D, d M Y H:i:s T', $pageindex->{$env->page}->lastmodified)); + + // Perform a redirect if the requested page is a redirect page + if(isset($pageindex->$page->redirect) && + $pageindex->$page->redirect === true) + { + $send_redirect = true; + if(isset($_GET["redirect"]) && $_GET["redirect"] == "no") + $send_redirect = false; + + if($send_redirect) + { + // Todo send an explanatory page along with the redirect + http_response_code(307); + $redirectUrl = "?action=$env->action&redirected_from=" . rawurlencode($env->page); + + $hashCode = ""; + $newPage = $pageindex->$page->redirect_target; + if(strpos($newPage, "#") !== false) + { + // Extract the part after the hash symbol + $hashCode = substr($newPage, strpos($newPage, "#") + 1); + // Remove the hash from the new page name + $newPage = substr($newPage, 0, strpos($newPage, "#")); + } + $redirectUrl .= "&page=" . rawurlencode($newPage); + if(!empty($pageindex->$newPage->redirect)) + $redirectUrl .= "&redirect=no"; + if(strlen($hashCode) > 0) + $redirectUrl .= "#$hashCode"; + + header("location: $redirectUrl"); + exit(); + } + } + + $title = "$env->page - $settings->sitename"; + if(isset($pageindex->$page->protect) && $pageindex->$page->protect === true) + $title = $settings->protectedpagechar . $title; + $content = ""; + if(!$env->is_history_revision) + $content .= "

    $env->page

    \n"; + else + { + $content .= "

    Revision #{$env->history->revision_number} of $env->page

    \n"; + $content .= "

    (Revision saved by {$env->history->revision_data->editor} " . render_timestamp($env->history->revision_data->timestamp) . ". Jump to the current revision or see a list of all revisions for this page.)

    \n"; + } + + // Add a visit parent page link if we're a subpage + if(get_page_parent($env->page) !== false) { + $content .= "\n"; + } + + // Add an extra message if the requester was redirected from another page + if(isset($_GET["redirected_from"])) + $content .= "

    Redirected from " . $_GET["redirected_from"] . ".

    \n"; + + $parsing_start = microtime(true); + + $rawRenderedSource = parse_page_source(file_get_contents($env->page_filename)); + $content .= $rawRenderedSource; + + if(!empty($pageindex->$page->tags)) + { + $content .= "\n"; + } + /*else + { + $content .= "\n"; + }*/ + + if($settings->show_subpages) + { + $subpages = get_object_vars(get_subpages($pageindex, $env->page)); + + if(count($subpages) > 0) + { + $content .= "
    "; + $content .= "Subpages: "; + foreach($subpages as $subpage => $times_removed) + { + if($times_removed <= $settings->subpages_display_depth) + { + $content .= "$subpage, "; + } + } + // Remove the last comma from the content + $content = substr($content, 0, -2); + } + } + + $content .= "\n\t\t\n"; + + // Prevent indexing of this page if it's still within the noindex + // time period + if(isset($settings->delayed_indexing_time) and + time() - $pageindex->{$env->page}->lastmodified < $settings->delayed_indexing_time) + header("x-robots-tag: noindex"); + + $settings->footer_message = "$env->page was last edited by {$pageindex->{$env->page}->lasteditor} at " . date('h:ia T \o\n j F Y', $pageindex->{$env->page}->lastmodified) . ".

    \n

    " . $settings->footer_message; // Add the last edited time to the footer + + $mode = isset($_GET["mode"]) ? strtolower(trim($_GET["mode"])) : "normal"; + switch($mode) + { + case "contentonly": + // Content only mode: Send only the content of the page + exit($content); + case "parsedsourceonly": + // Parsed source only mode: Send only the raw rendered source + exit($rawRenderedSource); + case "printable": + // Printable mode: Sends a printable version of the page + exit(page_renderer::render_minimal($title, $content)); + case "normal": + default: + // Normal mode: Send a normal page + exit(page_renderer::render_main($title, $content)); + } + }); + } +]); + +?> + diff --git a/docs/ModuleApi/files/modules/parser-default-old.php.txt b/docs/ModuleApi/files/modules/parser-default-old.php.txt new file mode 100644 index 0000000..f4ae0fd --- /dev/null +++ b/docs/ModuleApi/files/modules/parser-default-old.php.txt @@ -0,0 +1,166 @@ + "Old Default Parser", + "version" => "0.10", + "author" => "Johnny Broadway & Starbeamrainbowlabs", + "description" => "The *old* default parser for Pepperminty Wiki. Based on Johnny Broadway's Slimdown (with more than a few modifications). This parser's features are documented in the help page. Superceded by a customised extension of parsedown extra.", + "id" => "parser-default-old", + "optional" => true, + "code" => function() { + global $settings; + + add_parser("default", function($markdown) { + return Slimdown::render($markdown); + }); + + // Register the help section + if($settings->parser != "default") + return; // Don't register the help section if we aren't the currently set parser. + add_help_section("20-parser-default", "Editor Syntax", "

    $settings->sitename's editor uses a modified version of slimdown, a flavour of markdown that is implementated using regular expressions. See the credits page for more information and links to the original source for this. A quick reference can be found below:

    + + + + + + + + + + + + + + + + + +
    Type ThisTo get this
    _italics_italics
    *bold*bold
    ~~Strikethrough~~Strikethough
    `code`code
    # Heading

    Heading

    ## Sub Heading

    Sub Heading

    [[Internal Link]]Internal Link
    [[Display Text|Internal Link]]Display Text
    [Display text](//google.com/)Display Text
    > Blockquote
    > Some text
    Blockquote
    Some text
    - Apples
    * Oranges
    • Apples
    • Oranges
    1. This is
    2. an ordered list
    1. This is
    2. an ordered list
    + --- +
    ![Alt text](//starbeamrainbowlabs.com/favicon-small.png)Alt text
    + +

    In addition, the following extra syntax is supported for images:

    + +
    Size the image to at most 250 pixels wide:
    +	![Alt text](//starbeamrainbowlabs.com/favicon-small.png 250px)
    +	
    +	Size the image to at most 120px wide and have it float at the right ahnd size of the page:
    +	![Alt text](//starbeamrainbowlabs.com/favicon-small.png 120px right)
    "); + } +]); + +/*********************************************************************** + * ███████ ██ ██ ███ ███ ██████ ██████ ██ ██ ███ ██ * + * ██ ██ ██ ████ ████ ██ ██ ██ ██ ██ ██ ████ ██ * + * ███████ ██ ██ ██ ████ ██ ██ ██ ██ ██ ██ █ ██ ██ ██ ██ * + * ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ███ ██ ██ ██ ██ * + * ███████ ███████ ██ ██ ██ ██████ ██████ ███ ███ ██ ████ * + ***********************************************************************/ +/** + * Slimdown - A very basic regex-based Markdown parser. Supports the + * following elements (and can be extended via Slimdown::add_rule()): + * + * - Headers + * - Links + * - Bold + * - Emphasis + * - Deletions + * - Quotes + * - Inline code + * - Blockquotes + * - Ordered/unordered lists + * - Horizontal rules + * + * Author: Johnny Broadway + * Website: https://gist.github.com/jbroadway/2836900 + * License: MIT + */ + +/** + * Modified by Starbeamrainbowlabs (starbeamrainbowlabs) + * + * Changed bold to use single asterisks + * Changed italics to use single underscores + * Added one to add the heading levels (no

    tags allowed) + * Added wiki style internal link parsing + * Added wiki style internal link parsing with display text + * Added image support + */ +class Slimdown { + public static $rules = array ( + '/\r\n/' => "\n", // new line normalisation + '/^(#+)(.*)/' => 'self::header', // headers + '/(\*+)(.*?)\1/' => '\2', // bold + '/(_)(.*?)\1/' => '\2', // emphasis + + '/!\[(.*)\]\(([^\s]+)\s(\d+.+)\s(left|right)\)/' => '\1', // images with size + '/!\[(.*)\]\(([^\s]+)\s(\d+.+)\)/' => '\1', // images with size + '/!\[(.*)\]\((.*)\)/' => '\1', // basic images + + '/\[\[([a-zA-Z0-9\_\- ]+)\|([a-zA-Z0-9\_\- ]+)\]\]/' => '\2', //internal links with display text + '/\[\[([a-zA-Z0-9\_\- ]+)\]\]/' => '\1', //internal links + '/\[([^\[]+)\]\(([^\)]+)\)/' => '\1', // links + '/\~\~(.*?)\~\~/' => '\1', // del + '/\:\"(.*?)\"\:/' => '\1', // quote + '/`(.*?)`/' => '\1', // inline code + '/\n\s*(\*|-)(.*)/' => 'self::ul_list', // ul lists + '/\n[0-9]+\.(.*)/' => 'self::ol_list', // ol lists + '/\n(>|\>)(.*)/' => 'self::blockquote', // blockquotes + '/\n-{3,}/' => "\n
    ", // horizontal rule + '/\n([^\n]+)\n\n/' => 'self::para', // add paragraphs + '/<\/ul>\s?
      /' => '', // fix extra ul + '/<\/ol>\s?
        /' => '', // fix extra ol + '/<\/blockquote>
        /' => "\n" // fix extra blockquote + ); + private static function para ($regs) { + $line = $regs[1]; + $trimmed = trim ($line); + if (preg_match ('/^<\/?(ul|ol|li|h|p|bl)/', $trimmed)) { + return "\n" . $line . "\n"; + } + return sprintf ("\n

        %s

        \n", $trimmed); + } + private static function ul_list ($regs) { + $item = $regs[2]; + return sprintf ("\n
          \n\t
        • %s
        • \n
        ", trim($item)); + } + private static function ol_list ($regs) { + $item = $regs[1]; + return sprintf ("\n
          \n\t
        1. %s
        2. \n
        ", trim($item)); + } + private static function blockquote ($regs) { + $item = $regs[2]; + return sprintf ("\n
        %s
        ", trim($item)); + } + private static function header ($regs) { + list ($tmp, $chars, $header) = $regs; + $level = strlen ($chars); + return sprintf ('%s', $level + 1, trim($header), $level + 1); + } + + /** + * Add a rule. + */ + public static function add_rule ($regex, $replacement) { + self::$rules[$regex] = $replacement; + } + /** + * Render some Markdown into HTML. + */ + public static function render ($text) { + foreach (self::$rules as $regex => $replacement) { + if (is_callable ( $replacement)) { + $text = preg_replace_callback ($regex, $replacement, $text); + } else { + $text = preg_replace ($regex, $replacement, $text); + } + } + return trim ($text); + } +} +//////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////// + +?> + diff --git a/docs/ModuleApi/files/modules/parser-parsedown.php.txt b/docs/ModuleApi/files/modules/parser-parsedown.php.txt new file mode 100644 index 0000000..15f3a1d --- /dev/null +++ b/docs/ModuleApi/files/modules/parser-parsedown.php.txt @@ -0,0 +1,669 @@ + "Parsedown", + "version" => "0.9.9", + "author" => "Emanuil Rusev & Starbeamrainbowlabs", + "description" => "An upgraded (now default!) parser based on Emanuil Rusev's Parsedown Extra PHP library (https://github.com/erusev/parsedown-extra), which is licensed MIT. Please be careful, as this module adds some weight to your installation, and also *requires* write access to the disk on first load.", + "id" => "parser-parsedown", + "code" => function() { + global $settings; + + $parser = new PeppermintParsedown(); + $parser->setInternalLinkBase("?page=%s"); + add_parser("parsedown", function($source) use ($parser) { + global $settings; + if($settings->clean_raw_html) + $parser->setMarkupEscaped(true); + else + $parser->setMarkupEscaped(false); + $result = $parser->text($source); + + return $result; + }); + + // Wanted pages + statistic_add([ + "id" => "wanted-pages", + "name" => "Wanted Pages", + "update" => function($old_stats) { + global $pageindex, $env; + + $result = new stdClass(); // completed, value, state + $pages = []; + foreach($pageindex as $pagename => $pagedata) { + if(!file_exists($env->storage_prefix . $pagedata->filename)) { + continue; + } + $page_content = file_get_contents($env->storage_prefix . $pagedata->filename); + preg_match_all("/\[\[([^\]]+)\]\]/", $page_content, $linked_pages); + if(count($linked_pages[1]) === 0) + continue; // No linked pages here + foreach($linked_pages[1] as $linked_page) { + // Strip everything after the | and the # + if(strpos($linked_page, "|") !== false) + $linked_page = substr($linked_page, 0, strpos($linked_page, "|")); + if(strpos($linked_page, "#") !== false) + $linked_page = substr($linked_page, 0, strpos($linked_page, "#")); + if(strlen($linked_page) === 0) + continue; + // Make sure we try really hard to find this page in the + // pageindex + if(!empty($pageindex->{ucfirst($linked_page)})) + $linked_page = ucfirst($linked_page); + else if(!empty($pageindex->{ucwords($linked_page)})) + $linked_page = ucwords($linked_page); + + // We're only interested in pages that don't exist + if(!empty($pageindex->$linked_page)) continue; + + if(empty($pages[$linked_page])) + $pages[$linked_page] = 0; + $pages[$linked_page]++; + } + } + + arsort($pages); + + $result->value = $pages; + $result->completed = true; + return $result; + }, + "render" => function($stats_data) { + $result = "

        $stats_data->name

        \n"; + $result .= "\n"; + $result .= "\t\n"; + foreach($stats_data->value as $pagename => $linking_pages) { + $result .= "\t\n"; + } + $result .= "
        Page NameLinking Pages
        $pagename$linking_pages
        \n"; + return $result; + } + ]); + + add_help_section("20-parser-default", "Editor Syntax", + "

        $settings->sitename's editor uses an extended version of Parsedown to render pages, which is a fantastic open source Github flavoured markdown parser. You can find a quick reference guide on Github flavoured markdown here by adam-p, or if you prefer a book Mastering Markdown by KB is a good read, and free too!

        +

        Tips

        +
          +
        • Put 2 spaces at the end of a line to add a soft line break. Leave a blank line to add a head line break (i.e. a new paragraph).
        • +
        • You can add an id to a header that you can link to. Put it in curly braces after the heading name like this: # Heading Name {#HeadingId}. Then you can link to like like this: [[Page name#HeadingId}]]. You can also link to a heading id on the current page by omitting the page name: [[#HeadingId]].
        • +
        +

        Extra Syntax

        +

        $settings->sitename's editor also supports some extra custom syntax, some of which is inspired by Mediawiki. + + + + + + + +
        Type thisTo get thisComments
        [[Internal link]]Internal LinkAn internal link.
        [[Display Text|Internal link]]Display TextAn internal link with some display text.
        ![Alt text](http://example.com/path/to/image.png | 256x256 | right)Alt textAn image floating to the right of the page that fits inside a 256px x 256px box, preserving aspect ratio.
        ![Alt text](http://example.com/path/to/image.png | 256x256 | caption)
        Alt text
        Alt text
        An image with a caption that fits inside a 256px x 256px box, preserving aspect ratio. The caption is taken from the alt text.
        ![Alt text](Files/Cheese.png)Alt textAn example of the short url syntax for images. Simply enter the page name of an image (or video / audio file), and Pepperminty Wiki will sort out the url for you.
        +

        Note that the all image image syntax above can be mixed and matched to your liking. The caption option in particular must come last or next to last.

        +

        Templating

        +

        $settings->sitename also supports including one page in another page as a template. The syntax is very similar to that of Mediawiki. For example, {{Announcement banner}} will include the contents of the \"Announcement banner\" page, assuming it exists.

        +

        You can also use variables. Again, the syntax here is very similar to that of Mediawiki - they can be referenced in the included page by surrrounding the variable name in triple curly braces (e.g. {{{Announcement text}}}), and set when including a page with the bar syntax (e.g. {{Announcement banner | importance = high | text = Maintenance has been planned for tonight.}}). Currently the only restriction in templates and variables is that you may not include a closing curly brace (}) in the page name, variable name, or value.

        +
        Special Variables
        +

        $settings->sitename also supports a number of special built-in variables. Their syntax and function are described below:

        + + + + + + + +
        Type thisTo get this
        {{{@}}}Lists all variables and their values in a table.
        {{{#}}}Shows a 'stack trace', outlining all the parent includes of the current page being parsed.
        {{{~}}}Outputs the requested page's name.
        {{{*}}}Outputs a comma separated list of all the subpages of the current page.
        {{{+}}}Shows a gallery containing all the files that are sub pages of the current page.
        "); + } +]); + +/*** Parsedown versions *** + * Parsedown Core: 1.6.0 * + * Parsedown Extra: 0.7.0 * + **************************/ +$env->parsedown_paths = new stdClass(); +$env->parsedown_paths->parsedown = "https://cdn.rawgit.com/erusev/parsedown/3ebbd730b5c2cf5ce78bc1bf64071407fc6674b7/Parsedown.php"; +$env->parsedown_paths->parsedown_extra = "https://cdn.rawgit.com/erusev/parsedown-extra/11a44e076d02ffcc4021713398a60cd73f78b6f5/ParsedownExtra.php"; + +// Download parsedown and parsedown extra if they don't already exist +if(!file_exists("./Parsedown.php") || filesize("./Parsedown.php") === 0) + file_put_contents("./Parsedown.php", fopen($env->parsedown_paths->parsedown, "r")); +if(!file_exists("./ParsedownExtra.php") || filesize("./ParsedownExtra.php") === 0) + file_put_contents("./ParsedownExtra.php", fopen($env->parsedown_paths->parsedown_extra, "r")); + +require_once("./Parsedown.php"); +require_once("./ParsedownExtra.php"); + +/* + * ██████ █████ ██████ ███████ ███████ ██████ ██████ ██ ██ ███ ██ + * ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ████ ██ + * ██████ ███████ ██████ ███████ █████ ██ ██ ██ ██ ██ █ ██ ██ ██ ██ + * ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ███ ██ ██ ██ ██ + * ██ ██ ██ ██ ██ ███████ ███████ ██████ ██████ ███ ███ ██ ████ + * + * ███████ ██ ██ ████████ ███████ ███ ██ ███████ ██ ██████ ███ ██ ███████ + * ██ ██ ██ ██ ██ ████ ██ ██ ██ ██ ██ ████ ██ ██ + * █████ ███ ██ █████ ██ ██ ██ ███████ ██ ██ ██ ██ ██ ██ ███████ + * ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ + * ███████ ██ ██ ██ ███████ ██ ████ ███████ ██ ██████ ██ ████ ███████ +*/ +class PeppermintParsedown extends ParsedownExtra +{ + private $internalLinkBase = "./%s"; + + protected $maxParamDepth = 0; + protected $paramStack = []; + + function __construct() + { + // Prioritise our internal link parsing over the regular link parsing + array_unshift($this->InlineTypes["["], "InternalLink"); + // Prioritise our image parser over the regular image parser + array_unshift($this->InlineTypes["!"], "ExtendedImage"); + + $this->inlineMarkerList .= "{"; + if(!isset($this->InlineTypes["{"]) or !is_array($this->InlineTypes["{"])) + $this->InlineTypes["{"] = []; + $this->InlineTypes["{"][] = "Template"; + } + + /* + * ████████ ███████ ███ ███ ██████ ██ █████ ████████ ██ ███ ██ ██████ + * ██ ██ ████ ████ ██ ██ ██ ██ ██ ██ ██ ████ ██ ██ + * ██ █████ ██ ████ ██ ██████ ██ ███████ ██ ██ ██ ██ ██ ██ ███ + * ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ + * ██ ███████ ██ ██ ██ ███████ ██ ██ ██ ██ ██ ████ ██████ + */ + protected function inlineTemplate($fragment) + { + global $env, $pageindex; + + // Variable parsing + if(preg_match("/\{\{\{([^}]+)\}\}\}/", $fragment["text"], $matches)) + { + $params = []; + if(!empty($this->paramStack)) + { + $stackEntry = array_slice($this->paramStack, -1)[0]; + $params = !empty($stackEntry) ? $stackEntry["params"] : false; + } + + $variableKey = trim($matches[1]); + + $variableValue = false; + switch ($variableKey) + { + case "@": // Lists all variables and their values + if(!empty($params)) + { + $variableValue = " + \n"; + foreach($params as $key => $value) + { + $variableValue .= "\t\n"; + } + $variableValue .= "
        KeyValue
        " . $this->escapeText($key) . "" . $this->escapeText($value) . "
        "; + } + break; + case "#": // Shows a stack trace + $variableValue = "
          \n"; + $variableValue .= "\t
        1. $env->page
        2. \n"; + foreach($this->paramStack as $curStackEntry) + { + $variableValue .= "\t
        3. " . $curStackEntry["pagename"] . "
        4. \n"; + } + $variableValue .= "
        \n"; + break; + case "~": // Show requested page's name + if(!empty($this->paramStack)) + $variableValue = $this->escapeText($env->page); + break; + case "*": // Lists subpages + $subpages = get_subpages($pageindex, $env->page); + $variableValue = []; + foreach($subpages as $pagename => $depth) + { + $variableValue[] = $pagename; + } + $variableValue = implode(", ", $variableValue); + if(strlen($variableValue) === 0) + $variableValue = "(none yet!)"; + break; + case "+": // Shows a file gallery for subpages with files + // If the upload module isn't present, then there's no point + // in checking for uploaded files + if(!module_exists("feature-upload")) + break; + + $variableValue = []; + $subpages = get_subpages($pageindex, $env->page); + foreach($subpages as $pagename => $depth) + { + // Make sure that this is an uploaded file + if(!$pageindex->$pagename->uploadedfile) + continue; + + $mime_type = $pageindex->$pagename->uploadedfilemime; + + $previewSize = 300; + $previewUrl = "?action=preview&size=$previewSize&page=" . rawurlencode($pagename); + + $previewHtml = ""; + switch(substr($mime_type, 0, strpos($mime_type, "/"))) + { + case "video": + $previewHtml .= "\n"; + break; + case "audio": + $previewHtml .= "\n"; + break; + case "application": + case "image": + default: + $previewHtml .= "\n"; + break; + } + $previewHtml = "$previewHtml$pagename"; + + $variableValue[$pagename] = "
      1. $previewHtml
      2. "; + } + + if(count($variableValue) === 0) + $variableValue["default"] = "
      3. (No files found)
      4. \n"; + $variableValue = implode("\n", $variableValue); + $variableValue = ""; + break; + } + if(isset($params[$variableKey])) + { + $variableValue = $params[$variableKey]; + $variableValue = $this->escapeText($variableValue); + } + + if($variableValue !== false) + { + return [ + "extent" => strlen($matches[0]), + "markup" => $variableValue + ]; + } + } + else if(preg_match("/\{\{([^}]+)\}\}/", $fragment["text"], $matches)) + { + $templateElement = $this->templateHandler($matches[1]); + + if(!empty($templateElement)) + { + return [ + "extent" => strlen($matches[0]), + "element" => $templateElement + ]; + } + } + } + + protected function templateHandler($source) + { + global $pageindex, $env; + + + $parts = preg_split("/\\||¦/", trim($source, "{}")); + $parts = array_map("trim", $parts); + + // Extract the name of the template page + $templatePagename = array_shift($parts); + // If the page that we are supposed to use as the tempalte doesn't + // exist, then there's no point in continuing. + if(empty($pageindex->$templatePagename)) + return false; + + // Parse the parameters + $this->maxParamDepth++; + $params = []; + $i = 0; + foreach($parts as $part) + { + if(strpos($part, "=") !== false) + { + // This param contains an equals sign, so it's a named parameter + $keyValuePair = explode("=", $part, 2); + $keyValuePair = array_map("trim", $keyValuePair); + $params[$keyValuePair[0]] = $keyValuePair[1]; + } + else + { + // This isn't a named parameter + $params["$i"] = trim($part); + + $i++; + } + } + // Add the parsed parameters to the parameter stack + $this->paramStack[] = [ + "pagename" => $templatePagename, + "params" => $params + ]; + + $templateFilePath = $env->storage_prefix . $pageindex->$templatePagename->filename; + + $parsedTemplateSource = $this->text(file_get_contents($templateFilePath)); + + // Remove the parsed parameters from the stack + array_pop($this->paramStack); + + return [ + "name" => "div", + "text" => $parsedTemplateSource, + "attributes" => [ + "class" => "template" + ] + ]; + } + + /* + * ██ ███ ██ ████████ ███████ ██████ ███ ██ █████ ██ + * ██ ████ ██ ██ ██ ██ ██ ████ ██ ██ ██ ██ + * ██ ██ ██ ██ ██ █████ ██████ ██ ██ ██ ███████ ██ + * ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ + * ██ ██ ████ ██ ███████ ██ ██ ██ ████ ██ ██ ███████ + * + * ██ ██ ███ ██ ██ ██ ███████ + * ██ ██ ████ ██ ██ ██ ██ + * ██ ██ ██ ██ ██ █████ ███████ + * ██ ██ ██ ██ ██ ██ ██ ██ + * ███████ ██ ██ ████ ██ ██ ███████ + */ + protected function inlineInternalLink($fragment) + { + global $pageindex, $env; + + if(preg_match('/^\[\[([^\]]*)\]\]([^\s!?",.()\[\]{}*=+\/]*)/u', $fragment["text"], $matches)) + { + $linkPage = trim($matches[1]); + $display = $linkPage . trim($matches[2]); + if(strpos($matches[1], "|") !== false || strpos($matches[1], "¦") !== false) + { + // We have a bar character + $parts = preg_split("/\\||¦/", $matches[1], 2); + $linkPage = trim($parts[0]); // The page to link to + $display = trim($parts[1]); // The text to display + } + + $hashCode = ""; + if(strpos($linkPage, "#") !== false) + { + // We want to link to a subsection of a page + $hashCode = substr($linkPage, strpos($linkPage, "#") + 1); + $linkPage = substr($linkPage, 0, strpos($linkPage, "#")); + + // If $linkPage is empty then we want to link to the current page + if(strlen($linkPage) === 0) + $linkPage = $env->page; + } + + // If the page doesn't exist, check varying different + // capitalisations to see if it exists under some variant. + if(empty($pageindex->$linkPage)) + { + if(!empty($pageindex->{ucfirst($linkPage)})) + $linkPage = ucfirst($linkPage); + else if(!empty($pageindex->{ucwords($linkPage)})) + $linkPage = ucwords($linkPage); + } + + + // Construct the full url + $linkUrl = str_replace( + "%s", rawurlencode($linkPage), + $this->internalLinkBase + ); + + if(strlen($hashCode) > 0) + $linkUrl .= "#$hashCode"; + + $result = [ + "extent" => strlen($matches[0]), + "element" => [ + "name" => "a", + "text" => $display, + "attributes" => [ + "href" => $linkUrl + ] + ] + ]; + + if(empty($pageindex->{makepathsafe($linkPage)})) + $result["element"]["attributes"]["class"] = "redlink"; + + return $result; + } + return; + } + + /* + * ███████ ██ ██ ████████ ███████ ███ ██ ██████ ███████ ██████ + * ██ ██ ██ ██ ██ ████ ██ ██ ██ ██ ██ ██ + * █████ ███ ██ █████ ██ ██ ██ ██ ██ █████ ██ ██ + * ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ + * ███████ ██ ██ ██ ███████ ██ ████ ██████ ███████ ██████ + * + * ██ ███ ███ █████ ██████ ███████ ███████ + * ██ ████ ████ ██ ██ ██ ██ ██ + * ██ ██ ████ ██ ███████ ██ ███ █████ ███████ + * ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ + * ██ ██ ██ ██ ██ ██████ ███████ ███████ + */ + protected function inlineExtendedImage($fragment) + { + global $pageindex; + + if(preg_match('/^!\[(.*)\]\(([^|¦)]+)\s*(?:(?:\||¦)([^|¦)]*))?(?:(?:\||¦)([^|¦)]*))?(?:(?:\||¦)([^)]*))?\)/', $fragment["text"], $matches)) + { + /* + * 0 - Everything + * 1 - Alt text + * 2 - Url + * 3 - First param (optional) + * 4 - Second param (optional) + * 5 - Third param (optional) + */ + $altText = $matches[1]; + $imageUrl = trim(str_replace("&", "&", $matches[2])); // Decode & to allow it in preview urls + $param1 = empty($matches[3]) ? false : strtolower(trim($matches[3])); + $param2 = empty($matches[4]) ? false : strtolower(trim($matches[4])); + $param3 = empty($matches[5]) ? false : strtolower(trim($matches[5])); + $floatDirection = false; + $imageSize = false; + $imageCaption = false; + $shortImageUrl = false; + + if($this->isFloatValue($param1)) + { + // Param 1 is a valid css float: ... value + $floatDirection = $param1; + $imageSize = $this->parseSizeSpec($param2); + } + else if($this->isFloatValue($param2)) + { + // Param 2 is a valid css float: ... value + $floatDirection = $param2; + $imageSize = $this->parseSizeSpec($param1); + } + else if($this->isFloatValue($param3)) + { + $floatDirection = $param3; + $imageSize = $this->parseSizeSpec($param1); + } + else if($param1 === false and $param2 === false) + { + // Neither params were specified + $floatDirection = false; + $imageSize = false; + } + else + { + // Neither of them are floats, but at least one is specified + // This must mean that the first param is a size spec like + // 250x128. + $imageSize = $this->parseSizeSpec($param1); + } + + if($param1 !== false && strtolower(trim($param1)) == "caption") + $imageCaption = true; + if($param2 !== false && strtolower(trim($param2)) == "caption") + $imageCaption = true; + if($param3 !== false && strtolower(trim($param3)) == "caption") + $imageCaption = true; + + //echo("Image url: $imageUrl, Pageindex entry: " . var_export(isset($pageindex->$imageUrl), true) . "\n"); + + if(isset($pageindex->$imageUrl) and $pageindex->$imageUrl->uploadedfile) + { + // We have a short url! Expand it. + $shortImageUrl = $imageUrl; + $imageUrl = "index.php?action=preview&size=" . max($imageSize["x"], $imageSize["y"]) ."&page=" . rawurlencode($imageUrl); + } + + $style = ""; + if($imageSize !== false) + $style .= " max-width: " . $imageSize["x"] . "px; max-height: " . $imageSize["y"] . "px;"; + if($floatDirection) + $style .= " float: $floatDirection;"; + + $urlExtension = pathinfo($imageUrl, PATHINFO_EXTENSION); + $urlType = system_extension_mime_type($urlExtension); + $result = []; + switch(substr($urlType, 0, strpos($urlType, "/"))) + { + case "audio": + $result = [ + "extent" => strlen($matches[0]), + "element" => [ + "name" => "audio", + "text" => $altText, + "attributes" => [ + "src" => $imageUrl, + "controls" => "controls", + "preload" => "metadata", + "style" => trim($style) + ] + ] + ]; + break; + case "video": + $result = [ + "extent" => strlen($matches[0]), + "element" => [ + "name" => "video", + "text" => $altText, + "attributes" => [ + "src" => $imageUrl, + "controls" => "controls", + "preload" => "metadata", + "style" => trim($style) + ] + ] + ]; + break; + case "image": + default: + // If we can't work out what it is, then assume it's an image + $result = [ + "extent" => strlen($matches[0]), + "element" => [ + "name" => "img", + "attributes" => [ + "src" => $imageUrl, + "alt" => $altText, + "title" => $altText, + "style" => trim($style) + ] + ] + ]; + break; + } + + // ~ Image linker ~ + + $imageHref = $shortImageUrl !== false ? "?page=" . rawurlencode($shortImageUrl) : $imageUrl; + $result["element"] = [ + "name" => "a", + "attributes" => [ + "href" => $imageHref + ], + "text" => [$result["element"]], + "handler" => "elements" + ]; + + // ~ + + if($imageCaption) + { + $rawStyle = $result["element"]["attributes"]["style"]; + $containerStyle = preg_replace('/^.*float/', "float", $rawStyle); + $mediaStyle = preg_replace('/\s*float.*;/', "", $rawStyle); + $result["element"] = [ + "name" => "figure", + "attributes" => [ + "style" => $containerStyle + ], + "text" => [ + $result["element"], + [ + "name" => "figcaption", + "text" => $altText + ], + ], + "handler" => "elements" + ]; + $result["element"]["text"][0]["attributes"]["style"] = $mediaStyle; + } + return $result; + } + } + + # ~ + # Utility Methods + # ~ + + private function isFloatValue($value) + { + return in_array(strtolower($value), [ "left", "right" ]); + } + + private function parseSizeSpec($text) + { + if(strpos($text, "x") === false) + return false; + $parts = explode("x", $text, 2); + + if(count($parts) != 2) + return false; + + array_map("trim", $parts); + array_map("intval", $parts); + + if(in_array(0, $parts)) + return false; + + return [ + "x" => $parts[0], + "y" => $parts[1] + ]; + } + + protected function escapeText($text) + { + return htmlentities($text, ENT_COMPAT | ENT_HTML5); + } + + /** + * Sets the base url to be used for internal links. '%s' will be replaced + * with a URL encoded version of the page name. + * @param string $url The url to use when parsing internal links. + */ + public function setInternalLinkBase($url) + { + $this->internalLinkBase = $url; + } +} + +?> + diff --git a/docs/ModuleApi/files/pack.html b/docs/ModuleApi/files/pack.html new file mode 100644 index 0000000..784a3cf --- /dev/null +++ b/docs/ModuleApi/files/pack.html @@ -0,0 +1,250 @@ + + + + + + Pepperminty Wiki Module API + + + + + + + + + + + + + + + + + + + + + + + + + + +
        +
        + +
        +
        +
        +
        +
        + + + +

        pack.php

        +

        + + + + +
        + + +
        + + + +
        +
        + + + + +
        + + + diff --git a/docs/ModuleApi/files/pack.php.txt b/docs/ModuleApi/files/pack.php.txt new file mode 100644 index 0000000..5a083b4 --- /dev/null +++ b/docs/ModuleApi/files/pack.php.txt @@ -0,0 +1,99 @@ +optional && + ( + isset($argv) && + strrpos(implode(" ", $argv), $module->id) === false && + !in_array("all", $argv) + ) + ) + continue; + $module_list[] = $module->id; +} + +if(isset($_GET["modules"])) +{ + $module_list = explode(",", $_GET["modules"]); +} + +if(php_sapi_name() != "cli") +{ + header("content-type: text/php"); +} + +if(php_sapi_name() == "cli") echo("Reading in core files..."); + +$core = file_get_contents("core.php"); +$settings = file_get_contents("settings.fragment.php"); +$settings = str_replace([ "" ], "", $settings); +$core = str_replace([ + "//{settings}", + "{version}", + "{guiconfig}", + "{default-css}" +], [ + $settings, + trim(file_get_contents("version")), + trim(file_get_contents("peppermint.guiconfig.json")), + trim(file_get_contents("theme_default.css")) +], $core); + +$result = $core; + +foreach($module_list as $module_id) +{ + if($module_id == "") continue; + + if(php_sapi_name() == "cli") echo("Adding $module_id\n"); + + $module_filepath = "modules/" . preg_replace("[^a-zA-Z0-9\-]", "", $module_id) . ".php"; + + //echo("id: $module_id | filepath: $module_filepath\n"); + + if(!file_exists($module_filepath)) + { + http_response_code(400); + exit("Failed to load module with name: $module_filepath"); + } + + $modulecode = file_get_contents($module_filepath); + $modulecode = str_replace([ "" ], "", $modulecode); + $result = str_replace( + "// %next_module% //", + "$modulecode\n// %next_module% //", + $result); +} + +if(php_sapi_name() == "cli") +{ + if(file_exists("build/index.php")) + { + echo("index.php already exists in the build folder, exiting\n"); + exit(1); + } + else + { + echo("Done. Saving to disk..."); + file_put_contents("build/index.php", $result); + echo("complete!\n"); + echo("*** Build completed! ***\n"); + } +} +else +{ + exit($result); +} + +?> + diff --git a/docs/ModuleApi/files/settings.fragment.html b/docs/ModuleApi/files/settings.fragment.html new file mode 100644 index 0000000..1503024 --- /dev/null +++ b/docs/ModuleApi/files/settings.fragment.html @@ -0,0 +1,250 @@ + + + + + + Pepperminty Wiki Module API + + + + + + + + + + + + + + + + + + + + + + + + + + +
        +
        + +
        +
        +
        +
        +
        + + + +

        settings.fragment.php

        +

        + + + + +
        + + +
        + + + +
        +
        + + + + +
        + + + diff --git a/docs/ModuleApi/files/settings.fragment.php.txt b/docs/ModuleApi/files/settings.fragment.php.txt new file mode 100644 index 0000000..63b630e --- /dev/null +++ b/docs/ModuleApi/files/settings.fragment.php.txt @@ -0,0 +1,65 @@ + + * Rendering: MathJax (https://www.mathjax.org/) + * Bug reports: + * #2 - Incorrect closing tag - nibreh + * #8 - Rogue tag - nibreh + */ +$guiConfig = <<<'GUICONFIG' +{guiconfig} +GUICONFIG; + +$settingsFilename = "peppermint.json"; + +$guiConfig = json_decode($guiConfig); +$settings = new stdClass(); +if(!file_exists($settingsFilename)) +{ + // Copy the default settings over to the main settings array + foreach ($guiConfig as $key => $value) + $settings->$key = $value->default; + // Generate a random secret + $settings->secret = bin2hex(openssl_random_pseudo_bytes(16)); + file_put_contents("peppermint.json", json_encode($settings, JSON_PRETTY_PRINT)); +} +else + $settings = json_decode(file_get_contents("peppermint.json")); + +if($settings === null) +{ + header("content-type: text/plain"); + exit("Error: Failed to decode the settings file! Does it contain a syntax error?"); +} + +// Fill in any missing properties +$settingsUpgraded = false; +foreach($guiConfig as $key => $propertyData) +{ + if(!isset($settings->$key)) + { + $settings->$key = $propertyData->default; + $settingsUpgraded = true; + } +} +if($settingsUpgraded) + file_put_contents("peppermint.json", json_encode($settings, JSON_PRETTY_PRINT)); + +// Insert the default CSS if requested +$defaultCSS = <<css === "auto") + $settings->css = $defaultCSS; + +?> + diff --git a/docs/ModuleApi/font/FontAwesome.otf b/docs/ModuleApi/font/FontAwesome.otf new file mode 100644 index 0000000..3461e3f Binary files /dev/null and b/docs/ModuleApi/font/FontAwesome.otf differ diff --git a/docs/ModuleApi/font/fontawesome-webfont.eot b/docs/ModuleApi/font/fontawesome-webfont.eot new file mode 100644 index 0000000..6cfd566 Binary files /dev/null and b/docs/ModuleApi/font/fontawesome-webfont.eot differ diff --git a/docs/ModuleApi/font/fontawesome-webfont.svg b/docs/ModuleApi/font/fontawesome-webfont.svg new file mode 100644 index 0000000..a9f8469 --- /dev/null +++ b/docs/ModuleApi/font/fontawesome-webfont.svg @@ -0,0 +1,504 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/docs/ModuleApi/font/fontawesome-webfont.ttf b/docs/ModuleApi/font/fontawesome-webfont.ttf new file mode 100644 index 0000000..5cd6cff Binary files /dev/null and b/docs/ModuleApi/font/fontawesome-webfont.ttf differ diff --git a/docs/ModuleApi/font/fontawesome-webfont.woff b/docs/ModuleApi/font/fontawesome-webfont.woff new file mode 100644 index 0000000..9eaecb3 Binary files /dev/null and b/docs/ModuleApi/font/fontawesome-webfont.woff differ diff --git a/docs/ModuleApi/graphs/class.html b/docs/ModuleApi/graphs/class.html new file mode 100644 index 0000000..d92f416 --- /dev/null +++ b/docs/ModuleApi/graphs/class.html @@ -0,0 +1,163 @@ + + + + + + Pepperminty Wiki Module API + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
        +
        +
        +
        +
        +
        +
        +
        + + +
        + + + diff --git a/docs/ModuleApi/images/apple-touch-icon-114x114.png b/docs/ModuleApi/images/apple-touch-icon-114x114.png new file mode 100644 index 0000000..1506f6a Binary files /dev/null and b/docs/ModuleApi/images/apple-touch-icon-114x114.png differ diff --git a/docs/ModuleApi/images/apple-touch-icon-72x72.png b/docs/ModuleApi/images/apple-touch-icon-72x72.png new file mode 100644 index 0000000..d813259 Binary files /dev/null and b/docs/ModuleApi/images/apple-touch-icon-72x72.png differ diff --git a/docs/ModuleApi/images/apple-touch-icon.png b/docs/ModuleApi/images/apple-touch-icon.png new file mode 100644 index 0000000..2d320cb Binary files /dev/null and b/docs/ModuleApi/images/apple-touch-icon.png differ diff --git a/docs/ModuleApi/images/custom-icons.svg b/docs/ModuleApi/images/custom-icons.svg new file mode 100644 index 0000000..c6b8037 --- /dev/null +++ b/docs/ModuleApi/images/custom-icons.svg @@ -0,0 +1,116 @@ + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + diff --git a/docs/ModuleApi/images/favicon.ico b/docs/ModuleApi/images/favicon.ico new file mode 100644 index 0000000..9575ac8 Binary files /dev/null and b/docs/ModuleApi/images/favicon.ico differ diff --git a/docs/ModuleApi/images/hierarchy-item.png b/docs/ModuleApi/images/hierarchy-item.png new file mode 100644 index 0000000..c7756e7 Binary files /dev/null and b/docs/ModuleApi/images/hierarchy-item.png differ diff --git a/docs/ModuleApi/images/icon-class-13x13.png b/docs/ModuleApi/images/icon-class-13x13.png new file mode 100644 index 0000000..731f0bd Binary files /dev/null and b/docs/ModuleApi/images/icon-class-13x13.png differ diff --git a/docs/ModuleApi/images/icon-class.svg b/docs/ModuleApi/images/icon-class.svg new file mode 100644 index 0000000..7dacd0c --- /dev/null +++ b/docs/ModuleApi/images/icon-class.svg @@ -0,0 +1,77 @@ + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + diff --git a/docs/ModuleApi/images/icon-interface-13x13.png b/docs/ModuleApi/images/icon-interface-13x13.png new file mode 100644 index 0000000..aa24fa9 Binary files /dev/null and b/docs/ModuleApi/images/icon-interface-13x13.png differ diff --git a/docs/ModuleApi/images/icon-interface.svg b/docs/ModuleApi/images/icon-interface.svg new file mode 100644 index 0000000..7c6371e --- /dev/null +++ b/docs/ModuleApi/images/icon-interface.svg @@ -0,0 +1,73 @@ + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + diff --git a/docs/ModuleApi/images/icon-trait-13x13.png b/docs/ModuleApi/images/icon-trait-13x13.png new file mode 100644 index 0000000..3c2792b Binary files /dev/null and b/docs/ModuleApi/images/icon-trait-13x13.png differ diff --git a/docs/ModuleApi/images/icon-trait.svg b/docs/ModuleApi/images/icon-trait.svg new file mode 100644 index 0000000..03cf08f --- /dev/null +++ b/docs/ModuleApi/images/icon-trait.svg @@ -0,0 +1,73 @@ + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + diff --git a/docs/ModuleApi/images/iviewer/grab.cur b/docs/ModuleApi/images/iviewer/grab.cur new file mode 100644 index 0000000..ef540be Binary files /dev/null and b/docs/ModuleApi/images/iviewer/grab.cur differ diff --git a/docs/ModuleApi/images/iviewer/hand.cur b/docs/ModuleApi/images/iviewer/hand.cur new file mode 100644 index 0000000..1a5bafb Binary files /dev/null and b/docs/ModuleApi/images/iviewer/hand.cur differ diff --git a/docs/ModuleApi/images/iviewer/iviewer.rotate_left.png b/docs/ModuleApi/images/iviewer/iviewer.rotate_left.png new file mode 100644 index 0000000..df0956d Binary files /dev/null and b/docs/ModuleApi/images/iviewer/iviewer.rotate_left.png differ diff --git a/docs/ModuleApi/images/iviewer/iviewer.rotate_right.png b/docs/ModuleApi/images/iviewer/iviewer.rotate_right.png new file mode 100644 index 0000000..7a6c829 Binary files /dev/null and b/docs/ModuleApi/images/iviewer/iviewer.rotate_right.png differ diff --git a/docs/ModuleApi/images/iviewer/iviewer.zoom_fit.png b/docs/ModuleApi/images/iviewer/iviewer.zoom_fit.png new file mode 100644 index 0000000..364e01d Binary files /dev/null and b/docs/ModuleApi/images/iviewer/iviewer.zoom_fit.png differ diff --git a/docs/ModuleApi/images/iviewer/iviewer.zoom_in.png b/docs/ModuleApi/images/iviewer/iviewer.zoom_in.png new file mode 100644 index 0000000..7899332 Binary files /dev/null and b/docs/ModuleApi/images/iviewer/iviewer.zoom_in.png differ diff --git a/docs/ModuleApi/images/iviewer/iviewer.zoom_out.png b/docs/ModuleApi/images/iviewer/iviewer.zoom_out.png new file mode 100644 index 0000000..893f350 Binary files /dev/null and b/docs/ModuleApi/images/iviewer/iviewer.zoom_out.png differ diff --git a/docs/ModuleApi/images/iviewer/iviewer.zoom_zero.png b/docs/ModuleApi/images/iviewer/iviewer.zoom_zero.png new file mode 100644 index 0000000..c981db6 Binary files /dev/null and b/docs/ModuleApi/images/iviewer/iviewer.zoom_zero.png differ diff --git a/docs/ModuleApi/index.html b/docs/ModuleApi/index.html new file mode 100644 index 0000000..85bb330 --- /dev/null +++ b/docs/ModuleApi/index.html @@ -0,0 +1,3373 @@ + + + + + + Pepperminty Wiki Module API + + + + + + + + + + + + + + + + + + + + + + + + + +
        +
        + +
        +
        +
        +
        +
        + +

        \

        + + + + +

        Classes

        + + + + + + + + + + + + + + + + + + + + + +
        idsProvides an interface to interact with page ids.
        page_rendererRenders the HTML page that is sent to the client.
        PeppermintParsedown
        search
        SlimdownModified by Starbeamrainbowlabs (starbeamrainbowlabs)
        +
        + + +
        + + +
        +
        +

        Functions

        +
        + +
        + +
        +
        + +
        +

        accept_contains_mime()

        + +
        accept_contains_mime(string  $accept_header, string  $mime_type) : boolean
        +

        Figures out whether a given http accepts header contains a +specified mime type.

        + + +

        Parameters

        + + + + + + + + + + + +
        string$accept_header

        The accept header to search.

        string$mime_type

        The mime type to search for.

        + + +

        Returns

        + boolean + —

        Whether the specified mime type was found +in the specified accepts header.

        + +
        +
        + +
        + +
        +
        + +
        +

        add_action()

        + +
        add_action(string  $action_name, \function  $func) 
        +

        Registers a new action handler.

        + + +

        Parameters

        + + + + + + + + + + + +
        string$action_name

        The action to register.

        \function$func

        The function to call when the specified +action is requested.

        + + + +
        +
        + +
        + +
        +
        + +
        +

        add_help_section()

        + +
        add_help_section(string  $index, string  $title, string  $content) 
        +

        Adds a new help section to the help page.

        + + +

        Parameters

        + + + + + + + + + + + + + + + + +
        string$index

        The string to index the new section under.

        string$title

        The title to display above the section.

        string$content

        The content to display.

        + + + +
        +
        + +
        + +
        +
        + +
        +

        add_parser()

        + +
        add_parser(string  $name, \function  $parser_code) 
        +

        Registers a new parser.

        + + +

        Parameters

        + + + + + + + + + + + +
        string$name

        The name of the new parser to register.

        \function$parser_code

        The function to register as a new parser.

        + + + +
        +
        + +
        + +
        +
        + +
        +

        add_recent_change()

        + +
        add_recent_change(array  $rchange) 
        +

        Adds a new recent change to the recent changes file.

        + + +

        Parameters

        + + + + + + +
        array$rchange

        The new change to add.

        + + + +
        +
        + +
        + +
        +
        + +
        +

        check_subpage_parents()

        + +
        check_subpage_parents(  $pagename) 
        +

        Makes sure that a subpage's parents exist.

        +

        Note this doesn't check the pagename itself.

        + +

        Parameters

        + + + + + + +
        $pagename

        The pagename to check.

        + + + +
        +
        + +
        + +
        +
        + +
        +

        email_user()

        + +
        email_user(string  $username, string  $subject, string  $body) : boolean
        +

        Sends a plain text email to a user, replacing {username} with the specified username.

        + + +

        Parameters

        + + + + + + + + + + + + + + + + +
        string$username

        The username to send the email to.

        string$subject

        The subject of the email.

        string$body

        The body of the email.

        + + +

        Returns

        + boolean + —

        Whether the email was sent successfully or not. Currently, this may fail if the user doesn't have a registered email address.

        + +
        +
        + +
        + +
        +
        + +
        +

        email_users()

        + +
        email_users(array<mixed,string>  $usernames, string  $subject, string  $body) : integer
        +

        Sends a plain text email to a list of users, replacing {username} with each user's name.

        + + +

        Parameters

        + + + + + + + + + + + + + + + + +
        array<mixed,string>$usernames

        A list of usernames to email.

        string$subject

        The subject of the email.

        string$body

        The body of the email.

        + + +

        Returns

        + integer + —

        The number of emails sent successfully.

        + +
        +
        + +
        + +
        +
        + +
        +

        endsWith()

        + +
        endsWith(string  $whole, string  $end) : boolean
        +

        Tests whether a string ends with a given substring.

        + + +

        Parameters

        + + + + + + + + + + + +
        string$whole

        The string to test against.

        string$end

        The substring test for.

        + + +

        Returns

        + boolean + —

        Whether $whole ends in $end.

        + +
        +
        + +
        + +
        +
        + +
        +

        errorimage()

        + +
        errorimage(string  $text, integer  $target_size = null) : \image
        +

        Creates an images containing the specified text.

        +

        Useful for sending errors back to the client.

        + +

        Parameters

        + + + + + + + + + + + +
        string$text

        The text to include in the image.

        integer$target_size

        The target width to aim for when creating +the image.

        + + +

        Returns

        + \image + —

        The handle to the generated GD image.

        + +
        +
        + +
        + +
        +
        + +
        +

        extract_user_from_userpage()

        + +
        extract_user_from_userpage(string  $userPagename) : string
        +

        Extracts a username from a user page path.

        + + +

        Parameters

        + + + + + + +
        string$userPagename

        The suer page path to extract from.

        + + +

        Returns

        + string + —

        The name of the user that the user page belongs to.

        + +
        +
        + +
        + +
        +
        + +
        +

        fetch_comment_thread()

        + +
        fetch_comment_thread(array  $comment_data, string  $comment_id) : array<mixed,object>
        +

        Fetches all the parent comments of the specified comment id, including the +comment itself at the end.

        +

        Useful for figuring out who needs notifying when a new comment is posted.

        + +

        Parameters

        + + + + + + + + + + + +
        array$comment_data

        The comment data to search.

        string$comment_id

        The comment id to fetch the thread for.

        + + +

        Returns

        + array<mixed,object> + —

        A list of the comments in the thread, with the deepest +one at the end.

        + +
        +
        + +
        + +
        +
        + +
        +

        find_comment()

        + +
        find_comment(array  $comment_data, string  $comment_id) : object
        +

        Finds the comment with specified id by way of an almost-breadth-first search.

        + + +

        Parameters

        + + + + + + + + + + + +
        array$comment_data

        The comment data to search.

        string$comment_id

        The id of the comment to find.

        + + +

        Returns

        + object + —

        The comment data with the specified id, or +false if it wasn't found.

        + +
        +
        + +
        + +
        +
        + +
        +

        full_url()

        + +
        full_url(array  $s = false, boolean  $use_forwarded_host = false) : string
        +

        Get the full url, as requested by the client.

        + + +

        Parameters

        + + + + + + + + + + + +
        array$s

        The $_SERVER variable. Defaults to $_SERVER.

        boolean$use_forwarded_host

        Whether to take the X-Forwarded-Host header into account.

        + + +

        Returns

        + string + —

        The full url, as requested by the client.

        + +
        +
        + +
        + +
        +
        + +
        +

        generate_comment_id()

        + +
        generate_comment_id() : string
        +

        Generates a new random comment id.

        + + + + +

        Returns

        + string + —

        A new random comment id.

        + +
        +
        + +
        + +
        +
        + +
        +

        generate_page_list()

        + +
        generate_page_list(array<mixed,string>  $pagelist) : string
        +

        Renders a list of pages as HTML.

        + + +

        Parameters

        + + + + + + +
        array<mixed,string>$pagelist

        A list of page names to include in the list.

        + + +

        Returns

        + string + —

        The specified list of pages as HTML.

        + +
        +
        + +
        + +
        +
        + +
        +

        get_comment_filename()

        + +
        get_comment_filename(string  $pagename) : string
        +

        Given a page name, returns the absolute file path in which that page's +comments are stored.

        + + +

        Parameters

        + + + + + + +
        string$pagename

        The name pf the page to fetch the comments filename for.

        + + +

        Returns

        + string + —

        The path to the file that the

        + +
        +
        + +
        + +
        +
        + +
        +

        get_max_upload_size()

        + +
        get_max_upload_size() : integer
        +

        Calculates the actual maximum upload size supported by the server +Returns a file size limit in bytes based on the PHP upload_max_filesize and +post_max_size

        + + + + +

        Returns

        + integer + —

        The maximum upload size supported bythe server, in bytes.

        + +
        +
        + +
        + +
        +
        + +
        +

        get_page_parent()

        + +
        get_page_parent(string  $pagename) : string|boolean
        +

        Gets the name of the parent page to the specified page.

        + + +

        Parameters

        + + + + + + +
        string$pagename

        The child page to get the parent +page name for.

        + + +

        Returns

        + string|boolean + +
        +
        + +
        + +
        +
        + +
        +

        get_subpages()

        + +
        get_subpages(object  $pageindex, string  $pagename) : object
        +

        Gets a list of all the sub pages of the current page.

        + + +

        Parameters

        + + + + + + + + + + + +
        object$pageindex

        The pageindex to use to search.

        string$pagename

        The name of the page to list the sub pages of.

        + + +

        Returns

        + object + —

        An object containing all the subpages and their +respective distances from the given page name in the pageindex tree.

        + +
        +
        + +
        + +
        +
        + +
        +

        get_user_pagename()

        + +
        get_user_pagename(string  $username) : string
        +

        Figures out the path to the user page for a given username.

        +

        Does not check to make sure the user acutally exists.

        + +

        Parameters

        + + + + + + +
        string$username

        The username to get the path to their user page for.

        + + +

        Returns

        + string + —

        The path to the given user's page.

        + +
        +
        + +
        + +
        +
        + +
        +

        getallheaders()

        + +
        getallheaders() 
        +

        Polyfill for PHP's native getallheaders() function on platforms that +don't have it.

        + + + + + +
        +
        + +
        + +
        +
        + +
        +

        getsvgsize()

        + +
        getsvgsize(string  $svgFilename) : array<mixed,integer>
        +

        Calculates the size of the specified SVG file.

        + + +

        Parameters

        + + + + + + +
        string$svgFilename

        The filename to calculate the size of.

        + + +

        Returns

        + array<mixed,integer> + —

        The width and height respectively of the +specified SVG file.

        + +
        +
        + +
        + +
        +
        + +
        +

        glob_recursive()

        + +
        glob_recursive(string  $pattern, integer  $flags) : array
        +

        A recursive glob() function.

        + + +

        Parameters

        + + + + + + + + + + + +
        string$pattern

        The glob pattern to use to find filenames.

        integer$flags

        The glob flags to use when finding filenames.

        + + +

        Returns

        + array + —

        An array of the filepaths that match the given glob.

        + +
        +
        + +
        + +
        +
        + +
        +

        has_action()

        + +
        has_action(string  $action_name) : boolean
        +

        Figures out whether a given action is currently registered.

        +

        Only guaranteed to be accurate in inside an existing action function

        + +

        Parameters

        + + + + + + +
        string$action_name

        The name of the action to search for

        + + +

        Returns

        + boolean + —

        Whether an action with the specified name exists.

        + +
        +
        + +
        + +
        +
        + +
        +

        has_statistic()

        + +
        has_statistic(string  $stat_id) : boolean
        +

        Checks whether a specified statistic has been registered.

        + + +

        Parameters

        + + + + + + +
        string$stat_id

        The id of the statistic to check the existence of.

        + + +

        Returns

        + boolean + —

        Whether the specified statistic has been registered.

        + +
        +
        + +
        + +
        +
        + +
        +

        hash_password()

        + +
        hash_password(string  $pass) : string
        +

        Hashes the given password according to the current settings defined +in $settings.

        + + +

        Parameters

        + + + + + + +
        string$pass

        The password to hash.

        + + +

        Returns

        + string + —

        The hashed password. Uses sha3 if $settings->use_sha3 is +enabled, or sha256 otherwise.

        + +
        +
        + +
        + +
        +
        + +
        +

        hide_email()

        + +
        hide_email(string  $str) : string
        +

        Hides an email address from bots by adding random html entities.

        + + +

        Parameters

        + + + + + + +
        string$str

        The original email address

        + + +

        Returns

        + string + —

        The mangled email address.

        + +
        +
        + +
        + +
        +
        + +
        +

        history_add_revision()

        + +
        history_add_revision(  $pageinfo,   $newsource,   $oldsource,   $save_pageindex = true) 
        +

        + + +

        Parameters

        + + + + + + + + + + + + + + + + + + + + + +
        $pageinfo
        $newsource
        $oldsource
        $save_pageindex
        + + + +
        +
        + +
        + +
        +
        + +
        +

        human_filesize()

        + +
        human_filesize(\number  $bytes, \number  $decimals = 2) : string
        +

        Converts a filesize into a human-readable string.

        + + +

        Parameters

        + + + + + + + + + + + +
        \number$bytes

        The number of bytes to convert.

        \number$decimals

        The number of decimal places to preserve.

        + + +

        Returns

        + string + —

        A human-readable filesize.

        + +
        +
        + +
        + +
        +
        + +
        +

        human_time()

        + +
        human_time(integer  $seconds) : string
        +

        Renders a given number of seconds as something that humans can understand more easily.

        + + +

        Parameters

        + + + + + + +
        integer$seconds

        The number of seconds to render.

        + + +

        Returns

        + string + —

        The rendered time.

        + +
        +
        + +
        + +
        +
        + +
        +

        human_time_since()

        + +
        human_time_since(integer  $time) : string
        +

        Calculates the time since a particular timestamp and returns a +human-readable result.

        + + +

        Parameters

        + + + + + + +
        integer$time

        The timestamp to convert.

        + + +

        Returns

        + string + —

        The time since the given timestamp as +a human-readable string.

        + +
        +
        + +
        + +
        +
        + +
        +

        makepathsafe()

        + +
        makepathsafe(string  $string) : string
        +

        Makes a path safe.

        +

        Paths may only contain alphanumeric characters, spaces, underscores, and +dashes.

        + +

        Parameters

        + + + + + + +
        string$string

        The string to make safe.

        + + +

        Returns

        + string + —

        A safe version of the given string.

        + +
        +
        + +
        + +
        +
        + +
        +

        mb_stripos_all()

        + +
        mb_stripos_all(string  $haystack, string  $needle) : array|false
        +

        Case-insensitively finds all occurrences of $needle in $haystack. Handles +UTF-8 characters correctly.

        + + +

        Parameters

        + + + + + + + + + + + +
        string$haystack

        The string to search.

        string$needle

        The string to find.

        + + +

        Returns

        + array|false + —

        An array of match indices, or false if +nothing was found.

        + +
        +
        + +
        + +
        +
        + +
        +

        module_exists()

        + +
        module_exists(string  $id) : boolean
        +

        Checks to see whether a module with the given id exists.

        + + +

        Parameters

        + + + + + + +
        string$id

        The id to search for.

        + + +

        Returns

        + boolean + —

        Whether a module is currently loaded with the given id.

        + +
        +
        + +
        + +
        +
        + +
        +

        parse_page_source()

        + +
        parse_page_source(string  $source) : string
        +

        Parses the specified page source using the parser specified in the settings +into HTML.

        +

        The specified parser may (though it's unilkely) render it to other things.

        + +

        Parameters

        + + + + + + +
        string$source

        The source to render.

        + + +

        Returns

        + string + —

        The source rendered to HTML.

        + +
        +
        + +
        + +
        +
        + +
        +

        parse_size()

        + +
        parse_size(string  $size) : integer
        +

        Parses a PHP size to an integer

        + + +

        Parameters

        + + + + + + +
        string$size

        The size to parse.

        + + +

        Returns

        + integer + —

        The number of bytees represented by the specified +size string.

        + +
        +
        + +
        + +
        +
        + +
        +

        register_module()

        + +
        register_module(array  $moduledata) 
        +

        Registers a module.

        + + +

        Parameters

        + + + + + + +
        array$moduledata

        The module data to register.

        + + + +
        +
        + +
        + +
        +
        + +
        +

        register_save_preprocessor()

        + +
        register_save_preprocessor(\function  $func) 
        +

        Register a new proprocessor that will be executed just before +an edit is saved.

        + + +

        Parameters

        + + + + + + +
        \function$func

        The function to register.

        + + + +
        +
        + +
        + +
        +
        + +
        +

        render_comments()

        + +
        render_comments(array<mixed,object>  $comments_data, integer  $depth) : string
        +

        Renders a given comments tree to html.

        + + +

        Parameters

        + + + + + + + + + + + +
        array<mixed,object>$comments_data

        The comments tree to render.

        integer$depth

        For internal use only. Specifies the depth +at which the comments are being rendered.

        + + +

        Returns

        + string + —

        The given comments tree as html.

        + +
        +
        + +
        + +
        +
        + +
        +

        render_editor()

        + +
        render_editor(string  $editorName) : string
        +

        Renders an editor's or a group of editors name(s) in HTML.

        + + +

        Parameters

        + + + + + + +
        string$editorName

        The name of the editor to render.

        + + +

        Returns

        + string + —

        HTML representing the given editor's name.

        + +
        +
        + +
        + +
        +
        + +
        +

        render_pagename()

        + +
        render_pagename(object  $rchange) : string
        +

        Renders a page name in HTML.

        + + +

        Parameters

        + + + + + + +
        object$rchange

        The recent change to render as a page name

        + + +

        Returns

        + string + —

        HTML representing the name of the given page.

        + +
        +
        + +
        + +
        +
        + +
        +

        render_recent_change()

        + +
        render_recent_change(object  $rchange) : string
        +

        Renders a single recent change

        + + +

        Parameters

        + + + + + + +
        object$rchange

        The recent change to render.

        + + +

        Returns

        + string + —

        The recent change, rendered to HTML.

        + +
        +
        + +
        + +
        +
        + +
        +

        render_recent_changes()

        + +
        render_recent_changes(array  $recent_changes) : string
        +

        Renders a list of recent changes to HTML.

        + + +

        Parameters

        + + + + + + +
        array$recent_changes

        The recent changes to render.

        + + +

        Returns

        + string + —

        The given recent changes as HTML.

        + +
        +
        + +
        + +
        +
        + +
        +

        render_sidebar()

        + +
        render_sidebar(array  $pageindex, string  $root_pagename = "") : string
        +

        Renders the sidebar for a given pageindex.

        + + +

        Parameters

        + + + + + + + + + + + +
        array$pageindex

        The pageindex to render the sidebar for

        string$root_pagename

        The pagename that should be considered the root of the rendering. You don't usually need to use this, it is used by the algorithm itself since it is recursive.

        + + +

        Returns

        + string + —

        A HTML rendering of the sidebar for the given pageindex.

        + +
        +
        + +
        + +
        +
        + +
        +

        render_timestamp()

        + +
        render_timestamp(integer  $timestamp) : string
        +

        Renders a timestamp in HTML.

        + + +

        Parameters

        + + + + + + +
        integer$timestamp

        The timestamp to render.

        + + +

        Returns

        + string + —

        HTML representing the given timestamp.

        + +
        +
        + +
        + +
        +
        + +
        +

        save_userdata()

        + +
        save_userdata() : boolean
        +

        Saves the currently logged in uesr's data back to peppermint.json.

        + + + + +

        Returns

        + boolean + —

        Whether the user's data was saved successfully. Returns false if the user isn't logged in.

        + +
        +
        + +
        + +
        +
        + +
        +

        stack_trace()

        + +
        stack_trace(boolean  $log_trace = true, boolean  $full = false) : string
        +

        Generates a stack trace.

        + + +

        Parameters

        + + + + + + + + + + + +
        boolean$log_trace

        Whether to send the stack trace to the error log.

        boolean$full

        Whether to output a full description of all the variables involved.

        + + +

        Returns

        + string + —

        A string prepresentation of a stack trace.

        + +
        +
        + +
        + +
        +
        + +
        +

        starts_with()

        + +
        starts_with(string  $haystack, string  $needle) : boolean
        +

        Checks to see if $haystack starts with $needle.

        + + +

        Parameters

        + + + + + + + + + + + +
        string$haystack

        The string to search.

        string$needle

        The string to search for at the beginning +of $haystack.

        + + +

        Returns

        + boolean + —

        Whether $needle can be found at the beginning +of $haystack.

        + +
        +
        + +
        + +
        +
        + +
        +

        startsWith()

        + +
        startsWith(string  $haystack, string  $needle) : boolean
        +

        Tests whether a string starts with a specified substring.

        + + +

        Parameters

        + + + + + + + + + + + +
        string$haystack

        The string to check against.

        string$needle

        The substring to look for.

        + + +

        Returns

        + boolean + —

        Whether the string starts with the specified substring.

        + +
        +
        + +
        + +
        +
        + +
        +

        statistic_add()

        + +
        statistic_add(array  $stat_data) 
        +

        Registers a statistic calculator against the system.

        + + +

        Parameters

        + + + + + + +
        array$stat_data

        The statistic object to register.

        + + + +
        +
        + +
        + +
        +
        + +
        +

        stats_load()

        + +
        stats_load() : object
        +

        Loads and returns the statistics cache file.

        + + + + +

        Returns

        + object + —

        The loaded & decoded statistics.

        + +
        +
        + +
        + +
        +
        + +
        +

        stats_save()

        + +
        stats_save(  $stats) : boolean
        +

        Saves the statistics back to disk.

        + + +

        Parameters

        + + + + + + +
        $stats
        + + +

        Returns

        + boolean + —

        Whether saving succeeded or not.

        + +
        +
        + +
        + +
        +
        + +
        +

        str_replace_once()

        + +
        str_replace_once(string  $find, string  $replace, string  $subject) : string
        +

        Replaces the first occurrence of $find with $replace.

        + + +

        Parameters

        + + + + + + + + + + + + + + + + +
        string$find

        The string to search for.

        string$replace

        The string to replace the search string with.

        string$subject

        The string ot perform the search and replace on.

        + + +

        Returns

        + string + —

        The source string after the find and replace has been performed.

        + +
        +
        + +
        + +
        +
        + +
        +

        system_extension_mime_type()

        + +
        system_extension_mime_type(string  $ext) : string
        +

        Converts a given file extension to it's associated mime type.

        + + +

        Parameters

        + + + + + + +
        string$ext

        The extension to convert.

        + + +

        Returns

        + string + —

        The mime type associated with the given extension.

        + +
        +
        + +
        + +
        +
        + +
        +

        system_extension_mime_types()

        + +
        system_extension_mime_types() : array
        +

        Returns the system MIME type mapping of extensions to MIME types.

        + + + + +

        Returns

        + array + —

        An array mapping file extensions to their associated mime types.

        + +
        +
        + +
        + +
        +
        + +
        +

        system_mime_type_extension()

        + +
        system_mime_type_extension(string  $type) : string
        +

        Converts a given mime type to it's associated file extension.

        + + +

        Parameters

        + + + + + + +
        string$type

        The mime type to convert.

        + + +

        Returns

        + string + —

        The extension for the given mime type.

        + +
        +
        + +
        + +
        +
        + +
        +

        system_mime_type_extensions()

        + +
        system_mime_type_extensions() : array
        +

        Returns the system's mime type mappings, considering the first extension +listed to be cacnonical.

        + + + + +

        Returns

        + array + —

        An array of mime type mappings.

        + +
        +
        + +
        + +
        +
        + +
        +

        update_statistics()

        + +
        update_statistics(  $update_all = false) 
        +

        + + +

        Parameters

        + + + + + + +
        $update_all
        + + + +
        +
        + +
        + +
        +
        + +
        +

        upload_check_svg()

        + +
        upload_check_svg(string  $temp_filename) : array<mixed,integer>
        +

        Checks an uploaded SVG file to make sure it's (at least somewhat) safe.

        +

        Sends an error to the client if a problem is found.

        + +

        Parameters

        + + + + + + +
        string$temp_filename

        The filename of the SVG file to check.

        + + +

        Returns

        + array<mixed,integer> + —

        The size of the SVG image.

        + +
        +
        + +
        + +
        +
        + +
        +

        url_origin()

        + +
        url_origin(array  $s = false, boolean  $use_forwarded_host = false) : string
        +

        Get the actual absolute origin of the request sent by the user.

        + + +

        Parameters

        + + + + + + + + + + + +
        array$s

        The $_SERVER variable contents. Defaults to $_SERVER.

        boolean$use_forwarded_host

        Whether to utilise the X-Forwarded-Host header when calculating the actual origin.

        + + +

        Returns

        + string + —

        The actual origin of the user's request.

        + +
        +
        + +
        + +
        +
        + +
        +

        var_dump_ret()

        + +
        var_dump_ret(mixed  $var) : string
        +

        Calls var_dump() and returns the output.

        + + +

        Parameters

        + + + + + + +
        mixed$var

        The thing to pass to var_dump().

        + + +

        Returns

        + string + —

        The output captured from var_dump().

        + +
        +
        + +
        + +
        +
        + +
        +

        var_dump_short()

        + +
        var_dump_short(mixed  $var) : string
        +

        Calls var_dump(), shortening the output for various types.

        + + +

        Parameters

        + + + + + + +
        mixed$var

        The thing to pass to var_dump().

        + + +

        Returns

        + string + —

        A shortened version of the var_dump() output.

        + +
        +
        + +
        + + +
        +
        + + +
        + + + diff --git a/docs/ModuleApi/js/bootstrap.min.js b/docs/ModuleApi/js/bootstrap.min.js new file mode 100644 index 0000000..319a85d --- /dev/null +++ b/docs/ModuleApi/js/bootstrap.min.js @@ -0,0 +1,7 @@ +/** +* Bootstrap.js by @fat & @mdo +* plugins: bootstrap-transition.js, bootstrap-modal.js, bootstrap-dropdown.js, bootstrap-scrollspy.js, bootstrap-tab.js, bootstrap-tooltip.js, bootstrap-popover.js, bootstrap-affix.js, bootstrap-alert.js, bootstrap-button.js, bootstrap-collapse.js, bootstrap-carousel.js, bootstrap-typeahead.js +* Copyright 2012 Twitter, Inc. +* http://www.apache.org/licenses/LICENSE-2.0.txt +*/ +!function(a){a(function(){a.support.transition=function(){var a=function(){var a=document.createElement("bootstrap"),b={WebkitTransition:"webkitTransitionEnd",MozTransition:"transitionend",OTransition:"oTransitionEnd otransitionend",transition:"transitionend"},c;for(c in b)if(a.style[c]!==undefined)return b[c]}();return a&&{end:a}}()})}(window.jQuery),!function(a){var b=function(b,c){this.options=c,this.$element=a(b).delegate('[data-dismiss="modal"]',"click.dismiss.modal",a.proxy(this.hide,this)),this.options.remote&&this.$element.find(".modal-body").load(this.options.remote)};b.prototype={constructor:b,toggle:function(){return this[this.isShown?"hide":"show"]()},show:function(){var b=this,c=a.Event("show");this.$element.trigger(c);if(this.isShown||c.isDefaultPrevented())return;this.isShown=!0,this.escape(),this.backdrop(function(){var c=a.support.transition&&b.$element.hasClass("fade");b.$element.parent().length||b.$element.appendTo(document.body),b.$element.show(),c&&b.$element[0].offsetWidth,b.$element.addClass("in").attr("aria-hidden",!1),b.enforceFocus(),c?b.$element.one(a.support.transition.end,function(){b.$element.focus().trigger("shown")}):b.$element.focus().trigger("shown")})},hide:function(b){b&&b.preventDefault();var c=this;b=a.Event("hide"),this.$element.trigger(b);if(!this.isShown||b.isDefaultPrevented())return;this.isShown=!1,this.escape(),a(document).off("focusin.modal"),this.$element.removeClass("in").attr("aria-hidden",!0),a.support.transition&&this.$element.hasClass("fade")?this.hideWithTransition():this.hideModal()},enforceFocus:function(){var b=this;a(document).on("focusin.modal",function(a){b.$element[0]!==a.target&&!b.$element.has(a.target).length&&b.$element.focus()})},escape:function(){var a=this;this.isShown&&this.options.keyboard?this.$element.on("keyup.dismiss.modal",function(b){b.which==27&&a.hide()}):this.isShown||this.$element.off("keyup.dismiss.modal")},hideWithTransition:function(){var b=this,c=setTimeout(function(){b.$element.off(a.support.transition.end),b.hideModal()},500);this.$element.one(a.support.transition.end,function(){clearTimeout(c),b.hideModal()})},hideModal:function(){var a=this;this.$element.hide(),this.backdrop(function(){a.removeBackdrop(),a.$element.trigger("hidden")})},removeBackdrop:function(){this.$backdrop&&this.$backdrop.remove(),this.$backdrop=null},backdrop:function(b){var c=this,d=this.$element.hasClass("fade")?"fade":"";if(this.isShown&&this.options.backdrop){var e=a.support.transition&&d;this.$backdrop=a('