@@ -0,0 +1,9 @@ | |||
Chyrp is written and maintained by the Chyrp Team: | |||
Lead Developer: | |||
- Arian Xhezairi <arian@xhezairi.com> | |||
Project Founder: | |||
- Alex Suraci <i.am@toogeneric.com> |
@@ -0,0 +1,27 @@ | |||
Copyright (c) 2011 Chyrp Team (see AUTHORS) and individual contributors. | |||
Permission is hereby granted, free of charge, to any person | |||
obtaining a copy of this software and associated documentation | |||
files (the "Software"), to deal in the Software without | |||
restriction, including without limitation the rights to use, | |||
copy, modify, merge, publish, distribute, sublicense, and/or sell | |||
copies of the Software, and to permit persons to whom the | |||
Software is furnished to do so, subject to the following | |||
conditions: | |||
The above copyright notice and this permission notice shall be | |||
included in all copies or substantial portions of the Software. | |||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, | |||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES | |||
OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND | |||
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT | |||
HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, | |||
WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING | |||
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR | |||
OTHER DEALINGS IN THE SOFTWARE. | |||
Except as contained in this notice, the name(s) of the above | |||
copyright holders shall not be used in advertising or otherwise | |||
to promote the sale, use or other dealings in this Software | |||
without prior written authorization. |
@@ -0,0 +1,97 @@ | |||
Chyrp is a blogging engine designed to be lightweight while retaining functionality. It is driven by PHP and MySQL (or SQLite), and has a great standard theme and robust module engine. You can personalize and modify it any way you want. | |||
All of your content is powered by a unique Feathers system that allows Chyrp to be whatever you want it to be. You can post anything and everything, or just stick to the default Text feather and run a regular blog. Chyrp destroys the fine line between a blog and a tumblelog. | |||
Requirements | |||
============ | |||
Chyrp will thrive on virtually any server setup, but we guarantee Chyrp to run on no less than: | |||
* PHP 5 >= 5.2.0 | |||
* MySQL: | |||
- MySQL 4.1+ | |||
* SQLite: | |||
- SQLite 3+ | |||
- PDO | |||
These requirements are more of guidelines, as these are the earliest versions of the services that we have tested Chyrp on. If you are successfully running Chyrp on an earlier version of these services, let us know. | |||
Installation | |||
============ | |||
Installing Chyrp is easier than you expect. You can do it in four steps: | |||
1. If using MySQL, create a MySQL database with a username and password. | |||
2. Download, unzip, and upload. | |||
3. Open your web browser and navigate to where you uploaded Chyrp. | |||
4. Follow through the installer at [index.php](). | |||
That's it! Chyrp will be up and running and ready for you to use. | |||
Upgrading | |||
========= | |||
Keeping Chyrp up to date is important to make sure that your blog is as safe and as awesome as possible. | |||
1. Download the latest version of Chyrp from [http://chyrp.net/](http://chyrp.net/). | |||
2. Copy your config files<sup>1</sup> to somewhere safe. | |||
3. Disable any Modules/Feathers that you downloaded for the release you're upgrading from. | |||
4. Overwrite your current Chyrp installation files with the new ones. | |||
5. Restore your config files<sup>1</sup> back to /includes/. | |||
6. Upgrade by navigating to [upgrade.php](), and restore any backups. | |||
7. Re-enable your Modules/Feathers. | |||
8. Run the upgrader again. It will run the Module/Feather upgrade tasks. | |||
<sup>1</sup> The config files vary depending on what you're upgrading from. Any of these in are considered "config files": | |||
* `/includes/config.yaml.php` | |||
* `/includes/database.yaml.php` | |||
* `/includes/config.yml.php` | |||
* `/includes/database.yml.php` | |||
* `/includes/config.php` | |||
* `/includes/database.php` | |||
Extensions | |||
========== | |||
Chyrp isn't complete without activating a few extensions. Extensions add functionality (ex. audio clips, video, photos) to Chyrp. You can find extensions for Chyrp made by the Chyrp community at [http://chyrp.net/extend](http://chyrp.net/extend). | |||
Installing Extensions | |||
===================== | |||
To install extensions, you have to determine what type of extension it is. It can be a *module*, a *feather*, a *theme*, or a *localization*. There's a different setup process for each type. | |||
## Feathers | |||
Feathers add new *post types* to Chyrp. Post types determine what kind of media you can display in your blog. | |||
1. Download and unzip the feather | |||
2. Upload the feather to the `feathers/` folder. | |||
3. Open your web browser and navigate to your Chyrp administration panel. | |||
4. Click on the *Extend* tab, and then the *Feathers* sub tab. | |||
5. Drag it from the Disabled pane to the Enabled pane. | |||
You can now use the feather by navigating to the Write tab and choosing the feather you uploaded. | |||
## Modules | |||
Installing modules is quick, easy, and painless with Chyrp. They add extra functionality to Chyrp. | |||
1. Download and unzip the module. | |||
2. Upload the module to the `modules/` folder. | |||
3. Open your web browser and navigate to your Chyrp administration panel. | |||
4. Click on the *Extend* tab and drag it from the Disabled pane to the Enabled pane. | |||
The module is now installed and is ready for action. Keep in mind that some modules may conflict with each other if they do similar tasks. They are marked with red lines between them on the Modules page. | |||
## Themes | |||
Chyrp makes applying themes to your blog easy. With a single click you can change the look of your blog. | |||
1. Download and unzip the theme. | |||
2. Upload the theme to the `themes/` folder. Make sure that it is contained in it's own folder. | |||
3. Open your web browser and navigate to your Chyrp administration panel. | |||
4. Click on the *Extend* tab, and then the *Themes* sub tab. | |||
5. Click on the screenshot of the theme you just uploaded to apply it to your blog. | |||
Chyrp can even show you what the theme will look like before anyone else sees it. In the Themes sub tab, click on the Preview button below the theme screenshot to see the theme. | |||
## Localization | |||
Chyrp is multilingual! If your first language isn't English, you can apply a new localization to Chyrp to make it speak your language. | |||
1. Download and unzip the localization. | |||
1. Upload the `.mo` file to the `includes/locale/` folder. You don't need anything else for the translation to work. | |||
1. Open your web browser and navigate to your Chyrp administration panel. | |||
1. Click on the *Settings* tab, and change the *Language* option to the language you just uploaded. |
@@ -0,0 +1,82 @@ | |||
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" | |||
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> | |||
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en"> | |||
<head> | |||
<meta http-equiv="Content-Type" content="$theme.type; charset=utf-8"/> | |||
<title>Chyrp: <?php echo $title; ?></title> | |||
<style type="text/css"> | |||
html, body, ul, ol, li, | |||
h1, h2, h3, h4, h5, h6, | |||
form, fieldset, a, p { | |||
margin: 0; | |||
padding: 0; | |||
border: 0; | |||
} | |||
html { | |||
font-size: 62.5%; | |||
} | |||
body { | |||
font: 1.25em/1.5em normal Verdana, Helvetica, Arial, sans-serif; | |||
color: #626262; | |||
background: #fff; | |||
padding: 1em 0 1em; | |||
overflow: auto; | |||
} | |||
code { | |||
color: #06B; | |||
font-family: Monaco, monospace; | |||
} | |||
h2 { | |||
margin-bottom: .75em; | |||
} | |||
.title { | |||
color: #aaa; | |||
font-size: 2em; | |||
font-weight: bold; | |||
margin: .25em 0 .5em; | |||
text-align: center; | |||
} | |||
.body { | |||
padding: 1em; | |||
} | |||
.body p { | |||
margin: 0 0 1em; | |||
} | |||
.body cite, | |||
.body pre { | |||
font-style: normal; | |||
display: block; | |||
padding: .25em 1em; | |||
background: #f0f0f0; | |||
margin: 0 -1em 1em; | |||
} | |||
.body ul, | |||
.body ol { | |||
margin: 0 0 1em 2em; | |||
} | |||
.body li { | |||
margin: 0; | |||
} | |||
a:link, a:visited { | |||
color: #6B0; | |||
} | |||
a:hover { | |||
text-decoration: underline; | |||
} | |||
a.big { | |||
font-size: 16px; | |||
color: #6B0; | |||
font-weight: bold; | |||
} | |||
a:hover { | |||
text-decoration: underline; | |||
} | |||
</style> | |||
</head> | |||
<body> | |||
<div class="title"><?php echo $title; ?></div> | |||
<div class="body"> | |||
<?php echo $body; ?> | |||
</div> | |||
</body> | |||
</html> |
@@ -0,0 +1,27 @@ | |||
<?php | |||
define('ADMIN', true); | |||
require_once "../includes/common.php"; | |||
# Prepare the controller. | |||
$admin = AdminController::current(); | |||
# Parse the route. | |||
$route = Route::current($admin); | |||
# Check if the user can view the site. | |||
if (!$visitor->group->can("view_site")) | |||
if ($trigger->exists("can_not_view_site")) | |||
$trigger->call("can_not_view_site"); | |||
else | |||
show_403(__("Access Denied"), __("You are not allowed to view this site.")); | |||
# Execute the appropriate Controller responder. | |||
$route->init(); | |||
if (!$route->success and !$admin->displayed) | |||
$admin->display($route->action); # Attempt to display it; it'll go through Modules and Feathers. | |||
$trigger->call("end", $route); | |||
ob_end_flush(); |
@@ -0,0 +1,7 @@ | |||
name: Default | |||
version: 2.1 | |||
url: http://chyrp.net/ | |||
description: The default theme provided with Chyrp, which all themes fallback onto. | |||
author: | |||
name: Alex Suraci | |||
url: http://toogeneric.com/ |
@@ -0,0 +1,67 @@ | |||
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" | |||
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"> | |||
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="${ site.locale | split("_") | first }" lang="${ site.locale | split("_") | first }"> | |||
<head> | |||
<meta http-equiv="Content-type" content="$theme.type; charset=utf-8" /> | |||
<title>$site.name: {% block title %}$title{% endblock %}</title> | |||
<link rel="stylesheet" href="$theme_url/style.css" type="text/css" media="screen" title="no title" charset="utf-8" /> | |||
<script src="$site.chyrp_url/includes/lib/gz.php?file=jquery.js" type="text/javascript" charset="utf-8"></script> | |||
<script src="$site.chyrp_url/includes/lib/gz.php?file=plugins.js" type="text/javascript" charset="utf-8"></script> | |||
<script src="$site.chyrp_url/includes/admin.js.php?action=$route.action" type="text/javascript" charset="utf-8"></script> | |||
${ trigger.call("admin_head") } | |||
</head> | |||
<body> | |||
<div id="header"> | |||
<div class="column"> | |||
<ul id="navigation"> | |||
{% block navigation %} | |||
{% for action, nav in navigation | items %} | |||
{% if nav.show %} | |||
<li class="$action{% if nav.selected %} selected{% endif %}"$nav.attributes><a href="{% admin action %}">$nav.title</a></li> | |||
{% endif %} | |||
{% endfor %} | |||
{% endblock %} | |||
</ul> | |||
<h1><a href="$site.url">$site.name</a></h1> | |||
</div> | |||
</div> | |||
<div id="welcome"> | |||
<div class="column"> | |||
<a href="{% url "/?action=logout" %}" class="right">${ "Log Out ›" | translate }</a> | |||
${ "Hello, %s!" | translate | format(visitor.full_name | split | first | fallback(visitor.login | fallback("Guest" | translate))) } | |||
<a href="$site.url">${ "View Site ›" | translate }</a> | |||
</div> | |||
</div> | |||
<ul class="column ${ route.action }_nav" id="sub-nav"> | |||
{% block subnav %} | |||
{% if subnav[route.action] %} | |||
{% for action, nav in subnav[route.action] | items %} | |||
{% if nav.show %} | |||
<li${ route.action | selected(nav.selected, action) }$nav.attributes><a href="{% admin action %}">$nav.title</a></li> | |||
{% endif %} | |||
{% endfor %} | |||
{% endif %} | |||
{% endblock %} | |||
</ul> | |||
<div class="clear"></div> | |||
<div class="column" id="content"> | |||
{% for notice in flash.notices %} | |||
<p class="message yay">$notice</p> | |||
{% endfor %} | |||
{% for warning in flash.warnings %} | |||
<p class="message boo">$warning</p> | |||
{% endfor %} | |||
{% for message in flash.messages %} | |||
<p class="message">$message</p> | |||
{% endfor %} | |||
{% block content %}{% endblock %} | |||
<div class="clear"></div> | |||
</div> | |||
<div class="column" id="footer"> | |||
${ "Chyrp loves you." | translate }<br /> | |||
<span class="sub"> | |||
${ "v%s ‐ © %d Chyrp Team" | translate | format(version, 2011) } | |||
</span> | |||
</div> | |||
</body> | |||
</html> |
@@ -0,0 +1,287 @@ | |||
{% if not done %} | |||
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" | |||
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"> | |||
<html xmlns="http://www.w3.org/1999/xhtml"> | |||
<head> | |||
<meta http-equiv="Content-Type" content="$theme.type; charset=utf-8" /> | |||
<title>${ "Chyrp!" | translate }</title> | |||
<style type="text/css"> | |||
<!--/*--><![CDATA[/*><!--*/ | |||
/* Reset */ | |||
body,div,dl,dt,dd,ul,ol,li,h1,h2,h3,h4,h5,h6,pre,code,form,fieldset,legend,input,textarea,p,blockquote,th,td { margin: 0; padding: 0; } | |||
table { border-collapse: collapse; border-spacing: 0; } | |||
fieldset,img { border: 0; } | |||
address,caption,cite,code,dfn,em,strong,th,var { font-style: normal; font-weight: normal; } | |||
li { list-style: none; } | |||
caption,th { text-align: left; } | |||
h1,h2,h3,h4,h5,h6 { font-size: 100%; font-weight: normal; } | |||
abbr,acronym { border: 0; font-variant: normal; } | |||
input, textarea, select { font-family: inherit; font-size: inherit; font-weight: inherit; } | |||
/* End Reset */ | |||
html { | |||
font-size: 62.5%; | |||
} | |||
body { | |||
font: 1.25em/1.5em normal Verdana, Helvetica, Arial, sans-serif; | |||
color: #626262; | |||
background: #e8e8e8; | |||
margin: 0; | |||
padding: 1.25em; | |||
overflow-y: auto; | |||
overflow-x: hidden; | |||
} | |||
a:link, a:visited { | |||
text-decoration: none; | |||
color: #222; | |||
border-bottom: 1px solid #ddd; | |||
} | |||
a:hover { | |||
color: #555; | |||
border-bottom-color: #aaa; | |||
} | |||
label { | |||
display: block; | |||
font-weight: bold; | |||
} | |||
p { | |||
margin: 0 0 1em; | |||
} | |||
input.text, textarea { | |||
font-size: 1.25em; | |||
padding: 3px; | |||
border: 1px solid #ddd; | |||
background: #fff; | |||
} | |||
input.code, code { | |||
font-family: "Consolas", "Monaco", monospace; | |||
} | |||
.navigation { | |||
_border: 1px solid #e8e8e8; | |||
} | |||
.navigation li a { | |||
float: left; | |||
padding: .4em .75em; | |||
background: #dfdfdf; | |||
border-top: .2em solid #e8e8e8; | |||
border-bottom: 0 !important; | |||
color: #737373; | |||
} | |||
.navigation li.selected a { | |||
background: #fff; | |||
border-top-color: #c7c7c7; | |||
} | |||
.navigation li.right { | |||
margin: .75em 0 0; | |||
} | |||
.navigation li.right a { | |||
float: none; | |||
background: transparent; | |||
padding: 0; | |||
font-size: .95em; | |||
color: #777; | |||
} | |||
.navigation li.right a { | |||
color: #444; | |||
} | |||
.content { | |||
background: #fff; | |||
padding: 1em; | |||
position: absolute; | |||
z-index: 100; | |||
{% if site.enabled_feathers | length == 1 %} | |||
position: absolute; | |||
border: 0; | |||
top: 1em; | |||
left: 1em; | |||
bottom: 1em; | |||
right: 1em; | |||
height: auto; | |||
{% endif %} | |||
} | |||
.clear { | |||
clear: both; | |||
} | |||
.wide { | |||
width: 100%; | |||
} | |||
textarea.wide, input.text.wide { | |||
width: 98%; /* Compensating for the 6px added from the padding */ | |||
_width: 100%; | |||
} | |||
.sub { | |||
display: none; | |||
} | |||
.buttons { | |||
text-align: center; | |||
} | |||
button { | |||
background: #eee; | |||
padding: .75em 1.5em; | |||
color: #777; | |||
text-shadow: #fff .1em .1em 0; | |||
font: 1em normal "Lucida Grande", Verdana, Helvetica, Arial, sans-serif; | |||
text-decoration: none; | |||
border: 0; | |||
cursor: pointer; | |||
-webkit-border-radius: .5em; | |||
-moz-border-radius: .5em; | |||
} | |||
button:hover { | |||
background: #f5f5f5; | |||
} | |||
button:active { | |||
background: #e0e0e0; | |||
} | |||
/*]]>*/--> | |||
</style> | |||
<script src="$site.chyrp_url/includes/lib/gz.php?file=jquery.js" type="text/javascript" charset="utf-8"></script> | |||
<script src="$site.chyrp_url/includes/lib/gz.php?file=plugins.js" type="text/javascript" charset="utf-8"></script> | |||
<script type="text/javascript"> | |||
<!--//--><![CDATA[//><!-- | |||
function activate_nav_tab(id) { | |||
$$("[class^='nav_']").removeClass("selected") | |||
$$("[id$$='_form']").hide() | |||
$$("#"+id+"_form").show() | |||
$$(".nav_" + id).addClass("selected") | |||
$$("#"+id+"_form input.text").expand() | |||
} | |||
$$(function(){ | |||
$$("form:visible input.text").expand() | |||
$$(".navigation li").css("float", "left") | |||
$$(".navigation").sortable({ | |||
axis: "x", | |||
containment: ".navigation", | |||
placeholder: "feathers_sort", | |||
opacity: 0.8, | |||
delay: 1, | |||
revert: true, | |||
update: function(){ | |||
$$.post("$site.chyrp_url/includes/ajax.php", "action=reorder_feathers&"+$$(".navigation").sortable("serialize")) | |||
} | |||
}) | |||
}) | |||
//--><!]]> | |||
</script> | |||
</head> | |||
<body> | |||
{% if site.enabled_feathers | length > 1 %} | |||
<ul class="navigation"> | |||
{% for feather in feathers %} | |||
<li id="list_feathers[$feather.safename]" class="nav_$feather.safename${ feather.safename | selected(selected_feather.safename, true) }"> | |||
<a href="javascript:activate_nav_tab('$feather.safename')">$feather.name</a> | |||
</li> | |||
{% endfor %} | |||
</ul> | |||
<div class="clear"></div> | |||
{% endif %} | |||
<div class="content"> | |||
{% for feather in feathers %} | |||
<form action="$site.chyrp_url/admin/?action=add_post" id="${ feather.safename }_form"{% if feather.safename != selected_feather.safename %} style="display: none"{% endif %} method="post" accept-charset="utf-8" enctype="multipart/form-data"> | |||
<fieldset> | |||
${ trigger.call("before_bookmarklet_fields", feather) } | |||
{% for field in feather.fields %} | |||
<p> | |||
<label for="$field.attr"> | |||
$field.label | |||
{% if field.optional %} | |||
<span class="sub">${ "(optional)" | translate }</span> | |||
{% endif %} | |||
{% if field.help %} | |||
<span class="sub"> | |||
<a href="{% admin "help&id="~field.help %}" class="help emblem"><img src="$theme_url/images/icons/help.png" alt="help" /></a> | |||
</span> | |||
{% endif %} | |||
</label> | |||
{% if field.type == "text" or field.type == "file" %} | |||
<input class="$field.type{% if field.classes %} ${ field.classes | join(" ") }{% endif %}" type="$field.type" name="$field.attr" value="{% if not field.no_value %}${ field.value | fallback(args[field.bookmarklet] | escape) }{% endif %}" id="$field.attr" /> | |||
{% elseif field.type == "text_block" %} | |||
<textarea class="wide{% if field.classes %} ${ field.classes | join(" ") }{% endif %}" rows="${ field.rows | fallback(10) }" name="$field.attr" id="$field.attr" cols="50">{% if not field.no_value %}${ field.value | fallback(args[field.bookmarklet] | escape) }{% endif %}</textarea> | |||
{% elseif field.type == "select" %} | |||
<select name="$field.attr" id="$field.attr"{% if field.classes %} class="${ field.classes | join(" ") }"{% endif %}> | |||
{% for value, name in field.options | items %} | |||
<option value="${ value | escape }"{% if not field.no_value %}${ value | option_selected(field.value | fallback(args[field.bookmarklet] | escape)) }{% endif %}>${ name | escape }</option> | |||
{% endfor %} | |||
</select> | |||
{% endif %} | |||
</p> | |||
{% endfor %} | |||
${ trigger.call("after_post_fields", feather) } | |||
<div class="buttons"> | |||
<button type="submit"> | |||
${ "Publish" | translate } | |||
</button> | |||
<input type="hidden" name="feather" value="$feather.safename" id="feather" /> | |||
<input type="hidden" name="slug" value="" id="slug" /> | |||
<input type="hidden" name="bookmarklet" value="true" id="bookmarklet" /> | |||
<input type="hidden" name="hash" value="$site.secure_hashkey" id="hash" /> | |||
</div> | |||
${ trigger.call("bookmarklet_fields") } | |||
</fieldset> | |||
</form> | |||
{% endfor %} | |||
</div> | |||
</body> | |||
</html> | |||
{% else %} {# This one is 100% credited to Tumblr. They did it perfectly, didn't want to muck it up. #} | |||
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" | |||
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> | |||
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en"> | |||
<head> | |||
<meta http-equiv="Content-Type" content="$theme.type; charset=utf-8"/> | |||
<title>${ "Chyrp!" | translate }</title> | |||
<style type="text/css"> | |||
<!--/*--><![CDATA[/*><!--*/ | |||
body { | |||
background-color: #e1e1e1; | |||
margin: 0px; | |||
font: 15px normal 'Trebuchet MS',Verdana,Helvetica,sans-serif; | |||
text-align: center; | |||
} | |||
div#content { | |||
margin: 137px 30px 0px 30px; | |||
padding: 15px; | |||
} | |||
/*]]>*/--> | |||
</style> | |||
<script type="text/javascript"> | |||
<!--//--><![CDATA[//><!-- | |||
function countdown_func() { | |||
countdown-- | |||
el = document.getElementById('countdown') | |||
if (countdown == 1) | |||
el.firstChild.nodeValue = "${ "or wait 1 seconds." | translate }" | |||
else if (countdown > 0) | |||
el.firstChild.nodeValue = "${ "or wait 2 seconds." | translate }" | |||
else | |||
self.close() | |||
if (countdown > 0) | |||
setTimeout('countdown_func()', 1000) | |||
} | |||
var countdown = 3; | |||
//--><!]]> | |||
</script> | |||
</head> | |||
<body> | |||
<div id="content"> | |||
<div style="margin-bottom: 10px; font-size: 40px; color: #777;">${ "Done!" | translate }</div> | |||
<a href="javascript:void(0)" onclick="javascript:self.close(); return false;" style="color: #777;">${ "Close this window" | translate }</a> | |||
<span id="countdown" style="color:#777;"> | |||
${ "or wait 3 seconds." | translate } | |||
</span> | |||
</div> | |||
<script type="text/javascript"> | |||
setTimeout('countdown_func()', 1000); | |||
</script> | |||
</body> | |||
</html> | |||
{% endif %} |
@@ -0,0 +1,69 @@ | |||
{% extends "layout.twig" %} | |||
{% block title %}${ "Content Settings" | translate }{% endblock %} | |||
{% block content %} | |||
<form id="content_settings" class="split" action="{% admin "content_settings" %}" method="post" accept-charset="utf-8" enctype="multipart/form-data"> | |||
<fieldset> | |||
<p> | |||
<label for="posts_per_page">${ "Posts Per Page" | translate }</label> | |||
<input class="text" type="text" name="posts_per_page" value="${ site.posts_per_page | escape }" size="2" id="posts_per_page" /> | |||
</p> | |||
<p> | |||
<label for="feed_items">${ "Feed Posts Limit" | translate }</label> | |||
<input class="text" type="text" name="feed_items" value="${ site.feed_items | escape }" size="2" id="feed_items" /> | |||
</p> | |||
<p> | |||
<label for="feed_url">${ "Feed URL" | translate }</label> | |||
<input class="text" type="text" name="feed_url" value="${ site.feed_url | escape }" id="feed_url" /> | |||
<small> | |||
{% if site.clean_urls %} | |||
${ "Allows you to set an alternative URL for <code>/feed/</code>, e.g. your feed on <a href=\"http://feedburner.com/\">FeedBurner</a>." | translate } | |||
{% else %} | |||
${ "Allows you to set an alternative URL for <code>/?feed</code>, e.g. your feed on FeedBurner." | translate } | |||
{% endif %} | |||
</small> | |||
</p> | |||
<p> | |||
<label for="uploads_path">${ "Uploads Path" | translate }</label> | |||
<input class="text" type="text" name="uploads_path" value="${ site.uploads_path | escape }" id="uploads_path" /> | |||
<small> | |||
${ "The directory, relative to your Chyrp install, to upload files to. You can use <code>/../</code> to go up one directory." | translate } | |||
</small> | |||
</p> | |||
<p> | |||
<label for="enable_trackbacking">${ "Enable Trackbacking" | translate }</label> | |||
<input class="checkbox" type="checkbox" name="enable_trackbacking" id="enable_trackbacking"${ site.enable_trackbacking | checked } /> | |||
<small> | |||
${ "Trackbacking allows sites to notify you when they write a new entry, usually because they link to or reference yours." | translate } | |||
</small> | |||
</p> | |||
<p> | |||
<label for="send_pingbacks">${ "Send Pingbacks" | translate }</label> | |||
<input class="checkbox" type="checkbox" name="send_pingbacks" id="send_pingbacks"${ site.send_pingbacks | checked } /> | |||
<small> | |||
${ "Attempts to notify sites linked to from your posts. It'll slow down things a bit when you submit them, depending on how many links you've got in it." | translate } | |||
</small> | |||
</p> | |||
<p> | |||
<label for="enable_xmlrpc">${ "Enable XML-RPC Support" | translate }</label> | |||
<input class="checkbox" type="checkbox" name="enable_xmlrpc" id="enable_xmlrpc"${ site.enable_xmlrpc | checked } /> | |||
<small> | |||
${ "XML-RPC support allows for remote access to your site. This allows you to use remote clients (e.g., <a href=\"http://www.red-sweater.com/marsedit/\">MarsEdit</a> or <a href=\"http://flickr.com/help/blogging/\">Flickr</a>) to create/edit content on your site." | translate } | |||
</small> | |||
</p> | |||
<p> | |||
<label for="enable_ajax">${ "Enable Inline Post Management" | translate }</label> | |||
<input class="checkbox" type="checkbox" name="enable_ajax" id="enable_ajax"${ site.enable_ajax | checked } /> | |||
</p> | |||
<p class="buttons"> | |||
<button type="submit" class="yay"> | |||
<img src="$theme_url/images/icons/success.png" alt="success" />${ "Update" | translate } | |||
</button> | |||
</p> | |||
<input type="hidden" name="hash" value="$site.secure_hashkey" id="hash" /> | |||
</fieldset> | |||
</form> | |||
{% endblock %} |
@@ -0,0 +1,60 @@ | |||
{% extends "layout.twig" %} | |||
{% block title %}${ "Delete Group “%s”?" | translate | format(group.name | escape) }{% endblock %} | |||
{% block content %} | |||
<h1>${ "Are you sure you want to delete the “%s” group?" | translate | format(group.name | escape) }</h1> | |||
<form class="delete{% if group.id == visitor.group.id %} confirm{% endif %}" action="{% admin "destroy_group" %}" method="post" accept-charset="utf-8"> | |||
<fieldset> | |||
<blockquote class="noitalic"> | |||
{% if group.members %} | |||
<h2>${ "Members:" | translate }</h2> | |||
<ul> | |||
{% for member in group.members %} | |||
<li>${ member.full_name | fallback(member.login) }</li> | |||
{% endfor %} | |||
</ul> | |||
<br /> | |||
{% if groups %} | |||
<h2 class="inline">${ "Move members to:" | translate }</h2> | |||
<select name="move_group" id="move_group" class="big2"> | |||
{% for group in groups %} | |||
<option value="$group.id"${ group.id | option_selected(site.default_group) }>$group.name</option> | |||
{% endfor %} | |||
</select> | |||
<br /> | |||
{% endif %} | |||
<br /> | |||
{% endif %} | |||
{% if group.id == site.default_group and groups %} | |||
<h2 class="inline">${ "New default group:" | translate }</h2> | |||
<select name="default_group" id="default_group" class="big2"> | |||
{% for group in groups %} | |||
<option value="$group.id"${ group.id | option_selected(site.default_group) }>$group.name</option> | |||
{% endfor %} | |||
</select> | |||
<br /> | |||
<br /> | |||
{% endif %} | |||
{% if group.id == site.guest_group and groups %} | |||
<h2 class="inline">${ "New “guest” group:" | translate }</h2> | |||
<select name="guest_group" id="guest_group" class="big2"> | |||
{% for group in groups %} | |||
<option value="$group.id"${ group.id | option_selected(site.default_group) }>$group.name</option> | |||
{% endfor %} | |||
</select> | |||
<br /> | |||
<br /> | |||
{% endif %} | |||
</blockquote> | |||
<div class="center"> | |||
<button name="destroy" value="indubitably" class="center boo">${ "DESTROY!" | translate }</button> | |||
<button name="destroy" value="bollocks" type="submit" class="yay">${ "Cancel" | translate }</button> | |||
</div> | |||
<input type="hidden" name="id" value="$group.id" id="id" /> | |||
<input type="hidden" name="hash" value="$site.secure_hashkey" id="hash" /> | |||
</fieldset> | |||
</form> | |||
{% endblock %} |
@@ -0,0 +1,33 @@ | |||
{% extends "layout.twig" %} | |||
{% block title %}${ "Delete Page “%s”?" | translate | format(page.title | escape) }{% endblock %} | |||
{% block content %} | |||
<h1>${ "Are you sure you want to delete “%s”?" | translate | format(page.title) }</h1> | |||
<form class="delete" action="{% admin "destroy_page" %}" method="post" accept-charset="utf-8"> | |||
<fieldset> | |||
<blockquote> | |||
<h2>${ "Excerpt" | translate }</h2> | |||
${ page.body | truncate(500) } | |||
{% if page.children %} | |||
<br /> | |||
<h2><input type="checkbox" name="destroy_children{# OH, THE HORROR! #}" value="" id="destroy_children" /> ${ "Delete children?" | translate }</h2> | |||
<ul class="noitalic"> | |||
{% for child in page.children %} | |||
<li><a href="$child.url">$child.title</a></li> | |||
{% endfor %} | |||
</ul> | |||
{% endif %} | |||
</blockquote> | |||
<br /> | |||
<div class="center"> | |||
<button name="destroy" value="indubitably" class="center boo">${ "DESTROY!" | translate }</button> | |||
<button name="destroy" value="bollocks" type="submit" class="yay">${ "Cancel" | translate }</button> | |||
</div> | |||
<input type="hidden" name="id" value="$page.id" id="id" /> | |||
<input type="hidden" name="hash" value="$site.secure_hashkey" id="hash" /> | |||
</fieldset> | |||
</form> | |||
{% endblock %} |
@@ -0,0 +1,24 @@ | |||
{% extends "layout.twig" %} | |||
{% block title %}${ "Delete “%s”?" | translate | format(post.title | escape) }{% endblock %} | |||
{% block content %} | |||
<h1>${ "Are you sure you want to delete “%s”?" | translate | format(post.title) }</h1> | |||
<form class="delete" action="{% admin "destroy_post" %}" method="post" accept-charset="utf-8"> | |||
<fieldset> | |||
<blockquote> | |||
<h2>${ "Excerpt" | translate }</h2> | |||
$post.excerpt | |||
</blockquote> | |||
<br /> | |||
<div class="center"> | |||
<button name="destroy" value="indubitably" class="center boo">${ "DESTROY!" | translate }</button> | |||
<button name="destroy" value="bollocks" type="submit" class="yay">${ "Cancel" | translate }</button> | |||
</div> | |||
<input type="hidden" name="id" value="$post.id" id="id" /> | |||
<input type="hidden" name="hash" value="$site.secure_hashkey" id="hash" /> | |||
</fieldset> | |||
</form> | |||
{% endblock %} |
@@ -0,0 +1,89 @@ | |||
{% extends "layout.twig" %} | |||
{% block title %}${ "Delete User “%s”?" | translate | format(user.login | escape) }{% endblock %} | |||
{% block content %} | |||
<h1>${ "Are you sure you want to delete user “%s”?" | translate | format(user.login | escape) }</h1> | |||
<form class="delete" action="{% admin "destroy_user" %}" method="post" accept-charset="utf-8"> | |||
<fieldset> | |||
<blockquote class="noitalic"> | |||
<h2>${ "Information" | translate }</h2> | |||
<ul> | |||
{% if user.full_name %} | |||
<li><strong>${ "Full Name:" | translate }</strong> $user.full_name</li> | |||
{% endif %} | |||
<li><strong>${ "E-Mail:" | translate }</strong> $user.email</li> | |||
{% if user.website %} | |||
<li><strong>${ "Website:" | translate }</strong> <a href="$user.website">$user.website</a></li> | |||
{% endif %} | |||
<li><strong>${ "Group:" | translate }</strong> $user.group.name</li> | |||
</ul> | |||
<br /> | |||
{% if user.posts %} | |||
<h2>${ "Posts:" | translate }</h2> | |||
<ul> | |||
{% for post in user.posts %} | |||
<li><a href="$post.url">$post.title</a></li> | |||
{% endfor %} | |||
</ul> | |||
<br /> | |||
{% if users %} | |||
<h2 class="inline"> | |||
<input type="radio" name="posts" value="move" id="posts_move" /> | |||
${ "Attribute posts to:" | translate } | |||
</h2> | |||
<select name="move_posts" id="move_posts" class="big2"> | |||
{% for user in users %} | |||
<option value="$user.id">$user.full_name ($user.login)</option> | |||
{% endfor %} | |||
</select> | |||
<br /> | |||
{% endif %} | |||
<h2 class="inline"> | |||
<input type="radio" name="posts" value="delete" id="posts_delete" checked="checked" /> | |||
${ "Delete posts." | translate } | |||
</h2> | |||
<br /> | |||
<br /> | |||
{% endif %} | |||
{% if user.pages %} | |||
<h2>${ "Pages:" | translate }</h2> | |||
<ul> | |||
{% for page in user.pages %} | |||
<li><a href="$page.url">$page.title</a></li> | |||
{% endfor %} | |||
</ul> | |||
<br /> | |||
{% if users %} | |||
<h2 class="inline"> | |||
<input type="radio" name="pages" value="move" id="pages_move" /> | |||
${ "Attribute pages to:" | translate } | |||
</h2> | |||
<select name="move_pages" id="move_pages" class="big2"> | |||
{% for user in users %} | |||
<option value="$user.id">$user.full_name ($user.login)</option> | |||
{% endfor %} | |||
</select> | |||
<br /> | |||
{% endif %} | |||
<h2 class="inline"> | |||
<input type="radio" name="pages" value="delete" id="pages_delete" checked="checked" /> | |||
${ "Delete pages." | translate } | |||
</h2> | |||
<br /> | |||
<br /> | |||
{% endif %} | |||
${ trigger.call("delete_user_form") } | |||
</blockquote> | |||
<br /> | |||
<div class="center"> | |||
<button name="destroy" value="indubitably" class="center boo">${ "DESTROY!" | translate }</button> | |||
<button name="destroy" value="bollocks" type="submit" class="yay">${ "Cancel" | translate }</button> | |||
</div> | |||
<input type="hidden" name="id" value="$user.id" id="id" /> | |||
<input type="hidden" name="hash" value="$site.secure_hashkey" id="hash" /> | |||
</fieldset> | |||
</form> | |||
{% endblock %} |
@@ -0,0 +1,36 @@ | |||
{% extends "layout.twig" %} | |||
{% block title %}${ "Edit Group “%s”" | translate | format(group.name | escape) }{% endblock %} | |||
{% block content %} | |||
${ group.delete_link('<img src="'~ theme_url ~'/images/icons/delete.png" alt="delete" />'~ ("Delete" | translate), null, null, "button boo right") } | |||
<h1>${ "Editing Group “%s”" | translate | format(group.name | escape) }</h1> | |||
<form id="group_edit" class="split{% if group.id == visitor.group.id %} confirm{% endif %}" action="{% admin "update_group" %}" method="post" accept-charset="utf-8" enctype="multipart/form-data"> | |||
<fieldset> | |||
<p> | |||
<label for="name">${ "Name" | translate }</label> | |||
<input class="text" type="text" name="name" value="${ group.name | escape }" id="name" /> | |||
</p> | |||
<h2>${ "Permissions" | translate }</h2> | |||
<p id="toggler"> | |||
</p> | |||
<hr class="js_enabled" /> | |||
{% for permission in permissions %} | |||
<p> | |||
<label for="permission_$permission.id">${ permission.name | translate }</label> | |||
<input class="checkbox" type="checkbox" name="permissions[$permission.id]" id="permission_$permission.id"{% if group.can(permission.id) %} checked="checked"{% endif %} /> | |||
</p> | |||
{% endfor %} | |||
<br /> | |||
<p> | |||
<button type="submit" class="yay"><img src="$theme_url/images/icons/success.png" alt="success" />${ "Update" | translate }</button> | |||
</p> | |||
<input type="hidden" name="id" value="$group.id" id="id" /> | |||
<input type="hidden" name="hash" value="$site.secure_hashkey" id="hash" /> | |||
</fieldset> | |||
</form> | |||
{% endblock %} |
@@ -0,0 +1,16 @@ | |||
{% extends "layout.twig" %} | |||
{% block title %}${ "Edit “%s”" | translate | format(page.title | escape) }{% endblock %} | |||
{% block content %} | |||
${ page.delete_link('<img src="'~ theme_url ~'/images/icons/delete.png" alt="delete" />'~ ("Delete" | translate), null, null, "button boo right") } | |||
<h1>${ "Editing “%s”" | translate | format(page.title) }</h1> | |||
<form id="edit_form" action="{% admin "update_page" %}" method="post" accept-charset="utf-8" enctype="multipart/form-data"> | |||
<fieldset> | |||
{% include "partials/page_fields.twig" %} | |||
<input type="hidden" name="hash" value="$site.secure_hashkey" id="hash" /> | |||
<input type="hidden" name="id" value="$page.id" id="id" /> | |||
</fieldset> | |||
</form> | |||
{% endblock %} |
@@ -0,0 +1,17 @@ | |||
{% extends "layout.twig" %} | |||
{% block title %}${ "Edit “%s”" | translate | format(post.title | escape) }{% endblock %} | |||
{% block content %} | |||
${ post.delete_link('<img src="'~ theme_url ~'/images/icons/delete.png" alt="delete" />'~ ("Delete" | translate), null, null, "button boo right") } | |||
<h1>${ "Editing “%s”" | translate | format(post.title | escape) }</h1> | |||
<form id="edit_form" class="${ post.feather | escape(true) }" action="{% admin "update_post" %}" method="post" accept-charset="utf-8" enctype="multipart/form-data"> | |||
<fieldset> | |||
{% include "partials/post_fields.twig" %} | |||
<input type="hidden" name="id" value="$post.id" id="id" /> | |||
<input type="hidden" name="feather" value="${ post.feather | escape(true) }" id="feather" /> | |||
<input type="hidden" name="hash" value="$site.secure_hashkey" id="hash" /> | |||
</fieldset> | |||
</form> | |||
{% endblock %} |
@@ -0,0 +1,57 @@ | |||
{% extends "layout.twig" %} | |||
{% block title %}${ "Edit User “%s”" | translate | format(user.login | escape) }{% endblock %} | |||
{% block content %} | |||
${ user.delete_link('<img src="'~ theme_url ~'/images/icons/delete.png" alt="delete" />'~ ("Delete" | translate), null, null, "button boo right") } | |||
<h1>${ "Editing User “%s”" | translate | format(user.login | escape) }</h1> | |||
<form id="user_edit" class="split" action="{% admin "update_user" %}" method="post" accept-charset="utf-8" enctype="multipart/form-data"> | |||
<fieldset> | |||
<h2>${ "Information" | translate }</h2> | |||
<p> | |||
<label for="full_name">${ "Full Name" | translate }</label> | |||
<input class="text" type="text" name="full_name" value="${ user.full_name | escape }" id="full_name" /> | |||
</p> | |||
<p> | |||
<label for="email">${ "E-Mail" | translate }</label> | |||
<input class="text" type="text" name="email" value="${ user.email | escape }" id="email" /> | |||
</p> | |||
<p> | |||
<label for="website">${ "Website" | translate }</label> | |||
<input class="text" type="text" name="website" value="${ user.website | escape }" id="website" /> | |||
</p> | |||
<h2>${ "Settings" | translate }</h2> | |||
<p> | |||
<label for="login">${ "Login" | translate }</label> | |||
<input class="text" type="text" name="login" value="${ user.login | escape }" id="full_name" /> | |||
</p> | |||
<p> | |||
<label for="group">${ "Group" | translate }</label> | |||
<select name="group" id="group"> | |||
{% for group in groups %} | |||
<option value="$group.id"${ group.id | option_selected(user.group_id) }>$group.name</option> | |||
{% endfor %} | |||
</select> | |||
</p> | |||
<p> | |||
<label for="new_password1">${ "New Password?" | translate }</label> | |||
<input class="text" type="password" name="new_password1" value="" id="new_password1" /> | |||
</p> | |||
<p> | |||
<label for="new_password2">${ "Confirm" | translate }</label> | |||
<input class="text" type="password" name="new_password2" value="" id="new_password2" /> | |||
</p> | |||
${ trigger.call("edit_user_fields", user) } | |||
<br /> | |||
<p> | |||
<button type="submit" class="yay"><img src="$theme_url/images/icons/success.png" alt="success" />${ "Update" | translate }</button> | |||
</p> | |||
<input type="hidden" name="id" value="$user.id" id="id" /> | |||
<input type="hidden" name="hash" value="$site.secure_hashkey" id="hash" /> | |||
</fieldset> | |||
</form> | |||
{% endblock %} |
@@ -0,0 +1,62 @@ | |||
{% extends "layout.twig" %} | |||
{% block title %}${ "Export" | translate }{% endblock %} | |||
{% block content %} | |||
<h2>${ "What would you like to export?" | translate }</h2> | |||
<form id="export_form" class="split" action="{% admin "export" %}" method="post" accept-charset="utf-8" enctype="multipart/form-data"> | |||
<fieldset> | |||
<p> | |||
<label for="posts">${ "Posts" | translate }</label> | |||
<input class="checkbox" type="checkbox" name="posts" value="" id="posts" checked="checked" /> | |||
<small> | |||
${ "filter:" | translate } | |||
<input class="text" type="text" name="filter_posts" value="" id="filter_posts" /> | |||
<a href="{% admin "help&id=filtering_results" %}" class="help emblem"><img src="$theme_url/images/icons/help.png" alt="help" /></a> | |||
${ "(optional)" | translate } | |||
</small> | |||
</p> | |||
<p> | |||
<label for="pages">${ "Pages" | translate }</label> | |||
<input class="checkbox" type="checkbox" name="pages" value="" id="pages" checked="checked" /> | |||
<small> | |||
${ "filter:" | translate } | |||
<input class="text" type="text" name="filter_pages" value="" id="filter_pages" /> | |||
<a href="{% admin "help&id=filtering_results" %}" class="help emblem"><img src="$theme_url/images/icons/help.png" alt="help" /></a> | |||
${ "(optional)" | translate } | |||
</small> | |||
</p> | |||
<p> | |||
<label for="groups">${ "Groups" | translate }</label> | |||
<input class="checkbox" type="checkbox" name="groups" value="" id="groups" checked="checked" /> | |||
<small> | |||
${ "filter:" | translate } | |||
<input class="text" type="text" name="filter_groups" value="" id="filter_groups" /> | |||
<a href="{% admin "help&id=filtering_results" %}" class="help emblem"><img src="$theme_url/images/icons/help.png" alt="help" /></a> | |||
${ "(optional)" | translate } | |||
</small> | |||
</p> | |||
<p> | |||
<label for="users">${ "Users" | translate }</label> | |||
<input class="checkbox" type="checkbox" name="users" value="" id="users" checked="checked" /> | |||
<span class="sub"> | |||
${ "(warning: this also exports the hashed passwords, keep it safe)" | translate } | |||
</span> | |||
<small> | |||
${ "filter:" | translate } | |||
<input class="text" type="text" name="filter_users" value="" id="filter_users" /> | |||
<a href="{% admin "help&id=filtering_results" %}" class="help emblem"><img src="$theme_url/images/icons/help.png" alt="help" /></a> | |||
${ "(optional)" | translate } | |||
</small> | |||
</p> | |||
${ trigger.call("export_choose") } | |||
<p class="buttons"> | |||
<button type="submit" class="yay"><img src="$theme_url/images/icons/success.png" alt="success" />${ "Export" | translate }</button> | |||
</p> | |||
<input type="hidden" name="hash" value="$site.secure_hashkey" id="hash" /> | |||
</fieldset> | |||
</form> | |||
{% endblock %} |
@@ -0,0 +1,49 @@ | |||
{% extends "layout.twig" %} | |||
{% block title %}${ "Feathers" | translate }{% endblock %} | |||
{% block content %} | |||
<div class="enable feather left"> | |||
<h2>${ "Enabled" | translate }</h2> | |||
<ul class="extend"> | |||
{% for safename, feather in enabled_feathers | items %} | |||
<li class="$safename"> | |||
<a class="$safename info_link" href="javascript:void(0)"><img src="$theme_url/images/icons/info.png" class="info right" /></a> | |||
{% if feather.help %} | |||
<a href="{% admin "help&id="~feather.help %}" class="help emblem"><img src="$theme_url/images/icons/help.png" alt="help" /></a> | |||
{% endif %} | |||
${ "<a href=\"%s\">%s</a> v%s <span class=\"sub\">by %s</span>" | translate | format(feather.url, feather.name | translate(safename), feather.version, feather.author.link) } | |||
<div class="expand"> | |||
<div class="description"> | |||
$feather.description {# translation is done in the controller #} | |||
</div> | |||
<noscript><a class="enable_button" href="{% admin "disable&feather="~safename %}">${ "Disable" | translate }</a></noscript> | |||
</div> | |||
</li> | |||
{% endfor %} | |||
</ul> | |||
</div> | |||
<div class="disable feather right"> | |||
<h2>${ "Disabled" | translate }</h2> | |||
<ul class="extend"> | |||
{% for safename, feather in disabled_feathers | items %} | |||
<li class="$safename"> | |||
<a class="$safename info_link" href="javascript:void(0)"><img src="$theme_url/images/icons/info.png" class="info right" /></a> | |||
{% if feather.help %} | |||
<a href="{% admin "help&id="~feather.help %}" class="help emblem"><img src="$theme_url/images/icons/help.png" alt="help" /></a> | |||
{% endif %} | |||
${ "<a href=\"%s\">%s</a> v%s <span class=\"sub\">by %s</span>" | translate | format(feather.url, feather.name | translate(safename), feather.version, feather.author.link) } | |||
<div class="expand"> | |||
<div class="description"> | |||
$feather.description {# translation is done in the controller #} | |||
</div> | |||
<noscript><a class="disable_button" href="{% admin "enable&feather="~safename %}">${ "Enable" | translate }</a></noscript> | |||
</div> | |||
</li> | |||
{% endfor %} | |||
</ul> | |||
</div> | |||
<div class="clear tip_here"></div> | |||
<br /> | |||
<a class="button right" href="http://chyrp.net/extend/type/feather">${ "Get More Feathers ›" | translate }</a> | |||
{% endblock %} |
@@ -0,0 +1,60 @@ | |||
{% extends "layout.twig" %} | |||
{% block title %}${ "Site Configuration" | translate }{% endblock %} | |||
{% block content %} | |||
<form id="general_settings" class="split" action="{% admin "general_settings" %}" method="post" accept-charset="utf-8" enctype="multipart/form-data"> | |||
<fieldset> | |||
<p> | |||
<label for="name">${ "Site Name" | translate }</label> | |||
<input class="text" type="text" name="name" value="${ site.name | escape }" id="name" /> | |||
</p> | |||
<p> | |||
<label for="description">${ "Description" | translate }</label> | |||
<textarea name="description" rows="2" cols="40">${ site.description | escape(false, false) }</textarea> | |||
</p> | |||
<p> | |||
<label for="chyrp_url">${ "Chyrp URL" | translate }</label> | |||
<input class="text" type="text" name="chyrp_url" value="${ site.chyrp_url | escape }" id="chyrp_url" /> | |||
</p> | |||
<p> | |||
<label for="url"> | |||
${ "Alternate URL" | translate } | |||
<span class="sub">${ "(optional)" | translate }</span> | |||
</label> | |||
<input class="text" type="text" name="url" value="{% if site.url != site.chyrp_url %}${ site.url | escape }{% endif %}" id="url" /> | |||
<a href="{% admin "help&id=alternate_urls" %}" class="help emblem"><img src="$theme_url/images/icons/help.png" alt="help" /></a> | |||
<small> | |||
${ "Enter an alternate address here if you want your homepage URL to be different from the URL where Chyrp is normally available." | translate } | |||
</small> | |||
</p> | |||
<p> | |||
<label for="email">${ "Contact E-Mail Address" | translate }</label> | |||
<input class="text" type="text" name="email" value="${ site.email | escape }" id="email" /> | |||
</p> | |||
<p> | |||
<label for="timezone">${ "What time is it?" | translate }</label> | |||
<select name="timezone" id="timezone"> | |||
{% for zone in timezones %} | |||
<option value="$zone.name"${ zone.name | option_selected(site.timezone) }>${ zone.now | strftime("%m/%d/%y %H:%M" | translate) } — ${ zone.name | replace("_", " ") | replace("St ", "St. ") }</option> | |||
{% endfor %} | |||
</select> | |||
</p> | |||
<p> | |||
<label for="locale">${ "Language" | translate }</label> | |||
<select name="locale" id="locale"> | |||
{% for locale in locales %} | |||
<option value="$locale.code"${ locale.code | option_selected(site.locale) }>$locale.name</option> | |||
{% endfor %} | |||
<option value="en_US"${ "en_US" | option_selected(site.locale) }>English (US)</option> | |||
</select> | |||
</p> | |||
<p class="buttons"> | |||
<button type="submit" class="yay"><img src="$theme_url/images/icons/success.png" alt="success" />${ "Update" | translate }</button> | |||
</p> | |||
<input type="hidden" name="hash" value="$site.secure_hashkey" id="hash" /> | |||
</fieldset> | |||
</form> | |||
{% endblock %} |
@@ -0,0 +1,176 @@ | |||
{% extends "layout.twig" %} | |||
{% block title %}${ "Import" | translate }{% endblock %} | |||
{% block content %} | |||
<h2>Chyrp</h2> | |||
<form id="import_chyrp_form" class="split" action="{% admin "import_chyrp" %}" method="post" accept-charset="utf-8" enctype="multipart/form-data"> | |||
<fieldset> | |||
<p> | |||
<label for="posts_file">${ "Posts .atom File" | translate }</label> | |||
<input type="file" name="posts_file" value="" id="posts_file" /> | |||
</p> | |||
<p> | |||
<label for="pages_file">${ "Pages .atom File" | translate }</label> | |||
<input type="file" name="pages_file" value="" id="pages_file" /> | |||
</p> | |||
<p> | |||
<label for="groups_file">${ "Groups .yaml File" | translate }</label> | |||
<input type="file" name="groups_file" value="" id="groups_file" /> | |||
</p> | |||
<p> | |||
<label for="users_file">${ "Users .yaml File" | translate }</label> | |||
<input type="file" name="users_file" value="" id="users_file" /> | |||
</p> | |||
<p> | |||
<label for="media_url"> | |||
${ "What URL is used for embedded media?" | translate } | |||
<span class="sub">${ "(optional)" | translate }</span> | |||
</label> | |||
<input class="text" type="text" name="media_url" value="" id="media_url" /> | |||
<small> | |||
${ "Usually something like <code>http://example.com/uploads/</code>." | translate } | |||
</small> | |||
</p> | |||
${ trigger.call("import_choose") } | |||
<p class="buttons"> | |||
<button type="submit" class="yay"><img src="$theme_url/images/icons/success.png" alt="success" />${ "Import" | translate }</button> | |||
</p> | |||
<input type="hidden" name="hash" value="$site.secure_hashkey" id="hash" /> | |||
</fieldset> | |||
</form> | |||
<br /> | |||
<hr /> | |||
<br /> | |||
<h2>WordPress</h2> | |||
<form id="import_wordpress_form" class="split" action="{% admin "import_wordpress" %}" method="post" accept-charset="utf-8" enctype="multipart/form-data"> | |||
<fieldset> | |||
<p> | |||
<label for="xml_file">${ "eXtended .XML File" | translate }</label> | |||
<input type="file" name="xml_file" value="" id="xml_file" /> | |||
</p> | |||
<p> | |||
<label for="media_url"> | |||
${ "What URL is used for embedded media?" | translate } | |||
<span class="sub">${ "(optional)" | translate }</span> | |||
</label> | |||
<input class="text" type="text" name="media_url" value="" id="media_url" /> | |||
<small> | |||
${ "Usually something like <code>http://example.com/wp-content/uploads/</code>." | translate } | |||
</small> | |||
</p> | |||
<p class="buttons"> | |||
<button type="submit" class="yay"><img src="$theme_url/images/icons/success.png" alt="success" />${ "Import" | translate }</button> | |||
</p> | |||
<input type="hidden" name="hash" value="$site.secure_hashkey" id="hash" /> | |||
</fieldset> | |||
</form> | |||
<br /> | |||
<hr /> | |||
<br /> | |||
<h2>Tumblr</h2> | |||
<form id="import_tumblr_form" class="split" action="{% admin "import_tumblr" %}" method="post" accept-charset="utf-8"> | |||
<fieldset> | |||
<p> | |||
<label for="tumblr_url">${ "Your Tumblr URL" | translate }</label> | |||
<input class="text" type="text" name="tumblr_url" value="" id="tumblr_url" /> | |||
<small>${ "Note: Audio tumbles cannot be imported." | translate }</small> | |||
</p> | |||
<p class="buttons"> | |||
<button type="submit" class="yay"><img src="$theme_url/images/icons/success.png" alt="success" />${ "Import" | translate }</button> | |||
</p> | |||
<input type="hidden" name="hash" value="$site.secure_hashkey" id="hash" /> | |||
</fieldset> | |||
</form> | |||
<br /> | |||
<hr /> | |||
<br /> | |||
<h2>TextPattern</h2> | |||
<form id="import_textpattern_form" class="split" action="{% admin "import_textpattern" %}" method="post" accept-charset="utf-8"> | |||
<fieldset> | |||
<p> | |||
<label for="host">${ "Host" | translate }</label> | |||
<input class="text" type="text" name="host" value="localhost" id="host" /> | |||
</p> | |||
<p> | |||
<label for="username">${ "Username" | translate }</label> | |||
<input class="text" type="text" name="username" value="" id="username" /> | |||
</p> | |||
<p> | |||
<label for="password">${ "Password" | translate }</label> | |||
<input class="text" type="password" name="password" value="" id="password" /> | |||
</p> | |||
<p> | |||
<label for="database">${ "Database" | translate }</label> | |||
<input class="text" type="text" name="database" value="" id="database" /> | |||
</p> | |||
<p> | |||
<label for="prefix">${ "Table Prefix" | translate }</label> | |||
<input class="text" type="text" name="prefix" value="" id="prefix" /> | |||
<span class="sub">${ "(if any)" | translate }</span> | |||
</p> | |||
<p> | |||
<label for="media_url"> | |||
${ "What URL is used for embedded media?" | translate } | |||
<span class="sub">${ "(optional)" | translate }</span> | |||
</label> | |||
<input class="text" type="text" name="media_url" value="" id="media_url" /> | |||
<small> | |||
${ "Usually something like <code>http://example.com/images/</code>." | translate } | |||
</small> | |||
</p> | |||
<p class="buttons"> | |||
<button type="submit" class="yay"><img src="$theme_url/images/icons/success.png" alt="success" />${ "Import" | translate }</button> | |||
</p> | |||
<input type="hidden" name="hash" value="$site.secure_hashkey" id="hash" /> | |||
</fieldset> | |||
</form> | |||
<br /> | |||
<hr /> | |||
<br /> | |||
<h2>MovableType</h2> | |||
<form id="import_movabletype_form" class="split" action="{% admin "import_movabletype" %}" method="post" accept-charset="utf-8"> | |||
<fieldset> | |||
<p> | |||
<label for="host">${ "Host" | translate }</label> | |||
<input class="text" type="text" name="host" value="localhost" id="host" /> | |||
</p> | |||
<p> | |||
<label for="username">${ "Username" | translate }</label> | |||
<input class="text" type="text" name="username" value="" id="username" /> | |||
</p> | |||
<p> | |||
<label for="password">${ "Password" | translate }</label> | |||
<input class="text" type="password" name="password" value="" id="password" /> | |||
</p> | |||
<p> | |||
<label for="database">${ "Database" | translate }</label> | |||
<input class="text" type="text" name="database" value="" id="database" /> | |||
</p> | |||
<p> | |||
<label for="media_url"> | |||
${ "What URL is used for embedded media?" | translate } | |||
<span class="sub">${ "(optional)" | translate }</span> | |||
</label> | |||
<input class="text" type="text" name="media_url" value="" id="media_url" /> | |||
<small> | |||
${ "Usually something like <code>http://example.com/images/</code>." | translate } | |||
</small> | |||
</p> | |||
<p class="buttons"> | |||
<button type="submit" class="yay"><img src="$theme_url/images/icons/success.png" alt="success" />${ "Import" | translate }</button> | |||
</p> | |||
<input type="hidden" name="hash" value="$site.secure_hashkey" id="hash" /> | |||
</fieldset> | |||
</form> | |||
${ trigger.call("import_choose") } | |||
{% endblock %} |
@@ -0,0 +1,47 @@ | |||
{% extends "layout.twig" %} | |||
{% block title %}${ "Manage Groups" | translate }{% endblock %} | |||
{% block content %} | |||
<h2>${ "Need more detail?" | translate }</h2> | |||
<form class="detail" action="index.php" method="get" accept-charset="utf-8"> | |||
<fieldset> | |||
<input type="hidden" name="action" value="manage_groups" /> | |||
{% if visitor.group.can("add_group") %} | |||
<a href="{% admin "new_group" %}" class="button yay right"> | |||
<img src="$theme_url/images/icons/add.png" alt="add" /> ${ "New Group" | translate } | |||
</a> | |||
{% endif %} | |||
<div class="pad"> | |||
<h3>${ "Search all groups for user…" | translate }</h3> | |||
<input class="text" type="text" name="search" value="${ GET.search | escape }" id="search" /> <button type="submit" class="inline">${ "Search →" | translate }</button> | |||
</div> | |||
</fieldset> | |||
</form> | |||
<br /> | |||
<h2>${ "Groups" | translate }</h2> | |||
{% for group in groups.paginated %} | |||
<div class="box"> | |||
<h1> | |||
<span class="right"> | |||
${ group.edit_link('<img src="'~ theme_url ~'/images/icons/edit.png" alt="edit" /> '~("edit" | translate)) } | |||
${ group.delete_link('<img src="'~ theme_url ~'/images/icons/delete.png" alt="delete" /> '~("delete" | translate)) } | |||
</span> | |||
{% if group.id == site.guest_group %} | |||
${ "“%s” is the group for guests." | translate | format(group.name) } | |||
{% elseif group.id == site.default_group %} | |||
${ "“%s” is the default group and has %d <a href=\"%s\">member</a>." | translate_plural("“%s” is the default group and has %d <a href=\"%s\">members</a>.", group.size) | format(group.name, group.size, route.url("/admin/?action=manage_users&query=group%3A"~group.name)) } | |||
{% else %} | |||
${ "“%s” has %d <a href=\"%s\">member</a>." | translate_plural("“%s” has %d <a href=\"%s\">members</a>.", group.size) | format(group.name, group.size, route.url("/admin/?action=manage_users&query=group%3A"~group.name)) } | |||
{% endif %} | |||
</h1> | |||
</div> | |||
{% endfor %} | |||
{% if groups.paginated and groups.pages > 1 %} | |||
<div class="pagination"> | |||
$groups.next_link | |||
$groups.prev_link | |||
<span class="pages">${ "Page %d of %d" | translate | format(groups.page, groups.pages) }</span> | |||
</div> | |||
{% endif %} | |||
{% endblock %} |
@@ -0,0 +1,91 @@ | |||
{% extends "layout.twig" %} | |||
{% block title %}${ "Manage Pages" | translate }{% endblock %} | |||
{% block content %} | |||
<h2>${ "Need more detail?" | translate }</h2> | |||
<form class="detail" action="index.php" method="get" accept-charset="utf-8"> | |||
<fieldset> | |||
<input type="hidden" name="action" value="manage_pages" /> | |||
<div class="pad"> | |||
<h3> | |||
${ "Search…" | translate } | |||
<a href="{% admin "help&id=filtering_results" %}" class="help emblem"><img src="$theme_url/images/icons/help.png" alt="help" /></a> | |||
</h3> | |||
<input class="text" type="text" name="query" value="${ GET.query | escape }" id="query" /> <button type="submit" class="inline">${ "Search →" | translate }</button> | |||
</div> | |||
</fieldset> | |||
</form> | |||
<br /> | |||
<h2>{% if GET.query %}${ "Search Results" | translate }{% else %}${ "Last 25 Pages" | translate }{% endif %}</h2> | |||
<table border="0" cellspacing="0" cellpadding="0" class="wide"> | |||
<thead> | |||
<tr class="head"> | |||
<th>${ "Title" | translate }</th> | |||
<th>${ "Created" | translate }</th> | |||
<th>${ "Last Updated" | translate }</th> | |||
<th>${ "Author" | translate }</th> | |||
${ trigger.call("manage_pages_column_header") } | |||
<th colspan="2">${ "Controls" | translate }</th> | |||
</tr> | |||
</thead> | |||
<tbody> | |||
{% for page in pages.paginated %} | |||
<tr id="page_$page.id" class="page{% if loop.last %} last{% endif %}"> | |||
<td class="main"><a href="$page.url">${ page.title | truncate }</a></td> | |||
<td>${ page.created_at | strftime }</td> | |||
<td>{% if page.updated %}${ page.updated_at | strftime }{% else %}<span class="sub">${ "never" | translate }</span>{% endif %}</td> | |||
<td>$page.user.login</td> | |||
${ trigger.call("manage_pages_column", page) } | |||
{% if page.editable and page.deletable %} | |||
<td class="controls">${ page.edit_link('<img src="'~ theme_url ~'/images/icons/edit.png" alt="edit" /> '~("edit" | translate)) }</td> | |||
<td class="controls">${ page.delete_link('<img src="'~ theme_url ~'/images/icons/delete.png" alt="delete" /> '~("delete" | translate)) }</td> | |||
{% else %} | |||
${ page.edit_link('<img src="'~ theme_url ~'/images/icons/edit.png" alt="edit" /> '~("edit" | translate), '<td class="controls" colspan="2">', '</td>') } | |||
${ page.delete_link('<img src="'~ theme_url ~'/images/icons/delete.png" alt="delete" /> '~("delete" | translate), '<td class="controls" colspan="2">', '</td>') } | |||
{% endif %} | |||
</tr> | |||
{% else %} | |||
<tr class="last"> | |||
<td colspan="6" class="center"><span class="sub">${ "(none)" | translate }</span></td> | |||
</tr> | |||
{% endfor %} | |||
</tbody> | |||
</table> | |||
{% if pages.paginated and pages.pages > 1 %} | |||
<br /> | |||
<div class="pagination"> | |||
$pages.next_link | |||
$pages.prev_link | |||
<span class="pages">${ "Page %d of %d" | translate | format(pages.page, pages.pages) }</span> | |||
</div> | |||
{% endif %} | |||
{% if pages.total %} | |||
<br /> | |||
<h2>${ "Reorder Pages" | translate }</h2> | |||
<form id="reorder_pages" action="{% admin "reorder_pages" %}" method="post"> | |||
<ul class="sort_pages"> | |||
{% for item in theme.pages_list %} | |||
<li class="page-item" id="page_list_$item.page.id"> | |||
<noscript><input type="text" size="2" name="list_order[$item.page.id]" value="$item.page.list_order" class="center" /></noscript> | |||
<div>$item.page.title</div> | |||
{% if item.has_children %}<ul>{% endif %} | |||
{% if not item.has_children %}</li>{% endif %} | |||
{% for ul, li in item.end_tags %} | |||
$ul | |||
$li | |||
{% endfor %} | |||
{% endfor %} | |||
</ul> | |||
<noscript> | |||
<div class="buttons"> | |||
<button type="submit" class="yay"> | |||
<img src="$theme_url/images/icons/success.png" alt="success" />${ "Reorder" | translate } | |||
</button> | |||
</div> | |||
</noscript> | |||
</form> | |||
{% endif %} | |||
{% endblock %} |
@@ -0,0 +1,74 @@ | |||
{% extends "layout.twig" %} | |||
{% block title %}${ "Manage Posts" | translate }{% endblock %} | |||
{% block content %} | |||
<h2>${ "Need more detail?" | translate }</h2> | |||
<form class="detail" action="index.php" method="get" accept-charset="utf-8"> | |||
<fieldset> | |||
<input type="hidden" name="action" value="manage_posts" /> | |||
<div class="left pad margin-right"> | |||
<h3> | |||
${ "Search…" | translate } | |||
<a href="{% admin "help&id=filtering_results" %}" class="help emblem"><img src="$theme_url/images/icons/help.png" alt="help" /></a> | |||
</h3> | |||
<input class="text" type="text" name="query" value="${ GET.query | escape }" id="query" /> <button type="submit" class="inline">${ "Search →" | translate }</button> | |||
</div> | |||
<div class="left pad"> | |||
<h3>${ "Browse by month:" | translate }</h3> | |||
<select name="month"> | |||
<option value="">----------</option> | |||
{% for archive in theme.archives_list %} | |||
<option value="${ archive.when | strftime("%Y-%m") }"${ GET.month | option_selected(archive.when | strftime("%Y-%m")) }>${ archive.when | strftime("%B %Y") } ($archive.count)</option> | |||
{% endfor %} | |||
</select> | |||
<button type="submit" class="inline">${ "Show →" | translate }</button> | |||
</div> | |||
<div class="clear"></div> | |||
</fieldset> | |||
</form> | |||
<br /> | |||
<h2>{% if GET.query %}${ "Search Results" | translate }{% else %}${ "Last 25 Posts" | translate }{% endif %}</h2> | |||
<table border="0" cellspacing="0" cellpadding="0" class="wide"> | |||
<thead> | |||
<tr class="head"> | |||
<th>${ "Title" | translate }</th> | |||
<th>${ "Posted" | translate }</th> | |||
<th>${ "Status" | translate }</th> | |||
<th>${ "Author" | translate }</th> | |||
${ trigger.call("manage_posts_column_header") } | |||
<th colspan="2">${ "Controls" | translate }</th> | |||
</tr> | |||
</thead> | |||
<tbody> | |||
{% for post in posts.paginated %} | |||
<tr id="post_$post.id" class="post $post.status_class{% if loop.last %} last{% endif %}"> | |||
<td class="main"><a href="$post.url">${ post.title | truncate }</a></td> | |||
<td>${ post.created_at | strftime }</td> | |||
<td>$post.status_name</td> | |||
<td>$post.user.login</td> | |||
${ trigger.call("manage_posts_column", post) } | |||
{% if post.editable and post.deletable %} | |||
<td class="controls">${ post.edit_link('<img src="'~ theme_url ~'/images/icons/edit.png" alt="edit" /> '~("edit" | translate)) }</td> | |||
<td class="controls">${ post.delete_link('<img src="'~ theme_url ~'/images/icons/delete.png" alt="delete" /> '~("delete" | translate)) }</td> | |||
{% elseif post.editable or post.deletable %} | |||
${ post.edit_link('<img src="'~ theme_url ~'/images/icons/edit.png" alt="edit" /> '~("edit" | translate), '<td class="controls" colspan="2">', '</td>') } | |||
${ post.delete_link('<img src="'~ theme_url ~'/images/icons/delete.png" alt="delete" /> '~("delete" | translate), '<td class="controls" colspan="2">', '</td>') } | |||
{% endif %} | |||
</tr> | |||
{% else %} | |||
<tr class="last"> | |||
<td colspan="6" class="center"><span class="sub">${ "(none)" | translate }</span></td> | |||
</tr> | |||
{% endfor %} | |||
</tbody> | |||
</table> | |||
{% if posts.paginated and posts.pages > 1 %} | |||
<br /> | |||
<div class="pagination"> | |||
$posts.next_link | |||
$posts.prev_link | |||
<span class="pages">${ "Page %d of %d" | translate | format(posts.page, posts.pages) }</span> | |||
</div> | |||
{% endif %} | |||
{% endblock %} |
@@ -0,0 +1,74 @@ | |||
{% extends "layout.twig" %} | |||
{% block title %}${ "Manage Users" | translate }{% endblock %} | |||
{% block content %} | |||
<h2>${ "Need more detail?" | translate }</h2> | |||
<form class="detail" action="index.php" method="get" accept-charset="utf-8"> | |||
<fieldset> | |||
<input type="hidden" name="action" value="manage_users" /> | |||
{% if visitor.group.can("add_user") %} | |||
<a href="{% admin "new_user" %}" class="button yay right"> | |||
<img src="$theme_url/images/icons/add.png" alt="add" /> ${ "New User" | translate } | |||
</a> | |||
{% endif %} | |||
<div class="pad"> | |||
<h3> | |||
${ "Search…" | translate } | |||
<a href="{% admin "help&id=filtering_results" %}" class="help emblem"><img src="$theme_url/images/icons/help.png" alt="help" /></a> | |||
</h3> | |||
<input class="text" type="text" name="query" value="${ GET.query | escape }" id="query" /> <button type="submit" class="inline">${ "Search →" | translate }</button> | |||
</div> | |||
</fieldset> | |||
</form> | |||
<br /> | |||
<h2>${ "Users" | translate }</h2> | |||
<table border="0" cellspacing="0" cellpadding="0" class="wide"> | |||
<thead> | |||
<tr class="head"> | |||
<th>${ "Name" | translate }</th> | |||
<th>${ "Group" | translate }</th> | |||
<th>${ "Joined" | translate }</th> | |||
<th>${ "Website" | translate }</th> | |||
${ trigger.call("manage_users_column_header") } | |||
<th colspan="2">${ "Controls" | translate }</th> | |||
</tr> | |||
</thead> | |||
<tbody> | |||
{% for user in users.paginated %} | |||
<tr id="user_$user.id" class="user{% if loop.last %} last{% endif %}"> | |||
<td class="main"> | |||
{% if user.full_name != "" %} | |||
<a href="mailto:$user.email">$user.full_name</a> <span class="sub">($user.login)</span> | |||
{% else %} | |||
<a href="mailto:$user.email">$user.login</a> | |||
{% endif %} | |||
</td> | |||
<td>${ user.group.name | escape }</td> | |||
<td>${ user.joined_at | strftime }</td> | |||
<td>{% if user.website != "" %}<a href="$user.website">$user.website</a>{% endif %}</td> | |||
${ trigger.call("manage_users_column", user) } | |||
{% if user.editable and user.deletable %} | |||
<td class="controls">${ user.edit_link('<img src="'~ theme_url ~'/images/icons/edit.png" alt="edit" /> '~("edit" | translate)) }</td> | |||
<td class="controls">${ user.delete_link('<img src="'~ theme_url ~'/images/icons/delete.png" alt="delete" /> '~("delete" | translate)) }</td> | |||
{% else %} | |||
${ user.edit_link('<img src="'~ theme_url ~'/images/icons/edit.png" alt="edit" /> '~("edit" | translate), '<td class="controls" colspan="2">', '</td>') } | |||
${ user.delete_link('<img src="'~ theme_url ~'/images/icons/delete.png" alt="delete" /> '~("delete" | translate), '<td class="controls" colspan="2">', '</td>') } | |||
{% endif %} | |||
</tr> | |||
{% else %} | |||
<tr class="last"> | |||
<td colspan="5" class="center"><span class="sub">${ "(none)" | translate }</span></td> | |||
</tr> | |||
{% endfor %} | |||
</tbody> | |||
</table> | |||
{% if users.paginated and users.pages > 1 %} | |||
<br /> | |||
<div class="pagination"> | |||
$users.next_link | |||
$users.prev_link | |||
<span class="pages">${ "Page %d of %d" | translate | format(users.page, users.pages) }</span> | |||
</div> | |||
{% endif %} | |||
{% endblock %} |
@@ -0,0 +1,71 @@ | |||
{% extends "layout.twig" %} | |||
{% block title %}${ "Modules" | translate }{% endblock %} | |||
{% block content %} | |||
<div class="enable module left"> | |||
<h2>${ "Enabled" | translate }</h2> | |||
<ul class="extend"> | |||
{% for safename, module in enabled_modules | items %} | |||
<li class="${ module.classes | join(" ") }" id="module_$safename"> | |||
<a class="$safename info_link" href="javascript:void(0)"><img src="$theme_url/images/icons/info.png" class="info right" /></a> | |||
{% if module.help %} | |||
<a href="{% admin "help&id="~module.help %}" class="help emblem"><img src="$theme_url/images/icons/help.png" alt="help" /></a> | |||
{% endif %} | |||
${ "<a href=\"%s\">%s</a> v%s <span class=\"sub\">by %s</span>" | translate | format(module.url, module.name | translate(safename), module.version, module.author.link) } | |||
<div class="expand"> | |||
<div class="description{% if module.dependencies_needed %} expanded{% endif %}"> | |||
$module.description {# translation is done in the controller #} | |||
<p class="dependencies_message"{% if not module.dependencies_needed %} style="display: none"{% endif %}> | |||
${ "This Module requires the following Modules to be enabled:" | translate } | |||
</p> | |||
<ul class="dependencies_list"{% if not module.dependencies_needed %} style="display: none"{% endif %}> | |||
{% for dependency in module.dependencies_needed %} | |||
<li class="$dependency">$dependency</li> | |||
{% endfor %} | |||
</ul> | |||
</div> | |||
{% if not module.dependencies_needed %} | |||
<noscript><a class="enable_button" href="{% admin "disable&module="~safename %}">${ "Disable" | translate }</a></noscript> | |||
{% endif %} | |||
</div> | |||
</li> | |||
{% endfor %} | |||
</ul> | |||
</div> | |||
<div class="disable module right"> | |||
<h2>${ "Disabled" | translate }</h2> | |||
<ul class="extend"> | |||
{% for safename, module in disabled_modules | items %} | |||
<li class="${ module.classes | join(" ") }" id="module_$safename"> | |||
<a class="$safename info_link" href="javascript:void(0)"><img src="$theme_url/images/icons/info.png" class="info right" /></a> | |||
{% if module.help %} | |||
<a href="{% admin "help&id="~module.help %}" class="help emblem"><img src="$theme_url/images/icons/help.png" alt="help" /></a> | |||
{% endif %} | |||
${ "<a href=\"%s\">%s</a> v%s <span class=\"sub\">by %s</span>" | translate | format(module.url, module.name | translate(safename), module.version, module.author.link) } | |||
<div class="expand"> | |||
<div class="description{% if module.dependencies_needed %} expanded{% endif %}"> | |||
$module.description {# translation is done in the controller #} | |||
<p class="dependencies_message"{% if not module.dependencies_needed %} style="display: none"{% endif %}> | |||
${ "This Module requires the following Modules to be enabled:" | translate } | |||
</p> | |||
<ul class="dependencies_list"{% if not module.dependencies_needed %} style="display: none"{% endif %}> | |||
{% for dependency in module.dependencies_needed %} | |||
<li class="$dependency">$dependency</li> | |||
{% endfor %} | |||
</ul> | |||
</div> | |||
{% if not module.dependencies_needed %} | |||
<noscript><a class="disable_button" href="{% admin "enable&module="~safename %}">${ "Enable" | translate }</a></noscript> | |||
{% endif %} | |||
</div> | |||
</li> | |||
{% endfor %} | |||
</ul> | |||
</div> | |||
<div class="clear"></div> | |||
<br /> | |||
<a class="button right" href="http://chyrp.net/extend/type/module">${ "Get More Modules ›" | translate }</a> | |||
{% endblock %} |
@@ -0,0 +1,34 @@ | |||
{% extends "layout.twig" %} | |||
{% block title %}${ "New Group" | translate }{% endblock %} | |||
{% block content %} | |||
<h1>${ "New Group" | translare }</h1> | |||
<form id="new_group" class="split" action="{% admin "add_group" %}" method="post" accept-charset="utf-8" enctype="multipart/form-data"> | |||
<fieldset> | |||
<p> | |||
<label for="name">${ "Name" | translate }</label> | |||
<input class="text" type="text" name="name" value="" id="name" /> | |||
</p> | |||
<h2>${ "Permissions" | translate }</h2> | |||
<p id="toggler"> | |||
</p> | |||
<hr class="js_enabled" /> | |||
{% for permission in permissions %} | |||
<p> | |||
<label for="permission_$permission.id">${ permission.name | translate }</label> | |||
<input class="checkbox" type="checkbox" name="permissions[$permission.id]" id="permission_$permission.id" /> | |||
</p> | |||
{% endfor %} | |||
<br /> | |||
<p> | |||
<button type="submit" class="yay"><img src="$theme_url/images/icons/success.png" alt="success" />${ "Add Group" | translate }</button> | |||
</p> | |||
<input type="hidden" name="hash" value="$site.secure_hashkey" id="hash" /> | |||
</fieldset> | |||
</form> | |||
{% endblock %} |
@@ -0,0 +1,58 @@ | |||
{% extends "layout.twig" %} | |||
{% block title %}${ "Add User" | translate }{% endblock %} | |||
{% block content %} | |||
<h1>${ "New User" | translare }</h1> | |||
<form id="new_user" class="split" action="{% admin "add_user" %}" method="post" accept-charset="utf-8" enctype="multipart/form-data"> | |||
<fieldset> | |||
<h2>${ "Settings" | translate }</h2> | |||
<p> | |||
<label for="login">${ "Login" | translate }</label> | |||
<input class="text" type="text" name="login" value="" id="full_name" /> | |||
</p> | |||
<p> | |||
<label for="group">${ "Group" | translate }</label> | |||
<select name="group" id="group"> | |||
<option value="$default_group.id">$default_group.name</option> | |||
{% for group in groups %} | |||
<option value="$group.id">$group.name</option> | |||
{% endfor %} | |||
</select> | |||
</p> | |||
<p> | |||
<label for="password1">${ "Password" | translate }</label> | |||
<input class="text" type="password" name="password1" value="" id="password1" /> | |||
</p> | |||
<p> | |||
<label for="password2">${ "Confirm" | translate }</label> | |||
<input class="text" type="password" name="password2" value="" id="password2" /> | |||
</p> | |||
<h2>${ "Information" | translate }</h2> | |||
<p> | |||
<label for="full_name">${ "Full Name" | translate }</label> | |||
<input class="text" type="text" name="full_name" value="" id="full_name" /> | |||
</p> | |||
<p> | |||
<label for="email">${ "E-Mail" | translate }</label> | |||
<input class="text" type="text" name="email" value="" id="email" /> | |||
</p> | |||
<p> | |||
<label for="website">${ "Website" | translate }</label> | |||
<input class="text" type="text" name="website" value="" id="website" /> | |||
</p> | |||
${ trigger.call("new_user_fields") } | |||
<br /> | |||
<p> | |||
<button type="submit" class="yay"><img src="$theme_url/images/icons/success.png" alt="success" />${ "Add User" | translate }</button> | |||
</p> | |||
<input type="hidden" name="hash" value="$site.secure_hashkey" id="hash" /> | |||
</fieldset> | |||
</form> | |||
{% endblock %} |
@@ -0,0 +1,50 @@ | |||
{% extends "layout.twig" %} | |||
{% block title %}${ "Route Settings" | translate }{% endblock %} | |||
{% block content %} | |||
<form id="route_settings" class="split" action="{% admin "route_settings" %}" method="post" accept-charset="utf-8" enctype="multipart/form-data"> | |||
<fieldset> | |||
<p> | |||
<label for="clean_urls">${ "Clean URLs?" | translate }</label> | |||
<input type="checkbox" name="clean_urls" id="clean_urls"${ site.clean_urls | checked } /> | |||
<span class="sub">${ "(recommended)" | translate }</span> | |||
<small> | |||
${ "Gives your site prettier urls." | translate }<br /> | |||
${ "Requires .htaccess support (pretty common). If you're unsure, it's safe to test and find out. Just come back and disable it if it breaks your site." | translate } | |||
</small> | |||
</p> | |||
<p> | |||
<label for="post_url"> | |||
${ "Post View URL" | translate } | |||
<span class="sub">${ "(requires clean URLs)" | translate }</span> | |||
</label> | |||
<input class="text code" type="text" name="post_url" value="${ site.post_url | escape }" size="30" id="post_url" /> | |||
</p> | |||
<div class="small"> | |||
<strong>${ "Syntax:" | translate }</strong> | |||
<ul> | |||
<li><code>(year)</code>: ${ "Year submitted" | translate } <span class="sub">${ "(ex. 2007)" | translate }</span></li> | |||
<li><code>(month)</code>: ${ "Month submitted" | translate } <span class="sub">${ "(ex. 12)" | translate }</span></li> | |||
<li><code>(day)</code>: ${ "Day submitted" | translate } <span class="sub">${ "(ex. 25)" | translate }</span></li> | |||
<li><code>(hour)</code>: ${ "Hour submitted" | translate } <span class="sub">${ "(ex. 03)" | translate }</span></li> | |||
<li><code>(minute)</code>: ${ "Minute submitted" | translate } <span class="sub">${ "(ex. 59)" | translate }</span></li> | |||
<li><code>(second)</code>: ${ "Second submitted" | translate } <span class="sub">${ "(ex. 30)" | translate }</span></li> | |||
<li><code>(id)</code>: ${ "Post ID" | translate }</li> | |||
<li><code>(author)</code>: ${ "Post author (username)" | translate } <span class="sub">${ "(ex. Alex)" | translate }</span></li> | |||
<li><code>(clean)</code>: ${ "The non-unique sanitized name" | translate } <span class="sub">${ "(ex. this_is_clean)" | translate }</span></li> | |||
<li><code>(url)</code>: ${ "The unique form of (clean)" | translate } <span class="sub">${ "(ex. this_one_is_taken_2)" | translate }</span></li> | |||
<li><code>(feather)</code>: ${ "The post's feather" | translate } <span class="sub">${ "(ex. text)" | translate }</span></li> | |||
<li><code>(feathers)</code>: ${ "The plural form of the post's feather" | translate } <span class="sub">${ "(ex. links)" | translate }</span></li> | |||
${ trigger.call("post_view_url_settings") } | |||
</ul> | |||
</div> | |||
<p class="buttons"> | |||
<button type="submit" class="yay"><img src="$theme_url/images/icons/success.png" alt="success" />${ "Update" | translate }</button> | |||
</p> | |||
<input type="hidden" name="hash" value="$site.secure_hashkey" id="hash" /> | |||
</fieldset> | |||
</form> | |||
{% endblock %} |
@@ -0,0 +1,55 @@ | |||
{% extends "layout.twig" %} | |||
{% block title %}${ "Themes" | translate }{% endblock %} | |||
{% block content %} | |||
{% for theme in themes %} | |||
<div class="theme{% if theme.name == site.theme %} current{% endif %}{% if loop.index % 2 == 0 %} last{% endif %} box left"> | |||
<a href="{% admin "change_theme&theme=" ~ theme.name %}" class="link"> | |||
<span>${ theme.info.name | translate(theme.name) | escape }</span> | |||
{% if theme.screenshot %} | |||
<img src="$theme.screenshot" alt="${ ('"' ~ theme.info.name ~ '"') | escape } screenshot" class="screenshot" width="285" /> | |||
{% endif %} | |||
</a> | |||
{% if theme.screenshot %} | |||
<br /> | |||
{% endif %} | |||
<small>${ "v%s by %s" | translate | format(theme.info.version, theme.info.author.link) }</small> | |||
<br /> | |||
<br /> | |||
{% if theme.name == site.theme %} | |||
<span class="button yay center"><img src="$theme_url/images/icons/success.png" alt="current theme" />${ "Current Theme" | translate }</span> | |||
{% else %} | |||
<a class="button center" href="{% admin "change_theme&theme=" ~ theme.name %}"><img src="$theme_url/images/icons/appearance.png" alt="Select" />${ "Select" | translate }</a> | |||
<a class="button center${ preview | selected(theme.name, true) }" href="{% admin "preview_theme&theme=" ~ theme.name %}"><img src="$theme_url/images/icons/magnifier.png" alt="Preview" />${ "Preview" | translate }</a> | |||
{% endif %} | |||
</div> | |||
{% endfor %} | |||
<div class="clear"></div> | |||
<br /> | |||
<h1>${ "Admin Themes" | translate }</h1> | |||
{% for theme in admin_themes %} | |||
<div class="theme{% if theme.name == admin_theme %} current{% endif %}{% if loop.index % 2 == 0 %} last{% endif %} box left"> | |||
<a href="{% admin "change_theme&theme=" ~ theme.name %}" class="link"> | |||
<span>${ theme.info.name | translate(theme.name) | escape }</span> | |||
{% if theme.screenshot %} | |||
<img src="$theme.screenshot" alt="${ ('"' ~ theme.info.name ~ '"') | escape } screenshot" class="screenshot" width="285" /> | |||
{% endif %} | |||
</a> | |||
{% if theme.screenshot %} | |||
<br /> | |||
{% endif %} | |||
<small>${ "v%s by %s" | translate | format(theme.info.version, theme.info.author.link) }</small> | |||
<br /> | |||
<br /> | |||
{% if theme.name == admin_theme %} | |||
<span class="button yay center"><img src="$theme_url/images/icons/success.png" alt="current theme" />${ "Current Theme" | translate }</span> | |||
{% else %} | |||
<a class="button center" href="{% admin "change_admin_theme&theme=" ~ theme.name %}"><img src="$theme_url/images/icons/appearance.png" alt="Select" />${ "Select" | translate }</a> | |||
{% endif %} | |||
</div> | |||
{% endfor %} | |||
<div class="clear"></div> | |||
<br /> | |||
<a class="button right" href="http://chyrp.net/extend/type/theme">${ "Get More Themes ›" | translate }</a> | |||
{% endblock %} |
@@ -0,0 +1,37 @@ | |||
{% extends "layout.twig" %} | |||
{% block title %}${ "User Settings" | translate }{% endblock %} | |||
{% block content %} | |||
<form id="user_settings" class="split" action="{% admin "user_settings" %}" method="post" accept-charset="utf-8" enctype="multipart/form-data"> | |||
<fieldset> | |||
<p> | |||
<label for="can_register">${ "Registration" | translate }</label> | |||
<input class="checkbox" type="checkbox" name="can_register" id="can_register"${ site.can_register | checked } /> | |||
${ "Allow people to register" | translate } | |||
</p> | |||
<p> | |||
<label for="default_group">${ "Default User Group" | translate }</label> | |||
<select name="default_group" id="default_group"> | |||
{% for group in groups %} | |||
<option value="$group.id"${ group.id | option_selected(site.default_group) }>$group.name</option> | |||
{% endfor %} | |||
</select> | |||
</p> | |||
<p> | |||
<label for="guest_group">${ "“Guest” Group" | translate }</label> | |||
<select name="guest_group" id="guest_group"> | |||
{% for group in groups %} | |||
<option value="$group.id"${ group.id | option_selected(site.guest_group) }>$group.name</option> | |||
{% endfor %} | |||
</select> | |||
</p> | |||
<p class="buttons"> | |||
<button type="submit" class="yay"><img src="$theme_url/images/icons/success.png" alt="success" />${ "Update" | translate }</button> | |||
</p> | |||
<input type="hidden" name="hash" value="$site.secure_hashkey" id="hash" /> | |||
</fieldset> | |||
</form> | |||
{% endblock %} |
@@ -0,0 +1,13 @@ | |||
{% extends "layout.twig" %} | |||
{% block title %}${ "Add Page" | translate }{% endblock %} | |||
{% block content %} | |||
<form id="write_form" action="{% admin "add_page" %}" method="post" accept-charset="utf-8" enctype="multipart/form-data"> | |||
<fieldset> | |||
{% include "partials/page_fields.twig" %} | |||
<input type="hidden" name="hash" value="$site.secure_hashkey" id="hash" /> | |||
</fieldset> | |||
</form> | |||
{% endblock %} |
@@ -0,0 +1,14 @@ | |||
{% extends "layout.twig" %} | |||
{% block title %}${ "Write" | translate }{% endblock %} | |||
{% block content %} | |||
<form id="write_form" class="${ feather.safename | escape(true) }" action="{% admin "add_post" %}" method="post" accept-charset="utf-8" enctype="multipart/form-data"> | |||
<fieldset> | |||
{% include "partials/post_fields.twig" %} | |||
<input type="hidden" name="feather" value="${ feather.safename | escape(true) }" id="feather" /> | |||
<input type="hidden" name="hash" value="$site.secure_hashkey" id="hash" /> | |||
</fieldset> | |||
</form> | |||
{% endblock %} |
@@ -0,0 +1,49 @@ | |||
${ trigger.call("before_page_fields") } | |||
<p> | |||
<label for="title">${ "Title" | translate }</label> | |||
<input class="text" type="text" name="title" value="${ page.title | escape }" id="title" /> | |||
</p> | |||
<p> | |||
<label for="body">${ "Body" | translate }</label> | |||
<textarea class="wide preview_me" rows="12" name="body" id="body" cols="50">${ page.body | escape }</textarea> | |||
</p> | |||
${ trigger.call("after_page_fields") } | |||
<div class="buttons"> | |||
<button type="submit" class="yay right" accesskey="s"> | |||
{% if route.action == "write_page" %} | |||
<img src="$theme_url/images/icons/success.png" alt="success" />${ "Publish" | translate } | |||
{% else %} | |||
<img src="$theme_url/images/icons/success.png" alt="success" />${ "Save" | translate } | |||
{% endif %} | |||
</button> | |||
</div> | |||
<div class="clear"></div> | |||
<noscript><br /></noscript> | |||
<div id="more_options" class="more_options js_disabled"> | |||
<p> | |||
<label for="show_in_list">${ "Show in pages list?" | translate }</label> | |||
<input type="checkbox" name="show_in_list" id="show_in_list" tabindex="3"{% if page.show_in_list or route.action == "write_page" %} checked="checked"{% endif %} /> | |||
</p> | |||
<p> | |||
<label for="slug">${ "Slug" | translate }</label> | |||
<input class="text" type="text" name="slug" value="${ page.slug | escape }" id="slug" /> | |||
</p> | |||
<p> | |||
<label for="parent_id">${ "Parent" | translate }</label> | |||
<select name="parent_id" id="parent_id"> | |||
<option value="0">----------</option> | |||
{% for item in theme.pages_list(0, page.id) %} | |||
<option value="$item.page.id"${ page.parent_id | option_selected(item.page.id) }>${ "—" | repeat(item.page.depth - 1) } ${ item.page.title | escape }</option> | |||
{% endfor %} | |||
</select> | |||
</p> | |||
{% if route.action == "write_page" %} | |||
${ trigger.call("new_page_options") } | |||
{% else %} | |||
${ trigger.call("edit_page_options", page) } | |||
{% endif %} | |||
<div class="clear"></div> | |||
</div> | |||
</div> |
@@ -0,0 +1,133 @@ | |||
${ trigger.call("before_post_fields", feather) } | |||
{% for field in feather.fields %} | |||
<p> | |||
<label for="${ field.attr }_field"> | |||
$field.label | |||
{% if field.optional %} | |||
<span class="sub">${ "(optional)" | translate }</span> | |||
{% endif %} | |||
{% if field.help %} | |||
<span class="sub"> | |||
<a href="{% admin "help&id="~field.help %}" class="help emblem"><img src="$theme_url/images/icons/help.png" alt="help" /></a> | |||
</span> | |||
{% endif %} | |||
{% if field.note %} | |||
<span class="sub">$field.note</span> | |||
{% endif %} | |||
</label> | |||
{% if field.type == "text" or field.type == "file" %} | |||
<input class="$field.type{% if field.classes %} ${ field.classes | join(" ") }{% endif %}" type="$field.type" name="$field.attr" value="{% if not field.no_value %}${ field.value | fallback(post[field.attr] | escape(true, false)) }{% endif %}" id="${ field.attr }_field" /> | |||
{% if post.filename and route.action == "edit_post" %} | |||
<em>Current file name: <strong>${ post.filename | escape('') }</strong></em> | |||
{% endif %} | |||
{% elseif field.type == "text_block" %} | |||
<textarea class="wide{% if field.classes %} ${ field.classes | join(" ") }{% endif %}" rows="${ field.rows | fallback(12) }" name="$field.attr" id="${ field.attr }_field" cols="50">{% if not field.no_value %}${ field.value | fallback(post[field.attr] | escape(false, false)) }{% endif %}</textarea> | |||
{% elseif field.type == "checkbox" %} | |||
<input class="$field.type{% if field.classes %} ${ field.classes | join(" ") }{% endif %}" type="$field.type" name="$field.attr"{% if field.checked %}checked="checked"{% endif %} id="${ field.attr }_field" /> | |||
{% elseif field.type == "select" %} | |||
<select name="$field.attr" id="${ field.attr }_field"{% if field.classes %} class="${ field.classes | join(" ") }"{% endif %}> | |||
{% for value, name in field.options | items %} | |||
<option value="${ value | escape }"{% if not field.no_value %}${ value | option_selected(post[field.attr]) }{% endif %}>${ name | escape }</option> | |||
{% endfor %} | |||
</select> | |||
{% endif %} | |||
$field.extra | |||
</p> | |||
{% endfor %} | |||
${ trigger.call("after_post_fields", feather) } | |||
<div class="buttons"> | |||
{% if route.action == "edit_post" %} | |||
<button type="submit" class="yay right" accesskey="s" id="save"> | |||
<img src="$theme_url/images/icons/success.png" alt="success" />${ "Save" | translate } | |||
</button> | |||
{% else %} | |||
{% if visitor.group.can("add_post") %} | |||
<button type="submit" class="yay right" accesskey="s" id="publish"> | |||
<img src="$theme_url/images/icons/success.png" alt="success" />${ "Publish" | translate } | |||
</button> | |||
{% endif %} | |||
<button type="submit" class="right" accesskey="s" id="save" name="draft" value="true"> | |||
<img src="$theme_url/images/icons/save.png" alt="success" />${ "Save" | translate } | |||
</button> | |||
{% endif %} | |||
</div> | |||
<div class="clear"></div> | |||
<noscript><br /></noscript> | |||
<div id="more_options" class="more_options js_disabled"> | |||
{% if visitor.group.can("add_post") %} | |||
<p> | |||
<label for="status">${ "Status" | translate }</label> | |||
<select name="status" id="status"> | |||
{% if route.action == "edit_post" %} | |||
<option value="draft"${ post.status | option_selected("draft") }>${ "Draft" | translate }</option> | |||
{% endif %} | |||
<option value="public"${ post.status | option_selected("public") }>${ "Public" | translate }</option> | |||
<option value="private"${ post.status | option_selected("private") }>${ "Private" | translate }</option> | |||
<option value="registered_only"${ post.status | option_selected("registered_only") }>${ "Registered Only" | translate }</option> | |||
{% if groups %} | |||
<optgroup label="${ "Group" | translate }"> | |||
{% for group in groups %} | |||
<option value="{$group.id}"${ post.status | option_selected("{"~ group.id ~"}") }>${ group.name | escape }</option> | |||
{% endfor %} | |||
</optgroup> | |||
{% endif %} | |||
</select> | |||
</p> | |||
{% endif %} | |||
<p> | |||
<label for="pinned">${ "Pinned?" | translate }</label> | |||
<input type="checkbox" name="pinned" id="pinned"{% if post.pinned %} checked="checked"{% endif %}/> | |||
<small>${ "(shows this post above all others)" | translate }</small> | |||
</p> | |||
<p> | |||
<label for="slug"> | |||
${ "Slug" | translate } | |||
<a href="{% admin "help&id=slugs" %}" class="help emblem"><img src="$theme_url/images/icons/help.png" alt="help" /></a> | |||
</label> | |||
<input class="text" type="text" name="slug" value="${ post.slug | escape }" id="slug" /> | |||
</p> | |||
<p> | |||
<label for="created_at">${ "Timestamp" | translate }</label> | |||
<input class="text" type="text" name="created_at" value="${ post.created_at | fallback(now | date("r")) | strftime }" id="created_at" /> | |||
<input type="hidden" name="original_time" value="${ post.created_at | fallback(now | date("r")) | strftime }" /> | |||
</p> | |||
<p> | |||
<label for="trackbacks"> | |||
${ "Trackbacks" | translate } | |||
<a href="{% admin "help&id=trackbacks" %}" class="help emblem"><img src="$theme_url/images/icons/help.png" alt="help" /></a> | |||
</label> | |||
<input class="text" type="text" name="trackbacks" value="" id="trackbacks" /> | |||
</p> | |||
{% for field in options %} | |||
<p> | |||
<label for="${ field.attr | replace("[", "_") | replace("]", "") }"> | |||
$field.label | |||
{% if field.help %} | |||
<span class="sub"> | |||
<a href="{% admin "help&id="~field.help %}" class="help emblem"><img src="$theme_url/images/icons/help.png" alt="help" /></a> | |||
</span> | |||
{% endif %} | |||
{% if field.note %} | |||
<span class="sub">$field.note</span> | |||
{% endif %} | |||
</label> | |||
{% if field.type == "text" or field.type == "file" %} | |||
<input class="$field.type{% if field.classes %} ${ field.classes | join(" ") }{% endif %}" type="$field.type" name="$field.attr" value="{% if not field.no_value %}${ field.value | fallback(post[field.attr] | escape(true, false)) }{% endif %}" id="$field.attr" /> | |||
{% elseif field.type == "text_block" %} | |||
<textarea class="wide{% if field.classes %} ${ field.classes | join(" ") }{% endif %}" rows="${ field.rows | fallback(12) }" name="$field.attr" id="$field.attr" cols="50">{% if not field.no_value %}${ field.value | fallback(post[field.attr] | escape(false, false)) }{% endif %}</textarea> | |||
{% elseif field.type == "select" %} | |||
<select name="$field.attr" id="$field.attr"{% if field.classes %} class="${ field.classes | join(" ") }"{% endif %}> | |||
{% for option in field.options %} | |||
<option value="${ option.value | escape }"${ option.selected | option_selected(true) }>${ option.name | escape }</option> | |||
{% endfor %} | |||
</select> | |||
{% endif %} | |||
$field.extra | |||
</p> | |||
{% endfor %} | |||
<div class="clear"></div> | |||
</div> | |||
</div> |
@@ -0,0 +1,948 @@ | |||
/* @group Reset */ | |||
body,div,dl,dt,dd,ul,ol,li,h1,h2,h3,h4,h5,h6,pre,code,form,fieldset,legend,input,textarea,p,blockquote,th,td { margin: 0; padding: 0; } | |||
table { border-collapse: collapse; border-spacing: 0; } | |||
fieldset,img { border: 0; } | |||
address,caption,cite,code,dfn,em,strong,th,var { font-style: normal; font-weight: normal; } | |||
li { list-style: none; } | |||
caption,th { text-align: left; } | |||
h1,h2,h3,h4,h5,h6 { font-size: 100%; font-weight: normal; } | |||
abbr,acronym { border: 0; font-variant: normal; } | |||
input,textarea,select { font-family: inherit; font-size: inherit; font-weight: inherit; } | |||
a:link, a:visited { text-decoration: none; color: inherit; } | |||
/* @end */ | |||
/* @group Core */ | |||
html { | |||
font-size: 62.5%; | |||
} | |||
body { | |||
font: 1.25em/1.5em normal Lucida Grande, Helvetica, Arial, sans-serif; | |||
color: #333; | |||
background: #efefef; | |||
} | |||
h1, h2, h3, h4, h5, h6{ | |||
font-weight: normal !important; | |||
font-family: "Palatino Linotype", Chaparral Pro, Georgia, serif; | |||
} | |||
em { | |||
font-style: italic; | |||
} | |||
strong { | |||
font-weight: bold; | |||
} | |||
small, | |||
.small { | |||
font-size: .9em; | |||
color: #999; | |||
} | |||
p { | |||
margin: 0 0 1em; | |||
} | |||
a:link, | |||
a:visited { | |||
text-decoration: underline; | |||
} | |||
/* @end */ | |||
/* @group Header */ | |||
#header { | |||
background: #353535; | |||
padding: 2em 0; | |||
color: #fff; | |||
position: relative; | |||
} | |||
#header h1 { | |||
font-size: 2em; | |||
line-height: 1.25em; | |||
text-shadow: #000 0 1px 1px; | |||
} | |||
#header h1 a { | |||
text-decoration: none; | |||
} | |||
#header #navigation { | |||
float: right; | |||
position: absolute; | |||
bottom: 0; | |||
right: 5em; | |||
} | |||
#header #navigation li { | |||
display: inline; | |||
} | |||
#header #navigation li a:link, | |||
#header #navigation li a:visited { | |||
float: left; | |||
margin-left: 1em; | |||
text-shadow: #000 0 0 0; | |||
background: #444; | |||
color: #ebeeee; | |||
height: 4em; | |||
line-height: 4em; | |||
text-transform: uppercase; | |||
width: 8em; | |||
text-align: center; | |||
text-decoration: none; | |||
font-family: "Palatino Linotype", Chaparral Pro, Georgia, serif; | |||
letter-spacing: 0.2em; | |||
font-size: 0.8em; | |||
} | |||
#header #navigation li.selected a:link, | |||
#header #navigation li.selected a:visited { | |||
background: #fff; | |||
color: #333; | |||
} | |||
#header #navigation li a:hover { | |||
background: #4c4c4c; | |||
} | |||
#welcome { | |||
background: #fff; | |||
padding: .9em 0; | |||
border-bottom: 1px solid #e1e1e1; | |||
} | |||
#welcome a:link, | |||
#welcome a:visited { | |||
text-decoration: none; | |||
background: #fcfcfc; | |||
padding: 1em; | |||
margin: -1em 0 -.8em; | |||
} | |||
#welcome .right { | |||
padding: .9em 1em !important; | |||
margin-top: -.9em !important; | |||
} | |||
#welcome a:hover { | |||
background: #f5f5f5; | |||
} | |||
#welcome a:active { | |||
background: #f0f0f0; | |||
} | |||
/* @end */ | |||
/* @group Footer */ | |||
#footer { | |||
width: 425px; | |||
margin: 2.5em auto !important; | |||
font-size: 1.25em; | |||
text-align: center; | |||
} | |||
#footer .sub { | |||
color: #acacac; | |||
font-size: .7em; | |||
} | |||
/* @end */ | |||
/* @group Sub-Navigation */ | |||
#sub-nav { | |||
margin-top: 2em; | |||
} | |||
#sub-nav li { | |||
float: left; | |||
position: relative; | |||
top: 1px; | |||
z-index: 1; | |||
} | |||
#sub-nav li a:link, | |||
#sub-nav li a:visited { | |||
float: left; | |||
padding: .4em .75em; | |||
background: #dfdfdf; | |||
border-bottom: 0; | |||
color: #737373; | |||
text-decoration: none; | |||
margin: 0 0.7em 0 0; | |||
} | |||
#sub-nav li a:hover { | |||
background: #e5e5e5; | |||
} | |||
#sub-nav li.selected a:link, | |||
#sub-nav li.selected a:visited { | |||
background: #fff; | |||
border: 1px solid #e1e1e1; | |||
border-bottom: none; | |||
} | |||
#sub-nav li.right { | |||
margin: .75em 0 0; | |||
} | |||
#sub-nav li.right a:link, | |||
#sub-nav li.right a:visited { | |||
float: none; | |||
background: transparent; | |||
padding: 0; | |||
font-size: .95em; | |||
color: #444; | |||
} | |||
.feathers_sort { | |||
background: #eee; | |||
margin-top: .2em; | |||
height: 2.3em !important; | |||
border-left: .1em dashed #ccc; | |||
border-right: .1em dashed #ccc; | |||
} | |||
/* @end */ | |||
/* @group Content */ | |||
#content { | |||
background: #fff; | |||
padding: 1em; | |||
border: 1px solid #e1e1e1; | |||
position: relative; | |||
} | |||
#content > form { | |||
/* margin-top: .5em; */ | |||
} | |||
#content h1 { | |||
font-size: 2em; | |||
margin: .2em 0 1em; | |||
color: #aaa; | |||
font-weight: bold; | |||
} | |||
#content h2 { | |||
font-size: 1.5em; | |||
margin: 0 0 .5em; | |||
color: #666; | |||
font-weight: normal; | |||
} | |||
#content h3 { | |||
font-weight: bold; | |||
font-size: 1.25em; | |||
margin-bottom: 1.5em; | |||
} | |||
#content h4 { | |||
font-weight: bold; | |||
font-size: 1.2em; | |||
} | |||
/* @end */ | |||
/* @group Extend */ | |||
.enable, | |||
.disable { | |||
width: 49%; | |||
} | |||
.enable h2, | |||
.disable h2 { | |||
font-weight: normal !important; | |||
font-size: 1.1em !important; | |||
} | |||
.enable h2 .sub, | |||
.disable h2 .sub { | |||
font-size: .8em; | |||
color: #bcbcbc; | |||
} | |||
.enable ul, | |||
.disable ul { | |||
padding: 1em 1em .5em; | |||
-webkit-border-radius: 3px; | |||
-moz-border-radius: 3px; | |||
} | |||
.enable ul li, | |||
.disable ul li { | |||
padding: .1em .25em .1em .7em; | |||
margin: 0 0 .5em; | |||
list-style: disc inside !important; | |||
position: relative; | |||
} | |||
.enable ul li img, | |||
.disable ul li img { | |||
position: absolute; | |||
top: 4px; | |||
right: 4px; | |||
} | |||
.enable ul li a.help img, | |||
.disable ul li a.help img { | |||
top: 3px; | |||
right: 20px; | |||
width: 15px; | |||
height: 15px; | |||
} | |||
.enable ul li .sub, | |||
.disable ul li .sub { | |||
font-size: .9em; | |||
} | |||
.enable ul li .enable_button, | |||
.disable ul li .disable_button { | |||
display: block; | |||
padding: .25em .5em; | |||
text-align: right; | |||
} | |||
ul.extend .expand { | |||
margin: 0 -.25em 0 -.7em; | |||
} | |||
.enable h2 { | |||
color: #4f8f62 !important; | |||
} | |||
.enable ul { | |||
background: #dbffe6; | |||
border: 1px solid #dbffe6; | |||
} | |||
.enable ul li { | |||
background: #e6ffee; | |||
color: #386345; | |||
} | |||
.enable ul.hover { | |||
border: 1px solid #b3d1bb; | |||
} | |||
.enable ul li a:link, | |||
.enable ul li a:visited, | |||
.enable ul li .sub a:hover { | |||
text-decoration: none; | |||
color: #386345; | |||
} | |||
.enable ul li .sub, | |||
.enable ul li .sub a:link, | |||
.enable ul li .sub a:visited { | |||
color: #74a683; | |||
} | |||
.enable ul li .description { | |||
padding: .5em; | |||
background: #F7FFF9; | |||
} | |||
.enable ul li .enable_button { | |||
color: #189100; | |||
} | |||
.enable ul li .enable_button:hover { | |||
background: #189100; | |||
color: #fff; | |||
} | |||
.enable ul li .enable_button:active { | |||
background: #067000; | |||
} | |||
.disable h2 { | |||
color: #ff7070 !important; | |||
} | |||
.disable ul { | |||
background: #ffdbdb; | |||
border: 1px solid #ffdbdb; | |||
} | |||
.disable h2 .sub { | |||
color: #bcbcbc; | |||
} | |||
.disable ul li { | |||
background: #ffe6e6; | |||
color: #653b3d; | |||
} | |||
.disable ul.hover { | |||
border: 1px solid #cfb2b2; | |||
} | |||
.disable ul li a:link, | |||
.disable ul li a:visited, | |||
.disable ul li .sub a:hover { | |||
text-decoration: none; | |||
color: #7b4d4d; | |||
} | |||
.disable ul li .sub, | |||
.disable ul li .sub a:link, | |||
.disable ul li .sub a:visited { | |||
color: #a97f7f; | |||
} | |||
.disable ul li .description { | |||
padding: .5em; | |||
background: #FFF1F1; | |||
} | |||
.disable ul li .disable_button { | |||
color: #d51800; | |||
} | |||
.disable ul li .disable_button:hover { | |||
background: #d51800; | |||
color: #fff; | |||
} | |||
.disable ul li .disable_button:active { | |||
background: #b30600; | |||
} | |||
/* @end */ | |||
/* @group Theme Changing */ | |||
.theme { | |||
text-align: center; | |||
margin-right: 15px !important; | |||
border: 0 !important; | |||
padding-bottom: 20px !important; | |||
position: relative; | |||
-webkit-border-radius: 3px; | |||
-moz-border-radius: 3px; | |||
} | |||
.theme.last { | |||
margin-right: 0 !important; | |||
} | |||
.theme .link { | |||
font-weight: bold; | |||
text-align: center; | |||
text-decoration: none; | |||
} | |||
.theme .link span { | |||
display: block; | |||
} | |||
.theme.current { | |||
background: #EBFAE4; | |||
color: #65845e; | |||
} | |||
.theme.current small, | |||
.theme.current small a:link, | |||
.theme.current small a:visited { | |||
color: #789f72; | |||
} | |||
.theme.current .button { | |||
background: #189100; | |||
color: #FFF; | |||
border: 0; | |||
} | |||
.theme img.screenshot { | |||
padding: 5px; | |||
background: #FFF; | |||
-webkit-border-radius: 2px; | |||
-moz-border-radius: 2px; | |||
} | |||
.theme a:hover img.screenshot { | |||
background: #CCC; | |||
} | |||
.theme small { | |||
font-weight: normal; | |||
} | |||
/* @end */ | |||
/* @group Forms */ | |||
form.delete blockquote { | |||
width: 75%; | |||
margin: 0 auto; | |||
font-style: italic; | |||
} | |||
form.delete blockquote h2, | |||
form.delete .noitalic { | |||
font-style: normal; | |||
} | |||
form.delete blockquote h2 input[type="checkbox"] { | |||
position: relative; | |||
top: -.125em; | |||
} | |||
form.delete h2.inline { | |||
display: inline; | |||
} | |||
form.delete select { | |||
position: relative; | |||
top: -2px; | |||
} | |||
form.split label { | |||
width: 41%; | |||
float: left; | |||
text-align: right; | |||
font-weight: bold; | |||
padding: 4px 2% 5px; | |||
color: #999; | |||
} | |||
form.split label .sub { | |||
display: block; | |||
} | |||
form.split label input { | |||
margin: -.5em 0; | |||
} | |||
form.split select { | |||
margin: 3px 0 .5em; | |||
} | |||
form.split small, | |||
form.split .small { | |||
display: block; | |||
margin-left: 45%; | |||
} | |||
form.split button { | |||
margin-left: 45%; | |||
} | |||
form .buttons { | |||
margin-top: 2em; | |||
} | |||
label { | |||
display: block; | |||
font-size: 1.1em; | |||
} | |||
form p .sub, | |||
label .sub, | |||
tr .sub { | |||
font-size: .8em; | |||
color: #999; | |||
font-weight: normal; | |||
} | |||
input.text, | |||
input[type="text"], | |||
input[type="password"], | |||
input.file, | |||
textarea { | |||
font-size: 1.25em; | |||
padding: 3px; | |||
border: 1px solid #ddd; | |||
background: #fff; | |||
} | |||
input.file { | |||
border: none; | |||
} | |||
input.text, | |||
input.file, | |||
select, | |||
textarea { | |||
margin-bottom: .25em; | |||
} | |||
input.text { | |||
/* width: 15em; */ | |||
} | |||
input.checkbox { | |||
margin: .6em .5em .75em 0; | |||
} | |||
span.checkbox { | |||
position: relative; | |||
top: -3px; | |||
} | |||
textarea { | |||
font-size: 1.1em; | |||
font-family: "Monaco", "Consolas", monospace; | |||
} | |||
textarea.wide, | |||
input.text.wide { | |||
padding: .5%; | |||
width: 99%; | |||
_width: 100%; /* Hooray for IE6's randomized box model. */ | |||
} | |||
input.code, | |||
textarea.code, | |||
code { | |||
font-family: "Consolas", "Monaco", monospace; | |||
} | |||
/* @end */ | |||
/* @group Tables */ | |||
table thead tr { | |||
background: #555; | |||
color: #fff; | |||
border: 0 !important; | |||
border-bottom: 2px solid #444; | |||
text-shadow: #333 0px 1px 0px; | |||
} | |||
table thead tr th { | |||
padding: .25em .5em; | |||
font-weight: bold; | |||
} | |||
table thead tr th input { | |||
vertical-align: middle; | |||
margin: -5px 0 0 !important; | |||
} | |||
table thead tr th.header { | |||
cursor: pointer; | |||
} | |||
table thead tr th.headerSortDown, | |||
table thead tr th.headerSortUp { | |||
background: #444; | |||
} | |||
table tbody tr { | |||
} | |||
table tbody tr.last { | |||
} | |||
table tbody tr td { | |||
padding: .5em .7em; | |||
border-right: 1px solid #eee; | |||
border-bottom: 1px solid #eee; | |||
vertical-align: middle; | |||
} | |||
table tbody tr td.main { | |||
background: #f9f9f9; | |||
} | |||
table tbody tr td.center { | |||
text-align: center; | |||
vertical-align: middle; | |||
} | |||
table tbody tr td.controls { | |||
text-align: center; | |||
font-size: .95em; | |||
color: #888; | |||
} | |||
table tbody tr td.controls img { | |||
margin-bottom: -3px; | |||
margin-right: 1px; | |||
} | |||
table tbody tr td.main a:link, | |||
table tbody tr td.main a:visited, | |||
table tbody tr td.controls a:link, | |||
table tbody tr td.controls a:visited { | |||
text-decoration: none; | |||
} | |||
tr.draft, | |||
tr.excerpt, | |||
tr.draft td, | |||
tr.excerpt td, | |||
tr.draft td.main { | |||
background-color: #dff4ff; | |||
border-color: #c2e1ef !important; | |||
color: #336699; | |||
} | |||
tr.excerpt p { | |||
margin: 0; | |||
} | |||
/* @end */ | |||
/* @group Preview */ | |||
h1.preview-header { | |||
margin: 0 0 .5em !important; | |||
} | |||
.preview-content { | |||
margin: 0 0 2em; | |||
padding-bottom: .75em; | |||
border-bottom: 1px dotted #ddd; | |||
} | |||
.preview-content ul, | |||
.preview-content ol { | |||
margin: 0 0 1em 2em; | |||
list-style: square; | |||
} | |||
.preview-content ol li { | |||
list-style: decimal; | |||
} | |||
.preview-content a:link, | |||
.preview-content a:visited { | |||
text-decoration: underline; | |||
color: #222; | |||
} | |||
.preview-content hr { | |||
margin: 2em 0; | |||
} | |||
.preview-content blockquote { | |||
margin: 0 2em; | |||
color: #999; | |||
} | |||
/* @end */ | |||
/* @group Messages */ | |||
p.message { | |||
font-size: 1.1em; | |||
padding: .6em; | |||
margin-bottom: 1.75em; | |||
background: #f1f1f1; | |||
border: 1px solid #ccc; | |||
} | |||
p.message.yay { | |||
background: #189100; | |||
color: #fff; | |||
border-color: #105f00; | |||
} | |||
p.message.boo { | |||
background: #d51800; | |||
color: #fff; | |||
border-color: #9f1000; | |||
} | |||
/* @end */ | |||
/* @group Buttons */ | |||
/* Inspiration from Chawlk. */ | |||
button, | |||
.button, | |||
a.next_page, | |||
a.prev_page { | |||
display: inline-block; | |||
font-family: Verdana, Helvetica, Arial, sans-serif; | |||
font-weight: bold; | |||
font-size: 1.125em; | |||
margin: 0 .5em 0 0; | |||
color: #333; | |||
padding: .5em .75em; | |||
background: #f1f1f1; | |||
border: 1px solid #ccc; | |||
border-top-color: #f1f1f1; | |||
border-left-color: #f1f1f1; | |||
border-bottom: 1px solid #ccc !important; | |||
cursor: pointer; | |||
text-decoration: none !important; | |||
} | |||
.button { | |||
padding: .6em 1em .5em .9em; | |||
} | |||
button.lite { | |||
padding: .15em .375em .25em .275em; | |||
} | |||
.button.lite, | |||
a.next_page, | |||
a.prev_page { | |||
padding: .1em .4em .2em; | |||
} | |||
button.inline { | |||
position: relative; | |||
top: -1px; | |||
padding: 4px 5px 4px 4px; | |||
} | |||
button.right, | |||
.button.right, | |||
a.next_page { | |||
margin: 0 0 0 .5em; | |||
float: right; | |||
} | |||
button:hover, | |||
.button:hover, | |||
a.next_page:hover, | |||
a.prev_page:hover { | |||
background: #f7f7f7; | |||
border-color: #f7f7f7; | |||
border-bottom-color: #d1d1d1 !important; | |||
border-right-color: #d1d1d1; | |||
} | |||
button:active, | |||
.button:active, | |||
.button.selected, | |||
a.next_page:active, | |||
a.prev_page:active { | |||
background-color: #d7d7d7; | |||
border-color: #d7d7d7; | |||
border-left-color: #b1b1b1; | |||
border-top-color: #b1b1b1; | |||
border-bottom-color: #d7d7d7 !important; | |||
} | |||
button.boo, | |||
.button.boo { | |||
background: #faebe4; | |||
color: #3e3634; | |||
border-color: #faebe4; | |||
border-bottom-color: #d6bdb5; | |||
border-right-color: #d6bdb5; | |||
} | |||
button.boo:hover, | |||
.button.boo:hover { | |||
background: #d51800; | |||
color: #fff; | |||
border-color: #d51800; | |||
border-bottom-color: #9f1000 !important; | |||
border-right-color: #9f1000; | |||
} | |||
button.boo:active, | |||
.button.boo:active { | |||
background: #b30600; | |||
border-color: #b30600; | |||
border-left-color: #7d0000; | |||
border-top-color: #7d0000; | |||
border-bottom-color: #b30600 !important; | |||
} | |||
button.yay, | |||
.button.yay { | |||
background: #ebfae4; | |||
color: #363e34; | |||
border-color: #ebfae4; | |||
border-bottom-color: #bdd6b5; | |||
border-right-color: #bdd6b5; | |||
} | |||
button.yay:hover, | |||
.button.yay:hover { | |||
background: #189100; | |||
color: #fff; | |||
border-color: #189100; | |||
border-bottom-color: #105f00 !important; | |||
border-right-color: #105f00; | |||
} | |||
button.yay:active, | |||
.button.yay:active { | |||
background: #067000; | |||
border-color: #067000; | |||
border-left-color: #003d00; | |||
border-top-color: #003d00; | |||
border-bottom-color: #067000 !important; | |||
} | |||
button[disabled="disabled"], | |||
button.disabled { | |||
background: #f0f0f0; | |||
border-color: #f0f0f0 !important; | |||
color: #999; | |||
} | |||
button[disabled="disabled"]:hover, | |||
button.disabled:hover { | |||
background: #f0f0f0; | |||
border-color: #f0f0f0 !important; | |||
color: #999; | |||
cursor: default; | |||
} | |||
button img, | |||
.button img { | |||
margin: 0 6px -3px -4px; | |||
} | |||
button.lite img, | |||
.button.lite img { | |||
margin: 0 -1px -3px 0; | |||
} | |||
/* @end */ | |||
/* @group Options */ | |||
.more_options_link { | |||
font-weight: bold; | |||
color: #999; | |||
border-bottom: 1px solid #ddd; | |||
display: block; | |||
text-decoration: none !important; | |||
} | |||
.more_options_link:hover { | |||
color: #666; | |||
border-bottom-color: #aaa; | |||
} | |||
.more_options { | |||
background: #f9f9f9; | |||
padding: 2% 0 0 2%; | |||
} | |||
.more_options p { | |||
width: 48%; | |||
margin: 0 2% 0 0; | |||
min-height: 5.5em; | |||
_height: 5.5em; | |||
float: left; | |||
} | |||
.more_options p input.text { | |||
width: 96.5%; | |||
margin: 0; | |||
} | |||
/* @end */ | |||
/* @group Page Reordering */ | |||
ul.sort_pages, | |||
ul.sort_pages ul { | |||
padding: 0; | |||
margin: 0 0 0 2em; | |||
} | |||
ul.sort_pages li { | |||
list-style: square; | |||
} | |||
ul.sort_pages li a:link, | |||
ul.sort_pages li a:visited { | |||
text-decoration: none !important; | |||
} | |||
ul.sort_pages input { | |||
font-size: 1em; | |||
margin: .25em .25em .25em 0; | |||
} | |||
ul.sort_pages .sort-placeholder { | |||
border: 1px dashed #777; | |||
display: list-item !important; | |||
margin-bottom: .5em; | |||
height: 1.8em; | |||
} | |||
ul.sort_pages .sort-hover { | |||
border: 1px dashed #777 !important; | |||
} | |||
/* @end */ | |||
/* @group Helpers & General */ | |||
.breakword { | |||
word-wrap: break-word !important; | |||
} | |||
div.controls.right { | |||
margin-top: -2px; | |||
} | |||
div.controls h4 { | |||
display: inline; | |||
} | |||
div.controls button { | |||
position: relative; | |||
top: -2px; | |||
right: -1px; | |||
margin: 0 0 0 .5em; | |||
} | |||
hr { | |||
border: 0; | |||
border-top: 1px solid #ddd; | |||
} | |||
#content ul li { | |||
list-style: square; | |||
} | |||
.box { | |||
background: #f5f5f5; | |||
border: 1px solid #ddd; | |||
padding: 1em; | |||
margin: 0 0 1em; | |||
} | |||
.box h1 { | |||
font-size: 1.25em !important; | |||
margin: 0 !important; | |||
} | |||
.box h1 .sub { | |||
font-weight: normal; | |||
font-size: .75em; | |||
} | |||
.box h1 span.right { | |||
font-size: .75em; | |||
color: #666; | |||
} | |||
.box h1 span.right a:link, | |||
.box h1 span.right a:visited { | |||
margin-left: 1em; | |||
text-decoration: none; | |||
} | |||
.box h1 span.right a img { | |||
margin-bottom: -3px; | |||
} | |||
.edit_link:hover { | |||
color: #333; | |||
} | |||
.delete_link { | |||
color: #b30600; | |||
} | |||
table tbody td .delete_link { | |||
margin: 0 -.4em 0 0; | |||
} | |||
.delete_link:after { | |||
content: "!"; | |||
opacity: 0; | |||
} | |||
.delete_link:hover { | |||
color: #f00; | |||
} | |||
.delete_link:hover:after { | |||
color: #f00; | |||
opacity: 1; | |||
} | |||
a.emblem { | |||
border: none; | |||
} | |||
a.emblem img { | |||
margin-bottom: -2px; | |||
} | |||
.pad { | |||
padding: .75em; | |||
} | |||
.margin-right { | |||
margin-right: 1em; | |||
} | |||
.margin-left { | |||
margin-left: 1em; | |||
} | |||
.detail .button.right { | |||
margin-top: 2.25em; | |||
} | |||
.detail h3 { | |||
margin-bottom: .5em !important; | |||
} | |||
.detail.inline { | |||
position: relative; | |||
bottom: -.75em; | |||
} | |||
.wide { | |||
width: 100%; | |||
} | |||
.clear { | |||
clear: both; | |||
} | |||
.left { | |||
float: left !important; | |||
} | |||
.right { | |||
float: right !important; | |||
} | |||
.center { | |||
margin: 0 auto; | |||
text-align: center; | |||
} | |||
.inline { | |||
display: inline; | |||
} | |||
.column { | |||
margin: 0 5em; | |||
} | |||
br#after_options { | |||
display: none; | |||
} | |||
.js_enabled { | |||
display: none; | |||
} | |||
/* @end */ | |||
@@ -0,0 +1,167 @@ | |||
<?php | |||
class Audio extends Feathers implements Feather { | |||
public function __init() { | |||
$this->setField(array("attr" => "audio", | |||
"type" => "file", | |||
"label" => __("MP3 File", "audio"), | |||
"note" => "<small>(Max. file size: ".ini_get('upload_max_filesize').")</small>")); | |||
if (isset($_GET['action']) and $_GET['action'] == "bookmarklet") | |||
$this->setField(array("attr" => "from_url", | |||
"type" => "text", | |||
"label" => __("From URL?", "audio"), | |||
"optional" => true, | |||
"no_value" => true)); | |||
$this->setField(array("attr" => "description", | |||
"type" => "text_block", | |||
"label" => __("Description", "audio"), | |||
"optional" => true, | |||
"preview" => true, | |||
"bookmarklet" => "selection")); | |||
$this->setFilter("description", array("markup_text", "markup_post_text")); | |||
$this->respondTo("delete_post", "delete_file"); | |||
$this->respondTo("javascript", "player_js"); | |||
$this->respondTo("feed_item", "enclose_mp3"); | |||
$this->respondTo("filter_post", "filter_post"); | |||
$this->respondTo("admin_write_post", "swfupload"); | |||
$this->respondTo("admin_edit_post", "swfupload"); | |||
$this->respondTo("post_options", "add_option"); | |||
} | |||
public function swfupload($admin, $post = null) { | |||
if (isset($post) and $post->feather != "audio" or | |||
isset($_GET['feather']) and $_GET['feather'] != "audio") | |||
return; | |||
Trigger::current()->call("prepare_swfupload", "audio", "*.mp3"); | |||
} | |||
public function submit() { | |||
if (!isset($_POST['filename'])) { | |||
if (isset($_FILES['audio']) and $_FILES['audio']['error'] == 0) | |||
$filename = upload($_FILES['audio'], "mp3"); | |||
elseif (!empty($_POST['from_url'])) | |||
$filename = upload_from_url($_POST['from_url'], "mp3"); | |||
else | |||
error(__("Error"), __("Couldn't upload audio file.")); | |||
} else | |||
$filename = $_POST['filename']; | |||
return Post::add(array("filename" => $filename, | |||
"description" => $_POST['description']), | |||
$_POST['slug'], | |||
Post::check_url($_POST['slug'])); | |||
} | |||
public function update($post) { | |||
if (!isset($_POST['filename'])) | |||
if (isset($_FILES['audio']) and $_FILES['audio']['error'] == 0) { | |||
$this->delete_file($post); | |||
$filename = upload($_FILES['audio'], "mp3"); | |||
} elseif (!empty($_POST['from_url'])) { | |||
$this->delete_file($post); | |||
$filename = upload_from_url($_POST['from_url'], "mp3"); | |||
} else | |||
$filename = $post->filename; | |||
else { | |||
$this->delete_file($post); | |||
$filename = $_POST['filename']; | |||
} | |||
$post->update(array("filename" => $filename, | |||
"description" => $_POST['description'])); | |||
} | |||
public function title($post) { | |||
return oneof($post->title, $post->title_from_excerpt()); | |||
} | |||
public function excerpt($post) { | |||
return $post->description; | |||
} | |||
public function feed_content($post) { | |||
return $post->description; | |||
} | |||
public function delete_file($post) { | |||
if ($post->feather != "audio") return; | |||
unlink(MAIN_DIR.Config::current()->uploads_path.$post->filename); | |||
} | |||
public function filter_post($post) { | |||
if ($post->feather != "audio") return; | |||
$post->audio_player = $this->flash_player_for($post->filename, array(), $post); | |||
} | |||
public function player_js() { | |||
?>//<script> | |||
var ap_instances = new Array(); | |||
function ap_stopAll(playerID) { | |||
for(var i = 0;i<ap_instances.length;i++) { | |||
try { | |||
if(ap_instances[i] != playerID) document.getElementById("audioplayer" + ap_instances[i].toString()).SetVariable("closePlayer", 1); | |||
else document.getElementById("audioplayer" + ap_instances[i].toString()).SetVariable("closePlayer", 0); | |||
} catch( errorObject ) { | |||
// stop any errors | |||
} | |||
} | |||
} | |||
function ap_registerPlayers() { | |||
var objectID; | |||
var objectTags = document.getElementsByTagName("object"); | |||
for(var i=0;i<objectTags.length;i++) { | |||
objectID = objectTags[i].id; | |||
if(objectID.indexOf("audioplayer") == 0) { | |||
ap_instances[i] = objectID.substring(11, objectID.length); | |||
} | |||
} | |||
} | |||
var ap_clearID = setInterval( ap_registerPlayers, 100 ); | |||
<?php | |||
} | |||
public function enclose_mp3($post) { | |||
$config = Config::current(); | |||
if ($post->feather != "audio" or !file_exists(uploaded($post->filename, false))) | |||
return; | |||
$length = filesize(uploaded($post->filename, false)); | |||
echo ' <link rel="enclosure" href="'.uploaded($post->filename).'" type="audio/mpeg" title="MP3" length="'.$length.'" />'."\n"; | |||
} | |||
public function flash_player_for($filename, $params = array(), $post) { | |||
$vars = ""; | |||
foreach ($params as $name => $val) | |||
$vars.= "&".$name."=".$val; | |||
$config = Config::current(); | |||
$player = '<script src="'.$config->chyrp_url.'/feathers/audio/lib/audio-player.js" type="text/javascript" charset="utf-8"></script>'."\n"; | |||
$player.= '<object type="application/x-shockwave-flash" data="'.$config->chyrp_url.'/feathers/audio/lib/player.swf" id="audioplayer'.$post->id.'" height="24" width="290">'."\n\t"; | |||
$player.= '<param name="movie" value="'.$config->chyrp_url.'/feathers/audio/lib/player.swf" />'."\n\t"; | |||
$player.= '<param name="FlashVars" value="playerID='.$post->id.'&soundFile='.$config->chyrp_url.$config->uploads_path.$filename.$vars.'" />'."\n\t"; | |||
$player.= '<param name="quality" value="high" />'."\n\t"; | |||
$player.= '<param name="menu" value="false" />'."\n\t"; | |||
$player.= '<param name="wmode" value="transparent" />'."\n"; | |||
$player.= '</object>'."\n"; | |||
return $player; | |||
} | |||
public function add_option($options, $post = null) { | |||
if (isset($post) and $post->feather != "audio") return; | |||
if (!isset($_GET['feather']) and Config::current()->enabled_feathers[0] != "audio" or | |||
isset($_GET['feather']) and $_GET['feather'] != "audio") return; | |||
$options[] = array("attr" => "from_url", | |||
"label" => __("From URL?", "audio"), | |||
"type" => "text"); | |||
return $options; | |||
} | |||
} |
@@ -0,0 +1,8 @@ | |||
name: Audio | |||
url: http://chyrp.net/ | |||
version: 2.0 | |||
description: An audio feather, including a flash player. | |||
author: | |||
name: Alex Suraci | |||
url: http://ecks.tc/ | |||
uploader: true |
@@ -0,0 +1,25 @@ | |||
var ap_instances = new Array(); | |||
function ap_stopAll(playerID) { | |||
for(var i = 0;i<ap_instances.length;i++) { | |||
try { | |||
if(ap_instances[i] != playerID) document.getElementById("audioplayer" + ap_instances[i].toString()).SetVariable("closePlayer", 1); | |||
else document.getElementById("audioplayer" + ap_instances[i].toString()).SetVariable("closePlayer", 0); | |||
} catch( errorObject ) { | |||
// stop any errors | |||
} | |||
} | |||
} | |||
function ap_registerPlayers() { | |||
var objectID; | |||
var objectTags = document.getElementsByTagName("object"); | |||
for(var i=0;i<objectTags.length;i++) { | |||
objectID = objectTags[i].id; | |||
if(objectID.indexOf("audioplayer") == 0) { | |||
ap_instances[i] = objectID.substring(11, objectID.length); | |||
} | |||
} | |||
} | |||
var ap_clearID = setInterval( ap_registerPlayers, 100 ); |
@@ -0,0 +1,40 @@ | |||
# Chyrp v2.1 Translation File. | |||
# Copyright (C) 2011 Chyrp Team | |||
# This file is distributed under the same license as the Chyrp v2.1 package. | |||
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR. | |||
# | |||
#, fuzzy | |||
msgid "" | |||
msgstr "" | |||
"Project-Id-Version: Chyrp v2.1\n" | |||
"Report-Msgid-Bugs-To: email@chyrp.net\n" | |||
"POT-Creation-Date: 2011-01-10 22:16+0000\n" | |||
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" | |||
"Last-Translator: FIRST LAST <EMAIL@EXAMPLE.COM>\n" | |||
"Language-Team: LANGUAGE <EMAIL@EXAMPLE.COM>\n" | |||
"MIME-Version: 1.0\n" | |||
"Content-Type: text/plain; charset=CHARSET\n" | |||
"Content-Transfer-Encoding: 8bit\n" | |||
"Plural-Forms: nplurals=INTEGER; plural=EXPRESSION;\n" | |||
#: feathers/audio/audio.php:6 | |||
msgid "MP3 File" | |||
msgstr "" | |||
#: feathers/audio/info.yaml:1 | |||
msgid "Audio" | |||
msgstr "" | |||
#: feathers/audio/info.yaml:6 | |||
msgid "An audio feather, including a flash player." | |||
msgstr "" | |||
#: feathers/audio/audio.php:17 | |||
msgid "Description" | |||
msgstr "" | |||
#: feathers/audio/audio.php:11 | |||
#: feathers/audio/audio.php:163 | |||
msgid "From URL?" | |||
msgstr "" | |||
@@ -0,0 +1,132 @@ | |||
<?php | |||
class Chat extends Feathers implements Feather { | |||
public function __init() { | |||
$this->setField(array("attr" => "title", | |||
"type" => "text", | |||
"label" => __("Title", "chat"), | |||
"optional" => true)); | |||
$this->setField(array("attr" => "dialogue", | |||
"type" => "text_block", | |||
"label" => __("Dialogue", "chat"), | |||
"preview" => true, | |||
"help" => "chat_dialogue", | |||
"bookmarklet" => "selection")); | |||
$this->customFilter("dialogue", "format_dialogue"); | |||
$this->setFilter("title", array("markup_title", "markup_post_title")); | |||
$this->setFilter("dialogue", array("markup_text", "markup_post_text")); | |||
$this->respondTo("preview_chat", "format_dialogue"); | |||
$this->respondTo("help_chat_dialogue", "help"); | |||
} | |||
public function submit() { | |||
if (empty($_POST['dialogue'])) | |||
error(__("Error"), __("Dialogue can't be blank.")); | |||
fallback($_POST['slug'], sanitize($_POST['title'])); | |||
return Post::add(array("title" => $_POST['title'], | |||
"dialogue" => $_POST['dialogue']), | |||
$_POST['slug'], | |||
Post::check_url($_POST['slug'])); | |||
} | |||
public function update($post) { | |||
if (empty($_POST['dialogue'])) | |||
error(__("Error"), __("Dialogue can't be blank.")); | |||
$post->update(array("title" => $_POST['title'], | |||
"dialogue" => $_POST['dialogue'])); | |||
} | |||
public function title($post) { | |||
$dialogue = oneof($post->dialogue_unformatted, $post->dialogue); | |||
$dialogue = explode("\n", $dialogue); | |||
$line = preg_replace("/^\s*[\[\(]?[0-9]{1,2}:[0-9]{2}(:[0-9]{2})?\s*(pm|am)?[\]|\)]?\s*/i", "", $dialogue[0]); | |||
$first_line = preg_replace("/([<]?)([^:|>]+)( \(me\)?)(:|>) (.+)/i", "\\1\\2\\4 \\5", $dialogue[0]); | |||
return oneof($post->title, $first_line); | |||
} | |||
public function excerpt($post) { | |||
return $post->dialogue; | |||
} | |||
public function feed_content($post) { | |||
return $post->dialogue; | |||
} | |||
public function format_dialogue($text, $post = null) { | |||
if (isset($post)) | |||
$post->dialogue_unformatted = $text; | |||
$split = explode("\n", $text); | |||
$return = '<ul class="dialogue">'; | |||
$count = 0; | |||
$my_name = ""; | |||
$links = array(); | |||
foreach ($split as $line) { | |||
# Remove the timstamps | |||
$line = preg_replace("/^\s*[\[\(]?[0-9]{1,2}:[0-9]{2}(:[0-9]{2})?\s*(pm|am)?[\]|\)]?\s*/i", "", $line); | |||
preg_match("/(<?)(.+)(:|>)\s*(.+)/i", $line, $matches); | |||
if (empty($matches)) | |||
continue; | |||
if (preg_match("/\s*\(([^\)]+)\)$/", $matches[2], $attribution)) | |||
if ($attribution[1] == "me") { | |||
$my_name = $matches[2] = str_replace($attribution[0], "", $matches[2]); | |||
} else { | |||
$matches[2] = str_replace($attribution[0], "", $matches[2]); | |||
$links[$matches[2]] = $attribution[1]; | |||
} | |||
$link = oneof(@$links[$matches[2]], ""); | |||
$me = ($my_name == $matches[2] ? " me" : ""); | |||
$username = $matches[1].$matches[2].$matches[3]; | |||
$class = ($count % 2 ? "even" : "odd"); | |||
$return.= '<li class="'.$class.$me.'">'; | |||
if (!empty($link)) | |||
$return.= '<span class="label">'.$matches[1].'<a href="'.$link.'">'.fix($matches[2], false).'</a>'.$matches[3].'</span> '.$matches[4]."\n"; | |||
else | |||
$return.= '<span class="label">'.fix($username, false).'</span> '.$matches[4]."\n"; | |||
$return.= '</li>'; | |||
$count++; | |||
} | |||
$return.= "</ul>"; | |||
# If they're previewing. | |||
if (!isset($post)) | |||
$return = preg_replace("/(<li class=\"(even|odd) me\"><span class=\"label\">)(.+)(<\/span> (.+)\n<\/li>)/", "\\1<strong>\\3</strong>\\4", $return); | |||
return $return; | |||
} | |||
public function help() { | |||
$title = __("Dialogue Formatting", "chat"); | |||
$body = "<p>".__("To give yourself a special CSS class, append \" (me)\" to your username, like so:", "chat")."</p>\n"; | |||
$body.= "<ul class=\"list\">\n"; | |||
$body.= "\t<li>"<Alex>" → "<Alex (me)>"</li>\n"; | |||
$body.= "\t<li>"Alex:" → "Alex (me):"</li>\n"; | |||
$body.= "</ul>\n"; | |||
$body.= "<p>".__("This only has to be done to the first occurrence of the username.", "chat")."</p>"; | |||
$body.= "<p>".__("To attribute a name to a URL, append the URL in parentheses, preceded by a space, to the username:", "chat")."</p>\n"; | |||
$body.= "<ul class=\"list\">\n"; | |||
$body.= "\t<li>"<John>" → "<John (http://example.com/)>"</li>\n"; | |||
$body.= "\t<li>"John:" → "John (http://example.com/):"</li>\n"; | |||
$body.= "</ul>\n"; | |||
$body.= "<p>".__("This also only has to be done to the first occurrence of the username. It cannot be combined with attributing someone as yourself (because they're already at your site anyway).", "chat")."</p>"; | |||
return array($title, $body); | |||
} | |||
} |
@@ -0,0 +1,7 @@ | |||
name: Chat | |||
url: http://chyrp.net/ | |||
version: 2.0 | |||
description: Post logs of IRC or other chat formats. | |||
author: | |||
name: Alex Suraci | |||
url: http://ecks.tc/ |
@@ -0,0 +1,55 @@ | |||
# Chyrp v2.1 Translation File. | |||
# Copyright (C) 2011 Chyrp Team | |||
# This file is distributed under the same license as the Chyrp v2.1 package. | |||
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR. | |||
# | |||
#, fuzzy | |||
msgid "" | |||
msgstr "" | |||
"Project-Id-Version: Chyrp v2.1\n" | |||
"Report-Msgid-Bugs-To: email@chyrp.net\n" | |||
"POT-Creation-Date: 2011-01-10 22:16+0000\n" | |||
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" | |||
"Last-Translator: FIRST LAST <EMAIL@EXAMPLE.COM>\n" | |||
"Language-Team: LANGUAGE <EMAIL@EXAMPLE.COM>\n" | |||
"MIME-Version: 1.0\n" | |||
"Content-Type: text/plain; charset=CHARSET\n" | |||
"Content-Transfer-Encoding: 8bit\n" | |||
"Plural-Forms: nplurals=INTEGER; plural=EXPRESSION;\n" | |||
#: feathers/chat/chat.php:123 | |||
msgid "To attribute a name to a URL, append the URL in parentheses, preceded by a space, to the username:" | |||
msgstr "" | |||
#: feathers/chat/info.yaml:1 | |||
msgid "Chat" | |||
msgstr "" | |||
#: feathers/chat/chat.php:121 | |||
msgid "This only has to be done to the first occurrence of the username." | |||
msgstr "" | |||
#: feathers/chat/chat.php:114 | |||
msgid "Dialogue Formatting" | |||
msgstr "" | |||
#: feathers/chat/chat.php:128 | |||
msgid "This also only has to be done to the first occurrence of the username. It cannot be combined with attributing someone as yourself (because they're already at your site anyway)." | |||
msgstr "" | |||
#: feathers/chat/chat.php:6 | |||
msgid "Title" | |||
msgstr "" | |||
#: feathers/chat/info.yaml:5 | |||
msgid "Post logs of IRC or other chat formats." | |||
msgstr "" | |||
#: feathers/chat/chat.php:116 | |||
msgid "To give yourself a special CSS class, append \" (me)\" to your username, like so:" | |||
msgstr "" | |||
#: feathers/chat/chat.php:10 | |||
msgid "Dialogue" | |||
msgstr "" | |||
@@ -0,0 +1,7 @@ | |||
name: Link | |||
url: http://chyrp.net/ | |||
version: 2.0 | |||
description: Link to other sites, name it, and add an optional description. | |||
author: | |||
name: Alex Suraci | |||
url: http://ecks.tc/ |
@@ -0,0 +1,72 @@ | |||
<?php | |||
class Link extends Feathers implements Feather { | |||
public function __init() { | |||
$this->setField(array("attr" => "source", | |||
"type" => "text", | |||
"label" => __("URL", "link"), | |||
"bookmarklet" => "url")); | |||
$this->setField(array("attr" => "name", | |||
"type" => "text", | |||
"label" => __("Name", "link"), | |||
"bookmarklet" => "title")); | |||
$this->setField(array("attr" => "description", | |||
"type" => "text_block", | |||
"label" => __("Description", "link"), | |||
"optional" => true, | |||
"preview" => true, | |||
"bookmarklet" => "selection")); | |||
$this->setFilter("name", array("markup_title", "markup_post_title")); | |||
$this->setFilter("description", array("markup_text", "markup_post_text")); | |||
$this->respondTo("feed_url", "set_feed_url"); | |||
} | |||
public function submit() { | |||
if (empty($_POST['source'])) | |||
error(__("Error"), __("URL can't be empty.")); | |||
if (!@parse_url($_POST['source'], PHP_URL_SCHEME)) | |||
$_POST['source'] = "http://".$_POST['source']; | |||
fallback($_POST['slug'], sanitize($_POST['name'])); | |||
return Post::add(array("name" => $_POST['name'], | |||
"source" => $_POST['source'], | |||
"description" => $_POST['description']), | |||
$_POST['slug'], | |||
Post::check_url($_POST['slug'])); | |||
} | |||
public function update($post) { | |||
if (empty($_POST['source'])) | |||
error(__("Error"), __("URL can't be empty.")); | |||
if (!@parse_url($_POST['source'], PHP_URL_SCHEME)) | |||
$_POST['source'] = "http://".$_POST['source']; | |||
$post->update(array("name" => $_POST['name'], | |||
"source" => $_POST['source'], | |||
"description" => $_POST['description'])); | |||
} | |||
public function title($post) { | |||
$return = $post->name; | |||
fallback($return, $post->title_from_excerpt()); | |||
fallback($return, $post->source); | |||
return $return; | |||
} | |||
public function excerpt($post) { | |||
return $post->description; | |||
} | |||
public function feed_content($post) { | |||
return $post->description; | |||
} | |||
public function set_feed_url($url, $post) { | |||
if ($post->feather != "link") return; | |||
return $url = $post->source; | |||
} | |||
} |
@@ -0,0 +1,39 @@ | |||
# Chyrp v2.1 Translation File. | |||
# Copyright (C) 2011 Chyrp Team | |||
# This file is distributed under the same license as the Chyrp v2.1 package. | |||
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR. | |||
# | |||
#, fuzzy | |||
msgid "" | |||
msgstr "" | |||
"Project-Id-Version: Chyrp v2.1\n" | |||
"Report-Msgid-Bugs-To: email@chyrp.net\n" | |||
"POT-Creation-Date: 2011-01-10 22:16+0000\n" | |||
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" | |||
"Last-Translator: FIRST LAST <EMAIL@EXAMPLE.COM>\n" | |||
"Language-Team: LANGUAGE <EMAIL@EXAMPLE.COM>\n" | |||
"MIME-Version: 1.0\n" | |||
"Content-Type: text/plain; charset=CHARSET\n" | |||
"Content-Transfer-Encoding: 8bit\n" | |||
"Plural-Forms: nplurals=INTEGER; plural=EXPRESSION;\n" | |||
#: feathers/link/link.php:10 | |||
msgid "Name" | |||
msgstr "" | |||
#: feathers/link/link.php:6 | |||
msgid "URL" | |||
msgstr "" | |||
#: feathers/link/info.yaml:5 | |||
msgid "Link to other sites, name it, and add an optional description." | |||
msgstr "" | |||
#: feathers/link/info.yaml:1 | |||
msgid "Link" | |||
msgstr "" | |||
#: feathers/link/link.php:14 | |||
msgid "Description" | |||
msgstr "" | |||
@@ -0,0 +1,8 @@ | |||
name: Photo | |||
url: http://chyrp.net/ | |||
version: 2.0 | |||
description: A photo feather, allowing you to upload any type of picture, with a caption. | |||
author: | |||
name: Alex Suraci | |||
url: http://ecks.tc/ | |||
uploader: true |
@@ -0,0 +1,47 @@ | |||
# Chyrp v2.1 Translation File. | |||
# Copyright (C) 2011 Chyrp Team | |||
# This file is distributed under the same license as the Chyrp v2.1 package. | |||
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR. | |||
# | |||
#, fuzzy | |||
msgid "" | |||
msgstr "" | |||
"Project-Id-Version: Chyrp v2.1\n" | |||
"Report-Msgid-Bugs-To: email@chyrp.net\n" | |||
"POT-Creation-Date: 2011-01-10 22:16+0000\n" | |||
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" | |||
"Last-Translator: FIRST LAST <EMAIL@EXAMPLE.COM>\n" | |||
"Language-Team: LANGUAGE <EMAIL@EXAMPLE.COM>\n" | |||
"MIME-Version: 1.0\n" | |||
"Content-Type: text/plain; charset=CHARSET\n" | |||
"Content-Transfer-Encoding: 8bit\n" | |||
"Plural-Forms: nplurals=INTEGER; plural=EXPRESSION;\n" | |||
#: feathers/photo/info.yaml:6 | |||
msgid "A photo feather, allowing you to upload any type of picture, with a caption." | |||
msgstr "" | |||
#: feathers/photo/photo.php:143 | |||
msgid "Source" | |||
msgstr "" | |||
#: feathers/photo/photo.php:138 | |||
msgid "Alt-Text" | |||
msgstr "" | |||
#: feathers/photo/photo.php:6 | |||
#: feathers/photo/info.yaml:1 | |||
msgid "Photo" | |||
msgstr "" | |||
#: feathers/photo/photo.php:17 | |||
msgid "Caption" | |||
msgstr "" | |||
#: feathers/photo/photo.php:11 | |||
#: feathers/photo/photo.php:39 | |||
#: feathers/photo/photo.php:49 | |||
#: feathers/photo/photo.php:148 | |||
msgid "From URL?" | |||
msgstr "" | |||
@@ -0,0 +1,153 @@ | |||
<?php | |||
class Photo extends Feathers implements Feather { | |||
public function __init() { | |||
$this->setField(array("attr" => "photo", | |||
"type" => "file", | |||
"label" => __("Photo", "photo"), | |||
"note" => "<small>(Max. file size: ".ini_get('upload_max_filesize').")</small>")); | |||
if (isset($_GET['action']) and $_GET['action'] == "bookmarklet") | |||
$this->setField(array("attr" => "from_url", | |||
"type" => "text", | |||
"label" => __("From URL?", "photo"), | |||
"optional" => true, | |||
"no_value" => true)); | |||
$this->setField(array("attr" => "caption", | |||
"type" => "text_block", | |||
"label" => __("Caption", "photo"), | |||
"optional" => true, | |||
"preview" => true, | |||
"bookmarklet" => "page_link")); | |||
$this->setFilter("caption", array("markup_text", "markup_post_text")); | |||
$this->respondTo("delete_post", "delete_file"); | |||
$this->respondTo("filter_post", "filter_post"); | |||
$this->respondTo("post_options", "add_option"); | |||
$this->respondTo("admin_write_post", "swfupload"); | |||
$this->respondTo("admin_edit_post", "swfupload"); | |||
if (isset($_GET['url']) and | |||
preg_match("/http:\/\/(www\.)?flickr\.com\/photos\/([^\/]+)\/([0-9]+)/", $_GET['url'])) { | |||
$this->bookmarkletSelected(); | |||
$page = get_remote($_GET['url']); | |||
preg_match("/class=\"photoImgDiv\">\n<img src=\"([^\?\"]+)/", $page, $image); | |||
$this->setField(array("attr" => "from_url", | |||
"type" => "text", | |||
"label" => __("From URL?", "photo"), | |||
"optional" => true, | |||
"value" => $image[1])); | |||
} | |||
if (isset($_GET['url']) and preg_match("/\.(jpg|jpeg|png|gif|bmp)$/", $_GET['url'])) { | |||
$this->bookmarkletSelected(); | |||
$this->setField(array("attr" => "from_url", | |||
"type" => "text", | |||
"label" => __("From URL?", "photo"), | |||
"optional" => true, | |||
"value" => $_GET['url'])); | |||
} | |||
} | |||
public function swfupload($admin, $post = null) { | |||
if (isset($post) and $post->feather != "photo" or | |||
isset($_GET['feather']) and $_GET['feather'] != "photo") | |||
return; | |||
Trigger::current()->call("prepare_swfupload", "photo", "*.jpg;*.jpeg;*.png;*.gif;*.bmp"); | |||
} | |||
public function submit() { | |||
if (!isset($_POST['filename'])) { | |||
if (isset($_FILES['photo']) and $_FILES['photo']['error'] == 0) | |||
$filename = upload($_FILES['photo'], array("jpg", "jpeg", "png", "gif", "bmp")); | |||
elseif (!empty($_POST['from_url'])) | |||
$filename = upload_from_url($_POST['from_url'], array("jpg", "jpeg", "png", "gif", "bmp")); | |||
else | |||
error(__("Error"), __("Couldn't upload photo.")); | |||
} else | |||
$filename = $_POST['filename']; | |||
return Post::add(array("filename" => $filename, | |||
"caption" => $_POST['caption']), | |||
$_POST['slug'], | |||
Post::check_url($_POST['slug'])); | |||
} | |||
public function update($post) { | |||
if (!isset($_POST['filename'])) | |||
if (isset($_FILES['photo']) and $_FILES['photo']['error'] == 0) { | |||
$this->delete_file($post); | |||
$filename = upload($_FILES['photo'], array("jpg", "jpeg", "png", "gif", "tiff", "bmp")); | |||
} elseif (!empty($_POST['from_url'])) { | |||
$this->delete_file($post); | |||
$filename = upload_from_url($_POST['from_url'], array("jpg", "jpeg", "png", "gif", "tiff", "bmp")); | |||
} else | |||
$filename = $post->filename; | |||
else { | |||
$this->delete_file($post); | |||
$filename = $_POST['filename']; | |||
} | |||
$post->update(array("filename" => $filename, | |||
"caption" => $_POST['caption'])); | |||
} | |||
public function title($post) { | |||
return oneof($post->title_from_excerpt(), $post->filename); | |||
} | |||
public function excerpt($post) { | |||
return $post->caption; | |||
} | |||
public function feed_content($post) { | |||
return self::image_tag($post, 500, 500)."<br /><br />".$post->caption; | |||
} | |||
public function delete_file($post) { | |||
if ($post->feather != "photo") return; | |||
unlink(MAIN_DIR.Config::current()->uploads_path.$post->filename); | |||
} | |||
public function filter_post($post) { | |||
if ($post->feather != "photo") return; | |||
$post->image = $this->image_tag($post); | |||
} | |||
public function image_tag($post, $max_width = 500, $max_height = null, $more_args = "quality=100") { | |||
$filename = $post->filename; | |||
$config = Config::current(); | |||
$alt = !empty($post->alt_text) ? fix($post->alt_text, true) : $filename ; | |||
return '<img src="'.$config->chyrp_url.'/includes/thumb.php?file=..'.$config->uploads_path.urlencode($filename).'&max_width='.$max_width.'&max_height='.$max_height.'&'.$more_args.'" alt="'.$alt.'" />'; | |||
} | |||
public function image_link($post, $max_width = 500, $max_height = null, $more_args="quality=100") { | |||
$source = !empty($post->source) ? $post->source : uploaded($post->filename) ; | |||
return '<a href="'.$source.'">'.$this->image_tag($post, $max_width, $max_height, $more_args).'</a>'; | |||
} | |||
public function add_option($options, $post = null) { | |||
if (isset($post) and $post->feather != "photo") return; | |||
if (!isset($_GET['feather']) and Config::current()->enabled_feathers[0] != "photo" or | |||
isset($_GET['feather']) and $_GET['feather'] != "photo") return; | |||
$options[] = array("attr" => "option[alt_text]", | |||
"label" => __("Alt-Text", "photo"), | |||
"type" => "text", | |||
"value" => oneof(@$post->alt_text, "")); | |||
$options[] = array("attr" => "option[source]", | |||
"label" => __("Source", "photo"), | |||
"type" => "text", | |||
"value" => oneof(@$post->source, "")); | |||
$options[] = array("attr" => "from_url", | |||
"label" => __("From URL?", "photo"), | |||
"type" => "text"); | |||
return $options; | |||
} | |||
} | |||
@@ -0,0 +1,7 @@ | |||
name: Quote | |||
url: http://chyrp.net/ | |||
version: 2.0 | |||
description: Post quotes and cite sources. | |||
author: | |||
name: Alex Suraci | |||
url: http://ecks.tc/ |
@@ -0,0 +1,36 @@ | |||
# Chyrp v2.1 Translation File. | |||
# Copyright (C) 2011 Chyrp Team | |||
# This file is distributed under the same license as the Chyrp v2.1 package. | |||
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR. | |||
# | |||
#, fuzzy | |||
msgid "" | |||
msgstr "" | |||
"Project-Id-Version: Chyrp v2.1\n" | |||
"Report-Msgid-Bugs-To: email@chyrp.net\n" | |||
"POT-Creation-Date: 2011-01-10 22:16+0000\n" | |||
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" | |||
"Last-Translator: FIRST LAST <EMAIL@EXAMPLE.COM>\n" | |||
"Language-Team: LANGUAGE <EMAIL@EXAMPLE.COM>\n" | |||
"MIME-Version: 1.0\n" | |||
"Content-Type: text/plain; charset=CHARSET\n" | |||
"Content-Transfer-Encoding: 8bit\n" | |||
"Plural-Forms: nplurals=INTEGER; plural=EXPRESSION;\n" | |||
#: feathers/quote/quote.php:7 | |||
#: feathers/quote/info.yaml:1 | |||
msgid "Quote" | |||
msgstr "" | |||
#: feathers/quote/info.yaml:5 | |||
msgid "Post quotes and cite sources." | |||
msgstr "" | |||
#: feathers/quote/quote.php:24 | |||
msgid "Quote can't be empty." | |||
msgstr "" | |||
#: feathers/quote/quote.php:13 | |||
msgid "Source" | |||
msgstr "" | |||
@@ -0,0 +1,59 @@ | |||
<?php | |||
class Quote extends Feathers implements Feather { | |||
public function __init() { | |||
$this->setField(array("attr" => "quote", | |||
"type" => "text_block", | |||
"rows" => 5, | |||
"label" => __("Quote", "quote"), | |||
"preview" => true, | |||
"bookmarklet" => "selection")); | |||
$this->setField(array("attr" => "source", | |||
"type" => "text_block", | |||
"rows" => 5, | |||
"label" => __("Source", "quote"), | |||
"optional" => true, | |||
"preview" => true, | |||
"bookmarklet" => "page_link")); | |||
$this->setFilter("quote", array("markup_text", "markup_post_text")); | |||
$this->setFilter("source", array("markup_text", "markup_post_text")); | |||
} | |||
public function submit() { | |||
if (empty($_POST['quote'])) | |||
error(__("Error"), __("Quote can't be empty.", "quote")); | |||
return Post::add(array("quote" => $_POST['quote'], | |||
"source" => $_POST['source']), | |||
$_POST['slug'], | |||
Post::check_url($_POST['slug'])); | |||
} | |||
public function update($post) { | |||
if (empty($_POST['quote'])) | |||
error(__("Error"), __("Quote can't be empty.")); | |||
$post->update(array("quote" => $_POST['quote'], | |||
"source" => $_POST['source'])); | |||
} | |||
public function title($post) { | |||
return $post->title_from_excerpt(); | |||
} | |||
public function excerpt($post) { | |||
return $post->quote; | |||
} | |||
public function add_dash($text) { | |||
return preg_replace("/(<p(\s+[^>]+)?>|^)/si", "\\1— ", $text, 1); | |||
} | |||
public function feed_content($post) { | |||
$body = "<blockquote>\n\t"; | |||
$body.= $post->quote; | |||
$body.= "\n</blockquote>\n"; | |||
$body.= $post->source; | |||
return $body; | |||
} | |||
} |
@@ -0,0 +1,7 @@ | |||
name: Text | |||
url: http://chyrp.net/ | |||
version: 2.0 | |||
description: A basic text feather. | |||
author: | |||
name: Alex Suraci | |||
url: http://ecks.tc/ |
@@ -0,0 +1,35 @@ | |||
# Chyrp v2.1 Translation File. | |||
# Copyright (C) 2011 Chyrp Team | |||
# This file is distributed under the same license as the Chyrp v2.1 package. | |||
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR. | |||
# | |||
#, fuzzy | |||
msgid "" | |||
msgstr "" | |||
"Project-Id-Version: Chyrp v2.1\n" | |||
"Report-Msgid-Bugs-To: email@chyrp.net\n" | |||
"POT-Creation-Date: 2011-01-10 22:17+0000\n" | |||
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" | |||
"Last-Translator: FIRST LAST <EMAIL@EXAMPLE.COM>\n" | |||
"Language-Team: LANGUAGE <EMAIL@EXAMPLE.COM>\n" | |||
"MIME-Version: 1.0\n" | |||
"Content-Type: text/plain; charset=CHARSET\n" | |||
"Content-Transfer-Encoding: 8bit\n" | |||
"Plural-Forms: nplurals=INTEGER; plural=EXPRESSION;\n" | |||
#: feathers/text/info.yaml:1 | |||
msgid "Text" | |||
msgstr "" | |||
#: feathers/text/text.php:11 | |||
msgid "Body" | |||
msgstr "" | |||
#: feathers/text/info.yaml:5 | |||
msgid "A basic text feather." | |||
msgstr "" | |||
#: feathers/text/text.php:6 | |||
msgid "Title" | |||
msgstr "" | |||
@@ -0,0 +1,50 @@ | |||
<?php | |||
class Text extends Feathers implements Feather { | |||
public function __init() { | |||
$this->setField(array("attr" => "title", | |||
"type" => "text", | |||
"label" => __("Title", "text"), | |||
"optional" => true, | |||
"bookmarklet" => "title")); | |||
$this->setField(array("attr" => "body", | |||
"type" => "text_block", | |||
"label" => __("Body", "text"), | |||
"preview" => true, | |||
"bookmarklet" => "selection")); | |||
$this->setFilter("title", array("markup_title", "markup_post_title")); | |||
$this->setFilter("body", array("markup_text", "markup_post_text")); | |||
} | |||
public function submit() { | |||
if (empty($_POST['body'])) | |||
error(__("Error"), __("Body can't be blank.")); | |||
fallback($_POST['slug'], sanitize($_POST['title'])); | |||
return Post::add(array("title" => $_POST['title'], | |||
"body" => $_POST['body']), | |||
$_POST['slug'], | |||
Post::check_url($_POST['slug'])); | |||
} | |||
public function update($post) { | |||
if (empty($_POST['body'])) | |||
error(__("Error"), __("Body can't be blank.")); | |||
$post->update(array("title" => $_POST['title'], | |||
"body" => $_POST['body'])); | |||
} | |||
public function title($post) { | |||
return oneof($post->title, $post->title_from_excerpt()); | |||
} | |||
public function excerpt($post) { | |||
return $post->body; | |||
} | |||
public function feed_content($post) { | |||
return $post->body; | |||
} | |||
} |
@@ -0,0 +1,7 @@ | |||
name: Video | |||
url: http://chyrp.net/ | |||
version: 2.0 | |||
description: Lets you post videos to your site. | |||
author: | |||
name: Alex Suraci | |||
url: http://ecks.tc/ |
@@ -0,0 +1,32 @@ | |||
# Chyrp v2.1 Translation File. | |||
# Copyright (C) 2011 Chyrp Team | |||
# This file is distributed under the same license as the Chyrp v2.1 package. | |||
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR. | |||
# | |||
#, fuzzy | |||
msgid "" | |||
msgstr "" | |||
"Project-Id-Version: Chyrp v2.1\n" | |||
"Report-Msgid-Bugs-To: email@chyrp.net\n" | |||
"POT-Creation-Date: 2011-01-10 22:17+0000\n" | |||
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" | |||
"Last-Translator: FIRST LAST <EMAIL@EXAMPLE.COM>\n" | |||
"Language-Team: LANGUAGE <EMAIL@EXAMPLE.COM>\n" | |||
"MIME-Version: 1.0\n" | |||
"Content-Type: text/plain; charset=CHARSET\n" | |||
"Content-Transfer-Encoding: 8bit\n" | |||
"Plural-Forms: nplurals=INTEGER; plural=EXPRESSION;\n" | |||
#: feathers/video/info.yaml:5 | |||
msgid "Lets you post videos to your site." | |||
msgstr "" | |||
#: feathers/video/video.php:7 | |||
#: feathers/video/info.yaml:1 | |||
msgid "Video" | |||
msgstr "" | |||
#: feathers/video/video.php:15 | |||
msgid "Caption" | |||
msgstr "" | |||
@@ -0,0 +1,125 @@ | |||
<?php | |||
class Video extends Feathers implements Feather { | |||
public function __init() { | |||
$this->setField(array("attr" => "video", | |||
"type" => "text_block", | |||
"rows" => 4, | |||
"label" => __("Video", "video"), | |||
"preview" => true, | |||
"bookmarklet" => $this->isVideo() ? | |||
"url" : | |||
"")); | |||
$this->setField(array("attr" => "caption", | |||
"type" => "text_block", | |||
"rows" => 4, | |||
"label" => __("Caption", "video"), | |||
"optional" => true, | |||
"preview" => true, | |||
"bookmarklet" => "selection")); | |||
if ($this->isVideo()) | |||
$this->bookmarkletSelected(); | |||
$this->setFilter("caption", array("markup_text", "markup_post_text")); | |||
$this->respondTo("preview_video", "embed_tag"); | |||
} | |||
public function submit() { | |||
if (empty($_POST['video'])) | |||
error(__("Error"), __("Video can't be blank.")); | |||
return Post::add(array("embed" => $this->embed_tag($_POST['video']), | |||
"video" => $_POST['video'], | |||
"caption" => $_POST['caption']), | |||
$_POST['slug'], | |||
Post::check_url($_POST['slug'])); | |||
} | |||
public function update($post) { | |||
if (empty($_POST['video'])) | |||
error(__("Error"), __("Video can't be blank.")); | |||
$post->update(array("embed" => $this->embed_tag($_POST['video']), | |||
"video" => $_POST['video'], | |||
"caption" => $_POST['caption'])); | |||
} | |||
public function title($post) { | |||
return $post->title_from_excerpt(); | |||
} | |||
public function excerpt($post) { | |||
return $post->caption; | |||
} | |||
public function feed_content($post) { | |||
return $post->embed."<br /><br />".$post->caption; | |||
} | |||
public function embed_tag($video, $field = null) { # We use this for previewing too | |||
if (isset($field) and $field != "embed") | |||
return $video; # If they're previewing and the field argument isn't the embed, return the original. | |||
if (preg_match("/http:\/\/(www\.|[a-z]{2}\.)?youtube\.com\/watch\?v=([^&]+)/", $video, $matches)) { | |||
return '<object type="application/x-shockwave-flash" class="object-youtube" data="http://'.$matches[1].'youtube.com/v/'.$matches[2].'" width="468" height="391"><param name="movie" value="http://'.$matches[1].'youtube.com/v/'.$matches[2].'" /><param name="FlashVars" value="playerMode=embedded" /></object>'; | |||
} else if (preg_match("/^http:\/\/(www\.)?vimeo.com\/([0-9]+)/", $video, $matches)) { | |||
$site = get_remote("http://vimeo.com/".$matches[2]); | |||
preg_match('/<div id="vimeo_player_[0-9]+" class="player" style="width:([0-9]+)px;height:([0-9]+)px;">/', | |||
$site, | |||
$scale); | |||
return '<object type="application/x-shockwave-flash" class="object-vimeo" width="'.$scale[1].'" height="'.$scale[2].'" data="http://www.vimeo.com/moogaloop.swf?clip_id='.$matches[2].'&server=www.vimeo.com&show_title=1&show_byline=1&show_portrait=0&color=00adef&fullscreen=1"><param name="allowfullscreen" value="true" /><param name="allowscriptaccess" value="always" /><param name="movie" value="http://www.vimeo.com/moogaloop.swf?clip_id='.$matches[2].'&server=www.vimeo.com&show_title=1&show_byline=1&show_portrait=0&color=00adef&fullscreen=1" /></object>'; | |||
} else if (preg_match("/http:\/\/(www\.)?metacafe.com\/watch\/([0-9]+)\/([^\/&\?]+)/", $video, $matches)) { | |||
return '<object type="application/x-shockwave-flash" class="object-metacafe" data="http://www.metacafe.com/fplayer/'.$matches[2].'/'.$matches[3].'.swf" width="400" height="345"></object>'; | |||
} else if (preg_match("/http:\/\/(www\.)?revver.com\/video\/([0-9]+)/", $video, $matches)) { | |||
return '<script src="http://flash.revver.com/player/1.0/player.js?mediaId:'.$matches[2].';width:468;height:391;" type="text/javascript"></script>'; | |||
} else if (preg_match("/http:\/\/(www\.)viddler\.com\/.+/", $video)) { | |||
$viddler_page = get_remote($video); | |||
if (preg_match("/<link\s+rel=\"video_src\"\s+href=\"http:\/\/(www\.)?viddler.com\/player\/([0-9a-fA-F]+)/", $viddler_page, $matches) and | |||
preg_match("/<meta\s+name=\"video_height\"\s+content=\"([0-9]+)\"/", $viddler_page, $height) and | |||
preg_match("/<meta\s+name=\"video_width\"\s+content=\"([0-9]+)\"/", $viddler_page, $width)) { | |||
return '<object type="application/x-shockwave-flash" data="http://www.viddler.com/player/'.$matches[2].'/" width="'.$width[1].'" height="'.$height[1].'" id="viddler_'.$matches[2].'" class="object-youtube"><param name="movie" value="http://www.viddler.com/player/'.$matches[2].'/" /><param name="allowScriptAccess" value="always" /><param name="allowFullScreen" value="true" /></object>'; | |||
} | |||
return $video; | |||
} | |||
return $video; | |||
} | |||
public function embed_tag_for($post, $max_width = 500) { | |||
$post->embed = preg_replace("/&([[:alnum:]_]+)=/", "&\\1=", $post->embed); | |||
if (preg_match("/width(=\"|='|:\s*)([0-9]+)/", $post->embed, $width)) { | |||
$sep_w = $width[1]; | |||
$original_width = $width[2]; | |||
} else | |||
return $post->embed; | |||
if (preg_match("/height(=\"|='|:\s*)([0-9]+)/", $post->embed, $height)) { | |||
$sep_h = $height[1]; | |||
$original_height = $height[2]; | |||
$new_height = (int) (($max_width / $original_width) * $original_height); | |||
} | |||
$post->embed = str_replace(array($width[0], $height[0]), array("width".$sep_w.$max_width, "height".$sep_h.$new_height), $post->embed); | |||
return $post->embed; | |||
} | |||
public function isVideo() { | |||
if (!isset($_GET['url'])) | |||
return false; | |||
if (preg_match("/http:\/\/(www\.|[a-z]{2}\.)?youtube\.com\/watch\?v=([^&]+)/", $_GET['url']) or | |||
preg_match("/http:\/\/(www\.)?vimeo.com\/([0-9]+)/", $_GET['url']) or | |||
preg_match('/http:\/\/(www\.)?metacafe.com\/watch\/([0-9]+)\/([^\/&\?]+)/', $_GET['url']) or | |||
preg_match("/http:\/\/(www\.)?revver.com\/video\/([0-9]+)/", $_GET['url']) or | |||
preg_match("/http:\/\/(www\.)viddler\.com\/.+/", $_GET['url'])) | |||
return true; | |||
return false; | |||
} | |||
} |
@@ -0,0 +1,741 @@ | |||
<?php | |||
define('JAVASCRIPT', true); | |||
require_once "common.php"; | |||
$route = Route::current(MainController::current()); | |||
?> | |||
<!-- --><script> | |||
$(function(){ | |||
// Scan AJAX responses for errors. | |||
$(document).ajaxComplete(function(event, request){ | |||
var response = request ? request.responseText : null | |||
if (isError(response)) | |||
alert(response.replace(/(HEY_JAVASCRIPT_THIS_IS_AN_ERROR_JUST_SO_YOU_KNOW|<([^>]+)>\n?)/gm, "")) | |||
})<?php echo "\n\n\n\n\n"; # Balance out the line numbers in this script and in the output to help debugging. ?> | |||
// Handle typing "\ct" to insert a <tab> | |||
$("textarea").keyup(function(event){ | |||
if ($(this).val().match(/([^\\]|^)\\ct/gm)) | |||
$(this).val($(this).val().replace(/([^\\]|^)\\ct/gm, " ")) | |||
}) | |||
// Automated PNG fixing. | |||
$.ifixpng("<?php echo $config->chyrp_url; ?>/admin/themes/default/images/icons/pixel.gif") | |||
$("img[src$=.png]").ifixpng() | |||
// "Help" links should open in popup windows. | |||
$(".help").live("click", function(){ | |||
window.open($(this).attr("href"), "help", "status=0, scrollbars=1, location=0, menubar=0, "+ | |||
"toolbar=0, resizable=1, height=450, width=400") | |||
return false | |||
}) | |||
// Auto-expand input fields | |||
$(".expand").expand() | |||
// Checkbox toggling. | |||
togglers() | |||
if ($.browser.safari) | |||
$("code, .code").each(function(){ | |||
$(this).css({ | |||
fontFamily: "Monaco, monospace", | |||
fontSize: "9px" | |||
}) | |||
if ($(this).parent().parent().parent().hasClass("split") && $(this).attr("type") == "text") { | |||
$(this).css("margin-top", "2px") | |||
$(this).parent().css("margin-top", "-2px") | |||
} | |||
}) | |||
if (/(edit|write)_/.test(Route.action)) | |||
Write.init() | |||
if (Route.action == "delete_group") | |||
$("form.confirm").submit(function(){ | |||
if (!confirm("<?php echo __("You are a member of this group. Are you sure you want to delete it?"); ?>")) | |||
return false | |||
}) | |||
if (Route.action == "manage_pages") | |||
Manage.pages.init() | |||
if (Route.action == "modules" || Route.action == "feathers") | |||
Extend.init() | |||
// Remove things that only exist for JS-disabled users. | |||
$(".js_disabled").remove() | |||
$(".js_enabled").css("display", "block") | |||
}) | |||
function togglers() { | |||
var all_checked = true | |||
$(document.createElement("label")).attr("for", "toggle").text("<?php echo __("Toggle All"); ?>").appendTo("#toggler") | |||
$(document.createElement("input")).attr({ | |||
type: "checkbox", | |||
name: "toggle", | |||
id: "toggle", | |||
"class": "checkbox" | |||
}).appendTo("#toggler, .toggler") | |||
$("#toggle").click(function(){ | |||
$("form#new_group, form#group_edit, table").find(":checkbox").not("#toggle").each(function(){ | |||
this.checked = document.getElementById("toggle").checked | |||
}) | |||
$(this).parent().parent().find(":checkbox").not("#toggle").each(function(){ | |||
this.checked = document.getElementById("toggle").checked | |||
}) | |||
}) | |||
// Some checkboxes are already checked when the page is loaded | |||
$("form#new_group, form#group_edit, table").find(":checkbox").not("#toggle").each(function(){ | |||
if (!all_checked) return | |||
all_checked = this.checked | |||
}) | |||
$(":checkbox:not(#toggle)").click(function(){ | |||
var action_all_checked = true | |||
$("form#new_group, form#group_edit, table").find(":checkbox").not("#toggle").each(function(){ | |||
if (!action_all_checked) return | |||
action_all_checked = this.checked | |||
}) | |||
$("#toggle").parent().parent().find(":checkbox").not("#toggle").each(function(){ | |||
if (!action_all_checked) return | |||
action_all_checked = this.checked | |||
}) | |||
document.getElementById("toggle").checked = action_all_checked | |||
}) | |||
if ($("#toggler").size()) | |||
document.getElementById("toggle").checked = all_checked | |||
$("td:has(:checkbox)").click(function(e){ | |||
$(this).find(":checkbox").each(function(){ | |||
if (e.target != this) | |||
this.checked = !(this.checked) | |||
}) | |||
}) | |||
} | |||
var Route = { | |||
action: "<?php echo $_GET['action']; ?>" | |||
} | |||
var site_url = "<?php echo $config->chyrp_url; ?>" | |||
var Write = { | |||
init: function(){ | |||
this.bookmarklet_link() | |||
this.auto_expand_fields() | |||
if (!$.browser.msie) | |||
this.sortable_feathers() | |||
this.prepare_previewer() | |||
this.more_options() | |||
this.watch_slug() | |||
if (Route.action == "edit_group") | |||
this.confirm_group() | |||
}, | |||
bookmarklet_link: function(){ | |||
// Add the list item | |||
$(document.createElement("li")).addClass("bookmarklet right").text("Bookmarklet: ").prependTo(".write_post_nav") | |||
// Add the link | |||
$(document.createElement("a")) | |||
.text("<?php echo __("Chyrp!"); ?>") | |||
.addClass("no_drag") | |||
.attr("href", "javascript:var%20d=document,w=window,e=w.getSelection,k=d.getSelection,x=d.selection,"+ | |||
"s=(e?e():(k)?k():(x?x.createRange().text:0)),f=\'"+site_url+"/admin/?action=bookmarklet\',"+ | |||
"l=d.location,e=encodeURIComponent,p=\'&url=\'+e(l.href)+\'&title=\'+e(d.title)+\'&selection=\'+"+ | |||
"e(s),u=f+p;a=function(){if(!w.open(u,\'t\',\'toolbar=0,resizable=1,status=1,width=450,"+ | |||
"height=430\'))l.href=u;};if(/Firefox/.test(navigator.userAgent))setTimeout(a,0);else%20a();void(0)") | |||
.appendTo(".bookmarklet") | |||
}, | |||
auto_expand_fields: function(){ | |||
$("input.text").expand() | |||
$("textarea").each(function(){ | |||
$(this).css({ | |||
minHeight: $(this).outerHeight() + 24, | |||
lineHeight: "18px", | |||
padding: "3px 5px" | |||
}).autogrow() | |||
}) | |||
}, | |||
sortable_feathers: function(){ | |||
// Make the Feathers sortable | |||
$("#sub-nav").sortable({ | |||
axis: "x", | |||
placeholder: "feathers_sort", | |||
opacity: 0.8, | |||
delay: 1, | |||
revert: true, | |||
cancel: "a.no_drag, a[href$=write_page]", | |||
start: function(e, ui) { | |||
$(ui.item).find("a").click(function(){ return false }) | |||
$(".feathers_sort").width($(ui.item).width() - 2) | |||
}, | |||
update: function(e, ui){ | |||
$(ui.item).find("a").unbind("click") | |||
$.post("<?php echo $config->chyrp_url; ?>/includes/ajax.php", | |||
"action=reorder_feathers&"+ $("#sub-nav").sortable("serialize")) | |||
} | |||
}) | |||
}, | |||
prepare_previewer: function() { | |||
if (!$(".preview_me").size()) | |||
return | |||
var feather = ($("#feather").size()) ? $("#feather").val() : "" | |||
$(".preview_me").each(function(){ | |||
var id = $(this).attr("id") | |||
$(document.createElement("div")) | |||
.css("display", "none") | |||
.attr("id", "preview_"+ id) | |||
.insertBefore("#write_form, #edit_form") | |||
}) | |||
$(document.createElement("button")) | |||
.append("<?php echo __("Preview →"); ?>").attr("accesskey", "p") | |||
.click(function(){ | |||
$(".preview_me").each(function(){ | |||
var id = $(this).attr("id") | |||
$("#preview_"+ id).load("<?php echo $config->chyrp_url; ?>/includes/ajax.php", { | |||
action: "preview", | |||
content: $("#"+ id).val(), | |||
feather: feather, | |||
field: id | |||
}, function(){ | |||
$(this).fadeIn("fast") | |||
}) | |||
}) | |||
return false | |||
}) | |||
.appendTo(".buttons") | |||
}, | |||
more_options: function(){ | |||
if ($("#more_options").size()) { | |||
if (Cookie.get("show_more_options") == "true") | |||
var more_options_text = "<?php echo __("↑ Fewer Options"); ?>"; | |||
else | |||
var more_options_text = "<?php echo __("More Options ↓"); ?>"; | |||
$(document.createElement("a")).attr({ | |||
id: "more_options_link", | |||
href: "javascript:void(0)" | |||
}).addClass("more_options_link").append(more_options_text).insertBefore(".buttons") | |||
$("#more_options").clone().insertAfter("#more_options_link").removeClass("js_disabled") | |||
$("#more_options").wrap("<div></div>") | |||
if (Cookie.get("show_more_options") == null) | |||
$("#more_options").parent().css("display", "none") | |||
$("#more_options_link").click(function(){ | |||
if ($("#more_options").parent().css("display") == "none") { | |||
$(this).empty().append("<?php echo __("↑ Fewer Options"); ?>") | |||
Cookie.set("show_more_options", "true", 30) | |||
} else { | |||
$(this).empty().append("<?php echo __("More Options ↓"); ?>") | |||
Cookie.destroy("show_more_options") | |||
} | |||
$("#more_options").parent().slideToggle() | |||
}) | |||
} | |||
}, | |||
watch_slug: function(){ | |||
$("input#slug").keyup(function(e){ | |||
if (/^([a-zA-Z0-9\-\._:]*)$/.test($(this).val())) | |||
$(this).css("background", "") | |||
else | |||
$(this).css("background", "#ffdddd") | |||
}) | |||
}, | |||
confirm_group: function(msg){ | |||
$("form.confirm").submit(function(){ | |||
if (!confirm("<?php echo __("You are a member of this group. Are you sure the permissions are as you want them?"); ?>")) | |||
return false | |||
}) | |||
} | |||
} | |||
var Manage = { | |||
pages: { | |||
init: function(){ | |||
Manage.pages.prepare_reordering() | |||
}, | |||
parent_hash: function(){ | |||
var parent_hash = "" | |||
$(".sort_pages li").each(function(){ | |||
var id = $(this).attr("id").replace(/page_list_/, "") | |||
var parent = $(this).parent().parent() // this > #sort_pages > page_list_(id) | |||
var parent_id = (/page_list_/.test(parent.attr("id"))) ? parent.attr("id").replace(/page_list_/, "") : 0 | |||
$(this).attr("parent", parent_id) | |||
parent_hash += "&parent["+id+"]="+parent_id | |||
}) | |||
return parent_hash | |||
}, | |||
prepare_reordering: function(){ | |||
$(".sort_pages li div").css({ | |||
background: "#f9f9f9", | |||
padding: ".15em .5em", | |||
marginBottom: ".5em", | |||
border: "1px solid #ddd", | |||
cursor: "move" | |||
}) | |||
$("ul.sort_pages").tree({ | |||
sortOn: "li", | |||
dropOn: "li:not(.dragging) div", | |||
hoverClass: "sort-hover", | |||
done: function(){ | |||
$("#content > form > ul.sort_pages").loader() | |||
$.post("<?php echo $config->chyrp_url; ?>/includes/ajax.php", | |||
"action=organize_pages&"+ $("ul.sort_pages").sortable("serialize") + Manage.pages.parent_hash(), | |||
function(){ $("#content > form > ul.sort_pages").loader(true) }) | |||
} | |||
}) | |||
} | |||
} | |||
} | |||
var Extend = { | |||
init: function(){ | |||
this.prepare_info() | |||
this.prepare_draggables() | |||
if (Route.action != "modules") | |||
return | |||
this.draw_conflicts() | |||
this.draw_dependencies() | |||
$(window).resize(function(){ | |||
Extend.draw_conflicts() | |||
Extend.draw_dependencies() | |||
}) | |||
}, | |||
Drop: { | |||
extension: { | |||
classes: [], | |||
name: null, | |||
type: null | |||
}, | |||
action: null, | |||
previous: null, | |||
pane: null, | |||
confirmed: null | |||
}, | |||
prepare_info: function(){ | |||
$(".description:not(.expanded)").wrap("<div></div>").parent().hide() | |||
$(".info_link").click(function(){ | |||
$(this).parent().find(".description").parent().slideToggle("normal", Extend.redraw) | |||
return false | |||
}) | |||
}, | |||
prepare_draggables: function(){ | |||
$(".enable h2, .disable h2").append(" <span class=\"sub\"><?php echo __("(drag)"); ?></span>") | |||
$(".disable > ul > li:not(.missing_dependency), .enable > ul > li").draggable({ | |||
zIndex: 100, | |||
cancel: "a", | |||
revert: true | |||
}) | |||
$(".enable > ul, .disable > ul").droppable({ | |||
accept: "ul.extend > li:not(.missing_dependency)", | |||
tolerance: "pointer", | |||
activeClass: "active", | |||
hoverClass: "hover", | |||
drop: Extend.handle_drop | |||
}) | |||
$(".enable > ul > li, .disable > ul > li:not(.missing_dependency)").css("cursor", "move") | |||
Extend.equalize_lists() | |||
if ($(".feather").size()) | |||
<?php $tip = _f("(tip: drag the tabs on the <a href=\\\"%s\\\">write</a> page to reorder them)", | |||
array(url("/admin/?action=write"))); ?> | |||
$(document.createElement("small")).html("<?php echo $tip; ?>").css({ | |||
position: "relative", | |||
bottom: "-1em", | |||
display: "block", | |||
textAlign: "center" | |||
}).appendTo(".tip_here") | |||
}, | |||
handle_drop: function(ev, ui) { | |||
var classes = $(this).parent().attr("class").split(" ") | |||
Extend.Drop.pane = $(this) | |||
Extend.Drop.action = classes[0] | |||
Extend.Drop.previous = $(ui.draggable).parent().parent().attr("class").split(" ")[0] | |||
Extend.Drop.extension.classes = $(ui.draggable).attr("class").split(" ") | |||
Extend.Drop.extension.name = Extend.Drop.extension.classes[0] | |||
Extend.Drop.extension.type = classes[1] | |||
Extend.Drop.confirmed = false | |||
if (Extend.Drop.previous == Extend.Drop.action) | |||
return | |||
$.post("<?php echo $config->chyrp_url; ?>/includes/ajax.php", { | |||
action: "check_confirm", | |||
check: Extend.Drop.extension.name, | |||
type: Extend.Drop.extension.type | |||
}, function(data){ | |||
if (data != "" && Extend.Drop.action == "disable") | |||
Extend.Drop.confirmed = (confirm(data)) ? 1 : 0 | |||
$.ajax({ | |||
type: "post", | |||
dataType: "json", | |||
url: "<?php echo $config->chyrp_url; ?>/includes/ajax.php", | |||
data: { | |||
action: Extend.Drop.action + "_" + Extend.Drop.extension.type, | |||
extension: Extend.Drop.extension.name, | |||
confirm: Extend.Drop.confirmed | |||
}, | |||
beforeSend: function(){ Extend.Drop.pane.loader() }, | |||
success: Extend.finish_drop, | |||
error: function() { | |||
if (Extend.Drop.action == "enable") | |||
alert("<?php echo __("There was an error enabling the extension."); ?>"); | |||
else | |||
alert("<?php echo __("There was an error disabling the extension."); ?>"); | |||
Extend.Drop.pane.loader(true) | |||
$(ui.draggable).css({ left: 0, right: 0, top: 0, bottom: 0 }).appendTo($(".disable ul")) | |||
Extend.redraw() | |||
} | |||
}) | |||
}) | |||
$(ui.draggable).css({ left: 0, right: 0, top: 0, bottom: 0 }).appendTo(this) | |||
Extend.redraw() | |||
return true | |||
}, | |||
finish_drop: function(json){ | |||
if (Extend.Drop.action == "enable") { | |||
var dependees = Extend.Drop.extension.classes.find(/depended_by_(.+)/) | |||
for (i = 0; i < dependees.length; i++) { | |||
var dependee = dependees[i].replace("depended_by", "module") | |||
// The module depending on this one no longer "needs" it | |||
$("#"+ dependee).removeClass("needs_"+ Extend.Drop.extension.name) | |||
// Remove from the dependee's dependency list | |||
$("#"+ dependee +" .dependencies_list ."+ Extend.Drop.extension.name).hide() | |||
if ($("#"+ dependee).attr("class").split(" ").find(/needs_(.+)/).length == 0) | |||
$("#"+ dependee).find(".description").parent().hide().end().end() | |||
.draggable({ | |||
zIndex: 100, | |||
cancel: "a", | |||
revert: true | |||
}) | |||
.css("cursor", "move") | |||
} | |||
} else if ($(".depends_"+ Extend.Drop.extension.name).size()) { | |||
$(".depends_"+ Extend.Drop.extension.name).find(".description").parent().show() | |||
$(".depends_"+ Extend.Drop.extension.name) | |||
.find(".dependencies_list") | |||
.append($(document.createElement("li")).text(Extend.Drop.extension.name).addClass(Extend.Drop.extension.name)) | |||
.show() | |||
.end() | |||
.find(".dependencies_message") | |||
.show() | |||
.end() | |||
.addClass("needs_"+ Extend.Drop.extension.name) | |||
} | |||
Extend.Drop.pane.loader(true) | |||
$(json.notifications).each(function(){ | |||
if (this == "") return | |||
alert(this.replace(/<([^>]+)>\n?/gm, "")) | |||
}) | |||
Extend.redraw() | |||
}, | |||
equalize_lists: function(){ | |||
$("ul.extend").height("auto") | |||
$("ul.extend").each(function(){ | |||
if ($(".enable ul.extend").height() > $(this).height()) | |||
$(this).height($(".enable ul.extend").height()) | |||
if ($(".disable ul.extend").height() > $(this).height()) | |||
$(this).height($(".disable ul.extend").height()) | |||
}) | |||
}, | |||
redraw: function(){ | |||
Extend.equalize_lists() | |||
Extend.draw_conflicts() | |||
Extend.draw_dependencies() | |||
}, | |||
draw_conflicts: function(){ | |||
if (!$.support.boxModel || | |||
Route.action != "modules" || | |||
(!$(".extend li.conflict").size())) | |||
return false | |||
$("#conflicts_canvas").remove() | |||
$("#header, #welcome, #sub-nav, #content a.button, .extend li, #footer, h1, h2").css({ | |||
position: "relative", | |||
zIndex: 2 | |||
}) | |||
$("#header ul li a").css({ | |||
position: "relative", | |||
zIndex: 3 | |||
}) | |||
$(document.createElement("canvas")).attr("id", "conflicts_canvas").prependTo("body") | |||
$("#conflicts_canvas").css({ | |||
position: "absolute", | |||
top: 0, | |||
bottom: 0, | |||
zIndex: 1 | |||
}).attr({ width: $(document).width(), height: $(document).height() }) | |||
var canvas = document.getElementById("conflicts_canvas").getContext("2d") | |||
var conflict_displayed = [] | |||
$(".extend li.conflict").each(function(){ | |||
var classes = $(this).attr("class").split(" ") | |||
classes.shift() // Remove the module's safename class | |||
classes.remove(["conflict", | |||
"depends", | |||
"missing_dependency", | |||
/depended_by_(.+)/, | |||
/needs_(.+)/, | |||
/depends_(.+)/, | |||
/ui-draggable(-dragging)?/]) | |||
for (i = 0; i < classes.length; i++) { | |||
var conflict = classes[i].replace("conflict_", "module_") | |||
if (conflict_displayed[$(this).attr("id")+" :: "+conflict]) | |||
continue | |||
canvas.strokeStyle = "#ef4646" | |||
canvas.fillStyle = "#fbe3e4" | |||
canvas.lineWidth = 3 | |||
var this_status = $(this).parent().parent().attr("class").split(" ")[0] + "d" | |||
var conflict_status = $("#"+conflict).parent().parent().attr("class").split(" ")[0] + "d" | |||
if (conflict_status != this_status) { | |||
var line_from_x = (conflict_status == "disabled") ? | |||
$("#"+conflict).offset().left : | |||
$("#"+conflict).offset().left + $("#"+conflict).outerWidth() | |||
var line_from_y = $("#"+conflict).offset().top + 12 | |||
var line_to_x = (conflict_status == "enabled") ? | |||
$(this).offset().left : | |||
$(this).offset().left + $(this).outerWidth() | |||
var line_to_y = $(this).offset().top + 12 | |||
// Line | |||
canvas.moveTo(line_from_x, line_from_y) | |||
canvas.lineTo(line_to_x, line_to_y) | |||
canvas.stroke() | |||
} else if (conflict_status == "disabled") { | |||
var line_from_x = $("#"+conflict).offset().left | |||
var line_from_y = $("#"+conflict).offset().top + 12 | |||
var line_to_x = $(this).offset().left | |||
var line_to_y = $(this).offset().top + 12 | |||
var median = line_from_y + ((line_to_y - line_from_y) / 2) | |||
var curve = line_from_x - 25 | |||
// Line | |||
canvas.beginPath() | |||
canvas.moveTo(line_from_x, line_from_y) | |||
canvas.quadraticCurveTo(curve, median, line_to_x, line_to_y) | |||
canvas.stroke() | |||
} else if (conflict_status == "enabled") { | |||
var line_from_x = $("#"+conflict).offset().left + $("#"+conflict).outerWidth() | |||
var line_from_y = $("#"+conflict).offset().top + 12 | |||
var line_to_x = $(this).offset().left + $(this).outerWidth() | |||
var line_to_y = $(this).offset().top + 12 | |||
var median = line_from_y + ((line_to_y - line_from_y) / 2) | |||
var curve = line_from_x + 25 | |||
// Line | |||
canvas.beginPath() | |||
canvas.moveTo(line_from_x, line_from_y) | |||
canvas.quadraticCurveTo(curve, median, line_to_x, line_to_y) | |||
canvas.stroke() | |||
} | |||
// Beginning circle | |||
canvas.beginPath() | |||
canvas.arc(line_from_x, line_from_y, 5, 0, Math.PI * 2, false) | |||
canvas.fill() | |||
canvas.stroke() | |||
// Ending circle | |||
canvas.beginPath() | |||
canvas.arc(line_to_x, line_to_y, 5, 0, Math.PI * 2, false) | |||
canvas.fill() | |||
canvas.stroke() | |||
conflict_displayed[conflict+" :: "+$(this).attr("id")] = true | |||
} | |||
}) | |||
return true | |||
}, | |||
draw_dependencies: function() { | |||
if (!$.support.boxModel || | |||
Route.action != "modules" || | |||
(!$(".extend li.depends").size())) | |||
return false | |||
$("#depends_canvas").remove() | |||
$("#header, #welcome, #sub-nav, #content a.button, .extend li, #footer, h1, h2").css({ | |||
position: "relative", | |||
zIndex: 2 | |||
}) | |||
$("#header ul li a").css({ | |||
position: "relative", | |||
zIndex: 3 | |||
}) | |||
$(document.createElement("canvas")).attr("id", "depends_canvas").prependTo("body") | |||
$("#depends_canvas").css({ | |||
position: "absolute", | |||
top: 0, | |||
bottom: 0, | |||
zIndex: 1 | |||
}).attr({ width: $(document).width(), height: $(document).height() }) | |||
var canvas = document.getElementById("depends_canvas").getContext("2d") | |||
var dependency_displayed = [] | |||
$(".extend li.depends").each(function(){ | |||
var classes = $(this).attr("class").split(" ") | |||
classes.shift() // Remove the module's safename class | |||
classes.remove(["conflict", | |||
"depends", | |||
"missing_dependency", | |||
/depended_by_(.+)/, | |||
/needs_(.+)/, | |||
/conflict_(.+)/, | |||
/ui-draggable(-dragging)?/]) | |||
var gradients = [] | |||
for (i = 0; i < classes.length; i++) { | |||
var depend = classes[i].replace("depends_", "module_") | |||
if (dependency_displayed[$(this).attr("id")+" :: "+depend]) | |||
continue | |||
canvas.fillStyle = "#e4e3fb" | |||
canvas.lineWidth = 3 | |||
var this_status = $(this).parent().parent().attr("class").split(" ")[0] + "d" | |||
var depend_status = $("#"+depend).parent().parent().attr("class").split(" ")[0] + "d" | |||
if (depend_status != this_status) { | |||
var line_from_x = (depend_status == "disabled") ? $("#"+depend).offset().left : $("#"+depend).offset().left + $("#"+depend).outerWidth() | |||
var line_from_y = $("#"+depend).offset().top + 12 | |||
var line_to_x = (depend_status == "enabled") ? $(this).offset().left : $(this).offset().left + $(this).outerWidth() | |||
var line_to_y = $(this).offset().top + 12 | |||
var height = line_to_y - line_from_y | |||
var width = line_to_x - line_from_x | |||
if (height <= 45) | |||
gradients[i] = canvas.createLinearGradient(line_from_x, 0, line_from_x + width, 0) | |||
else | |||
gradients[i] = canvas.createLinearGradient(0, line_from_y, 0, line_from_y + height) | |||
gradients[i].addColorStop(0, '#0052cc') | |||
gradients[i].addColorStop(1, '#0096ff') | |||
canvas.strokeStyle = gradients[i] | |||
// Line | |||
canvas.moveTo(line_from_x, line_from_y) | |||
canvas.lineTo(line_to_x, line_to_y) | |||
canvas.stroke() | |||
} else if (depend_status == "disabled") { | |||
var line_from_x = $("#"+depend).offset().left + $("#"+depend).outerWidth() | |||
var line_from_y = $("#"+depend).offset().top + 12 | |||
var line_to_x = $(this).offset().left + $(this).outerWidth() | |||
var line_to_y = $(this).offset().top + 12 | |||
var median = line_from_y + ((line_to_y - line_from_y) / 2) | |||
var height = line_to_y - line_from_y | |||
var curve = line_from_x + 25 | |||
gradients[i] = canvas.createLinearGradient(0, line_from_y, 0, line_from_y + height) | |||
gradients[i].addColorStop(0, '#0052cc') | |||
gradients[i].addColorStop(1, '#0096ff') | |||
canvas.strokeStyle = gradients[i] | |||
// Line | |||
canvas.beginPath() | |||
canvas.moveTo(line_from_x, line_from_y) | |||
canvas.quadraticCurveTo(curve, median, line_to_x, line_to_y) | |||
canvas.stroke() | |||
} else if (depend_status == "enabled") { | |||
var line_from_x = $("#"+depend).offset().left | |||
var line_from_y = $("#"+depend).offset().top + 12 | |||
var line_to_x = $(this).offset().left | |||
var line_to_y = $(this).offset().top + 12 | |||
var median = line_from_y + ((line_to_y - line_from_y) / 2) | |||
var height = line_to_y - line_from_y | |||
var curve = line_from_x - 25 | |||
gradients[i] = canvas.createLinearGradient(0, line_from_y, 0, line_from_y + height) | |||
gradients[i].addColorStop(0, '#0052cc') | |||
gradients[i].addColorStop(1, '#0096ff') | |||
canvas.strokeStyle = gradients[i] | |||
// Line | |||
canvas.beginPath() | |||
canvas.moveTo(line_from_x, line_from_y) | |||
canvas.quadraticCurveTo(curve, median, line_to_x, line_to_y) | |||
canvas.stroke() | |||
} | |||
// Beginning circle | |||
canvas.beginPath() | |||
canvas.arc(line_from_x, line_from_y, 5, 0, Math.PI * 2, false) | |||
canvas.fill() | |||
canvas.stroke() | |||
// Ending circle | |||
canvas.beginPath() | |||
canvas.arc(line_to_x, line_to_y, 5, 0, Math.PI * 2, false) | |||
canvas.fill() | |||
canvas.stroke() | |||
dependency_displayed[depend+" :: "+$(this).attr("id")] = true | |||
} | |||
}) | |||
return true | |||
} | |||
} | |||
<?php $trigger->call("admin_javascript"); ?> | |||
<!-- --></script> |
@@ -0,0 +1,207 @@ | |||
<?php | |||
define('AJAX', true); | |||
require_once "common.php"; | |||
# Prepare the controller. | |||
$main = MainController::current(); | |||
# Parse the route. | |||
$route = Route::current($main); | |||
if (!$visitor->group->can("view_site")) | |||
if ($trigger->exists("can_not_view_site")) | |||
$trigger->call("can_not_view_site"); | |||
else | |||
show_403(__("Access Denied"), __("You are not allowed to view this site.")); | |||
switch($_POST['action']) { | |||
case "edit_post": | |||
if (!isset($_POST['id'])) | |||
error(__("No ID Specified"), __("Please specify an ID of the post you would like to edit.")); | |||
$post = new Post($_POST['id'], array("filter" => false, "drafts" => true)); | |||
if ($post->no_results) { | |||
header("HTTP/1.1 404 Not Found"); | |||
$trigger->call("not_found"); | |||
exit; | |||
} | |||
if (!$post->editable()) | |||
show_403(__("Access Denied"), __("You do not have sufficient privileges to edit posts.")); | |||
$title = $post->title(); | |||
$theme_file = THEME_DIR."/forms/feathers/".$post->feather.".php"; | |||
$default_file = FEATHERS_DIR."/".$post->feather."/fields.php"; | |||
$options = array(); | |||
Trigger::current()->filter($options, array("edit_post_options", "post_options"), $post); | |||
$main->display("forms/post/edit", array("post" => $post, | |||
"feather" => Feathers::$instances[$post->feather], | |||
"options" => $options, | |||
"groups" => Group::find(array("order" => "id ASC")))); | |||
break; | |||
case "delete_post": | |||
$post = new Post($_POST['id'], array("drafts" => true)); | |||
if ($post->no_results) { | |||
header("HTTP/1.1 404 Not Found"); | |||
$trigger->call("not_found"); | |||
exit; | |||
} | |||
if (!$post->deletable()) | |||
show_403(__("Access Denied"), __("You do not have sufficient privileges to delete this post.")); | |||
Post::delete($_POST['id']); | |||
break; | |||
case "view_post": | |||
fallback($_POST['offset'], 0); | |||
fallback($_POST['context']); | |||
$reason = (isset($_POST['reason'])) ? $_POST['reason'] : "" ; | |||
if (isset($_POST['id'])) | |||
$post = new Post($_POST['id'], array("drafts" => true)); | |||
if ($post->no_results) { | |||
header("HTTP/1.1 404 Not Found"); | |||
$trigger->call("not_found"); | |||
exit; | |||
} | |||
$main->display("feathers/".$post->feather, array("post" => $post, "ajax_reason" => $reason)); | |||
break; | |||
case "preview": | |||
if (empty($_POST['content'])) | |||
break; | |||
$trigger->filter($_POST['content'], | |||
array("preview_".$_POST['feather'], "preview"), | |||
$_POST['field'], | |||
$_POST['feather']); | |||
echo "<h2 class=\"preview-header\">".__("Preview")."</h2>\n". | |||
"<div class=\"preview-content\">".fix($_POST['content'])."</div>"; | |||
break; | |||
case "check_confirm": | |||
if (!$visitor->group->can("toggle_extensions")) | |||
show_403(__("Access Denied"), __("You do not have sufficient privileges to enable/disable extensions.")); | |||
$dir = ($_POST['type'] == "module") ? MODULES_DIR : FEATHERS_DIR ; | |||
$info = YAML::load($dir."/".$_POST['check']."/info.yaml"); | |||
fallback($info["confirm"], ""); | |||
if (!empty($info["confirm"])) | |||
echo __($info["confirm"], $_POST['check']); | |||
break; | |||
case "organize_pages": | |||
foreach ($_POST['parent'] as $id => $parent) | |||
$sql->update("pages", array("id" => $id), array("parent_id" => $parent)); | |||
foreach ($_POST['page_list'] as $index => $page) | |||
$sql->update("pages", array("id" => $page), array("list_order" => $index)); | |||
break; | |||
case "enable_module": case "enable_feather": | |||
$type = ($_POST['action'] == "enable_module") ? "module" : "feather" ; | |||
if (!$visitor->group->can("change_settings")) | |||
if ($type == "module") | |||
exit("{ \"notifications\": [\"".__("You do not have sufficient privileges to enable/disable modules.")."\"] }"); | |||
else | |||
exit("{ \"notifications\": [\"".__("You do not have sufficient privileges to enable/disable feathers.")."\"] }"); | |||
if (($type == "module" and module_enabled($_POST['extension'])) or | |||
($type == "feather" and feather_enabled($_POST['extension']))) | |||
exit("{ \"notifications\": [] }"); | |||
$enabled_array = ($type == "module") ? "enabled_modules" : "enabled_feathers" ; | |||
$folder = ($type == "module") ? MODULES_DIR : FEATHERS_DIR ; | |||
if (file_exists($folder."/".$_POST["extension"]."/locale/".$config->locale.".mo")) | |||
load_translator($_POST["extension"], $folder."/".$_POST["extension"]."/locale/".$config->locale.".mo"); | |||
$info = YAML::load($folder."/".$_POST["extension"]."/info.yaml"); | |||
fallback($info["uploader"], false); | |||
fallback($info["notifications"], array()); | |||
foreach ($info["notifications"] as &$notification) | |||
$notification = addslashes(__($notification, $_POST["extension"])); | |||
require $folder."/".$_POST["extension"]."/".$_POST["extension"].".php"; | |||
if ($info["uploader"]) | |||
if (!file_exists(MAIN_DIR.$config->uploads_path)) | |||
$info["notifications"][] = _f("Please create the <code>%s</code> directory at your Chyrp install's root and CHMOD it to 777.", array($config->uploads_path)); | |||
elseif (!is_writable(MAIN_DIR.$config->uploads_path)) | |||
$info["notifications"][] = _f("Please CHMOD <code>%s</code> to 777.", array($config->uploads_path)); | |||
$class_name = camelize($_POST["extension"]); | |||
if ($type == "module" and !is_subclass_of($class_name, "Modules")) | |||
error("", __("Item is not a module.")); | |||
if ($type == "feather" and !is_subclass_of($class_name, "Feathers")) | |||
error("", __("Item is not a feather.")); | |||
if (method_exists($class_name, "__install")) | |||
call_user_func(array($class_name, "__install")); | |||
$new = $config->$enabled_array; | |||
array_push($new, $_POST["extension"]); | |||
$config->set($enabled_array, $new); | |||
exit('{ "notifications": ['. | |||
(!empty($info["notifications"]) ? '"'.implode('", "', $info["notifications"]).'"' : ""). | |||
'] }'); | |||
break; | |||
case "disable_module": case "disable_feather": | |||
$type = ($_POST['action'] == "disable_module") ? "module" : "feather" ; | |||
if (!$visitor->group->can("change_settings")) | |||
if ($type == "module") | |||
exit("{ \"notifications\": [\"".__("You do not have sufficient privileges to enable/disable modules.")."\"] }"); | |||
else | |||
exit("{ \"notifications\": [\"".__("You do not have sufficient privileges to enable/disable feathers.")."\"] }"); | |||
if (($type == "module" and !module_enabled($_POST['extension'])) or | |||
($type == "feather" and !feather_enabled($_POST['extension']))) | |||
exit("{ \"notifications\": [] }"); | |||
$class_name = camelize($_POST["extension"]); | |||
if (method_exists($class_name, "__uninstall")) | |||
call_user_func(array($class_name, "__uninstall"), ($_POST['confirm'] == "1")); | |||
$enabled_array = ($type == "module") ? "enabled_modules" : "enabled_feathers" ; | |||
$config->set($enabled_array, | |||
array_diff($config->$enabled_array, array($_POST['extension']))); | |||
exit('{ "notifications": [] }'); | |||
break; | |||
case "reorder_feathers": | |||
$reorder = oneof(@$_POST['list'], $config->enabled_feathers); | |||
foreach ($reorder as &$value) | |||
$value = preg_replace("/feathers\[([^\]]+)\]/", "\\1", $value); | |||
$config->set("enabled_feathers", $reorder); | |||
break; | |||
} | |||
$trigger->call("ajax"); | |||
if (!empty($_POST['action'])) | |||
$trigger->call("ajax_".$_POST['action']); |
@@ -0,0 +1,114 @@ | |||
<?php | |||
if (!defined("INCLUDES_DIR")) define("INCLUDES_DIR", dirname(__FILE__)); | |||
/** | |||
* Class: Config | |||
* Holds all of the configuration variables for the entire site, as well as Module settings. | |||
*/ | |||
class Config { | |||
# Variable: $yaml | |||
# Holds all of the YAML settings as a $key => $val array. | |||
private $yaml = array(); | |||
/** | |||
* Function: __construct | |||
* Loads the configuration YAML file. | |||
*/ | |||
private function __construct() { | |||
if (!file_exists(INCLUDES_DIR."/config.yaml.php")) | |||
return false; | |||
$contents = str_replace("<?php header(\"Status: 403\"); exit(\"Access denied.\"); ?>\n", | |||
"", | |||
file_get_contents(INCLUDES_DIR."/config.yaml.php")); | |||
$this->yaml = YAML::load($contents); | |||
$arrays = array("enabled_modules", "enabled_feathers", "routes"); | |||
foreach ($this->yaml as $setting => $value) | |||
if (in_array($setting, $arrays) and empty($value)) | |||
$this->$setting = array(); | |||
elseif (!is_int($setting)) | |||
$this->$setting = (is_string($value)) ? stripslashes($value) : $value ; | |||
fallback($this->url, $this->chyrp_url); | |||
} | |||
/** | |||
* Function: set | |||
* Adds or replaces a configuration setting with the given value. | |||
* | |||
* Parameters: | |||
* $setting - The setting name. | |||
* $value - The value. | |||
* $overwrite - If the setting exists and is the same value, should it be overwritten? | |||
*/ | |||
public function set($setting, $value, $overwrite = true) { | |||
if (isset($this->$setting) and $this->$setting == $value and !$overwrite) | |||
return false; | |||
if (isset($this->file) and file_exists($this->file)) { | |||
$contents = str_replace("<?php header(\"Status: 403\"); exit(\"Access denied.\"); ?>\n", | |||
"", | |||
file_get_contents($this->file)); | |||
$this->yaml = YAML::load($contents); | |||
} | |||
# Add the setting | |||
$this->yaml[$setting] = $this->$setting = $value; | |||
if (class_exists("Trigger")) | |||
Trigger::current()->call("change_setting", $setting, $value, $overwrite); | |||
# Add the PHP protection! | |||
$contents = "<?php header(\"Status: 403\"); exit(\"Access denied.\"); ?>\n"; | |||
# Generate the new YAML settings | |||
$contents.= YAML::dump($this->yaml); | |||
if (!@file_put_contents(INCLUDES_DIR."/config.yaml.php", $contents)) { | |||
Flash::warning(_f("Could not set \"<code>%s</code>\" configuration setting because <code>%s</code> is not writable.", | |||
array($setting, "/includes/config.yaml.php"))); | |||
return false; | |||
} else | |||
return true; | |||
} | |||
/** | |||
* Function: remove | |||
* Removes a configuration setting. | |||
* | |||
* Parameters: | |||
* $setting - The name of the setting to remove. | |||
*/ | |||
public function remove($setting) { | |||
if (isset($this->file) and file_exists($this->file)) { | |||
$contents = str_replace("<?php header(\"Status: 403\"); exit(\"Access denied.\"); ?>\n", | |||
"", | |||
file_get_contents($this->file)); | |||
$this->yaml = YAML::load($contents); | |||
} | |||
# Add the setting | |||
unset($this->yaml[$setting]); | |||
# Add the PHP protection! | |||
$contents = "<?php header(\"Status: 403\"); exit(\"Access denied.\"); ?>\n"; | |||
# Generate the new YAML settings | |||
$contents.= YAML::dump($this->yaml); | |||
file_put_contents(INCLUDES_DIR."/config.yaml.php", $contents); | |||
} | |||
/** | |||
* Function: current | |||
* Returns a singleton reference to the current configuration. | |||
*/ | |||
public static function & current() { | |||
static $instance = null; | |||
return $instance = (empty($instance)) ? new self() : $instance ; | |||
} | |||
} |
@@ -0,0 +1,106 @@ | |||
<?php | |||
/** | |||
* Class: Feathers | |||
* Contains various functions, acts as the backbone for all feathers. | |||
*/ | |||
class Feathers { | |||
# Array: $instances | |||
# Holds all Module instantiations. | |||
static $instances = array(); | |||
# Array: $custom_filters | |||
# Manages named Trigger filters for Feather fields. | |||
static $filters = array(); | |||
# Array: $custom_filters | |||
# Manages custom Feather-provided Trigger filters. | |||
static $custom_filters = array(); | |||
/** | |||
* Function: setFilter | |||
* Applies a filter to a specified field of the Feather. | |||
* | |||
* Parameters: | |||
* $field - Attribute of the post to filter. | |||
* $name - Name of the filter to use. | |||
* | |||
* See Also: | |||
* <Trigger.filter> | |||
*/ | |||
protected function setFilter($field, $name) { | |||
self::$filters[get_class($this)][] = array("field" => $field, "name" => $name); | |||
} | |||
/** | |||
* Function: customFilter | |||
* Allows a Feather to apply its own filter to a specified field. | |||
* | |||
* Parameters: | |||
* $field - Attribute of the post to filter. | |||
* $name - Name of the class function to use as the filter. | |||
* $priority - Priority of the filter. | |||
* | |||
* See Also: | |||
* <Trigger.filter> | |||
*/ | |||
protected function customFilter($field, $name, $priority = 10) { | |||
self::$custom_filters[get_class($this)][] = array("field" => $field, "name" => $name); | |||
} | |||
/** | |||
* Function: respondTo | |||
* Allows a Feather to respond to a Trigger as a Module would. | |||
* | |||
* Parameters: | |||
* $name - Name of the trigger to respond to. | |||
* $function - Name of the class function to respond with. | |||
* $priority - Priority of the response. | |||
* | |||
* See Also: | |||
* <Trigger> | |||
*/ | |||
protected function respondTo($name, $function = null, $priority = 10) { | |||
fallback($function, $name); | |||
Trigger::current()->priorities[$name][] = array("priority" => $priority, "function" => array($this, $function)); | |||
} | |||
/** | |||
* Function: setField | |||
* Sets the feather's fields for creating/editing posts with that feather. | |||
* | |||
* Parameters: | |||
* $options - An array of key => val options for the field. | |||
* | |||
* Options: | |||
* attr - The technical name for the field. Think $post->attr. | |||
* type - The field type. (text, file, text_block, or select) | |||
* label - The label for the field. | |||
* preview - Is this field previewable? | |||
* optional - Is this field optional? | |||
* bookmarklet - What to fill this field by in the bookmarklet. | |||
* url or page_url - The URL of the page they're viewing when they open the bookmarklet. | |||
* title or page_title - The title of the page they're viewing when they open the bookmarklet. | |||
* selection - Their selection on the page they're viewing when they open the bookmarklet. | |||
* extra - Stuff to output after the input field. Can be anything. | |||
* note - A minor note to display next to the label text. | |||
*/ | |||
protected function setField($options) { | |||
fallback($options["classes"], array()); | |||
if (isset($options["class"])) | |||
$options["classes"][] = $options["class"]; | |||
if (isset($options["preview"]) and $options["preview"]) | |||
$options["classes"][] = "preview_me"; | |||
$this->fields[$options["attr"]] = $options; | |||
} | |||
/** | |||
* Function: bookmarkletSelected | |||
* The Feather that this function is called from will be selected when they open the Bookmarklet. | |||
*/ | |||
protected function bookmarkletSelected() { | |||
AdminController::current()->selected_bookmarklet = $this->safename; | |||
} | |||
} |
@@ -0,0 +1,197 @@ | |||
<?php | |||
/** | |||
* Class: Flash | |||
* Stores messages (notice, warning, message) to display to the user after a redirect. | |||
*/ | |||
class Flash { | |||
# Array: $notices | |||
# Manages notices. | |||
private $notices = array(); | |||
# Array: $warnings | |||
# Manages warnings. | |||
private $warnings = array(); | |||
# Array: $messages | |||
# Manages messages. | |||
private $messages = array(); | |||
# Array: $all | |||
# Manages all Flashes. | |||
private $all = array(); | |||
# Boolean: $exists | |||
# Do any Flashes exist? | |||
static $exists = array("message" => false, | |||
"notice" => false, | |||
"warning" => false, | |||
null => false); | |||
/** | |||
* Function: __construct | |||
* Removes empty notification variables from the session. | |||
*/ | |||
private function __construct() { | |||
foreach (array("messages", "notices", "warnings") as $type) | |||
if (isset($_SESSION[$type]) and empty($_SESSION[$type])) | |||
unset($_SESSION[$type]); | |||
} | |||
/** | |||
* Function: prepare | |||
* Prepare the structure of the "flash" session value. | |||
*/ | |||
static function prepare($type) { | |||
if (!isset($_SESSION)) | |||
$_SESSION = array(); | |||
if (!isset($_SESSION[$type])) | |||
$_SESSION[$type] = array(); | |||
} | |||
/** | |||
* Function: message | |||
* Add a message (neutral) to the session. | |||
* | |||
* Parameters: | |||
* $message - Message to display. | |||
* $redirect_to - URL to redirect to after the message is stored. | |||
*/ | |||
static function message($message, $redirect_to = null) { | |||
self::prepare("messages"); | |||
$_SESSION['messages'][] = Trigger::current()->filter($message, "flash_message", $redirect_to); | |||
if (isset($redirect_to)) | |||
redirect($redirect_to); | |||
} | |||
/** | |||
* Function: notice | |||
* Add a notice (positive) message to the session. | |||
* | |||
* Parameters: | |||
* $message - Message to display. | |||
* $redirect_to - URL to redirect to after the message is stored. | |||
*/ | |||
static function notice($message, $redirect_to = null) { | |||
self::prepare("notices"); | |||
$_SESSION['notices'][] = Trigger::current()->filter($message, "flash_notice_message", $redirect_to); | |||
if (TESTER) | |||
exit("SUCCESS: ".$message); | |||
if (isset($redirect_to)) | |||
redirect($redirect_to); | |||
} | |||
/** | |||
* Function: warning | |||
* Add a warning (negative) message to the session. | |||
* | |||
* Parameters: | |||
* $message - Message to display. | |||
* $redirect_to - URL to redirect to after the message is stored. | |||
*/ | |||
static function warning($message, $redirect_to = null) { | |||
self::prepare("warnings"); | |||
$_SESSION['warnings'][] = Trigger::current()->filter($message, "flash_warning_message", $redirect_to); | |||
if (TESTER) | |||
exit("ERROR: ".$message); | |||
if (isset($redirect_to)) | |||
redirect($redirect_to); | |||
} | |||
/** | |||
* Function: messages | |||
* Calls <Flash.serve> "messages". | |||
*/ | |||
public function messages() { | |||
return $this->serve("messages"); | |||
} | |||
/** | |||
* Function: notices | |||
* Calls <Flash.serve> "notices". | |||
*/ | |||
public function notices() { | |||
return $this->serve("notices"); | |||
} | |||
/** | |||
* Function: warnings | |||
* Calls <Flash.serve> "warnings". | |||
*/ | |||
public function warnings() { | |||
return $this->serve("warnings"); | |||
} | |||
/** | |||
* Function: all | |||
* Returns an associative array of all messages and destroys their session values. | |||
* | |||
* Returns: | |||
* An array of every message available, in the form of [type => [messages]]. | |||
*/ | |||
public function all() { | |||
return array("messages" => $this->messages(), | |||
"notices" => $this->notices(), | |||
"warnings" => $this->warnings()); | |||
} | |||
/** | |||
* Function: serve | |||
* Serves a message of type $type and destroys it from the session. | |||
* | |||
* Parameters: | |||
* $type - Type of messages to serve. | |||
* | |||
* Returns: | |||
* An array of messages of the requested type. | |||
*/ | |||
public function serve($type) { | |||
if (!empty($_SESSION[$type])) | |||
self::$exists[depluralize($type)] = self::$exists[null] = true; | |||
if (isset($_SESSION[$type])) { | |||
$this->$type = $_SESSION[$type]; | |||
$_SESSION[$type] = array(); | |||
} | |||
return $this->$type; | |||
} | |||
/** | |||
* Function: exists | |||
* Checks for flash messages. | |||
* | |||
* Parameters: | |||
* $type - The type of message to check for. | |||
*/ | |||
static function exists($type = null) { | |||
if (self::$exists[$type]) | |||
return self::$exists[$type]; | |||
if (isset($type)) | |||
return self::$exists[$type] = !empty($_SESSION[pluralize($type)]); | |||
else | |||
foreach (array("messages", "notices", "warnings") as $type) | |||
if (!empty($_SESSION[$type])) | |||
return self::$exists[depluralize($type)] = self::$exists[null] = true; | |||
return false; | |||
} | |||
/** | |||
* Function: current | |||
* Returns a singleton reference to the current class. | |||
*/ | |||
public static function & current() { | |||
static $instance = null; | |||
return $instance = (empty($instance)) ? new self() : $instance ; | |||
} | |||
} |
@@ -0,0 +1,465 @@ | |||
<?php | |||
/** | |||
* Class: Model | |||
* The basis for the Models system. | |||
*/ | |||
class Model { | |||
# Array: $caches | |||
# Caches every loaded module into a clone of the object. | |||
static $caches = array(); | |||
# Array: $belongs_to | |||
# An array of models that this Model belongs to. | |||
# This model should have a [modelname]_id column. | |||
public $belongs_to = array(); | |||
# Array: $has_many | |||
# An array of models that belong to this Model. | |||
# They should have a [thismodel]_id column. | |||
public $has_many = array(); | |||
# Array: $has_one | |||
# An array of models that this model has only one of. | |||
# The models should have a [thismodel]_id column. | |||
public $has_one = array(); | |||
/** | |||
* Function: __get | |||
* Automatically handle model relationships when grabbing attributes of an object. | |||
* | |||
* Returns: | |||
* @mixed@ | |||
*/ | |||
public function __get($name) { | |||
$model_name = get_class($this); | |||
$placeholders = (isset($this->__placeholders) and $this->__placeholders); | |||
Trigger::current()->filter($filtered, $model_name."_".$name."_attr", $this); | |||
if ($filtered !== false) | |||
$this->$name = $filtered; | |||
$this->belongs_to = (array) $this->belongs_to; | |||
$this->has_many = (array) $this->has_many; | |||
$this->has_one = (array) $this->has_one; | |||
if (in_array($name, $this->belongs_to) or isset($this->belongs_to[$name])) { | |||
$class = (isset($this->belongs_to[$name])) ? $this->belongs_to[$name] : $name ; | |||
if (isset($this->belongs_to[$name])) { | |||
$opts =& $this->belongs_to[$name]; | |||
$model = oneof(@$opts["model"], $name); | |||
if (preg_match("/^\(([a-z0-9_]+)\)$/", $model, $match)) | |||
$model = $this->$match[1]; | |||
$match = oneof(@$opts["by"], strtolower($name)); | |||
fallback($opts["where"], array("id" => $this->{$match."_id"})); | |||
$opts["where"] = (array) $opts["where"]; | |||
foreach ($opts["where"] as &$val) | |||
if (preg_match("/^\(([a-z0-9_]+)\)$/", $val, $match)) | |||
$val = $this->$match[1]; | |||
fallback($opts["placeholders"], $placeholders); | |||
} else { | |||
$model = $name; | |||
$opts = array("where" => array("id" => $this->{$name."_id"})); | |||
} | |||
return $this->$name = new $model(null, $opts); | |||
} elseif (in_array($name, $this->has_many) or isset($this->has_many[$name])) { | |||
if (isset($this->has_many[$name])) { | |||
$opts =& $this->has_many[$name]; | |||
$model = oneof(@$opts["model"], depluralize($name)); | |||
if (preg_match("/^\(([a-z0-9_]+)\)$/", $model, $match)) | |||
$model = $this->$match[1]; | |||
$match = oneof(@$opts["by"], strtolower($name)); | |||
fallback($opts["where"], array($match."_id" => $this->id)); | |||
$opts["where"] = (array) $opts["where"]; | |||
foreach ($opts["where"] as &$val) | |||
if (preg_match("/^\(([a-z0-9_]+)\)$/", $val, $match)) | |||
$val = $this->$match[1]; | |||
fallback($opts["placeholders"], $placeholders); | |||
} else { | |||
$model = depluralize($name); | |||
$match = $model_name; | |||
$opts = array("where" => array(strtolower($match)."_id" => $this->id), | |||
"placeholders" => $placeholders); | |||
} | |||
return $this->$name = call_user_func(array($model, "find"), $opts); | |||
} elseif (in_array($name, $this->has_one) or isset($this->has_one[$name])) { | |||
if (isset($this->has_one[$name])) { | |||
$opts =& $this->has_one[$name]; | |||
$model = oneof(@$opts["model"], depluralize($name)); | |||
if (preg_match("/^\(([a-z0-9_]+)\)$/", $model, $match)) | |||
$model = $this->$match[1]; | |||
$match = oneof(@$opts["by"], strtolower($name)); | |||
fallback($opts["where"], array($match."_id" => $this->id)); | |||
$opts["where"] = (array) $opts["where"]; | |||
foreach ($opts["where"] as &$val) | |||
if (preg_match("/^\(([a-z0-9_]+)\)$/", $val, $match)) | |||
$val = $this->$match[1]; | |||
} else { | |||
$model = depluralize($name); | |||
$match = $model_name; | |||
$opts = array("where" => array(strtolower($match)."_id" => $this->id)); | |||
} | |||
return $this->$name = new $model(null, $opts); | |||
} | |||
if (isset($this->$name)) | |||
return $this->$name; | |||
} | |||
/** | |||
* Function: __getPlaceholders | |||
* Calls __get with the requested $name, but grabs everything as placeholders. | |||
* | |||
* Parameters: | |||
* $name - Name to call <Model.__get> with. | |||
* | |||
* Returns: | |||
* @mixed@ | |||
* | |||
* See Also: | |||
* <Model.__get> | |||
*/ | |||
public function __getPlaceholders($name) { | |||
$this->__placeholders = true; | |||
$return = $this->__get($name); | |||
unset($this->__placeholders); | |||
return $return; | |||
} | |||
/** | |||
* Function: grab | |||
* Grabs a single model from the database. | |||
* | |||
* Parameters: | |||
* $model - The instantiated model class to pass the object to (e.g. Post). | |||
* $id - The ID of the model to grab. Can be null. | |||
* $options - An array of options, mostly SQL things. | |||
* | |||
* Options: | |||
* select - What to grab from the table. @(modelname)s@ by default. | |||
* from - Which table(s) to grab from? @(modelname)s.*@ by default. | |||
* left_join - A @LEFT JOIN@ associative array. Example: @array("table" => "foo", "where" => "foo = :bar")@ | |||
* where - A string or array of conditions. @array("__(modelname)s.id = :id")@ by default. | |||
* params - An array of parameters to pass to PDO. @array(":id" => $id)@ by default. | |||
* group - A string or array of "GROUP BY" conditions. | |||
* order - What to order the SQL result by. @__(modelname)s.id DESC@ by default. | |||
* offset - Offset for SQL query. | |||
* read_from - An array to read from instead of performing another query. | |||
*/ | |||
protected static function grab($model, $id, $options = array()) { | |||
$model_name = strtolower(get_class($model)); | |||
if ($model_name == "visitor") | |||
$model_name = "user"; | |||
if (!isset($id) and isset($options["where"]["id"])) | |||
$id = $options["where"]["id"]; | |||
$cache = (is_numeric($id) and isset(self::$caches[$model_name][$id])) ? | |||
self::$caches[$model_name][$id] : | |||
((isset($options["read_from"]["id"]) and isset(self::$caches[$model_name][$options["read_from"]["id"]])) ? | |||
self::$caches[$model_name][$options["read_from"]["id"]] : | |||
(isset(self::$caches[$model_name][serialize($id)]) ? | |||
self::$caches[$model_name][serialize($id)] : | |||
array())) ; | |||
# Is this model already in the cache? | |||
if (!empty($cache)) { | |||
foreach ($cache as $attr => $val) | |||
$model->$attr = $val; | |||
return; | |||
} | |||
fallback($options["select"], "*"); | |||
fallback($options["from"], ($model_name == "visitor" ? "users" : pluralize($model_name))); | |||
fallback($options["left_join"], array()); | |||
fallback($options["where"], array()); | |||
fallback($options["params"], array()); | |||
fallback($options["group"], array()); | |||
fallback($options["order"], "id DESC"); | |||
fallback($options["offset"], null); | |||
fallback($options["read_from"], array()); | |||
fallback($options["ignore_dupes"], array()); | |||
$options["where"] = (array) $options["where"]; | |||
$options["from"] = (array) $options["from"]; | |||
$options["select"] = (array) $options["select"]; | |||
if (is_numeric($id)) | |||
$options["where"]["id"] = $id; | |||
elseif (is_array($id)) | |||
$options["where"] = array_merge($options["where"], $id); | |||
$trigger = Trigger::current(); | |||
$trigger->filter($options, $model_name."_grab"); | |||
$sql = SQL::current(); | |||
if (!empty($options["read_from"])) | |||
$read = $options["read_from"]; | |||
else { | |||
$query = $sql->select($options["from"], | |||
$options["select"], | |||
$options["where"], | |||
$options["order"], | |||
$options["params"], | |||
null, | |||
$options["offset"], | |||
$options["group"], | |||
$options["left_join"]); | |||
$all = $query->fetchAll(); | |||
if (count($all) == 1) | |||
$read = $all[0]; | |||
else { | |||
$merged = array(); | |||
foreach ($all as $index => $row) | |||
foreach ($row as $column => $val) | |||
$merged[$row["id"]][$column][] = $val; | |||
foreach ($all as $index => &$row) | |||
$row = $merged[$row["id"]]; | |||
if (count($all)) { | |||
$keys = array_keys($all); | |||
$read = $all[$keys[0]]; | |||
foreach ($read as $name => &$column) { | |||
$column = (!in_array($name, $options["ignore_dupes"]) ? | |||
array_unique($column) : | |||
$column); | |||
$column = (count($column) == 1) ? | |||
$column[0] : | |||
$column ; | |||
} | |||
} else | |||
$read = false; | |||
} | |||
} | |||
if (!count($read) or !$read) | |||
return $model->no_results = true; | |||
else | |||
$model->no_results = false; | |||
foreach ($read as $key => $val) | |||
if (!is_int($key)) | |||
$model->$key = $val; | |||
if (isset($query) and isset($query->queryString)) | |||
$model->queryString = $query->queryString; | |||
if (isset($model->updated_at)) | |||
$model->updated = (!empty($model->updated_at) and $model->updated_at != "0000-00-00 00:00:00"); | |||
$clone = clone $model; | |||
self::$caches[$model_name][$read["id"]] = $clone; | |||
if (!is_numeric($id) and !isset($options["read_from"]["id"]) and $id !== null) | |||
self::$caches[$model_name][serialize($id)] = $clone; | |||
} | |||
/** | |||
* Function: search | |||
* Returns an array of model objects that are found by the $options array. | |||
* | |||
* Parameters: | |||
* $options - An array of options, mostly SQL things. | |||
* $options_for_object - An array of options for the instantiation of the model. | |||
* | |||
* Options: | |||
* select - What to grab from the table. @(modelname)s@ by default. | |||
* from - Which table(s) to grab from? @(modelname)s.*@ by default. | |||
* left_join - A @LEFT JOIN@ associative array. Example: @array("table" => "foo", "where" => "foo = :bar")@ | |||
* where - A string or array of conditions. @array("__(modelname)s.id = :id")@ by default. | |||
* params - An array of parameters to pass to PDO. @array(":id" => $id)@ by default. | |||
* group - A string or array of "GROUP BY" conditions. | |||
* order - What to order the SQL result by. @__(modelname)s.id DESC@ by default. | |||
* offset - Offset for SQL query. | |||
* limit - Limit for SQL query. | |||
* | |||
* See Also: | |||
* <Model.grab> | |||
*/ | |||
protected static function search($model, $options = array(), $options_for_object = array()) { | |||
$model_name = strtolower($model); | |||
fallback($options["select"], "*"); | |||
fallback($options["from"], pluralize(strtolower($model))); | |||
fallback($options["left_join"], array()); | |||
fallback($options["where"], null); | |||
fallback($options["params"], array()); | |||
fallback($options["group"], array()); | |||
fallback($options["order"], "id DESC"); | |||
fallback($options["offset"], null); | |||
fallback($options["limit"], null); | |||
fallback($options["placeholders"], false); | |||
fallback($options["ignore_dupes"], array()); | |||
$options["where"] = (array) $options["where"]; | |||
$options["from"] = (array) $options["from"]; | |||
$options["select"] = (array) $options["select"]; | |||
$trigger = Trigger::current(); | |||
$trigger->filter($options, pluralize(strtolower($model_name))."_get"); | |||
$grab = SQL::current()->select($options["from"], | |||
$options["select"], | |||
$options["where"], | |||
$options["order"], | |||
$options["params"], | |||
$options["limit"], | |||
$options["offset"], | |||
$options["group"], | |||
$options["left_join"])->fetchAll(); | |||
$shown_dates = array(); | |||
$results = array(); | |||
$rows = array(); | |||
foreach ($grab as $row) | |||
foreach ($row as $column => $val) | |||
$rows[$row["id"]][$column][] = $val; | |||
foreach ($rows as &$row) | |||
foreach ($row as $name => &$column) { | |||
$column = (!in_array($name, $options["ignore_dupes"]) ? | |||
array_unique($column) : | |||
$column); | |||
$column = (count($column) == 1) ? | |||
$column[0] : | |||
$column ; | |||
} | |||
foreach ($rows as $result) { | |||
if ($options["placeholders"]) { | |||
$results[] = $result; | |||
continue; | |||
} | |||
$options_for_object["read_from"] = $result; | |||
$result = new $model(null, $options_for_object); | |||
if (isset($result->created_at)) { | |||
$pinned = (isset($result->pinned) and $result->pinned); | |||
$shown = in_array(when("m-d-Y", $result->created_at), $shown_dates); | |||
$result->first_of_day = (!$pinned and !$shown and !AJAX); | |||
if (!$pinned and !$shown) | |||
$shown_dates[] = when("m-d-Y", $result->created_at); | |||
} | |||
$results[] = $result; | |||
} | |||
return ($options["placeholders"]) ? array($results, $model_name) : $results ; | |||
} | |||
/** | |||
* Function: delete | |||
* Deletes a given object. Calls the @delete_(model)@ trigger with the objects ID. | |||
* | |||
* Parameters: | |||
* $model - The model name. | |||
* $id - The ID of the object to delete. | |||
*/ | |||
protected static function destroy($model, $id) { | |||
$model = strtolower($model); | |||
if (Trigger::current()->exists("delete_".$model)) | |||
Trigger::current()->call("delete_".$model, new $model($id)); | |||
SQL::current()->delete(pluralize($model), array("id" => $id)); | |||
} | |||
/** | |||
* Function: deletable | |||
* Checks if the <User> can delete the post. | |||
*/ | |||
public function deletable($user = null) { | |||
if ($this->no_results) | |||
return false; | |||
$name = strtolower(get_class($this)); | |||
fallback($user, Visitor::current()); | |||
return $user->group->can("delete_".$name); | |||
} | |||
/** | |||
* Function: editable | |||
* Checks if the <User> can edit the post. | |||
*/ | |||
public function editable($user = null) { | |||
if ($this->no_results) | |||
return false; | |||
$name = strtolower(get_class($this)); | |||
fallback($user, Visitor::current()); | |||
return $user->group->can("edit_".$name); | |||
} | |||
/** | |||
* Function: edit_link | |||
* Outputs an edit link for the model, if the visitor's <Group.can> edit_[model]. | |||
* | |||
* Parameters: | |||
* $text - The text to show for the link. | |||
* $before - If the link can be shown, show this before it. | |||
* $after - If the link can be shown, show this after it. | |||
* $classes - Extra CSS classes for the link, space-delimited. | |||
*/ | |||
public function edit_link($text = null, $before = null, $after = null, $classes = "") { | |||
if (!$this->editable()) | |||
return false; | |||
fallback($text, __("Edit")); | |||
$name = strtolower(get_class($this)); | |||
if (@Feathers::$instances[$this->feather]->disable_ajax_edit) | |||
$classes = empty($classes) ? "no_ajax" : $classes." no_ajax" ; | |||
echo $before.'<a href="'.Config::current()->chyrp_url.'/admin/?action=edit_'.$name.'&id='.$this->id.'" title="Edit" class="'.($classes ? $classes." " : '').$name.'_edit_link edit_link" id="'.$name.'_edit_'.$this->id.'">'.$text.'</a>'.$after; | |||
} | |||
/** | |||
* Function: delete_link | |||
* Outputs a delete link for the post, if the <User.can> delete_[model]. | |||
* | |||
* Parameters: | |||
* $text - The text to show for the link. | |||
* $before - If the link can be shown, show this before it. | |||
* $after - If the link can be shown, show this after it. | |||
* $classes - Extra CSS classes for the link, space-delimited. | |||
*/ | |||
public function delete_link($text = null, $before = null, $after = null, $classes = "") { | |||
if (!$this->deletable()) | |||
return false; | |||
fallback($text, __("Delete")); | |||
$name = strtolower(get_class($this)); | |||
echo $before.'<a href="'.Config::current()->chyrp_url.'/admin/?action=delete_'.$name.'&id='.$this->id.'" title="Delete" class="'.($classes ? $classes." " : '').$name.'_delete_link delete_link" id="'.$name.'_delete_'.$this->id.'">'.$text.'</a>'.$after; | |||
} | |||
} |
@@ -0,0 +1,39 @@ | |||
<?php | |||
/** | |||
* Class: Modules | |||
* Contains various functions, acts as the backbone for all modules. | |||
*/ | |||
class Modules { | |||
# Array: $instances | |||
# Holds all Module instantiations. | |||
static $instances = array(); | |||
# Boolean: $cancelled | |||
# Is the module's execution cancelled? | |||
public $cancelled = false; | |||
/** | |||
* Function: setPriority | |||
* Sets the priority of an action for the module this function is called from. | |||
* | |||
* Parameters: | |||
* $name - Name of the trigger to respond to. | |||
* $priority - Priority of the response. | |||
*/ | |||
protected function setPriority($name, $priority) { | |||
Trigger::current()->priorities[$name][] = array("priority" => $priority, "function" => array($this, $name)); | |||
} | |||
/** | |||
* Function: addAlias | |||
* Allows a module to respond to a trigger with multiple functions and custom priorities. | |||
* | |||
* Parameters: | |||
* $name - Name of the trigger to respond to. | |||
* $function - Name of the class function to respond with. | |||
* $priority - Priority of the response. | |||
*/ | |||
protected function addAlias($name, $function, $priority = 10) { | |||
Trigger::current()->priorities[$name][] = array("priority" => $priority, "function" => array($this, $function)); | |||
} | |||
} |
@@ -0,0 +1,229 @@ | |||
<?php | |||
/** | |||
* Class: Paginator | |||
* Paginates over an array. | |||
*/ | |||
class Paginator { | |||
# Array: $array | |||
# The original, unmodified data. | |||
public $array; | |||
# Integer: $per_page | |||
# Number of items per page. | |||
public $per_page; | |||
# String: $name | |||
# Name of the $_GET value for the current page. | |||
public $name; | |||
# Boolean: $model | |||
# Should the <$array> items be treated as Models? | |||
# In this case, <$array> should be in the form of array(<ids>, "ModelName") | |||
public $model; | |||
# Integer: $total | |||
# Total number of items to paginate. | |||
public $total; | |||
# Integer: $page | |||
# The current page. | |||
public $page; | |||
# Integer: $pages | |||
# Total number of pages. | |||
public $pages; | |||
# Array: $result | |||
# The result of the pagination. | |||
# @paginated@, @paginate@, and @list@ are references to this. | |||
public $result = array(); | |||
# Array: $names | |||
# An array of the currently-used pagination URL parameters. | |||
static $names = array(); | |||
/** | |||
* Function: __construct | |||
* Prepares an array for pagination. | |||
* | |||
* Parameters: | |||
* $array - The array to paginate. | |||
* $per_page - Number of items per page. | |||
* $name - The name of the $_GET parameter to use for determining the current page. | |||
* $model - If this is true, each item in $array that gets shown on the page will be | |||
* initialized as a model of whatever is passed as the second argument to $array. | |||
* The first argument of $array is expected to be an array of IDs. | |||
* $page - Page number to start at. | |||
* | |||
* Returns: | |||
* A paginated array of length $per_page or smaller. | |||
*/ | |||
public function __construct($array, $per_page = 5, $name = "page", $model = null, $page = null) { | |||
self::$names[] = $name; | |||
$this->array = (array) $array; | |||
$this->per_page = $per_page; | |||
$this->name = $name; | |||
$this->model = fallback($model, (count($this->array) == 2 and is_array($this->array[0]) and is_string($this->array[1]) and class_exists($this->array[1]))); | |||
if ($model) | |||
list($this->array, $model_name) = $this->array; | |||
$this->total = count($this->array); | |||
$this->page = oneof($page, @$_GET[$name], 1); | |||
$this->pages = ceil($this->total / $this->per_page); | |||
$offset = ($this->page - 1) * $this->per_page; | |||
$this->result = array(); | |||
if ($model) { | |||
for ($i = $offset; $i < ($offset + $this->per_page); $i++) | |||
if (isset($this->array[$i])) | |||
$this->result[] = new $model_name(null, array("read_from" => $this->array[$i])); | |||
} else | |||
$this->result = array_slice($this->array, $offset, $this->per_page); | |||
$shown_dates = array(); | |||
if ($model) | |||
foreach ($this->result as &$result) | |||
if (isset($result->created_at)) { | |||
$pinned = (isset($result->pinned) and $result->pinned); | |||
$shown = in_array(when("m-d-Y", $result->created_at), $shown_dates); | |||
$result->first_of_day = (!$pinned and !$shown and !AJAX); | |||
if (!$pinned and !$shown) | |||
$shown_dates[] = when("m-d-Y", $result->created_at); | |||
} | |||
$this->paginated = $this->paginate = $this->list =& $this->result; | |||
} | |||
/** | |||
* Function: next | |||
* Returns the next pagination sequence. | |||
*/ | |||
public function next() { | |||
return new self($this->array, $this->per_page, $this->name, $this->model, $this->page + 1); | |||
} | |||
/** | |||
* Function: prev | |||
* Returns the next pagination sequence. | |||
*/ | |||
public function prev() { | |||
return new self($this->array, $this->per_page, $this->name, $this->model, $this->page - 1); | |||
} | |||
/** | |||
* Function: next_page | |||
* Checks whether or not it makes sense to show the Next Page link. | |||
*/ | |||
public function next_page() { | |||
return ($this->page < $this->pages and $this->pages != 1 and $this->pages != 0); | |||
} | |||
/** | |||
* Function: prev_page | |||
* Checks whether or not it makes sense to show the Previous Page link. | |||
*/ | |||
public function prev_page() { | |||
return ($this->page != 1 and $this->page <= $this->pages); | |||
} | |||
/** | |||
* Function: next_link | |||
* Outputs a link to the next page. | |||
* | |||
* Parameters: | |||
* $text - The text for the link. | |||
* $class - The CSS class for the link. | |||
* $page - Page number to link to. | |||
*/ | |||
public function next_link($text = null, $class = "next_page", $page = null) { | |||
if (!$this->next_page()) | |||
return; | |||
fallback($text, __("Next →")); | |||
echo '<a class="'.$class.'" id="next_page_'.$this->name.'" href="'.$this->next_page_url($page).'">'. | |||
$text. | |||
'</a>'; | |||
} | |||
/** | |||
* Function: prev_link | |||
* Outputs a link to the previous page. | |||
* | |||
* Parameters: | |||
* $text - The text for the link. | |||
* $class - The CSS class for the link. | |||
* $page - Page number to link to. | |||
*/ | |||
public function prev_link($text = null, $class = "prev_page", $page = null) { | |||
if (!$this->prev_page()) | |||
return; | |||
fallback($text, __("← Previous")); | |||
echo '<a class="'.$class.'" id="prev_page_'.$this->name.'" href="'.$this->prev_page_url($page).'">'. | |||
$text. | |||
'</a>'; | |||
} | |||
/** | |||
* Function: next_page_url | |||
* Returns the URL to the next page. | |||
* | |||
* Parameters: | |||
* $page - Page number to link to. | |||
*/ | |||
public function next_page_url($page = null) { | |||
$config = Config::current(); | |||
$request = "http://".$_SERVER['HTTP_HOST'].$_SERVER['REQUEST_URI']; | |||
# Only used for adding to the end of the URL and clean URLs is off. | |||
$mark = (substr_count($request, "?")) ? "&" : "?" ; | |||
fallback($page, $this->page + 1); | |||
# No page is set, add it to the end. | |||
if (!isset($_GET[$this->name])) | |||
return ($config->clean_urls and !ADMIN) ? | |||
rtrim($request, "/")."/".$this->name."/".$page : | |||
$request.$mark.$this->name."=".$page ; | |||
return ($config->clean_urls and !ADMIN) ? | |||
preg_replace("/(\/{$this->name}\/([0-9]+)|$)/", "/".$this->name."/".$page, $request, 1) : | |||
preg_replace("/((\?|&){$this->name}=([0-9]+)|$)/", "\\2".$this->name."=".$page, $request, 1) ; | |||
} | |||
/** | |||
* Function: prev_page_url | |||
* Returns the URL to the previous page. | |||
* | |||
* Parameters: | |||
* $page - Page number to link to. | |||
*/ | |||
public function prev_page_url($page = null) { | |||
$config = Config::current(); | |||
$request = "http://".$_SERVER['HTTP_HOST'].$_SERVER['REQUEST_URI']; | |||
# Only used for adding to the end of the URL and clean URLs is off. | |||
$mark = (substr_count($request, "?")) ? "&" : "?" ; | |||
fallback($page, $this->page - 1); | |||
# No page is set, add it to the end. | |||
if (!isset($_GET[$this->name])) | |||
return ($config->clean_urls and !ADMIN) ? | |||
rtrim($request, "/")."/".$this->name."/".$page : | |||
$request.$mark.$this->name."=".$page ; | |||
return ($config->clean_urls and !ADMIN) ? | |||
preg_replace("/(\/{$this->name}\/([0-9]+)|$)/", "/".$this->name."/".$page, $request, 1) : | |||
preg_replace("/((\?|&){$this->name}=([0-9]+)|$)/", "\\2".$this->name."=".$page, $request, 1) ; | |||
} | |||
} |
@@ -0,0 +1,236 @@ | |||
<?php | |||
/** | |||
* Class: Query | |||
* Handles a query based on the <SQL.method>. | |||
*/ | |||
class Query { | |||
# Variable: $query | |||
# Holds the current query. | |||
public $query; | |||
/** | |||
* Function: __construct | |||
* Creates a query based on the <SQL.interface>. | |||
* | |||
* Parameters: | |||
* $sql - <SQL> instance. | |||
* $query - Query to execute. | |||
* $params - An associative array of parameters used in the query. | |||
* $throw_exceptions - Throw exceptions on error? | |||
*/ | |||
public function __construct($sql, $query, $params = array(), $throw_exceptions = false) { | |||
if (DEBUG) | |||
global $time_start; | |||
$this->sql = $sql; | |||
# Don't count config setting queries. | |||
$count = !preg_match("/^SET /", strtoupper($query)); | |||
if ($count) | |||
++$this->sql->queries; | |||
$this->db =& $this->sql->db; | |||
$this->params = $params; | |||
$this->throw_exceptions = $throw_exceptions; | |||
$this->queryString = $query; | |||
if ($count and defined('DEBUG') and DEBUG) { | |||
$trace = debug_backtrace(); | |||
$target = $trace[$index = 0]; | |||
# Getting a traceback from these files doesn't help much. | |||
while (match(array("/SQL\.php/", "/Model\.php/", "/\/model\//"), $target["file"])) | |||
if (isset($trace[$index + 1]["file"])) | |||
$target = $trace[$index++]; | |||
else | |||
break; | |||
$logQuery = $query; | |||
foreach ($params as $name => $val) | |||
$logQuery = preg_replace("/{$name}([^a-zA-Z0-9_]|$)/", str_replace("\\", "\\\\", $this->sql->escape($val))."\\1", $logQuery); | |||
$this->sql->debug[] = array("number" => $this->sql->queries, | |||
"file" => str_replace(MAIN_DIR."/", "", $target["file"]), | |||
"line" => $target["line"], | |||
"query" => $logQuery, | |||
"time" => timer_stop()); | |||
} | |||
switch($this->sql->method) { | |||
case "pdo": | |||
try { | |||
$this->query = $this->db->prepare($query); | |||
$result = $this->query->execute($params); | |||
$this->query->setFetchMode(PDO::FETCH_ASSOC); | |||
$this->queryString = $query; | |||
foreach ($params as $name => $val) | |||
$this->queryString = preg_replace("/{$name}([^a-zA-Z0-9_]|$)/", | |||
str_replace(array("\\", "\$"), | |||
array("\\\\", "\\\$"), | |||
$this->sql->escape($val))."\\1", | |||
$this->queryString); | |||
if (!$result) | |||
throw new PDOException; | |||
} catch (PDOException $error) { | |||
if (!empty($error->errorInfo[1]) and $error->errorInfo[1] == 17) | |||
return new self($sql, $query, $params, $throw_exceptions); | |||
return $this->handle($error); | |||
} | |||
break; | |||
case "mysqli": | |||
foreach ($params as $name => $val) | |||
$query = preg_replace("/{$name}([^a-zA-Z0-9_]|$)/", | |||
str_replace(array("\\", "\$"), | |||
array("\\\\", "\\\$"), | |||
$this->sql->escape($val))."\\1", | |||
$query); | |||
$this->queryString = $query; | |||
try { | |||
if (!$this->query = $this->db->query($query)) | |||
throw new Exception($this->db->error); | |||
} catch (Exception $error) { | |||
return $this->handle($error); | |||
} | |||
break; | |||
case "mysql": | |||
foreach ($params as $name => $val) | |||
$query = preg_replace("/{$name}([^a-zA-Z0-9_]|$)/", | |||
str_replace(array("\\", "\$"), | |||
array("\\\\", "\\\$"), | |||
$this->sql->escape($val))."\\1", | |||
$query); | |||
$this->queryString = $query; | |||
try { | |||
if (!$this->query = @mysql_query($query)) | |||
throw new Exception(mysql_error()); | |||
} catch (Exception $error) { | |||
return $this->handle($error); | |||
} | |||
break; | |||
} | |||
} | |||
/** | |||
* Function: fetchColumn | |||
* Fetches a column of the current row. | |||
* | |||
* Parameters: | |||
* $column - The offset of the column to grab. Default 0. | |||
*/ | |||
public function fetchColumn($column = 0) { | |||
switch($this->sql->method) { | |||
case "pdo": | |||
return $this->query->fetchColumn($column); | |||
case "mysqli": | |||
$result = $this->query->fetch_array(); | |||
return $result[$column]; | |||
case "mysql": | |||
$result = mysql_fetch_array($this->query); | |||
return $result[$column]; | |||
} | |||
} | |||
/** | |||
* Function: fetch | |||
* Returns the current row as an array. | |||
*/ | |||
public function fetch() { | |||
switch($this->sql->method) { | |||
case "pdo": | |||
return $this->query->fetch(); | |||
case "mysqli": | |||
return $this->query->fetch_array(); | |||
case "mysql": | |||
return mysql_fetch_array($this->query); | |||
} | |||
} | |||
/** | |||
* Function: fetchObject | |||
* Returns the current row as an object. | |||
*/ | |||
public function fetchObject() { | |||
switch($this->sql->method) { | |||
case "pdo": | |||
return $this->query->fetchObject(); | |||
case "mysqli": | |||
return $this->query->fetch_object(); | |||
case "mysql": | |||
return mysql_fetch_object($this->query); | |||
} | |||
} | |||
/** | |||
* Function: fetchAll | |||
* Returns an array of every result. | |||
*/ | |||
public function fetchAll($style = null) { | |||
switch($this->sql->method) { | |||
case "pdo": | |||
return $this->query->fetchAll($style); | |||
case "mysqli": | |||
$results = array(); | |||
while ($row = $this->query->fetch_assoc()) | |||
$results[] = $row; | |||
return $results; | |||
case "mysql": | |||
$results = array(); | |||
while ($row = mysql_fetch_assoc($this->query)) | |||
$results[] = $row; | |||
return $results; | |||
} | |||
} | |||
/** | |||
* Function: grab | |||
* Grabs all of the given column out of the full result of a query. | |||
* | |||
* Parameters: | |||
* $column - Name of the column to grab. | |||
* | |||
* Returns: | |||
* An array of all of the values of that column in the result. | |||
*/ | |||
public function grab($column) { | |||
$all = $this->fetchAll(); | |||
$result = array(); | |||
foreach ($all as $row) | |||
$result[] = $row[$column]; | |||
return $result; | |||
} | |||
/** | |||
* Function: handle | |||
* Handles exceptions thrown by failed queries. | |||
*/ | |||
public function handle($error) { | |||
$this->sql->error = $error; | |||
if (UPGRADING or $this->sql->silence_errors) return false; | |||
$message = $error->getMessage(); | |||
$message.= "\n\n<pre>".print_r($this->queryString, true)."\n\n<pre>".print_r($this->params, true)."</pre>\n\n<pre>".$error->getTraceAsString()."</pre>"; | |||
if (XML_RPC or $this->throw_exceptions) | |||
throw new Exception($message); | |||
error(__("Database Error"), $message); | |||
} | |||
} |
@@ -0,0 +1,449 @@ | |||
<?php | |||
/** | |||
* Class: QueryBuilder | |||
* A generic SQL query builder. | |||
*/ | |||
class QueryBuilder { | |||
/** | |||
* Function: build_select | |||
* Creates a full SELECT query. | |||
* | |||
* Parameters: | |||
* $tables - Tables to select from. | |||
* $fields - Columns to select. | |||
* $order - What to order by. | |||
* $limit - Limit of the result. | |||
* $offset - Starting point for the result. | |||
* $group - What to group by. | |||
* $left_join - Any @LEFT JOIN@s to add. | |||
* &$params - An associative array of parameters used in the query. | |||
* | |||
* Returns: | |||
* A @SELECT@ query string. | |||
*/ | |||
public static function build_select($tables, | |||
$fields, | |||
$conds, | |||
$order = null, | |||
$limit = null, | |||
$offset = null, | |||
$group = null, | |||
$left_join = array(), | |||
&$params = array()) { | |||
$query = "SELECT ".self::build_select_header($fields, $tables)."\n". | |||
"FROM ".self::build_from($tables)."\n"; | |||
foreach ($left_join as $join) | |||
$query.= "LEFT JOIN __".$join["table"]." ON ".self::build_where($join["where"], $join["table"], $params)."\n"; | |||
$query.= ($conds ? "WHERE ".self::build_where($conds, $tables, $params)."\n" : ""). | |||
($group ? "GROUP BY ".self::build_group($group, $tables)."\n" : ""). | |||
($order ? "ORDER BY ".self::build_order($order, $tables)."\n" : ""). | |||
self::build_limits($offset, $limit); | |||
return $query; | |||
} | |||
/** | |||
* Function: build_insert | |||
* Creates a full insert query. | |||
* | |||
* Parameters: | |||
* $table - Table to insert into. | |||
* $data - Data to insert. | |||
* &$params - An associative array of parameters used in the query. | |||
* | |||
* Returns: | |||
* An @INSERT@ query string. | |||
*/ | |||
public static function build_insert($table, $data, &$params = array()) { | |||
if (empty($params)) | |||
foreach ($data as $key => $val) | |||
$params[":".str_replace(array("(", ")", "."), "_", $key)] = $val; | |||
return "INSERT INTO __$table\n". | |||
self::build_insert_header($data)."\n". | |||
"VALUES\n". | |||
"(".implode(", ", array_keys($params)).")\n"; | |||
} | |||
/** | |||
* Function: build_update | |||
* Creates a full update query. | |||
* | |||
* Parameters: | |||
* $table - Table to update. | |||
* $conds - Conditions to update rows by. | |||
* $data - Data to update. | |||
* &$params - An associative array of parameters used in the query. | |||
* | |||
* Returns: | |||
* An @UPDATE@ query string. | |||
*/ | |||
public static function build_update($table, $conds, $data, &$params = array()) { | |||
return "UPDATE __$table\n". | |||
"SET ".self::build_update_values($data, $params)."\n". | |||
($conds ? "WHERE ".self::build_where($conds, $table, $params) : ""); | |||
} | |||
/** | |||
* Function: build_delete | |||
* Creates a full delete query. | |||
* | |||
* Parameters: | |||
* $table - Table to delete from. | |||
* $conds - Conditions to delete by. | |||
* &$params - An associative array of parameters used in the query. | |||
* | |||
* Returns: | |||
* A @DELETE@ query string. | |||
*/ | |||
public static function build_delete($table, $conds, &$params = array()) { | |||
return "DELETE FROM __$table\n". | |||
($conds ? "WHERE ".self::build_where($conds, $table, $params) : ""); | |||
} | |||
/** | |||
* Function: build_update_values | |||
* Creates an update data part. | |||
* | |||
* Parameters: | |||
* $data - Data to update. | |||
* &$params - An associative array of parameters used in the query. | |||
*/ | |||
public static function build_update_values($data, &$params = array()) { | |||
$set = self::build_conditions($data, $params, null, true); | |||
return implode(",\n ", $set); | |||
} | |||
/** | |||
* Function: build_insert_header | |||
* Creates an insert header. | |||
* | |||
* Parameters: | |||
* $data - Data to insert. | |||
*/ | |||
public static function build_insert_header($data) { | |||
$set = array(); | |||
foreach (array_keys($data) as $field) | |||
array_push($set, self::safecol($field)); | |||
return "(".implode(", ", $set).")"; | |||
} | |||
/** | |||
* Function: build_limits | |||
* Creates the LIMIT part of a query. | |||
* | |||
* Parameters: | |||
* $offset - Offset of the result. | |||
* $limit - Limit of the result. | |||
*/ | |||
public static function build_limits($offset, $limit) { | |||
if ($limit === null) | |||
return ""; | |||
if ($offset !== null) | |||
return "LIMIT ".$offset.", ".$limit; | |||
return "LIMIT ".$limit; | |||
} | |||
/** | |||
* Function: build_from | |||
* Creates a FROM header for select queries. | |||
* | |||
* Parameters: | |||
* $tables - Tables to select from. | |||
*/ | |||
public static function build_from($tables) { | |||
if (!is_array($tables)) | |||
$tables = array($tables); | |||
foreach ($tables as &$table) | |||
if (substr($table, 0, 2) != "__") | |||
$table = "__".$table; | |||
return implode(",\n ", $tables); | |||
} | |||
/** | |||
* Function: build_count | |||
* Creates a SELECT COUNT(1) query. | |||
* | |||
* Parameters: | |||
* $tables - Tables to tablefy with. | |||
* $conds - Conditions to select by. | |||
* &$params - An associative array of parameters used in the query. | |||
*/ | |||
public static function build_count($tables, $conds, &$params = array()) { | |||
return "SELECT COUNT(1) AS count\n". | |||
"FROM ".self::build_from($tables)."\n". | |||
($conds ? "WHERE ".self::build_where($conds, $tables, $params) : ""); | |||
} | |||
/** | |||
* Function: build_select_header | |||
* Creates a SELECT fields header. | |||
* | |||
* Parameters: | |||
* $fields - Columns to select. | |||
* $tables - Tables to tablefy with. | |||
*/ | |||
public static function build_select_header($fields, $tables = null) { | |||
if (!is_array($fields)) | |||
$fields = array($fields); | |||
$tables = (array) $tables; | |||
foreach ($fields as &$field) { | |||
self::tablefy($field, $tables); | |||
$field = self::safecol($field); | |||
} | |||
return implode(",\n ", $fields); | |||
} | |||
/** | |||
* Function: build_where | |||
* Creates a WHERE query. | |||
*/ | |||
public static function build_where($conds, $tables = null, &$params = array()) { | |||
$conds = (array) $conds; | |||
$tables = (array) $tables; | |||
$conditions = self::build_conditions($conds, $params, $tables); | |||
return (empty($conditions)) ? "" : "(".implode(")\n AND (", array_filter($conditions)).")"; | |||
} | |||
/** | |||
* Function: build_group | |||
* Creates a GROUP BY argument. | |||
* | |||
* Parameters: | |||
* $order - Columns to group by. | |||
* $tables - Tables to tablefy with. | |||
*/ | |||
public static function build_group($by, $tables = null) { | |||
$by = (array) $by; | |||
$tables = (array) $tables; | |||
foreach ($by as &$column) { | |||
self::tablefy($column, $tables); | |||
$column = self::safecol($column); | |||
} | |||
return implode(",\n ", array_unique(array_filter($by))); | |||
} | |||
/** | |||
* Function: build_order | |||
* Creates an ORDER BY argument. | |||
* | |||
* Parameters: | |||
* $order - Columns to order by. | |||
* $tables - Tables to tablefy with. | |||
*/ | |||
public static function build_order($order, $tables = null) { | |||
$tables = (array) $tables; | |||
if (!is_array($order)) | |||
$order = comma_sep($order); | |||
foreach ($order as &$by) { | |||
self::tablefy($by, $tables); | |||
$by = self::safecol($by); | |||
} | |||
return implode(",\n ", $order); | |||
} | |||
/** | |||
* Function: build_list | |||
* Returns ('one', 'two', '', 1, 0) from array("one", "two", null, true, false) | |||
*/ | |||
public static function build_list($vals, $params = array()) { | |||
$return = array(); | |||
foreach ($vals as $val) { | |||
if (is_object($val)) # Useful catch, e.g. empty SimpleXML objects. | |||
$val = ""; | |||
$return[] = (isset($params[$val])) ? $val : SQL::current()->escape($val) ; | |||
} | |||
return "(".join(", ", $return).")"; | |||
} | |||
/** | |||
* Function: safecol | |||
* Wraps a column in proper escaping if it is a SQL keyword. | |||
* | |||
* Doesn't check every keyword, just the common/sensible ones. | |||
* | |||
* ...Okay, it only does two. "order" and "group". | |||
* | |||
* Parameters: | |||
* $name - Name of the column. | |||
*/ | |||
public static function safecol($name) { | |||
return preg_replace("/(([^a-zA-Z0-9_]|^)(order|group)([^a-zA-Z0-9_]| | |||
$))/i", | |||
(SQL::current()->adapter == "mysql") ? "\\2`\\3` | |||
\\4" : '\\2"\\3"\\4', | |||
$name); | |||
} | |||
/** | |||
* Function: build_conditions | |||
* Builds an associative array of SQL values into PDO-esque paramized query strings. | |||
* | |||
* Parameters: | |||
* $conds - Conditions. | |||
* &$params - Parameters array to fill. | |||
* $tables - If specified, conditions will be tablefied with these tables. | |||
* $insert - Is this an insert/update query? | |||
*/ | |||
public static function build_conditions($conds, &$params, $tables = null, $insert = false) { | |||
$conditions = array(); | |||
foreach ($conds as $key => $val) { | |||
if (is_int($key)) # Full expression | |||
$cond = $val; | |||
else { # Key => Val expression | |||
if (is_string($val) and strlen($val) and $val[0] == ":") | |||
$cond = self::safecol($key)." = ".$val; | |||
else { | |||
if (is_bool($val)) | |||
$val = (int) $val; | |||
if (substr($key, -4) == " not") { # Negation | |||
$key = self::safecol(substr($key, 0, -4)); | |||
$param = str_replace(array("(", ")", "."), "_", $key); | |||
if (is_array($val)) | |||
$cond = $key." NOT IN ".self::build_list($val, $params); | |||
elseif ($val === null) | |||
$cond = $key." IS NOT NULL"; | |||
else { | |||
$cond = $key." != :".$param; | |||
$params[":".$param] = $val; | |||
} | |||
} elseif (substr($key, -5) == " like" and is_array($val)) { # multiple LIKE | |||
$key = self::safecol(substr($key, 0, -5)); | |||
$likes = array(); | |||
foreach ($val as $index => $match) { | |||
$param = str_replace(array("(", ")", "."), "_", $key)."_".$index; | |||
$likes[] = $key." LIKE :".$param; | |||
$params[":".$param] = $match; | |||
} | |||
$cond = "(".implode(" OR ", $likes).")"; | |||
} elseif (substr($key, -9) == " like all" and is_array($val)) { # multiple LIKE | |||
$key = self::safecol(substr($key, 0, -9)); | |||
$likes = array(); | |||
foreach ($val as $index => $match) { | |||
$param = str_replace(array("(", ")", "."), "_", $key)."_".$index; | |||
$likes[] = $key." LIKE :".$param; | |||
$params[":".$param] = $match; | |||
} | |||
$cond = "(".implode(" AND ", $likes).")"; | |||
} elseif (substr($key, -9) == " not like" and is_array($val)) { # multiple NOT LIKE | |||
$key = self::safecol(substr($key, 0, -9)); | |||
$likes = array(); | |||
foreach ($val as $index => $match) { | |||
$param = str_replace(array("(", ")", "."), "_", $key)."_".$index; | |||
$likes[] = $key." NOT LIKE :".$param; | |||
$params[":".$param] = $match; | |||
} | |||
$cond = "(".implode(" AND ", $likes).")"; | |||
} elseif (substr($key, -5) == " like") { # LIKE | |||
$key = self::safecol(substr($key, 0, -5)); | |||
$param = str_replace(array("(", ")", "."), "_", $key); | |||
$cond = $key." LIKE :".$param; | |||
$params[":".$param] = $val; | |||
} elseif (substr($key, -9) == " not like") { # NOT LIKE | |||
$key = self::safecol(substr($key, 0, -9)); | |||
$param = str_replace(array("(", ")", "."), "_", $key); | |||
$cond = $key." NOT LIKE :".$param; | |||
$params[":".$param] = $val; | |||
} elseif (substr_count($key, " ")) { # Custom operation, e.g. array("foo >" => $bar) | |||
list($param,) = explode(" ", $key); | |||
$param = str_replace(array("(", ")", "."), "_", $param); | |||
$cond = self::safecol($key)." :".$param; | |||
$params[":".$param] = $val; | |||
} else { # Equation | |||
if (is_array($val)) | |||
$cond = self::safecol($key)." IN ".self::build_list($val, $params); | |||
elseif ($val === null and $insert) | |||
$cond = self::safecol($key)." = ''"; | |||
elseif ($val === null) | |||
$cond = self::safecol($key)." IS NULL"; | |||
else { | |||
$param = str_replace(array("(", ")", "."), "_", $key); | |||
$cond = self::safecol($key)." = :".$param; | |||
$params[":".$param] = $val; | |||
} | |||
} | |||
} | |||
} | |||
if ($tables) | |||
self::tablefy($cond, $tables); | |||
$conditions[] = $cond; | |||
} | |||
return $conditions; | |||
} | |||
/** | |||
* Function: tablefy | |||
* Automatically prepends tables and table prefixes to a field if it doesn't already have them. | |||
* | |||
* Parameters: | |||
* &$field - The field to "tablefy". | |||
* $tables - An array of tables. The first one will be used for prepending. | |||
*/ | |||
public static function tablefy(&$field, $tables) { | |||
if (!preg_match_all("/(\(|[\s]+|^)(?!__)([a-z0-9_\.\*]+)(\)|[\s]+|$)/", $field, $matches)) | |||
return $field = str_replace("`", "", $field); # Method for bypassing the prefixer. | |||
foreach ($matches[0] as $index => $full) { | |||
$before = $matches[1][$index]; | |||
$name = $matches[2][$index]; | |||
$after = $matches[3][$index]; | |||
if (is_numeric($name)) | |||
continue; | |||
# Does it not already have a table specified? | |||
if (!substr_count($full, ".")) { | |||
# Don't replace things that are already either prefixed or paramized. | |||
$field = preg_replace("/([^\.:'\"_]|^)".preg_quote($full, "/")."/", | |||
"\\1".$before."__".$tables[0].".".$name.$after, | |||
$field, | |||
1); | |||
} else { | |||
# Okay, it does, but is the table prefixed? | |||
if (substr($full, 0, 2) != "__") { | |||
# Don't replace things that are already either prefixed or paramized. | |||
$field = preg_replace("/([^\.:'\"_]|^)".preg_quote($full, "/")."/", | |||
"\\1".$before."__".$name.$after, | |||
$field, | |||
1); | |||
} | |||
} | |||
} | |||
$field = preg_replace("/AS ([^ ]+)\./i", "AS ", $field); | |||
} | |||
} | |||
@@ -0,0 +1,235 @@ | |||
<?php | |||
/** | |||
* Class: Route | |||
* Holds information for URLs, redirecting, etc. | |||
*/ | |||
class Route { | |||
# String: $action | |||
# The current action. | |||
public $action = ""; | |||
# Array: $try | |||
# An array of (string) actions to try until one doesn't return false. | |||
public $try = array(); | |||
# Boolean: $ajax | |||
# Shortcut to the AJAX constant (useful for Twig). | |||
public $ajax = AJAX; | |||
# Boolean: $success | |||
# Did <Route.init> call a successful route? | |||
public $success = false; | |||
# Variable: $controller | |||
# The Route's Controller. | |||
public $controller; | |||
/** | |||
* Function: __construct | |||
* Parse the URL and to determine what to do. | |||
* | |||
* Parameters: | |||
* $controller - The controller to use. | |||
*/ | |||
private function __construct($controller) { | |||
$this->controller = $controller; | |||
$config = Config::current(); | |||
if (substr_count($_SERVER['REQUEST_URI'], "..") > 0 ) | |||
exit("GTFO."); | |||
elseif (isset($_GET['action']) and preg_match("/[^(\w+)]/", $_GET['action'])) | |||
exit("Nope!"); | |||
$this->action =& $_GET['action']; | |||
if (isset($_GET['feed'])) | |||
$this->feed = true; | |||
# Parse the current URL and extract information. | |||
$parse = parse_url($config->url); | |||
fallback($parse["path"], "/"); | |||
if (isset($controller->base)) | |||
$parse["path"] = trim($parse["path"], "/")."/".trim($controller->base, "/")."/"; | |||
$this->safe_path = str_replace("/", "\\/", $parse["path"]); | |||
$this->request = $parse["path"] == "/" ? | |||
$_SERVER['REQUEST_URI'] : | |||
preg_replace("/{$this->safe_path}?/", "", $_SERVER['REQUEST_URI'], 1) ; | |||
$this->arg = array_map("urldecode", explode("/", trim($this->request, "/"))); | |||
if (substr_count($this->arg[0], "?") > 0 and !preg_match("/\?\w+/", $this->arg[0])) | |||
exit("No-Go!"); | |||
if (method_exists($controller, "parse")) | |||
$controller->parse($this); | |||
Trigger::current()->call("parse_url", $this); | |||
$this->try[] = isset($this->action) ? | |||
oneof($this->action, "index") : | |||
(!substr_count($this->arg[0], "?") ? | |||
oneof(@$this->arg[0], "index") : | |||
"index") ; | |||
# Guess the action initially. | |||
# This is only required because of the view_site permission; | |||
# it has to know if they're viewing /login, in which case | |||
# it should allow the page to display. | |||
fallback($this->action, end($this->try)); | |||
} | |||
/** | |||
* Function: init | |||
* Attempt Controller actions until one of them doesn't return false. | |||
* | |||
* This will also call the @[controllername]_xxxxx@ and @route_xxxxx@ triggers. | |||
*/ | |||
public function init() { | |||
$trigger = Trigger::current(); | |||
$trigger->call("route_init", $this); | |||
$try = $this->try; | |||
if (isset($this->action)) | |||
array_unshift($try, $this->action); | |||
$count = 0; | |||
foreach ($try as $key => $val) { | |||
if (is_numeric($key)) | |||
list($method, $args) = array($val, array()); | |||
else | |||
list($method, $args) = array($key, $val); | |||
$this->action = $method; | |||
$name = strtolower(str_replace("Controller", "", get_class($this->controller))); | |||
if ($trigger->exists($name."_".$method) or $trigger->exists("route_".$method)) | |||
$call = $trigger->call(array($name."_".$method, "route_".$method), $this->controller); | |||
else | |||
$call = false; | |||
if ($call !== true and method_exists($this->controller, $method)) | |||
$response = call_user_func_array(array($this->controller, $method), $args); | |||
else | |||
$response = false; | |||
if ($response !== false or $call !== false) { | |||
$this->success = true; | |||
break; | |||
} | |||
if (++$count == count($try) and isset($this->controller->fallback) and method_exists($this->controller, "display")) | |||
call_user_func_array(array($this->controller, "display"), $this->controller->fallback); | |||
} | |||
if ($this->action != "login" and $this->success) | |||
$_SESSION['redirect_to'] = self_url(); | |||
$trigger->call("route_done", $this); | |||
return true; | |||
} | |||
/** | |||
* Function: url | |||
* Attempts to change the specified clean URL to a dirty URL if clean URLs is disabled. | |||
* Use this for linking to things. The applicable URL conversions are passed through the | |||
* parse_urls trigger. | |||
* | |||
* Parameters: | |||
* $url - The clean URL. | |||
* $use_chyrp_url - Use @Config.chyrp_url@ instead of @Config.url@, when the @$url@ begins with "/"? | |||
* | |||
* Returns: | |||
* A clean or dirty URL, depending on @Config.clean_urls@. | |||
*/ | |||
public function url($url, $controller = null) { | |||
$config = Config::current(); | |||
if ($url[0] == "/") | |||
return (ADMIN ? | |||
$config->chyrp_url.$url : | |||
$config->url.$url); | |||
else | |||
$url = substr($url, -1) == "/" ? $url : $url."/" ; | |||
fallback($controller, $this->controller); | |||
$base = !empty($controller->base) ? $config->url."/".$controller->base : $config->url ; | |||
if ($config->clean_urls) { # If their post URL doesn't have a trailing slash, remove it from these as well. | |||
if (substr($url, 0, 5) == "page/") # Different URL for viewing a page | |||
$url = substr($url, 5); | |||
return (substr($config->post_url, -1) == "/" or $url == "search/") ? | |||
$base."/".$url : | |||
$base."/".rtrim($url, "/") ; | |||
} | |||
$urls = fallback($controller->urls, array()); | |||
Trigger::current()->filter($urls, "parse_urls"); | |||
foreach (array_diff_assoc($urls, $controller->urls) as $key => $value) | |||
$urls[substr($key, 0, -1).preg_quote("feed/", $key[0]).$key[0]] = "/".$value."&feed"; | |||
$urls["|/([^/]+)/$|"] = "/?action=$1"; | |||
return $base.fix(preg_replace(array_keys($urls), array_values($urls), "/".$url, 1)); | |||
} | |||
/** | |||
* Function: add | |||
* Adds a route to Chyrp. Only needed for actions that have more than one parameter. | |||
* For example, for /tags/ you won't need to do this, but you will for /tag/tag-name/. | |||
* | |||
* Parameters: | |||
* $path - The path to add. Wrap variables with parentheses, e.g. "tag/(name)/". | |||
* $action - The action the path points to. | |||
* | |||
* See Also: | |||
* <remove> | |||
*/ | |||
public function add($path, $action) { | |||
$config = Config::current(); | |||
$new_routes = $config->routes; | |||
$new_routes[$path] = $action; | |||
$config->set("routes", $new_routes); | |||
} | |||
/** | |||
* Function: remove | |||
* Removes a route added by <add>. | |||
* | |||
* Parameters: | |||
* $path - The path to remove. | |||
* | |||
* See Also: | |||
* <add> | |||
*/ | |||
public function remove($path) { | |||
$config = Config::current(); | |||
unset($config->routes[$path]); | |||
$config->set("routes", $config->routes); | |||
} | |||
/** | |||
* Function: current | |||
* Returns a singleton reference to the current class. | |||
*/ | |||
public static function & current($controller = null) { | |||
static $instance = null; | |||
if (!isset($controller) and empty($instance)) | |||
error(__("Error"), __("Route was initiated without a Controller."), debug_backtrace()); | |||
return $instance = (empty($instance)) ? new self($controller) : $instance ; | |||
} | |||
} | |||
@@ -0,0 +1,473 @@ | |||
<?php | |||
/** | |||
* Class: SQL | |||
* Contains the database settings and functions for interacting with the SQL database. | |||
*/ | |||
# File: Query | |||
# See Also: | |||
# <Query> | |||
require_once INCLUDES_DIR."/class/Query.php"; | |||
# File: QueryBuilder | |||
# See Also: | |||
# <QueryBuilder> | |||
require_once INCLUDES_DIR."/class/QueryBuilder.php"; | |||
class SQL { | |||
# Array: $debug | |||
# Holds debug information for SQL queries. | |||
public $debug = array(); | |||
# Integer: $queries | |||
# Number of queries it takes to load the page. | |||
public $queries = 0; | |||
# Variable: $db | |||
# Holds the currently running database. | |||
public $db; | |||
# Variable: $error | |||
# Holds an error message from the last attempted query. | |||
public $error = ""; | |||
# Boolean: $silence_errors | |||
# Ignore errors? | |||
public $silence_errors = false; | |||
/** | |||
* Function: __construct | |||
* The class constructor is private so there is only one connection. | |||
* | |||
* Parameters: | |||
* $settings - Settings to load instead of the config. | |||
*/ | |||
private function __construct($settings = array()) { | |||
if (!UPGRADING and !INSTALLING and !isset(Config::current()->sql)) | |||
error(__("Error"), __("Database configuration is not set. Please run the upgrader.")); | |||
$database = !UPGRADING ? | |||
oneof(@Config::current()->sql, array()) : | |||
Config::get("sql") ; | |||
if (is_array($settings)) | |||
fallback($database, $settings); | |||
elseif ($settings === true) | |||
$this->silence_errors = true; | |||
if (!empty($database)) | |||
foreach ($database as $setting => $value) | |||
$this->$setting = $value; | |||
$this->connected = false; | |||
# We really don't need PDO anymore, since we have the two we supported with it hardcoded (kinda). | |||
# Keeping this here for when/if we decide to add support for more database engines, like Postgres and MSSQL. | |||
# if (class_exists("PDO") and (in_array("mysql", PDO::getAvailableDrivers()) or in_array("sqlite", PDO::getAvailableDrivers()))) | |||
# return $this->method = "pdo"; | |||
if (isset($this->adapter)) { | |||
if ($this->adapter == "mysql" and class_exists("MySQLi")) | |||
$this->method = "mysqli"; | |||
elseif ($this->adapter == "mysql" and function_exists("mysql_connect")) | |||
$this->method = "mysql"; | |||
elseif (class_exists("PDO") and | |||
($this->adapter == "sqlite" and in_array("sqlite", PDO::getAvailableDrivers()) or | |||
$this->adapter == "pgsql" and in_array("pgsql", PDO::getAvailableDrivers()))) | |||
$this->method = "pdo"; | |||
} else | |||
if (class_exists("MySQLi")) | |||
$this->method = "mysqli"; | |||
elseif (function_exists("mysql_connect")) | |||
$this->method = "mysql"; | |||
elseif (class_exists("PDO") and in_array("mysql", PDO::getAvailableDrivers())) | |||
$this->method = "pdo"; | |||
} | |||
/** | |||
* Function: set | |||
* Sets a variable's value. | |||
* | |||
* Parameters: | |||
* $setting - The setting name. | |||
* $value - The new value. Can be boolean, numeric, an array, a string, etc. | |||
* $overwrite - If the setting exists and is the same value, should it be overwritten? | |||
*/ | |||
public function set($setting, $value, $overwrite = true) { | |||
if (isset($this->$setting) and $this->$setting == $value and !$overwrite and !UPGRADING) | |||
return false; | |||
if (!UPGRADING) | |||
$config = Config::current(); | |||
$database = (!UPGRADING) ? fallback($config->sql, array()) : Config::get("sql") ; | |||
# Add the setting | |||
$database[$setting] = $this->$setting = $value; | |||
return (!UPGRADING) ? $config->set("sql", $database) : Config::set("sql", $database) ; | |||
} | |||
/** | |||
* Function: connect | |||
* Connects to the SQL database. | |||
* | |||
* Parameters: | |||
* $checking - Return a boolean of whether or not it could connect, instead of showing an error. | |||
*/ | |||
public function connect($checking = false) { | |||
if ($this->connected) | |||
return true; | |||
if (!isset($this->database)) | |||
self::__construct(); | |||
if (UPGRADING) | |||
$checking = true; | |||
switch($this->method) { | |||
case "pdo": | |||
try { | |||
if (empty($this->database)) | |||
throw new PDOException("No database specified."); | |||
if ($this->adapter == "sqlite") { | |||
$this->db = new PDO("sqlite:".$this->database, null, null, array(PDO::ATTR_PERSISTENT => true)); | |||
$this->db->sqliteCreateFunction("YEAR", array($this, "year_from_datetime"), 1); | |||
$this->db->sqliteCreateFunction("MONTH", array($this, "month_from_datetime"), 1); | |||
$this->db->sqliteCreateFunction("DAY", array($this, "day_from_datetime"), 1); | |||
$this->db->sqliteCreateFunction("HOUR", array($this, "hour_from_datetime"), 1); | |||
$this->db->sqliteCreateFunction("MINUTE", array($this, "minute_from_datetime"), 1); | |||
$this->db->sqliteCreateFunction("SECOND", array($this, "second_from_datetime"), 1); | |||
} else | |||
$this->db = new PDO($this->adapter.":host=".$this->host.";".((isset($this->port)) ? "port=".$this->port.";" : "")."dbname=".$this->database, | |||
$this->username, | |||
$this->password, | |||
array(PDO::ATTR_PERSISTENT => true)); | |||
$this->db->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); | |||
} catch (PDOException $error) { | |||
$this->error = $error->getMessage(); | |||
return ($checking) ? false : error(__("Database Error"), $this->error) ; | |||
} | |||
break; | |||
case "mysqli": | |||
$this->db = @new MySQLi($this->host, $this->username, $this->password, $this->database); | |||
$this->error = mysqli_connect_error(); | |||
if (mysqli_connect_errno()) | |||
return ($checking) ? false : error(__("Database Error"), $this->error) ; | |||
break; | |||
case "mysql": | |||
$this->db = @mysql_connect($this->host, $this->username, $this->password); | |||
$this->error = mysql_error(); | |||
if (!$this->db or !@mysql_select_db($this->database)) | |||
return ($checking) ? false : error(__("Database Error"), $this->error) ; | |||
break; | |||
} | |||
if ($this->adapter == "mysql") | |||
new Query($this, "SET NAMES 'utf8'"); # Note: This doesn't increase the query debug/count. | |||
return $this->connected = true; | |||
} | |||
/** | |||
* Function: query | |||
* Executes a query and increases <SQL->$queries>. | |||
* If the query results in an error, it will die and show the error. | |||
* | |||
* Parameters: | |||
* $query - Query to execute. | |||
* $params - An associative array of parameters used in the query. | |||
* $throw_exceptions - Should an exception be thrown if the query fails? | |||
*/ | |||
public function query($query, $params = array(), $throw_exceptions = false) { | |||
if (!$this->connected) | |||
return false; | |||
# Ensure that every param in $params exists in the query. | |||
# If it doesn't, remove it from $params. | |||
foreach ($params as $name => $val) | |||
if (!strpos($query, $name)) | |||
unset($params[$name]); | |||
$query = str_replace("__", $this->prefix, $query); | |||
if ($this->adapter == "sqlite") | |||
$query = str_ireplace(" DEFAULT CHARSET=utf8", "", str_ireplace("AUTO_INCREMENT", "AUTOINCREMENT", $query)); | |||
if ($this->adapter == "pgsql") | |||
$query = str_ireplace(array("CREATE TABLE IF NOT EXISTS", | |||
"INTEGER PRIMARY KEY AUTO_INCREMENT", | |||
") DEFAULT CHARSET=utf8", | |||
"TINYINT", | |||
"DATETIME", | |||
"DEFAULT '0000-00-00 00:00:00'", | |||
"LONGTEXT", | |||
"REPLACE INTO"), | |||
array("CREATE TABLE", | |||
"SERIAL PRIMARY KEY", | |||
")", | |||
"SMALLINT", | |||
"TIMESTAMP", | |||
"", | |||
"TEXT", | |||
"INSERT INTO"), | |||
$query); | |||
$query = new Query($this, $query, $params, $throw_exceptions); | |||
return (!$query->query and UPGRADING) ? false : $query ; | |||
} | |||
/** | |||
* Function: count | |||
* Performs a counting query and returns the number of matching rows. | |||
* | |||
* Parameters: | |||
* $tables - An array (or string) of tables to count results on. | |||
* $conds - An array (or string) of conditions to match. | |||
* $params - An associative array of parameters used in the query. | |||
* $throw_exceptions - Should exceptions be thrown on error? | |||
*/ | |||
public function count($tables, $conds = null, $params = array(), $throw_exceptions = false) { | |||
$query = $this->query(QueryBuilder::build_count($tables, $conds, $params), $params, $throw_exceptions); | |||
return ($query->query) ? $query->fetchColumn() : false ; | |||
} | |||
/** | |||
* Function: select | |||
* Performs a SELECT with given criteria and returns the query result object. | |||
* | |||
* Parameters: | |||
* $tables - An array (or string) of tables to grab results from. | |||
* $fields - Fields to select. | |||
* $conds - An array (or string) of conditions to match. | |||
* $order - ORDER BY statement. Can be an array. | |||
* $params - An associative array of parameters used in the query. | |||
* $limit - Limit for results. | |||
* $offset - Offset for the select statement. | |||
* $group - GROUP BY statement. Can be an array. | |||
* $left_join - An array of additional LEFT JOINs. | |||
* $throw_exceptions - Should exceptions be thrown on error? | |||
*/ | |||
public function select($tables, $fields = "*", $conds = null, $order = null, $params = array(), $limit = null, $offset = null, $group = null, $left_join = array(), $throw_exceptions = false) { | |||
return $this->query(QueryBuilder::build_select($tables, $fields, $conds, $order, $limit, $offset, $group, $left_join, $params), $params, $throw_exceptions); | |||
} | |||
/** | |||
* Function: insert | |||
* Performs an INSERT with given data. | |||
* | |||
* Parameters: | |||
* $table - Table to insert to. | |||
* $data - An associative array of data to insert. | |||
* $params - An associative array of parameters used in the query. | |||
* $throw_exceptions - Should exceptions be thrown on error? | |||
*/ | |||
public function insert($table, $data, $params = array(), $throw_exceptions = false) { | |||
return $this->query(QueryBuilder::build_insert($table, $data, $params), $params, $throw_exceptions); | |||
} | |||
/** | |||
* Function: replace | |||
* Performs either an INSERT or an UPDATE depending on | |||
* whether a row exists with the specified keys matching | |||
* their values in the data. | |||
* | |||
* Parameters: | |||
* $table - Table to update or insert into. | |||
* $keys - Columns to match on. | |||
* $data - Data for the insert and value matches for the keys. | |||
* $params - An associative array of parameters to be used in the query. | |||
* $throw_exceptions - Should exceptions be thrown on error? | |||
*/ | |||
public function replace($table, $keys, $data, $params = array(), $throw_exceptions = false) { | |||
$match = array(); | |||
foreach ((array) $keys as $key) | |||
$match[$key] = $data[$key]; | |||
if ($this->count($table, $match, $params)) | |||
$this->update($table, $match, $data, $params, $throw_exceptions); | |||
else | |||
$this->insert($table, $data, $params, $throw_exceptions); | |||
} | |||
/** | |||
* Function: update | |||
* Performs an UDATE with given criteria and data. | |||
* | |||
* Parameters: | |||
* $table - Table to update. | |||
* $conds - Rows to update. | |||
* $data - An associative array of data to update. | |||
* $params - An associative array of parameters used in the query. | |||
* $throw_exceptions - Should exceptions be thrown on error? | |||
*/ | |||
public function update($table, $conds, $data, $params = array(), $throw_exceptions = false) { | |||
return $this->query(QueryBuilder::build_update($table, $conds, $data, $params), $params, $throw_exceptions); | |||
} | |||
/** | |||
* Function: delete | |||
* Performs a DELETE with given criteria. | |||
* | |||
* Parameters: | |||
* $table - Table to delete from. | |||
* $conds - Rows to delete.. | |||
* $params - An associative array of parameters used in the query. | |||
* $throw_exceptions - Should exceptions be thrown on error? | |||
*/ | |||
public function delete($table, $conds, $params = array(), $throw_exceptions = false) { | |||
return $this->query(QueryBuilder::build_delete($table, $conds, $params), $params, $throw_exceptions); | |||
} | |||
/** | |||
* Function: latest | |||
* Returns the last inserted sequential value. | |||
* Both function arguments are only relevant for PostgreSQL. | |||
* | |||
* Parameters: | |||
* $table - Table to get the latest value from. | |||
* $seq - Name of the sequence. | |||
*/ | |||
public function latest($table, $seq = "id_seq") { | |||
if (!isset($this->db)) | |||
$this->connect(); | |||
switch($this->method) { | |||
case "pdo": | |||
return $this->db->lastInsertId($this->prefix.$table."_".$seq); | |||
break; | |||
case "mysqli": | |||
return $this->db->insert_id; | |||
break; | |||
case "mysql": | |||
return @mysql_insert_id(); | |||
break; | |||
} | |||
} | |||
/** | |||
* Function: escape | |||
* Escapes a string, escaping things like $1 and C:\foo\bar so that they don't get borked by the preg_replace. | |||
* | |||
* This also handles calling the SQL connection method's "escape_string" functions. | |||
* | |||
* Parameters: | |||
* $string - String to escape. | |||
* $quotes - Auto-wrap the string in quotes (@'@)? | |||
*/ | |||
public function escape($string, $quotes = true) { | |||
if (!isset($this->db)) | |||
$this->connect(); | |||
switch($this->method) { | |||
case "pdo": | |||
$string = ltrim(rtrim($this->db->quote($string), "'"), "'"); | |||
break; | |||
case "mysqli": | |||
$string = $this->db->escape_string($string); | |||
break; | |||
case "mysql": | |||
$string = mysql_real_escape_string($string); | |||
break; | |||
} | |||
# I don't think this ever worked how it intended. | |||
# I've tested PDO, MySQLi, and MySQL and they all | |||
# properly escape with this disabled, but get double | |||
# escaped with this uncommented: | |||
# $string = str_replace('\\', '\\\\', $string); | |||
$string = str_replace('$', '\$', $string); | |||
if ($quotes and !is_numeric($string)) | |||
$string = "'".$string."'"; | |||
return $string; | |||
} | |||
/** | |||
* Function: year_from_datetime | |||
* Returns the year of a datetime. | |||
* | |||
* Parameters: | |||
* $datetime - DATETIME value. | |||
*/ | |||
public function year_from_datetime($datetime) { | |||
return when("Y", $datetime); | |||
} | |||
/** | |||
* Function: month_from_datetime | |||
* Returns the month of a datetime. | |||
* | |||
* Parameters: | |||
* $datetime - DATETIME value. | |||
*/ | |||
public function month_from_datetime($datetime) { | |||
return when("m", $datetime); | |||
} | |||
/** | |||
* Function: day_from_datetime | |||
* Returns the day of a datetime. | |||
* | |||
* Parameters: | |||
* $datetime - DATETIME value. | |||
*/ | |||
public function day_from_datetime($datetime) { | |||
return when("d", $datetime); | |||
} | |||
/** | |||
* Function: hour_from_datetime | |||
* Returns the hour of a datetime. | |||
* | |||
* Parameters: | |||
* $datetime - DATETIME value. | |||
*/ | |||
public function hour_from_datetime($datetime) { | |||
return when("g", $datetime); | |||
} | |||
/** | |||
* Function: minute_from_datetime | |||
* Returns the minute of a datetime. | |||
* | |||
* Parameters: | |||
* $datetime - DATETIME value. | |||
*/ | |||
public function minute_from_datetime($datetime) { | |||
return when("i", $datetime); | |||
} | |||
/** | |||
* Function: second_from_datetime | |||
* Returns the second of a datetime. | |||
* | |||
* Parameters: | |||
* $datetime - DATETIME value. | |||
*/ | |||
public function second_from_datetime($datetime) { | |||
return when("s", $datetime); | |||
} | |||
/** | |||
* Function: current | |||
* Returns a singleton reference to the current connection. | |||
*/ | |||
public static function & current($settings = false) { | |||
if ($settings) { | |||
static $loaded_settings = null; | |||
return $loaded_settings = new self($settings); | |||
} else { | |||
static $instance = null; | |||
return $instance = (empty($instance)) ? new self() : $instance ; | |||
} | |||
} | |||
} |
@@ -0,0 +1,95 @@ | |||
<?php | |||
/** | |||
* Class: Session | |||
* Handles their session. | |||
*/ | |||
class Session { | |||
# Variable: $data | |||
# Caches session data. | |||
static $data = ""; | |||
/** | |||
* Function: open | |||
* Returns: @true@ | |||
*/ | |||
static function open() { | |||
return true; | |||
} | |||
/** | |||
* Function: close | |||
* Returns: @true@ | |||
*/ | |||
static function close() { | |||
return true; | |||
} | |||
/** | |||
* Function: read | |||
* Reads their session from the database. | |||
* | |||
* Parameters: | |||
* $id - Session ID. | |||
*/ | |||
static function read($id) { | |||
self::$data = SQL::current()->select("sessions", | |||
"data", | |||
array("id" => $id), | |||
"id")->fetchColumn(); | |||
return fallback(self::$data, ""); | |||
} | |||
/** | |||
* Function: write | |||
* Writes their session to the database, or updates it if it already exists. | |||
* | |||
* Parameters: | |||
* $id - Session ID. | |||
* $data - Data to write. | |||
*/ | |||
static function write($id, $data) { | |||
if (empty($data) or $data == self::$data) | |||
return; | |||
$sql = SQL::current(); | |||
if ($sql->count("sessions", array("id" => $id))) | |||
$sql->update("sessions", | |||
array("id" => $id), | |||
array("data" => $data, | |||
"user_id" => Visitor::current()->id, | |||
"updated_at" => datetime())); | |||
else | |||
$sql->insert("sessions", | |||
array("id" => $id, | |||
"data" => $data, | |||
"user_id" => Visitor::current()->id, | |||
"created_at" => datetime())); | |||
} | |||
/** | |||
* Function: destroy | |||
* Destroys their session. | |||
* | |||
* Parameters: | |||
* $id - Session ID. | |||
*/ | |||
static function destroy($id) { | |||
if (SQL::current()->delete("sessions", array("id" => $id))) | |||
return true; | |||
return false; | |||
} | |||
/** | |||
* Function: gc | |||
* Garbage collector. Removes sessions older than 30 days and sessions with no stored data. | |||
*/ | |||
static function gc() { | |||
SQL::current()->delete("sessions", | |||
"created_at >= :thirty_days OR data = '' OR data IS NULL", | |||
array(":thirty_days" => datetime(strtotime("+30 days")))); | |||
return true; | |||
} | |||
} |
@@ -0,0 +1,332 @@ | |||
<?php | |||
/** | |||
* Class: Theme | |||
* Various helper functions for the theming engine. | |||
*/ | |||
class Theme { | |||
# String: $title | |||
# The title for the current page. | |||
public $title = ""; | |||
/** | |||
* Function: __construct | |||
* Loads the Twig parser into <Theme>, and sets up the theme l10n domain. | |||
*/ | |||
private function __construct() { | |||
$config = Config::current(); | |||
# Load the theme translator | |||
if (file_exists(THEME_DIR."/locale/".$config->locale.".mo")) | |||
load_translator("theme", THEME_DIR."/locale/".$config->locale.".mo"); | |||
# Load the theme's info into the Theme class. | |||
foreach (YAML::load(THEME_DIR."/info.yaml") as $key => $val) | |||
$this->$key = $val; | |||
$this->url = THEME_URL; | |||
} | |||
/** | |||
* Function: pages_list | |||
* Returns a simple array of list items to be used by the theme to generate a recursive array of pages. | |||
* | |||
* Parameters: | |||
* $start - Page ID or slug to start at. | |||
* $exclude - Page ID to exclude from the list. Used in the admin area. | |||
*/ | |||
public function pages_list($start = 0, $exclude = null) { | |||
if (isset($this->pages_list[$start])) | |||
return $this->pages_list[$start]; | |||
$this->linear_children = array(); | |||
$this->pages_flat = array(); | |||
$this->children = array(); | |||
$this->end_tags_for = array(); | |||
if ($start and !is_numeric($start)) | |||
$begin_page = new Page(array("url" => $start)); | |||
$start = ($start and !is_numeric($start)) ? $begin_page->id : $start ; | |||
$where = ADMIN ? array("id not" => $exclude) : array("show_in_list" => true) ; | |||
$pages = Page::find(array("where" => $where, "order" => "list_order ASC")); | |||
if (empty($pages)) | |||
return $this->pages_list[$start] = array(); | |||
foreach ($pages as $page) | |||
$this->end_tags_for[$page->id] = $this->children[$page->id] = array(); | |||
foreach ($pages as $page) | |||
if ($page->parent_id != 0) | |||
$this->children[$page->parent_id][] = $page; | |||
foreach ($pages as $page) | |||
if ((!$start and $page->parent_id == 0) or ($start and $page->id == $start)) | |||
$this->recurse_pages($page); | |||
$array = array(); | |||
foreach ($this->pages_flat as $page) { | |||
$array[$page->id]["has_children"] = !empty($this->children[$page->id]); | |||
if ($array[$page->id]["has_children"]) | |||
$this->end_tags_for[$this->get_last_linear_child($page->id)][] = array("</ul>", "</li>"); | |||
$array[$page->id]["end_tags"] =& $this->end_tags_for[$page->id]; | |||
$array[$page->id]["page"] = $page; | |||
} | |||
return $this->pages_list[$start] = $array; | |||
} | |||
/** | |||
* Function: get_last_linear_child | |||
* Gets the last linear child of a page. | |||
* | |||
* Parameters: | |||
* $page - Page to get the last linear child of. | |||
* $origin - Where to start. | |||
*/ | |||
public function get_last_linear_child($page, $origin = null) { | |||
fallback($origin, $page); | |||
$this->linear_children[$origin] = $page; | |||
foreach ($this->children[$page] as $child) | |||
$this->get_last_linear_child($child->id, $origin); | |||
return $this->linear_children[$origin]; | |||
} | |||
/** | |||
* Function: recurse_pages | |||
* Prepares the pages into <Theme.$pages_flat>. | |||
* | |||
* Parameters: | |||
* $page - Page to start recursion at. | |||
*/ | |||
public function recurse_pages($page) { | |||
$page->depth = oneof(@$page->depth, 1); | |||
$this->pages_flat[] = $page; | |||
foreach ($this->children[$page->id] as $child) { | |||
$child->depth = $page->depth + 1; | |||
$this->recurse_pages($child); | |||
} | |||
} | |||
/** | |||
* Function: archive_list | |||
* Generates an array of all of the archives, by month. | |||
* | |||
* Parameters: | |||
* $limit - Amount of months to list | |||
* $order_by - What to sort it by | |||
* $order - "asc" or "desc" | |||
* | |||
* Returns: | |||
* The array. Each entry as "month", "year", and "url" values, stored as an array. | |||
*/ | |||
public function archives_list($limit = 0, $order_by = "created_at", $order = "desc") { | |||
if (isset($this->archives_list["$limit,$order_by,$order"])) | |||
return $this->archives_list["$limit,$order_by,$order"]; | |||
$sql = SQL::current(); | |||
$dates = $sql->select("posts", | |||
array("DISTINCT YEAR(created_at) AS year", | |||
"MONTH(created_at) AS month", | |||
"created_at AS created_at", | |||
"COUNT(id) AS posts"), | |||
array("status" => "public", Post::feathers()), | |||
$order_by." ".strtoupper($order), | |||
array(), | |||
($limit == 0) ? null : $limit, | |||
null, | |||
array("created_at")); | |||
$archives = array(); | |||
$grouped = array(); | |||
while ($date = $dates->fetchObject()) | |||
if (isset($grouped[$date->month." ".$date->year])) | |||
$archives[$grouped[$date->month." ".$date->year]]["count"]++; | |||
else { | |||
$grouped[$date->month." ".$date->year] = count($archives); | |||
$archives[] = array("month" => $date->month, | |||
"year" => $date->year, | |||
"when" => $date->created_at, | |||
"url" => url("archive/".when("Y/m/", $date->created_at)), | |||
"count" => $date->posts); | |||
} | |||
return $this->archives_list["$limit,$order_by,$order"] = $archives; | |||
} | |||
/** | |||
* Function: file_exists | |||
* Returns whether the specified Twig file exists or not. | |||
* | |||
* Parameters: | |||
* $file - The file's name | |||
*/ | |||
public function file_exists($file) { | |||
return file_exists(THEME_DIR."/".$file.".twig"); | |||
} | |||
/** | |||
* Function: stylesheets | |||
* Outputs the default stylesheet links. | |||
*/ | |||
public function stylesheets() { | |||
$visitor = Visitor::current(); | |||
$config = Config::current(); | |||
$trigger = Trigger::current(); | |||
$stylesheets = array(); | |||
Trigger::current()->filter($stylesheets, "stylesheets"); | |||
if (!empty($stylesheets)) | |||
$stylesheets = '<link rel="stylesheet" href="'. | |||
implode('" type="text/css" media="screen" charset="utf-8" /'."\n\t\t".'<link rel="stylesheet" href="', $stylesheets). | |||
'" type="text/css" media="screen" charset="utf-8" />'; | |||
else | |||
$stylesheets = ""; | |||
if (file_exists(THEME_DIR."/style.css")) | |||
$stylesheets = '<link rel="stylesheet" href="'.THEME_URL.'/style.css" type="text/css" media="screen" charset="utf-8" />'."\n\t\t"; | |||
if (!file_exists(THEME_DIR."/stylesheets/") and !file_exists(THEME_DIR."/css/")) | |||
return $stylesheets; | |||
$long = (array) glob(THEME_DIR."/stylesheets/*"); | |||
$short = (array) glob(THEME_DIR."/css/*"); | |||
$total = array_merge($long, $short); | |||
foreach($total as $file) { | |||
$path = preg_replace("/(.+)\/themes\/(.+)/", "/themes/\\2", $file); | |||
$file = basename($file); | |||
if (substr_count($file, ".inc.css") or (substr($file, -4) != ".css" and substr($file, -4) != ".php")) | |||
continue; | |||
if ($file == "ie.css") | |||
$stylesheets.= "<!--[if IE]>"; | |||
if (preg_match("/^ie([0-9\.]+)\.css/", $file, $matches)) | |||
$stylesheets.= "<!--[if IE ".$matches[1]."]>"; | |||
elseif (preg_match("/(lte?|gte?)ie([0-9\.]+)\.css/", $file, $matches)) | |||
$stylesheets.= "<!--[if ".$matches[1]." IE ".$matches[2]."]>"; | |||
$stylesheets.= '<link rel="stylesheet" href="'.$config->chyrp_url.$path.'" type="text/css" media="'.($file == "print.css" ? "print" : "screen").'" charset="utf-8" />'; | |||
if ($file == "ie.css" or preg_match("/(lt|gt)?ie([0-9\.]+)\.css/", $file)) | |||
$stylesheets.= "<![endif]-->"; | |||
$stylesheets.= "\n\t\t"; | |||
} | |||
return $stylesheets; | |||
} | |||
/** | |||
* Function: javascripts | |||
* Outputs the default JavaScript script references. | |||
*/ | |||
public function javascripts() { | |||
$route = Route::current(); | |||
$args = ""; | |||
foreach ($_GET as $key => $val) | |||
if (!empty($val) and $val != $route->action) | |||
$args.= "&".$key."=".urlencode($val); | |||
$config = Config::current(); | |||
$trigger = Trigger::current(); | |||
$javascripts = array($config->chyrp_url."/includes/lib/gz.php?file=jquery.js", | |||
$config->chyrp_url."/includes/lib/gz.php?file=plugins.js", | |||
$config->chyrp_url.'/includes/javascript.php?action='.$route->action.$args); | |||
Trigger::current()->filter($javascripts, "scripts"); | |||
$javascripts = '<script src="'. | |||
implode('" type="text/javascript" charset="utf-8"></script>'."\n\t\t".'<script src="', $javascripts). | |||
'" type="text/javascript" charset="utf-8"></script>'; | |||
if (file_exists(THEME_DIR."/javascripts/") or file_exists(THEME_DIR."/js/")) { | |||
$long = (array) glob(THEME_DIR."/javascripts/*.js"); | |||
$short = (array) glob(THEME_DIR."/js/*.js"); | |||
foreach(array_merge($long, $short) as $file) | |||
if ($file and !substr_count($file, ".inc.js")) | |||
$javascripts.= "\n\t\t".'<script src="'.$config->chyrp_url.'/includes/lib/gz.php?file='.preg_replace("/(.+)\/themes\/(.+)/", "/themes/\\2", $file).'" type="text/javascript" charset="utf-8"></script>'; | |||
$long = (array) glob(THEME_DIR."/javascripts/*.php"); | |||
$short = (array) glob(THEME_DIR."/js/*.php"); | |||
foreach(array_merge($long, $short) as $file) | |||
if ($file) | |||
$javascripts.= "\n\t\t".'<script src="'.$config->chyrp_url.preg_replace("/(.+)\/themes\/(.+)/", "/themes/\\2", $file).'" type="text/javascript" charset="utf-8"></script>'; | |||
} | |||
return $javascripts; | |||
} | |||
/** | |||
* Function: feeds | |||
* Outputs the Feed references. | |||
*/ | |||
public function feeds() { | |||
// Compute the URL of the per-page feed (if any): | |||
$config = Config::current(); | |||
$request = ($config->clean_urls) ? rtrim(Route::current()->request, "/") : fix($_SERVER['REQUEST_URI']) ; | |||
$append = $config->clean_urls ? | |||
"/feed" : | |||
((count($_GET) == 1 and Route::current()->action == "index") ? | |||
"/?feed" : | |||
"&feed") ; | |||
$append.= $config->clean_urls ? | |||
"/".urlencode($this->title) : | |||
"&title=".urlencode($this->title) ; | |||
# Create basic list of links (site and page Atom feeds): | |||
$feedurl = oneof(@$config->feed_url, url("feed")); | |||
$pagefeedurl = $config->url.$request.$append; | |||
$links = array(array("href" => $feedurl, "type" => "application/atom+xml", "title" => $config->name)); | |||
if ($pagefeedurl != $feedurl) | |||
$links[] = array("href" => $pagefeedurl, "type" => "application/atom+xml", "title" => "Current Page (if applicable)"); | |||
# Ask modules to pitch in by adding their own <link> tag items to $links. | |||
# Each item must be an array with "href" and "rel" properties (and optionally "title" and "type"): | |||
Trigger::current()->filter($links, "links"); | |||
# Generate <link> tags: | |||
$tags = array(); | |||
foreach ($links as $link) { | |||
$rel = oneof(@$link["rel"], "alternate"); | |||
$href = $link["href"]; | |||
$type = @$link["type"]; | |||
$title = @$link["title"]; | |||
$tag = '<link rel="'.$rel.'" href="'.$link["href"].'"'; | |||
if ($type) | |||
$tag.= ' type="'.$type.'"'; | |||
if ($title) | |||
$tag.= ' title="'.$title.'"'; | |||
$tags[] = $tag.' />'; | |||
} | |||
return implode("\n\t", $tags); | |||
} | |||
public function load_time() { | |||
return timer_stop(); | |||
} | |||
/** | |||
* Function: current | |||
* Returns a singleton reference to the current class. | |||
*/ | |||
public static function & current() { | |||
static $instance = null; | |||
return $instance = (empty($instance)) ? new self() : $instance ; | |||
} | |||
} |
@@ -0,0 +1,176 @@ | |||
<?php | |||
/** | |||
* Class: Trigger | |||
* Controls and keeps track of all of the Triggers and events. | |||
*/ | |||
class Trigger { | |||
# Array: $priorities | |||
# Custom prioritized callbacks. | |||
public $priorities = array(); | |||
# Array: $called | |||
# Keeps track of called Triggers. | |||
private $called = array(); | |||
# Array: $exists | |||
# Caches trigger exist states. | |||
private $exists = array(); | |||
/** | |||
* Function: cmp | |||
* Sorts actions by priority when used with usort. | |||
*/ | |||
private function cmp($a, $b) { | |||
if (empty($a) or empty($b)) return 0; | |||
return ($a["priority"] < $b["priority"]) ? -1 : 1 ; | |||
} | |||
/** | |||
* Function: call | |||
* Calls a trigger, passing any additional arguments to it. | |||
* | |||
* Parameters: | |||
* $name - The name of the trigger, or an array of triggers to call. | |||
*/ | |||
public function call($name) { | |||
if (is_array($name)) { | |||
$return = null; | |||
foreach ($name as $index => $call) { | |||
$args = func_get_args(); | |||
$args[0] = $call; | |||
if ($index + 1 == count($name)) | |||
return $this->exists($call) ? call_user_func_array(array($this, "call"), $args) : $return ; | |||
else | |||
$return = $this->exists($call) ? call_user_func_array(array($this, "call"), $args) : $return ; | |||
} | |||
} | |||
if (!$this->exists($name)) | |||
return false; | |||
$arguments = func_get_args(); | |||
array_shift($arguments); | |||
$return = null; | |||
$this->called[$name] = array(); | |||
if (isset($this->priorities[$name])) { # Predefined priorities? | |||
usort($this->priorities[$name], array($this, "cmp")); | |||
foreach ($this->priorities[$name] as $action) { | |||
$return = call_user_func_array($action["function"], $arguments); | |||
$this->called[$name][] = $action["function"]; | |||
} | |||
} | |||
foreach (Modules::$instances as $module) | |||
if (!in_array(array($module, $name), $this->called[$name]) and is_callable(array($module, $name))) | |||
$return = call_user_func_array(array($module, $name), $arguments); | |||
return $return; | |||
} | |||
/** | |||
* Function: filter | |||
* Filters a variable through a trigger's actions. Similar to <call>, except this is stackable and is intended to | |||
* modify something instead of inject code. | |||
* | |||
* Any additional arguments passed to this function are passed to the function being called. | |||
* | |||
* Parameters: | |||
* &$target - The variable to filter. | |||
* $name - The name of the trigger. | |||
* | |||
* Returns: | |||
* $target, filtered through any/all actions for the trigger $name. | |||
*/ | |||
public function filter(&$target, $name) { | |||
if (is_array($name)) | |||
foreach ($name as $index => $filter) { | |||
$args = func_get_args(); | |||
$args[0] =& $target; | |||
$args[1] = $filter; | |||
if ($index + 1 == count($name)) | |||
return $target = call_user_func_array(array($this, "filter"), $args); | |||
else | |||
$target = call_user_func_array(array($this, "filter"), $args); | |||
} | |||
if (!$this->exists($name)) | |||
return $target; | |||
$arguments = func_get_args(); | |||
array_shift($arguments); | |||
array_shift($arguments); | |||
$this->called[$name] = array(); | |||
if (isset($this->priorities[$name]) and usort($this->priorities[$name], array($this, "cmp"))) | |||
foreach ($this->priorities[$name] as $action) { | |||
$call = call_user_func_array($this->called[$name][] = $action["function"], | |||
array_merge(array(&$target), $arguments)); | |||
$target = fallback($call, $target); | |||
} | |||
foreach (Modules::$instances as $module) | |||
if (!in_array(array($module, $name), $this->called[$name]) and is_callable(array($module, $name))) { | |||
$call = call_user_func_array(array($module, $name), | |||
array_merge(array(&$target), $arguments)); | |||
$target = fallback($call, $target); | |||
} | |||
return $target; | |||
} | |||
/** | |||
* Function: remove | |||
* Unregisters a given $action from a $trigger. | |||
* | |||
* Parameters: | |||
* $trigger - The trigger to unregister from. | |||
* $action - The action name. | |||
*/ | |||
public function remove($trigger, $action) { | |||
foreach ($this->actions[$trigger] as $index => $func) { | |||
if ($func == $action) { | |||
unset($this->actions[$trigger][$key]); | |||
return; | |||
} | |||
} | |||
$this->actions[$trigger]["disabled"][] = $action; | |||
} | |||
/** | |||
* Function: exists | |||
* Checks if there are any actions for a given $trigger. | |||
* | |||
* Parameters: | |||
* $trigger - The trigger name. | |||
* | |||
* Returns: | |||
* @true@ or @false@ | |||
*/ | |||
public function exists($name) { | |||
if (isset($this->exists[$name])) | |||
return $this->exists[$name]; | |||
foreach (Modules::$instances as $module) | |||
if (is_callable(array($module, $name))) | |||
return $this->exists[$name] = true; | |||
if (isset($this->priorities[$name])) | |||
return $this->exists[$name] = true; | |||
return $this->exists[$name] = false; | |||
} | |||
/** | |||
* Function: current | |||
* Returns a singleton reference to the current class. | |||
*/ | |||
public static function & current() { | |||
static $instance = null; | |||
return $instance = (empty($instance)) ? new self() : $instance ; | |||
} | |||
} |
@@ -0,0 +1,55 @@ | |||
<?php | |||
/** | |||
* Twig | |||
* ~~~~ | |||
* | |||
* A simple cross-language template engine. | |||
* | |||
* Usage | |||
* ----- | |||
* | |||
* Using Twig in a application is straightfoward. All you have to do is to | |||
* make sure you have a folder where all your templates go and another one | |||
* where the compiled templates go (which we call the cache):: | |||
* | |||
* require 'Twig.php'; | |||
* $loader = new Twig_Loader('path/to/templates', 'path/to/cache'); | |||
* | |||
* After that you can load templates using the loader:: | |||
* | |||
* $template = $loader->getTemplate('index.html'); | |||
* | |||
* You can render templates by using the render and display methods. display | |||
* works like render just that it prints the output whereas render returns | |||
* the generated source as string. Both accept an array as context:: | |||
* | |||
* echo $template->render(array('users' => get_list_of_users())); | |||
* $template->display(array('users' => get_list_of_users())); | |||
* | |||
* Custom Loaders | |||
* -------------- | |||
* | |||
* For many applications it's a good idea to subclass the loader to add | |||
* support for multiple template locations. For example many applications | |||
* support plugins and you want to allow plugins to ship themes. | |||
* | |||
* The easiest way is subclassing Twig_Loader and override the getFilename | |||
* method which calculates the path to the template on the file system. | |||
* | |||
* | |||
* :copyright: 2008 by Armin Ronacher. | |||
* :license: BSD. | |||
*/ | |||
if (!defined('TWIG_BASE')) | |||
define('TWIG_BASE', dirname(__FILE__) . '/Twig'); | |||
define('TWIG_VERSION', '0.1-dev'); | |||
// the systems we load automatically on initialization. The compiler | |||
// and other stuff is loaded on first request. | |||
require TWIG_BASE . '/exceptions.php'; | |||
require TWIG_BASE . '/runtime.php'; | |||
require TWIG_BASE . '/api.php'; |
@@ -0,0 +1,192 @@ | |||
<?php | |||
/** | |||
* Twig::API | |||
* ~~~~~~~~~ | |||
* | |||
* The High-Level API | |||
* | |||
* :copyright: 2008 by Armin Ronacher. | |||
* :license: BSD. | |||
*/ | |||
/** | |||
* Load the compiler system. Call this before you access the | |||
* compiler! | |||
*/ | |||
function twig_load_compiler() | |||
{ | |||
if (!defined('TWIG_COMPILER_INCLUDED')) | |||
require TWIG_BASE . '/compiler.php'; | |||
} | |||
/** | |||
* A helper function that can be used by filters to get the | |||
* current active template. This use useful to access variables | |||
* on the template like the charset. | |||
*/ | |||
function twig_get_current_template() | |||
{ | |||
return $GLOBALS['twig_current_template']; | |||
} | |||
/* the current template that is rendered. This used used internally | |||
and is an implementation detail. Don't tamper with that. */ | |||
$twig_current_template = NULL; | |||
/** | |||
* This class wraps a template instance as returned by the compiler and | |||
* is usually constructed from the `Twig_Loader`. | |||
*/ | |||
class Twig_Template | |||
{ | |||
private $instance; | |||
public $charset; | |||
public $loader; | |||
public function __construct($instance, $charset=NULL, $loader) | |||
{ | |||
$this->instance = $instance; | |||
$this->charset = $charset; | |||
$this->loader = $loader; | |||
} | |||
/** | |||
* Render the template with the given context and return it | |||
* as string. | |||
*/ | |||
public function render($context=NULL) | |||
{ | |||
ob_start(); | |||
$this->display($context); | |||
return ob_get_clean(); | |||
} | |||
/** | |||
* Works like `render()` but prints the output. | |||
*/ | |||
public function display($context=NULL) | |||
{ | |||
global $twig_current_template; | |||
$old = $twig_current_template; | |||
$twig_current_template = $this; | |||
if (is_null($context)) | |||
$context = array(); | |||
$this->instance->render($context); | |||
$twig_current_template = $old; | |||
} | |||
} | |||
/** | |||
* Baseclass for custom loaders. Subclasses have to provide a | |||
* getFilename method. | |||
*/ | |||
class Twig_BaseLoader | |||
{ | |||
public $cache; | |||
public $charset; | |||
public function __construct($cache=NULL, $charset=NULL) | |||
{ | |||
$this->cache = $cache; | |||
$this->charset = $charset; | |||
} | |||
public function getTemplate($name) | |||
{ | |||
$cls = $this->requireTemplate($name); | |||
return new Twig_Template(new $cls, $this->charset, $this); | |||
} | |||
public function getCacheFilename($name) | |||
{ | |||
return $this->cache . '/twig_' . md5($name) . '.cache'; | |||
} | |||
public function requireTemplate($name) | |||
{ | |||
$cls = '__TwigTemplate_' . md5($name); | |||
if (!class_exists($cls)) { | |||
if (is_null($this->cache)) { | |||
$this->evalTemplate($name); | |||
return $cls; | |||
} | |||
$fn = $this->getFilename($name); | |||
if (!file_exists($fn)) | |||
throw new Twig_TemplateNotFound($name); | |||
$cache_fn = $this->getCacheFilename($name); | |||
if (!file_exists($cache_fn) || | |||
filemtime($cache_fn) < filemtime($fn)) { | |||
twig_load_compiler(); | |||
$fp = @fopen($cache_fn, 'wb'); | |||
if (!$fp) { | |||
$this->evalTemplate($name, $fn); | |||
return $cls; | |||
} | |||
$compiler = new Twig_FileCompiler($fp); | |||
$this->compileTemplate($name, $compiler, $fn); | |||
fclose($fp); | |||
} | |||
include $cache_fn; | |||
} | |||
return $cls; | |||
} | |||
public function compileTemplate($name, $compiler=NULL, $fn=NULL) | |||
{ | |||
twig_load_compiler(); | |||
if (is_null($compiler)) { | |||
$compiler = new Twig_StringCompiler(); | |||
$returnCode = true; | |||
} | |||
else | |||
$returnCode = false; | |||
if (is_null($fn)) | |||
$fn = $this->getFilename($name); | |||
$node = twig_parse(file_get_contents($fn, $name), $name); | |||
$node->compile($compiler); | |||
if ($returnCode) | |||
return $compiler->getCode(); | |||
} | |||
private function evalTemplate($name, $fn=NULL) | |||
{ | |||
$code = $this->compileTemplate($name, NULL, $fn); | |||
# echo "ORIGINAL: <textarea rows=15 style=\"width: 100%\">".fix(print_r($code, true))."</textarea>"; | |||
$code = preg_replace('/(?!echo twig_get_attribute.+)echo "[\\\\tn]+";/', "", $code); # Remove blank lines | |||
#echo "STRIPPED: <textarea rows=15 style=\"width: 100%\">".fix(print_r($code, true))."</textarea>"; | |||
eval('?>' . $code); | |||
} | |||
} | |||
/** | |||
* Helper class that loads templates. | |||
*/ | |||
class Twig_Loader extends Twig_BaseLoader | |||
{ | |||
public $folder; | |||
public function __construct($folder, $cache=NULL, $charset=NULL) | |||
{ | |||
parent::__construct($cache, $charset); | |||
$this->folder = $folder; | |||
} | |||
public function getFilename($name) | |||
{ | |||
if ($name[0] == '/' or preg_match("/[a-zA-Z]:\\\/", $name)) return $name; | |||
$path = array(); | |||
foreach (explode('/', $name) as $part) { | |||
if ($part[0] != '.') | |||
array_push($path, $part); | |||
} | |||
return $this->folder . '/' . implode('/', $path) ; | |||
} | |||
} |
@@ -0,0 +1,754 @@ | |||
<?php | |||
/** | |||
* Twig::AST | |||
* ~~~~~~~~~ | |||
* | |||
* This module implements the abstract syntax tree and compiler. | |||
* | |||
* :copyright: 2008 by Armin Ronacher. | |||
* :license: BSD. | |||
*/ | |||
class Twig_Node | |||
{ | |||
public $lineno; | |||
public function __construct($lineno) | |||
{ | |||
$this->lineno = $lineno; | |||
} | |||
public function compile($compiler) | |||
{ | |||
} | |||
} | |||
class Twig_NodeList extends Twig_Node | |||
{ | |||
public $nodes; | |||
public function __construct($nodes, $lineno) | |||
{ | |||
parent::__construct($lineno); | |||
$this->nodes = $nodes; | |||
} | |||
public function compile($compiler) | |||
{ | |||
foreach ($this->nodes as $node) | |||
$node->compile($compiler); | |||
} | |||
public static function fromArray($array, $lineno) | |||
{ | |||
if (count($array) == 1) | |||
return $array[0]; | |||
return new Twig_NodeList($array, $lineno); | |||
} | |||
} | |||
class Twig_Module extends Twig_Node | |||
{ | |||
public $body; | |||
public $extends; | |||
public $blocks; | |||
public $filename; | |||
public $id; | |||
public function __construct($body, $extends, $blocks, $filename) | |||
{ | |||
parent::__construct(1); | |||
$this->body = $body; | |||
$this->extends = $extends; | |||
$this->blocks = $blocks; | |||
$this->filename = $filename; | |||
} | |||
public function compile($compiler) | |||
{ | |||
$compiler->raw("<?php\n"); | |||
if (!is_null($this->extends)) { | |||
$compiler->raw('$this->requireTemplate('); | |||
$compiler->repr($this->extends); | |||
$compiler->raw(");\n"); | |||
} | |||
$compiler->raw('class __TwigTemplate_' . md5($this->filename)); | |||
if (!is_null($this->extends)) { | |||
$parent = md5($this->extends); | |||
$compiler->raw(" extends __TwigTemplate_$parent {\n"); | |||
} | |||
else { | |||
$compiler->raw(" {\npublic function render(\$context) {\n"); | |||
$this->body->compile($compiler); | |||
$compiler->raw("}\n"); | |||
} | |||
foreach ($this->blocks as $node) | |||
$node->compile($compiler); | |||
$compiler->raw("}\n"); | |||
} | |||
} | |||
class Twig_Print extends Twig_Node | |||
{ | |||
public $expr; | |||
public function __construct($expr, $lineno) | |||
{ | |||
parent::__construct($lineno); | |||
$this->expr = $expr; | |||
} | |||
public function compile($compiler) | |||
{ | |||
$compiler->addDebugInfo($this); | |||
$compiler->raw('echo '); | |||
$this->expr->compile($compiler); | |||
$compiler->raw(";\n"); | |||
} | |||
} | |||
class Twig_Text extends Twig_Node | |||
{ | |||
public $data; | |||
public function __construct($data, $lineno) | |||
{ | |||
parent::__construct($lineno); | |||
$this->data = $data; | |||
} | |||
public function compile($compiler) | |||
{ | |||
$compiler->addDebugInfo($this); | |||
$compiler->raw('echo '); | |||
$compiler->string($this->data); | |||
$compiler->raw(";\n"); | |||
} | |||
} | |||
class Twig_ForLoop extends Twig_Node | |||
{ | |||
public $is_multitarget; | |||
public $item; | |||
public $seq; | |||
public $body; | |||
public $else; | |||
public function __construct($is_multitarget, $item, $seq, $body, $else, | |||
$lineno) | |||
{ | |||
parent::__construct($lineno); | |||
$this->is_multitarget = $is_multitarget; | |||
$this->item = $item; | |||
$this->seq = $seq; | |||
$this->body = $body; | |||
$this->else = $else; | |||
$this->lineno = $lineno; | |||
} | |||
public function compile($compiler) | |||
{ | |||
$compiler->addDebugInfo($this); | |||
$compiler->pushContext(); | |||
$compiler->raw('foreach (twig_iterate($context, '); | |||
$this->seq->compile($compiler); | |||
$compiler->raw(") as \$iterator) {\n"); | |||
if ($this->is_multitarget) { | |||
$compiler->raw('twig_set_loop_context_multitarget($context, ' . | |||
'$iterator, array('); | |||
$idx = 0; | |||
foreach ($this->item as $node) { | |||
if ($idx++) | |||
$compiler->raw(', '); | |||
$compiler->repr($node->name); | |||
} | |||
$compiler->raw("));\n"); | |||
} | |||
else { | |||
$compiler->raw('twig_set_loop_context($context, $iterator, '); | |||
$compiler->repr($this->item->name); | |||
$compiler->raw(");\n"); | |||
} | |||
$this->body->compile($compiler); | |||
$compiler->raw("}\n"); | |||
if (!is_null($this->else)) { | |||
$compiler->raw("if (!\$context['loop']['iterated']) {\n"); | |||
$this->else->compile($compiler); | |||
$compiler->raw('}'); | |||
} | |||
$compiler->popContext(); | |||
} | |||
} | |||
class Twig_PaginateLoop extends Twig_Node | |||
{ | |||
public $item; | |||
public $seq; | |||
public $body; | |||
public $else; | |||
public function __construct($item, $per_page, $target, | |||
$as, $body, $else, $lineno) | |||
{ | |||
parent::__construct($lineno); | |||
$this->item = $item; | |||
$this->per_page = $per_page; | |||
$this->seq = $target; | |||
$this->as = $as; | |||
$this->body = $body; | |||
$this->else = $else; | |||
$this->lineno = $lineno; | |||
} | |||
public function compile($compiler) | |||
{ | |||
$compiler->addDebugInfo($this); | |||
$compiler->pushContext(); | |||
$compiler->raw('twig_paginate($context,'); | |||
$compiler->raw('"'.$this->as->name.'", '); | |||
if (isset($this->seq->node) and isset($this->seq->attr)) { | |||
$compiler->raw('array($context["::parent"]["'); | |||
$compiler->raw($this->seq->node->name.'"],'); | |||
$compiler->raw('"'.$this->seq->attr->value.'")'); | |||
} else | |||
$this->seq->compile($compiler); | |||
$compiler->raw(', '); | |||
$this->per_page->compile($compiler); | |||
$compiler->raw(");\n"); | |||
$compiler->raw('foreach (twig_iterate($context,'); | |||
$compiler->raw(' $context["::parent"]["'.$this->as->name); | |||
$compiler->raw("\"]->paginated) as \$iterator) {\n"); | |||
$compiler->raw('twig_set_loop_context($context, $iterator, '); | |||
$compiler->repr($this->item->name); | |||
$compiler->raw(");\n"); | |||
$this->body->compile($compiler); | |||
$compiler->raw("}\n"); | |||
if (!is_null($this->else)) { | |||
$compiler->raw("if (!\$context['loop']['iterated']) {\n"); | |||
$this->else->compile($compiler); | |||
$compiler->raw('}'); | |||
} | |||
$compiler->popContext(); | |||
} | |||
} | |||
class Twig_IfCondition extends Twig_Node | |||
{ | |||
public $tests; | |||
public $else; | |||
public function __construct($tests, $else, $lineno) | |||
{ | |||
parent::__construct($lineno); | |||
$this->tests = $tests; | |||
$this->else = $else; | |||
} | |||
public function compile($compiler) | |||
{ | |||
$compiler->addDebugInfo($this); | |||
$idx = 0; | |||
foreach ($this->tests as $test) { | |||
$compiler->raw(($idx++ ? "}\nelse" : '') . 'if ('); | |||
$test[0]->compile($compiler); | |||
$compiler->raw(") {\n"); | |||
$test[1]->compile($compiler); | |||
} | |||
if (!is_null($this->else)) { | |||
$compiler->raw("} else {\n"); | |||
$this->else->compile($compiler); | |||
} | |||
$compiler->raw("}\n"); | |||
} | |||
} | |||
class Twig_Block extends Twig_Node | |||
{ | |||
public $name; | |||
public $body; | |||
public $parent; | |||
public function __construct($name, $body, $lineno, $parent=NULL) | |||
{ | |||
parent::__construct($lineno); | |||
$this->name = $name; | |||
$this->body = $body; | |||
$this->parent = $parent; | |||
} | |||
public function replace($other) | |||
{ | |||
$this->body = $other->body; | |||
} | |||
public function compile($compiler) | |||
{ | |||
$compiler->addDebugInfo($this); | |||
$compiler->format('public function block_%s($context) {' . "\n", | |||
$this->name); | |||
if (!is_null($this->parent)) | |||
$compiler->raw('$context[\'::superblock\'] = array($this, ' . | |||
"'parent::block_$this->name');\n"); | |||
$this->body->compile($compiler); | |||
$compiler->format("}\n\n"); | |||
} | |||
} | |||
class Twig_BlockReference extends Twig_Node | |||
{ | |||
public $name; | |||
public function __construct($name, $lineno) | |||
{ | |||
parent::__construct($lineno); | |||
$this->name = $name; | |||
} | |||
public function compile($compiler) | |||
{ | |||
$compiler->addDebugInfo($this); | |||
$compiler->format('$this->block_%s($context);' . "\n", $this->name); | |||
} | |||
} | |||
class Twig_Super extends Twig_Node | |||
{ | |||
public $block_name; | |||
public function __construct($block_name, $lineno) | |||
{ | |||
parent::__construct($lineno); | |||
$this->block_name = $block_name; | |||
} | |||
public function compile($compiler) | |||
{ | |||
$compiler->addDebugInfo($this); | |||
$compiler->raw('parent::block_' . $this->block_name . '($context);' . "\n"); | |||
} | |||
} | |||
class Twig_Include extends Twig_Node | |||
{ | |||
public $expr; | |||
public function __construct($expr, $lineno) | |||
{ | |||
parent::__construct($lineno); | |||
$this->expr = $expr; | |||
} | |||
public function compile($compiler) | |||
{ | |||
$compiler->addDebugInfo($this); | |||
$compiler->raw('twig_get_current_template()->loader->getTemplate('); | |||
$this->expr->compile($compiler); | |||
$compiler->raw(')->display($context);' . "\n"); | |||
} | |||
} | |||
class Twig_URL extends Twig_Node | |||
{ | |||
public $expr; | |||
public function __construct($expr, $cont, $lineno) | |||
{ | |||
parent::__construct($lineno); | |||
$this->expr = $expr; | |||
$this->cont = $cont; | |||
} | |||
public function compile($compiler) | |||
{ | |||
$compiler->addDebugInfo($this); | |||
$compiler->raw('echo url('); | |||
$this->expr->compile($compiler); | |||
if (!empty($this->cont) and class_exists($this->cont->name."Controller") and is_callable(array($this->cont->name."Controller", "current"))) | |||
$compiler->raw(", ".$this->cont->name."Controller::current()"); | |||
$compiler->raw(');'."\n"); | |||
} | |||
} | |||
class Twig_AdminURL extends Twig_Node | |||
{ | |||
public $expr; | |||
public function __construct($expr, $lineno) | |||
{ | |||
parent::__construct($lineno); | |||
$this->expr = $expr; | |||
} | |||
public function compile($compiler) | |||
{ | |||
$compiler->addDebugInfo($this); | |||
$compiler->raw('echo fix(Config::current()->chyrp_url."/admin/?action=".('); | |||
$this->expr->compile($compiler); | |||
$compiler->raw('));'."\n"); | |||
} | |||
} | |||
class Twig_Expression extends Twig_Node | |||
{ | |||
} | |||
class Twig_ConditionalExpression extends Twig_Expression | |||
{ | |||
public $expr1; | |||
public $expr2; | |||
public $expr3; | |||
public function __construct($expr1, $expr2, $expr3, $lineno) | |||
{ | |||
parent::__construct($lineno); | |||
$this->expr1 = $expr1; | |||
$this->expr2 = $expr2; | |||
$this->expr3 = $expr3; | |||
} | |||
public function compile($compiler) | |||
{ | |||
$compiler->raw('('); | |||
$this->expr1->compile($compiler); | |||
$compiler->raw(') ? ('); | |||
$this->expr2->compile($compiler); | |||
$compiler->raw(') ; ('); | |||
$this->expr3->compile($compiler); | |||
$compiler->raw(')'); | |||
} | |||
} | |||
class Twig_BinaryExpression extends Twig_Expression | |||
{ | |||
public $left; | |||
public $right; | |||
public function __construct($left, $right, $lineno) | |||
{ | |||
parent::__construct($lineno); | |||
$this->left = $left; | |||
$this->right = $right; | |||
} | |||
public function compile($compiler) | |||
{ | |||
$compiler->raw('('); | |||
$this->left->compile($compiler); | |||
$compiler->raw(') '); | |||
$this->operator($compiler); | |||
$compiler->raw(' ('); | |||
$this->right->compile($compiler); | |||
$compiler->raw(')'); | |||
} | |||
} | |||
class Twig_OrExpression extends Twig_BinaryExpression | |||
{ | |||
public function operator($compiler) | |||
{ | |||
return $compiler->raw('||'); | |||
} | |||
} | |||
class Twig_AndExpression extends Twig_BinaryExpression | |||
{ | |||
public function operator($compiler) | |||
{ | |||
return $compiler->raw('&&'); | |||
} | |||
} | |||
class Twig_AddExpression extends Twig_BinaryExpression | |||
{ | |||
public function operator($compiler) | |||
{ | |||
return $compiler->raw('+'); | |||
} | |||
} | |||
class Twig_SubExpression extends Twig_BinaryExpression | |||
{ | |||
public function operator($compiler) | |||
{ | |||
return $compiler->raw('-'); | |||
} | |||
} | |||
class Twig_ConcatExpression extends Twig_BinaryExpression | |||
{ | |||
public function operator($compiler) | |||
{ | |||
return $compiler->raw('.'); | |||
} | |||
} | |||
class Twig_MulExpression extends Twig_BinaryExpression | |||
{ | |||
public function operator($compiler) | |||
{ | |||
return $compiler->raw('*'); | |||
} | |||
} | |||
class Twig_DivExpression extends Twig_BinaryExpression | |||
{ | |||
public function operator($compiler) | |||
{ | |||
return $compiler->raw('/'); | |||
} | |||
} | |||
class Twig_ModExpression extends Twig_BinaryExpression | |||
{ | |||
public function operator($compiler) | |||
{ | |||
return $compiler->raw('%'); | |||
} | |||
} | |||
class Twig_CompareExpression extends Twig_Expression | |||
{ | |||
public $expr; | |||
public $ops; | |||
public function __construct($expr, $ops, $lineno) | |||
{ | |||
parent::__construct($lineno); | |||
$this->expr = $expr; | |||
$this->ops = $ops; | |||
} | |||
public function compile($compiler) | |||
{ | |||
$this->expr->compile($compiler); | |||
$i = 0; | |||
foreach ($this->ops as $op) { | |||
if ($i) | |||
$compiler->raw(' && ($tmp' . $i); | |||
list($op, $node) = $op; | |||
$compiler->raw(' ' . $op . ' '); | |||
$compiler->raw('($tmp' . ++$i . ' = '); | |||
$node->compile($compiler); | |||
$compiler->raw(')'); | |||
} | |||
if ($i > 1) | |||
$compiler->raw(')'); | |||
} | |||
} | |||
class Twig_UnaryExpression extends Twig_Expression | |||
{ | |||
public $node; | |||
public function __construct($node, $lineno) | |||
{ | |||
parent::__construct($lineno); | |||
$this->node = $node; | |||
} | |||
public function compile($compiler) | |||
{ | |||
$compiler->raw('('); | |||
$this->operator($compiler); | |||
$this->node->compile($compiler); | |||
$compiler->raw(')'); | |||
} | |||
} | |||
class Twig_NotExpression extends Twig_UnaryExpression | |||
{ | |||
public function operator($compiler) | |||
{ | |||
$compiler->raw('!'); | |||
} | |||
} | |||
class Twig_NegExpression extends Twig_UnaryExpression | |||
{ | |||
public function operator($compiler) | |||
{ | |||
$compiler->raw('-'); | |||
} | |||
} | |||
class Twig_PosExpression extends Twig_UnaryExpression | |||
{ | |||
public function operator($compiler) | |||
{ | |||
$compiler->raw('+'); | |||
} | |||
} | |||
class Twig_Constant extends Twig_Expression | |||
{ | |||
public $value; | |||
public function __construct($value, $lineno) | |||
{ | |||
parent::__construct($lineno); | |||
$this->value = $value; | |||
} | |||
public function compile($compiler) | |||
{ | |||
$compiler->repr($this->value); | |||
} | |||
} | |||
class Twig_NameExpression extends Twig_Expression | |||
{ | |||
public $name; | |||
public function __construct($name, $lineno) | |||
{ | |||
parent::__construct($lineno); | |||
$this->name = $name; | |||
} | |||
public function compile($compiler) | |||
{ | |||
$compiler->format('(isset($context[\'%s\']) ? $context[\'%s\'] ' . | |||
': NULL)', $this->name, $this->name); | |||
} | |||
} | |||
class Twig_AssignNameExpression extends Twig_NameExpression | |||
{ | |||
public function compile($compiler) | |||
{ | |||
$compiler->format('$context[\'%s\']', $this->name); | |||
} | |||
} | |||
class Twig_GetAttrExpression extends Twig_Expression | |||
{ | |||
public $node; | |||
public $attr; | |||
public function __construct($node, $attr, $lineno, $token_value) | |||
{ | |||
parent::__construct($lineno); | |||
$this->node = $node; | |||
$this->attr = $attr; | |||
$this->token_value = $token_value; | |||
} | |||
public function compile($compiler) | |||
{ | |||
$compiler->raw('twig_get_attribute('); | |||
$this->node->compile($compiler); | |||
$compiler->raw(', '); | |||
$this->attr->compile($compiler); | |||
if ($this->token_value == "[") # Don't look for functions if they're using foo[bar] | |||
$compiler->raw(', false'); | |||
$compiler->raw(')'); | |||
} | |||
} | |||
class Twig_MethodCallExpression extends Twig_Expression | |||
{ | |||
public $node; | |||
public $method; | |||
public $arguments; | |||
public function __construct($node, $method, $arguments, $lineno) | |||
{ | |||
parent::__construct($lineno); | |||
$this->node = $node; | |||
$this->method = $method; | |||
$this->arguments = $arguments; | |||
} | |||
public function compile($compiler) | |||
{ | |||
$compiler->raw('call_user_func(array('); | |||
$this->node->compile($compiler); | |||
$compiler->raw(', '); | |||
$this->method->compile($compiler); | |||
$compiler->raw(')'); | |||
foreach ($this->arguments as $argument) { | |||
$compiler->raw(', '); | |||
$argument->compile($compiler); | |||
} | |||
$compiler->raw(')'); | |||
} | |||
} | |||
class Twig_FilterExpression extends Twig_Expression | |||
{ | |||
public $node; | |||
public $filters; | |||
public function __construct($node, $filters, $lineno) | |||
{ | |||
parent::__construct($lineno); | |||
$this->node = $node; | |||
$this->filters = $filters; | |||
} | |||
public function compile($compiler) | |||
{ | |||
global $twig_filters; | |||
$postponed = array(); | |||
for ($i = count($this->filters) - 1; $i >= 0; --$i) { | |||
list($name, $attrs) = $this->filters[$i]; | |||
if (!isset($twig_filters[$name])) { | |||
$compiler->raw('twig_missing_filter('); | |||
$compiler->repr($name); | |||
$compiler->raw(', '); | |||
} | |||
else | |||
$compiler->raw($twig_filters[$name] . '('); | |||
$postponed[] = $attrs; | |||
} | |||
$this->node->compile($compiler); | |||
foreach (array_reverse($postponed) as $attributes) { | |||
foreach ($attributes as $node) { | |||
$compiler->raw(', '); | |||
$node->compile($compiler); | |||
} | |||
$compiler->raw(')'); | |||
} | |||
} | |||
} |
@@ -0,0 +1,133 @@ | |||
<?php | |||
/** | |||
* Twig::Compiler | |||
* ~~~~~~~~~~~~~~ | |||
* | |||
* This module implements the Twig compiler. | |||
* | |||
* :copyright: 2008 by Armin Ronacher. | |||
* :license: BSD. | |||
*/ | |||
// mark the compiler as being included. This use used by the public | |||
// `twig_load_compiler` function that loads the compiler system. | |||
define('TWIG_COMPILER_INCLUDED', true); | |||
require TWIG_BASE . '/lexer.php'; | |||
require TWIG_BASE . '/parser.php'; | |||
require TWIG_BASE . '/ast.php'; | |||
function twig_compile($node, $fp=null) | |||
{ | |||
if (!is_null($fp)) | |||
$compiler = new Twig_FileCompiler($fp); | |||
else | |||
$compiler = new Twig_StringCompiler(); | |||
$node->compile($compiler); | |||
if (is_null($fp)) | |||
return $compiler->getCode(); | |||
} | |||
class Twig_Compiler | |||
{ | |||
private $last_lineno; | |||
public function __construct() | |||
{ | |||
$this->last_lineno = NULL; | |||
} | |||
public function format() | |||
{ | |||
$arguments = func_get_args(); | |||
$this->raw(call_user_func_array('sprintf', $arguments)); | |||
} | |||
public function string($value) | |||
{ | |||
$this->format('"%s"', addcslashes($value, "\t\"")); | |||
} | |||
public function repr($value) | |||
{ | |||
if (is_int($value) || is_float($value)) | |||
$this->raw($value); | |||
else if (is_null($value)) | |||
$this->raw('NULL'); | |||
else if (is_bool($value)) | |||
$this->raw($value ? 'true' : 'false'); | |||
else if (is_array($value)) { | |||
$this->raw('array('); | |||
$i = 0; | |||
foreach ($value as $key => $value) { | |||
if ($i++) | |||
$this->raw(', '); | |||
$this->repr($key); | |||
$this->raw(' => '); | |||
$this->repr($value); | |||
} | |||
$this->raw(')'); | |||
} | |||
else | |||
$this->string($value); | |||
} | |||
public function pushContext() | |||
{ | |||
$this->raw('$context[\'::parent\'] = $parent = $context;'. "\n"); | |||
} | |||
public function popContext() | |||
{ | |||
$this->raw('$context = $context[\'::parent\'];'. "\n"); | |||
} | |||
public function addDebugInfo($node) | |||
{ | |||
if ($node->lineno != $this->last_lineno) { | |||
$this->last_lineno = $node->lineno; | |||
$this->raw("/* LINE:$node->lineno */\n"); | |||
} | |||
} | |||
} | |||
class Twig_FileCompiler extends Twig_Compiler | |||
{ | |||
private $fp; | |||
public function __construct($fp) | |||
{ | |||
parent::__construct(); | |||
$this->fp = $fp; | |||
} | |||
public function raw($string) | |||
{ | |||
fwrite($this->fp, $string); | |||
} | |||
} | |||
class Twig_StringCompiler extends Twig_Compiler | |||
{ | |||
private $source; | |||
public function __construct() | |||
{ | |||
parent::__construct(); | |||
$this->source = ''; | |||
} | |||
public function raw($string) | |||
{ | |||
$this->source .= $string; | |||
} | |||
public function getCode() | |||
{ | |||
return $this->source; | |||
} | |||
} |
@@ -0,0 +1,66 @@ | |||
<?php | |||
/** | |||
* Twig::Exceptions | |||
* ~~~~~~~~~~~~~~~~ | |||
* | |||
* This module implements the Twig exceptions. | |||
* | |||
* :copyright: 2008 by Armin Ronacher. | |||
* :license: GNU GPL. | |||
*/ | |||
/** | |||
* Baseclass for all exceptions twig may throw. This is useful for | |||
* instance-checks to silence all twig errors for example. | |||
*/ | |||
class Twig_Exception extends Exception {} | |||
/** | |||
* This exception is raised when the template engine is unable to | |||
* parse or lex a template. Because the getFile() method and similar | |||
* methods are final we can't override them here but provide the real | |||
* filename and line number as public property. | |||
*/ | |||
class Twig_SyntaxError extends Twig_Exception | |||
{ | |||
public $lineno; | |||
public $filename; | |||
public function __construct($message, $lineno, $filename=null) | |||
{ | |||
parent::__construct($message); | |||
$this->lineno = $lineno; | |||
$this->filename = $filename; | |||
} | |||
} | |||
/** | |||
* Thrown when Twig encounters an exception at runtime in the Twig | |||
* core. | |||
*/ | |||
class Twig_RuntimeError extends Twig_Exception | |||
{ | |||
public function __construct($message) | |||
{ | |||
parent::__construct($message); | |||
} | |||
} | |||
/** | |||
* Raised if the loader is unable to find a template. | |||
*/ | |||
class Twig_TemplateNotFound extends Twig_Exception | |||
{ | |||
public $name; | |||
public function __construct($name) | |||
{ | |||
parent::__construct('Template not found: ' . $name); | |||
$this->name = $name; | |||
} | |||
} |
@@ -0,0 +1,430 @@ | |||
<?php | |||
/** | |||
* Twig::Lexer | |||
* ~~~~~~~~~~~ | |||
* | |||
* This module implements the Twig lexer. | |||
* | |||
* :copyright: 2008 by Armin Ronacher. | |||
* :license: BSD. | |||
*/ | |||
/** | |||
* Tokenizes a given string and returns a new Twig_TokenStream. | |||
*/ | |||
function twig_tokenize($source, $filename=NULL) | |||
{ | |||
$lexer = new Twig_Lexer($source, $filename); | |||
return new Twig_TokenStream($lexer, $filename); | |||
} | |||
/** | |||
* A simple lexer for twig templates. | |||
*/ | |||
class Twig_Lexer | |||
{ | |||
private $cursor; | |||
private $position; | |||
private $end; | |||
private $pushedBack; | |||
public $code; | |||
public $lineno; | |||
public $filename; | |||
const POSITION_DATA = 0; | |||
const POSITION_BLOCK = 1; | |||
const POSITION_VAR = 2; | |||
const REGEX_NAME = '/[A-Za-z_][A-Za-z0-9_]*/A'; | |||
const REGEX_NUMBER = '/[0-9]+(?:\.[0-9])?/A'; | |||
const REGEX_STRING = '/(?:"([^"\\\\]*(?:\\\\.[^"\\\\]*)*)"|\'([^\'\\\\]*(?:\\\\.[^\'\\\\]*)*)\')/Asm'; | |||
const REGEX_OPERATOR = '/<=?|>=?|[!=]=|[(){}.,%*\/+~|-]|\[|\]/A'; | |||
public function __construct($code, $filename=NULL) | |||
{ | |||
$this->code = preg_replace('/(\r\n|\r|\n)/', '\n', $code); | |||
$this->filename = $filename; | |||
$this->cursor = 0; | |||
$this->lineno = 1; | |||
$this->pushedBack = array(); | |||
$this->end = strlen($this->code); | |||
$this->position = self::POSITION_DATA; | |||
} | |||
/** | |||
* parse the nex token and return it. | |||
*/ | |||
public function nextToken() | |||
{ | |||
// do we have tokens pushed back? get one | |||
if (!empty($this->pushedBack)) | |||
return array_shift($this->pushedBack); | |||
// have we reached the end of the code? | |||
if ($this->cursor >= $this->end) | |||
return Twig_Token::EOF($this->lineno); | |||
// otherwise dispatch to the lexing functions depending | |||
// on our current position in the code. | |||
switch ($this->position) { | |||
case self::POSITION_DATA: | |||
$tokens = $this->lexData(); break; | |||
case self::POSITION_BLOCK: | |||
$tokens = $this->lexBlock(); break; | |||
case self::POSITION_VAR: | |||
$tokens = $this->lexVar(); break; | |||
} | |||
// if the return value is not an array it's a token | |||
if (!is_array($tokens)) | |||
return $tokens; | |||
// empty array, call again | |||
else if (empty($tokens)) | |||
return $this->nextToken(); | |||
// if we have multiple items we push them to the buffer | |||
else if (count($tokens) > 1) { | |||
$first = array_shift($tokens); | |||
$this->pushedBack = $tokens; | |||
return $first; | |||
} | |||
// otherwise return the first item of the array. | |||
return $tokens[0]; | |||
} | |||
private function lexData() | |||
{ | |||
$match = NULL; | |||
// if no matches are left we return the rest of the template | |||
// as simple text token | |||
if (!preg_match('/(.*?)(\{[%#]|\$(?!\$))/A', $this->code, $match, | |||
NULL, $this->cursor)) { | |||
$rv = Twig_Token::Text(substr($this->code, $this->cursor), | |||
$this->lineno); | |||
$this->cursor = $this->end; | |||
return $rv; | |||
} | |||
$this->cursor += strlen($match[0]); | |||
// update the lineno on the instance | |||
$lineno = $this->lineno; | |||
$this->lineno += substr_count($match[0], '\n'); | |||
// push the template text first | |||
$text = $match[1]; | |||
if (!empty($text)) { | |||
$result = array(Twig_Token::Text($text, $lineno)); | |||
$lineno += substr_count($text, '\n'); | |||
} | |||
else | |||
$result = array(); | |||
// block start token, let's return a token for that. | |||
if (($token = $match[2]) !== '$') { | |||
// if our section is a comment, just return the text | |||
if ($token[1] == '#') { | |||
if (!preg_match('/.*?#\}/A', $this->code, $match, | |||
NULL, $this->cursor)) | |||
throw new Twig_SyntaxError('unclosed comment', | |||
$this->lineno); | |||
$this->cursor += strlen($match[0]); | |||
$this->lineno += substr_count($match[0], '\n'); | |||
return $result; | |||
} | |||
$result[] = new Twig_Token(Twig_Token::BLOCK_START_TYPE, | |||
'', $lineno); | |||
$this->position = self::POSITION_BLOCK; | |||
} | |||
// quoted block | |||
else if (isset($this->code[$this->cursor]) && | |||
$this->code[$this->cursor] == '{') { | |||
$this->cursor++; | |||
$result[] = new Twig_Token(Twig_Token::VAR_START_TYPE, | |||
'', $lineno); | |||
$this->position = self::POSITION_VAR; | |||
} | |||
// inline variable expressions. If there is no name next we | |||
// fail silently. $ 42 could be common so no need to be a | |||
// dickhead. | |||
else if (preg_match(self::REGEX_NAME, $this->code, $match, | |||
NULL, $this->cursor)) { | |||
$result[] = new Twig_Token(Twig_Token::VAR_START_TYPE, | |||
'', $lineno); | |||
$result[] = Twig_Token::Name($match[0], $lineno); | |||
$this->cursor += strlen($match[0]); | |||
// allow attribute lookup | |||
while (isset($this->code[$this->cursor]) && | |||
$this->code[$this->cursor] === '.') { | |||
++$this->cursor; | |||
$result[] = Twig_Token::Operator('.', $this->lineno); | |||
if (preg_match(self::REGEX_NAME, $this->code, | |||
$match, NULL, $this->cursor)) { | |||
$this->cursor += strlen($match[0]); | |||
$result[] = Twig_Token::Name($match[0], | |||
$this->lineno); | |||
} | |||
else if (preg_match(self::REGEX_NUMBER, $this->code, | |||
$match, NULL, $this->cursor)) { | |||
$this->cursor += strlen($match[0]); | |||
$result[] = Twig_Token::Number($match[0], | |||
$this->lineno); | |||
} | |||
else { | |||
--$this->cursor; | |||
break; | |||
} | |||
} | |||
$result[] = new Twig_Token(Twig_Token::VAR_END_TYPE, | |||
'', $lineno); | |||
} | |||
return $result; | |||
} | |||
private function lexBlock() | |||
{ | |||
$match = NULL; | |||
if (preg_match('/\s*%\}/A', $this->code, $match, NULL, $this->cursor)) { | |||
$this->cursor += strlen($match[0]); | |||
$lineno = $this->lineno; | |||
$this->lineno += substr_count($match[0], '\n'); | |||
$this->position = self::POSITION_DATA; | |||
return new Twig_Token(Twig_Token::BLOCK_END_TYPE, '', $lineno); | |||
} | |||
return $this->lexExpression(); | |||
} | |||
private function lexVar() | |||
{ | |||
$match = NULL; | |||
if (preg_match('/\s*\}/A', $this->code, $match, NULL, $this->cursor)) { | |||
$this->cursor += strlen($match[0]); | |||
$lineno = $this->lineno; | |||
$this->lineno += substr_count($match[0], '\n'); | |||
$this->position = self::POSITION_DATA; | |||
return new Twig_Token(Twig_Token::VAR_END_TYPE, '', $lineno); | |||
} | |||
return $this->lexExpression(); | |||
} | |||
private function lexExpression() | |||
{ | |||
$match = NULL; | |||
// skip whitespace | |||
while (preg_match('/\s+/A', $this->code, $match, NULL, | |||
$this->cursor)) { | |||
$this->cursor += strlen($match[0]); | |||
$this->lineno += substr_count($match[0], '\n'); | |||
} | |||
// sanity check | |||
if ($this->cursor >= $this->end) | |||
throw new Twig_SyntaxError('unexpected end of stream', | |||
$this->lineno, $this->filename); | |||
// first parse operators | |||
if (preg_match(self::REGEX_OPERATOR, $this->code, $match, NULL, | |||
$this->cursor)) { | |||
$this->cursor += strlen($match[0]); | |||
return Twig_Token::Operator($match[0], $this->lineno); | |||
} | |||
// now names | |||
if (preg_match(self::REGEX_NAME, $this->code, $match, NULL, | |||
$this->cursor)) { | |||
$this->cursor += strlen($match[0]); | |||
return Twig_Token::Name($match[0], $this->lineno); | |||
} | |||
// then numbers | |||
else if (preg_match(self::REGEX_NUMBER, $this->code, $match, | |||
NULL, $this->cursor)) { | |||
$this->cursor += strlen($match[0]); | |||
$value = (float)$match[0]; | |||
if ((int)$value === $value) | |||
$value = (int)$value; | |||
return Twig_Token::Number($value, $this->lineno); | |||
} | |||
// and finally strings | |||
else if (preg_match(self::REGEX_STRING, $this->code, $match, | |||
NULL, $this->cursor)) { | |||
$this->cursor += strlen($match[0]); | |||
$this->lineno += substr_count($match[0], '\n'); | |||
$value = stripcslashes(substr($match[0], 1, strlen($match[0]) - 2)); | |||
return Twig_Token::String($value, $this->lineno); | |||
} | |||
// unlexable | |||
throw new Twig_SyntaxError("Unexpected character '" . | |||
$this->code[$this->cursor] . "'.", | |||
$this->lineno, $this->filename); | |||
} | |||
} | |||
/** | |||
* Wrapper around a lexer for simplified token access. | |||
*/ | |||
class Twig_TokenStream | |||
{ | |||
private $pushed; | |||
private $lexer; | |||
public $filename; | |||
public $current; | |||
public $eof; | |||
public function __construct($lexer, $filename) | |||
{ | |||
$this->pushed = array(); | |||
$this->lexer = $lexer; | |||
$this->filename = $filename; | |||
$this->next(); | |||
} | |||
public function push($token) | |||
{ | |||
$this->pushed[] = $token; | |||
} | |||
/** | |||
* set the pointer to the next token and return the old one. | |||
*/ | |||
public function next() | |||
{ | |||
if (!empty($this->pushed)) | |||
$token = array_shift($this->pushed); | |||
else | |||
$token = $this->lexer->nextToken(); | |||
$old = $this->current; | |||
$this->current = $token; | |||
$this->eof = $token->type === Twig_Token::EOF_TYPE; | |||
return $old; | |||
} | |||
/** | |||
* Look at the next token. | |||
*/ | |||
public function look() | |||
{ | |||
$old = $this->next(); | |||
$new = $this->current; | |||
$this->push($old); | |||
$this->push($new); | |||
return $new; | |||
} | |||
/** | |||
* Skip some tokens. | |||
*/ | |||
public function skip($times=1) | |||
{ | |||
for ($i = 0; $i < $times; ++$i) | |||
$this->next(); | |||
} | |||
/** | |||
* expect a token (like $token->test()) and return it or raise | |||
* a syntax error. | |||
*/ | |||
public function expect($primary, $secondary=NULL) | |||
{ | |||
$token = $this->current; | |||
if (!$token->test($primary, $secondary)) | |||
throw new Twig_SyntaxError('unexpected token', | |||
$this->current->lineno); | |||
$this->next(); | |||
return $token; | |||
} | |||
/** | |||
* Forward that call to the current token. | |||
*/ | |||
public function test($primary, $secondary=NULL) | |||
{ | |||
return $this->current->test($primary, $secondary); | |||
} | |||
} | |||
/** | |||
* Simple struct for tokens. | |||
*/ | |||
class Twig_Token | |||
{ | |||
public $type; | |||
public $value; | |||
public $lineno; | |||
const TEXT_TYPE = 0; | |||
const EOF_TYPE = -1; | |||
const BLOCK_START_TYPE = 1; | |||
const VAR_START_TYPE = 2; | |||
const BLOCK_END_TYPE = 3; | |||
const VAR_END_TYPE = 4; | |||
const NAME_TYPE = 5; | |||
const NUMBER_TYPE = 6; | |||
const STRING_TYPE = 7; | |||
const OPERATOR_TYPE = 8; | |||
public function __construct($type, $value, $lineno) | |||
{ | |||
$this->type = $type; | |||
$this->value = $value; | |||
$this->lineno = $lineno; | |||
} | |||
/** | |||
* Test the current token for a type. The first argument is the type | |||
* of the token (if not given Twig_Token::NAME_NAME), the second the | |||
* value of the token (if not given value is not checked). | |||
* the token value can be an array if multiple checks shoudl be | |||
* performed. | |||
*/ | |||
public function test($type, $values=NULL) | |||
{ | |||
if (is_null($values) && !is_int($type)) { | |||
$values = $type; | |||
$type = self::NAME_TYPE; | |||
} | |||
return ($this->type === $type) && ( | |||
is_null($values) || | |||
(is_array($values) && in_array($this->value, $values)) || | |||
$this->value == $values | |||
); | |||
} | |||
public static function Text($value, $lineno) | |||
{ | |||
return new Twig_Token(self::TEXT_TYPE, $value, $lineno); | |||
} | |||
public static function EOF($lineno) | |||
{ | |||
return new Twig_Token(self::EOF_TYPE, '', $lineno); | |||
} | |||
public static function Name($value, $lineno) | |||
{ | |||
return new Twig_Token(self::NAME_TYPE, $value, $lineno); | |||
} | |||
public static function Number($value, $lineno) | |||
{ | |||
return new Twig_Token(self::NUMBER_TYPE, $value, $lineno); | |||
} | |||
public static function String($value, $lineno) | |||
{ | |||
return new Twig_Token(self::STRING_TYPE, $value, $lineno); | |||
} | |||
public static function Operator($value, $lineno) | |||
{ | |||
return new Twig_Token(self::OPERATOR_TYPE, $value, $lineno); | |||
} | |||
} |
@@ -0,0 +1,603 @@ | |||
<?php | |||
/** | |||
* Twig::Parser | |||
* ~~~~~~~~~~~~ | |||
* | |||
* This module implements the Twig parser. | |||
* | |||
* :copyright: 2008 by Armin Ronacher. | |||
* :license: BSD. | |||
*/ | |||
function twig_parse($source, $filename=NULL) | |||
{ | |||
$stream = twig_tokenize($source, $filename); | |||
$parser = new Twig_Parser($stream); | |||
return $parser->parse(); | |||
} | |||
class Twig_Parser | |||
{ | |||
public $stream; | |||
public $blocks; | |||
public $extends; | |||
public $current_block; | |||
private $handlers; | |||
public function __construct($stream) | |||
{ | |||
$this->stream = $stream; | |||
$this->extends = NULL; | |||
$this->blocks = array(); | |||
$this->current_block = NULL; | |||
$this->handlers = array( | |||
'for' => array($this, 'parseForLoop'), | |||
'if' => array($this, 'parseIfCondition'), | |||
'extends' => array($this, 'parseExtends'), | |||
'include' => array($this, 'parseInclude'), | |||
'block' => array($this, 'parseBlock'), | |||
'super' => array($this, 'parseSuper'), | |||
# Chyrp specific extensions | |||
'url' => array($this, 'parseURL'), | |||
'admin' => array($this, 'parseAdminURL'), | |||
'paginate' => array($this, 'parsePaginate') | |||
); | |||
} | |||
public function parseForLoop($token) | |||
{ | |||
$lineno = $token->lineno; | |||
list($is_multitarget, $item) = $this->parseAssignmentExpression(); | |||
$this->stream->expect('in'); | |||
$seq = $this->parseExpression(); | |||
$this->stream->expect(Twig_Token::BLOCK_END_TYPE); | |||
$body = $this->subparse(array($this, 'decideForFork')); | |||
if ($this->stream->next()->value == 'else') { | |||
$this->stream->expect(Twig_Token::BLOCK_END_TYPE); | |||
$else = $this->subparse(array($this, 'decideForEnd'), true); | |||
} | |||
else | |||
$else = NULL; | |||
$this->stream->expect(Twig_Token::BLOCK_END_TYPE); | |||
return new Twig_ForLoop($is_multitarget, $item, $seq, $body, $else, | |||
$lineno); | |||
} | |||
public function parsePaginate($token) | |||
{ | |||
$lineno = $token->lineno; | |||
$per_page = $this->parseExpression(); | |||
$as = $this->parseExpression(); | |||
$this->stream->expect('in'); | |||
$loop = $this->parseExpression(); | |||
$this->stream->expect('as'); | |||
$item = $this->parseExpression(); | |||
$this->stream->expect(Twig_Token::BLOCK_END_TYPE); | |||
$body = $this->subparse(array($this, 'decidePaginateFork')); | |||
if ($this->stream->next()->value == 'else') { | |||
$this->stream->expect(Twig_Token::BLOCK_END_TYPE); | |||
$else = $this->subparse(array($this, 'decidePaginateEnd'), true); | |||
} | |||
else | |||
$else = NULL; | |||
$this->stream->expect(Twig_Token::BLOCK_END_TYPE); | |||
return new Twig_PaginateLoop($item, $per_page, | |||
$loop, $as, $body, $else, $lineno); | |||
} | |||
public function decideForFork($token) | |||
{ | |||
return $token->test(array('else', 'endfor')); | |||
} | |||
public function decideForEnd($token) | |||
{ | |||
return $token->test('endfor'); | |||
} | |||
public function decidePaginateFork($token) | |||
{ | |||
return $token->test(array('else', 'endpaginate')); | |||
} | |||
public function decidePaginateEnd($token) | |||
{ | |||
return $token->test('endpaginate'); | |||
} | |||
public function parseIfCondition($token) | |||
{ | |||
$lineno = $token->lineno; | |||
$expr = $this->parseExpression(); | |||
$this->stream->expect(Twig_Token::BLOCK_END_TYPE); | |||
$body = $this->subparse(array($this, 'decideIfFork')); | |||
$tests = array(array($expr, $body)); | |||
$else = NULL; | |||
$end = false; | |||
while (!$end) | |||
switch ($this->stream->next()->value) { | |||
case 'else': | |||
$this->stream->expect(Twig_Token::BLOCK_END_TYPE); | |||
$else = $this->subparse(array($this, 'decideIfEnd')); | |||
break; | |||
case 'elseif': | |||
$expr = $this->parseExpression(); | |||
$this->stream->expect(Twig_Token::BLOCK_END_TYPE); | |||
$body = $this->subparse(array($this, 'decideIfFork')); | |||
$tests[] = array($expr, $body); | |||
break; | |||
case 'endif': | |||
$end = true; | |||
break; | |||
} | |||
$this->stream->expect(Twig_Token::BLOCK_END_TYPE); | |||
return new Twig_IfCondition($tests, $else, $lineno); | |||
} | |||
public function decideIfFork($token) | |||
{ | |||
return $token->test(array('elseif', 'else', 'endif')); | |||
} | |||
public function decideIfEnd($token) | |||
{ | |||
return $token->test(array('endif')); | |||
} | |||
public function parseBlock($token) | |||
{ | |||
$lineno = $token->lineno; | |||
$name = $this->stream->expect(Twig_Token::NAME_TYPE)->value; | |||
if (isset($this->blocks[$name])) | |||
throw new Twig_SyntaxError("block '$name' defined twice.", | |||
$lineno); | |||
$this->current_block = $name; | |||
$this->stream->expect(Twig_Token::BLOCK_END_TYPE); | |||
$body = $this->subparse(array($this, 'decideBlockEnd'), true); | |||
$this->stream->expect(Twig_Token::BLOCK_END_TYPE); | |||
$block = new Twig_Block($name, $body, $lineno); | |||
$this->blocks[$name] = $block; | |||
$this->current_block = NULL; | |||
return new Twig_BlockReference($name, $lineno); | |||
} | |||
public function decideBlockEnd($token) | |||
{ | |||
return $token->test('endblock'); | |||
} | |||
public function parseExtends($token) | |||
{ | |||
$lineno = $token->lineno; | |||
if (!is_null($this->extends)) | |||
throw new Twig_SyntaxError('multiple extend tags', $lineno); | |||
$this->extends = $this->stream->expect(Twig_Token::STRING_TYPE)->value; | |||
$this->stream->expect(Twig_Token::BLOCK_END_TYPE); | |||
return NULL; | |||
} | |||
public function parseInclude($token) | |||
{ | |||
$expr = $this->parseExpression(); | |||
$this->stream->expect(Twig_Token::BLOCK_END_TYPE); | |||
return new Twig_Include($expr, $token->lineno); | |||
} | |||
public function parseSuper($token) | |||
{ | |||
if (is_null($this->current_block)) | |||
throw new Twig_SyntaxError('super outside block', $token->lineno); | |||
$this->stream->expect(Twig_Token::BLOCK_END_TYPE); | |||
return new Twig_Super($this->current_block, $token->lineno); | |||
} | |||
public function parseURL($token) | |||
{ | |||
$expr = $this->parseExpression(); | |||
if ($this->stream->test("in")) { | |||
$this->parseExpression(); | |||
$cont = $this->parseExpression(); | |||
} else | |||
$cont = null; | |||
$this->stream->expect(Twig_Token::BLOCK_END_TYPE); | |||
return new Twig_URL($expr, $cont, $token->lineno); | |||
} | |||
public function parseAdminURL($token) | |||
{ | |||
$expr = $this->parseExpression(); | |||
$this->stream->expect(Twig_Token::BLOCK_END_TYPE); | |||
return new Twig_AdminURL($expr, $token->lineno); | |||
} | |||
public function parseExpression() | |||
{ | |||
return $this->parseConditionalExpression(); | |||
} | |||
public function parseConditionalExpression() | |||
{ | |||
$lineno = $this->stream->current->lineno; | |||
$expr1 = $this->parseOrExpression(); | |||
while ($this->stream->test(Twig_Token::OPERATOR_TYPE, '?')) { | |||
$this->stream->next(); | |||
$expr2 = $this->parseOrExpression(); | |||
$this->stream->expect(Twig_Token::OPERATOR_TYPE, ':'); | |||
$expr3 = $this->parseConditionalExpression(); | |||
$expr1 = new Twig_ConditionalExpression($expr1, $expr2, $expr3, | |||
$this->lineno); | |||
$lineno = $this->stream->current->lineno; | |||
} | |||
return $expr1; | |||
} | |||
public function parseOrExpression() | |||
{ | |||
$lineno = $this->stream->current->lineno; | |||
$left = $this->parseAndExpression(); | |||
while ($this->stream->test('or')) { | |||
$this->stream->next(); | |||
$right = $this->parseAndExpression(); | |||
$left = new Twig_OrExpression($left, $right, $lineno); | |||
$lineno = $this->stream->current->lineno; | |||
} | |||
return $left; | |||
} | |||
public function parseAndExpression() | |||
{ | |||
$lineno = $this->stream->current->lineno; | |||
$left = $this->parseCompareExpression(); | |||
while ($this->stream->test('and')) { | |||
$this->stream->next(); | |||
$right = $this->parseCompareExpression(); | |||
$left = new Twig_AndExpression($left, $right, $lineno); | |||
$lineno = $this->stream->current->lineno; | |||
} | |||
return $left; | |||
} | |||
public function parseCompareExpression() | |||
{ | |||
static $operators = array('==', '!=', '<', '>', '>=', '<='); | |||
$lineno = $this->stream->current->lineno; | |||
$expr = $this->parseAddExpression(); | |||
$ops = array(); | |||
while ($this->stream->test(Twig_Token::OPERATOR_TYPE, $operators)) | |||
$ops[] = array($this->stream->next()->value, | |||
$this->parseAddExpression()); | |||
if (empty($ops)) | |||
return $expr; | |||
return new Twig_CompareExpression($expr, $ops, $lineno); | |||
} | |||
public function parseAddExpression() | |||
{ | |||
$lineno = $this->stream->current->lineno; | |||
$left = $this->parseSubExpression(); | |||
while ($this->stream->test(Twig_Token::OPERATOR_TYPE, '+')) { | |||
$this->stream->next(); | |||
$right = $this->parseSubExpression(); | |||
$left = new Twig_AddExpression($left, $right, $lineno); | |||
$lineno = $this->stream->current->lineno; | |||
} | |||
return $left; | |||
} | |||
public function parseSubExpression() | |||
{ | |||
$lineno = $this->stream->current->lineno; | |||
$left = $this->parseConcatExpression(); | |||
while ($this->stream->test(Twig_Token::OPERATOR_TYPE, '-')) { | |||
$this->stream->next(); | |||
$right = $this->parseConcatExpression(); | |||
$left = new Twig_SubExpression($left, $right, $lineno); | |||
$lineno = $this->stream->current->lineno; | |||
} | |||
return $left; | |||
} | |||
public function parseConcatExpression() | |||
{ | |||
$lineno = $this->stream->current->lineno; | |||
$left = $this->parseMulExpression(); | |||
while ($this->stream->test(Twig_Token::OPERATOR_TYPE, '~')) { | |||
$this->stream->next(); | |||
$right = $this->parseMulExpression(); | |||
$left = new Twig_ConcatExpression($left, $right, $lineno); | |||
$lineno = $this->stream->current->lineno; | |||
} | |||
return $left; | |||
} | |||
public function parseMulExpression() | |||
{ | |||
$lineno = $this->stream->current->lineno; | |||
$left = $this->parseDivExpression(); | |||
while ($this->stream->test(Twig_Token::OPERATOR_TYPE, '*')) { | |||
$this->stream->next(); | |||
$right = $this->parseDivExpression(); | |||
$left = new Twig_MulExpression($left, $right, $lineno); | |||
$lineno = $this->stream->current->lineno; | |||
} | |||
return $left; | |||
} | |||
public function parseDivExpression() | |||
{ | |||
$lineno = $this->stream->current->lineno; | |||
$left = $this->parseModExpression(); | |||
while ($this->stream->test(Twig_Token::OPERATOR_TYPE, '/')) { | |||
$this->stream->next(); | |||
$right = $this->parseModExpression(); | |||
$left = new Twig_DivExpression($left, $right, $lineno); | |||
$lineno = $this->stream->current->lineno; | |||
} | |||
return $left; | |||
} | |||
public function parseModExpression() | |||
{ | |||
$lineno = $this->stream->current->lineno; | |||
$left = $this->parseUnaryExpression(); | |||
while ($this->stream->test(Twig_Token::OPERATOR_TYPE, '%')) { | |||
$this->stream->next(); | |||
$right = $this->parseUnaryExpression(); | |||
$left = new Twig_ModExpression($left, $right, $lineno); | |||
$lineno = $this->stream->current->lineno; | |||
} | |||
return $left; | |||
} | |||
public function parseUnaryExpression() | |||
{ | |||
if ($this->stream->test('not')) | |||
return $this->parseNotExpression(); | |||
if ($this->stream->current->type == Twig_Token::OPERATOR_TYPE) { | |||
switch ($this->stream->current->value) { | |||
case '-': | |||
return $this->parseNegExpression(); | |||
case '+': | |||
return $this->parsePosExpression(); | |||
} | |||
} | |||
return $this->parsePrimaryExpression(); | |||
} | |||
public function parseNotExpression() | |||
{ | |||
$token = $this->stream->next(); | |||
$node = $this->parseUnaryExpression(); | |||
return new Twig_NotExpression($node, $token->lineno); | |||
} | |||
public function parseNegExpression() | |||
{ | |||
$token = $this->stream->next(); | |||
$node = $this->parseUnaryExpression(); | |||
return new Twig_NegExpression($node, $token->lineno); | |||
} | |||
public function parsePosExpression() | |||
{ | |||
$token = $this->stream->next(); | |||
$node = $this->parseUnaryExpression(); | |||
return new Twig_PosExpression($node, $token->lineno); | |||
} | |||
public function parsePrimaryExpression($assignment=false) | |||
{ | |||
$token = $this->stream->current; | |||
switch ($token->type) { | |||
case Twig_Token::NAME_TYPE: | |||
$this->stream->next(); | |||
switch ($token->value) { | |||
case 'true': | |||
$node = new Twig_Constant(true, $token->lineno); | |||
break; | |||
case 'false': | |||
$node = new Twig_Constant(false, $token->lineno); | |||
break; | |||
case 'none': | |||
$node = new Twig_Constant(NULL, $token->lineno); | |||
break; | |||
default: | |||
$cls = $assignment ? 'Twig_AssignNameExpression' | |||
: 'Twig_NameExpression'; | |||
$node = new $cls($token->value, $token->lineno); | |||
} | |||
break; | |||
case Twig_Token::NUMBER_TYPE: | |||
case Twig_Token::STRING_TYPE: | |||
$this->stream->next(); | |||
$node = new Twig_Constant($token->value, $token->lineno); | |||
break; | |||
default: | |||
if ($token->test(Twig_Token::OPERATOR_TYPE, '(')) { | |||
$this->stream->next(); | |||
$node = $this->parseExpression(); | |||
$this->stream->expect(Twig_Token::OPERATOR_TYPE, ')'); | |||
} | |||
else | |||
throw new Twig_SyntaxError('unexpected token', | |||
$token->lineno); | |||
} | |||
if (!$assignment) | |||
$node = $this->parsePostfixExpression($node); | |||
return $node; | |||
} | |||
public function parsePostfixExpression($node) | |||
{ | |||
$stop = false; | |||
while (!$stop && $this->stream->current->type == | |||
Twig_Token::OPERATOR_TYPE) | |||
switch ($this->stream->current->value) { | |||
case '.': | |||
case '[': | |||
$node = $this->parseSubscriptExpression($node); | |||
break; | |||
case '|': | |||
$node = $this->parseFilterExpression($node); | |||
break; | |||
default: | |||
$stop = true; | |||
break; | |||
} | |||
return $node; | |||
} | |||
public function parseSubscriptExpression($node) | |||
{ | |||
$token = $this->stream->next(); | |||
$lineno = $token->lineno; | |||
if ($token->value == '.') { | |||
$token = $this->stream->next(); | |||
if ($token->type == Twig_Token::NAME_TYPE || | |||
$token->type == Twig_Token::NUMBER_TYPE) | |||
$arg = new Twig_Constant($token->value, $lineno); | |||
else | |||
throw new Twig_SyntaxError('expected name or number', | |||
$lineno); | |||
} | |||
else { | |||
$arg = $this->parseExpression(); | |||
$this->stream->expect(Twig_Token::OPERATOR_TYPE, ']'); | |||
} | |||
if (!$this->stream->test(Twig_Token::OPERATOR_TYPE, '(')) | |||
return new Twig_GetAttrExpression($node, $arg, $lineno, $token->value); | |||
/* sounds like something wants to call a member with some | |||
arguments. Let's parse the parameters */ | |||
$this->stream->next(); | |||
$arguments = array(); | |||
while (!$this->stream->test(Twig_Token::OPERATOR_TYPE, ')')) { | |||
if (count($arguments)) | |||
$this->stream->expect(Twig_Token::OPERATOR_TYPE, ','); | |||
$arguments[] = $this->parseExpression(); | |||
} | |||
$this->stream->expect(Twig_Token::OPERATOR_TYPE, ')'); | |||
return new Twig_MethodCallExpression($node, $arg, $arguments, $lineno); | |||
} | |||
public function parseFilterExpression($node) | |||
{ | |||
$lineno = $this->stream->current->lineno; | |||
$filters = array(); | |||
while ($this->stream->test(Twig_Token::OPERATOR_TYPE, '|')) { | |||
$this->stream->next(); | |||
$token = $this->stream->expect(Twig_Token::NAME_TYPE); | |||
$args = array(); | |||
if ($this->stream->test( | |||
Twig_Token::OPERATOR_TYPE, '(')) { | |||
$this->stream->next(); | |||
while (!$this->stream->test( | |||
Twig_Token::OPERATOR_TYPE, ')')) { | |||
if (!empty($args)) | |||
$this->stream->expect( | |||
Twig_Token::OPERATOR_TYPE, ','); | |||
$args[] = $this->parseExpression(); | |||
} | |||
$this->stream->expect(Twig_Token::OPERATOR_TYPE, ')'); | |||
} | |||
$filters[] = array($token->value, $args); | |||
} | |||
return new Twig_FilterExpression($node, $filters, $lineno); | |||
} | |||
public function parseAssignmentExpression() | |||
{ | |||
$lineno = $this->stream->current->lineno; | |||
$targets = array(); | |||
$is_multitarget = false; | |||
while (true) { | |||
if (!empty($targets)) | |||
$this->stream->expect(Twig_Token::OPERATOR_TYPE, ','); | |||
if ($this->stream->test(Twig_Token::OPERATOR_TYPE, ')') || | |||
$this->stream->test(Twig_Token::VAR_END_TYPE) || | |||
$this->stream->test(Twig_Token::BLOCK_END_TYPE) || | |||
$this->stream->test('in')) | |||
break; | |||
$targets[] = $this->parsePrimaryExpression(true); | |||
if (!$this->stream->test(Twig_Token::OPERATOR_TYPE, ',')) | |||
break; | |||
$is_multitarget = true; | |||
} | |||
if (!$is_multitarget && count($targets) == 1) | |||
return array(false, $targets[0]); | |||
return array(true, $targets); | |||
} | |||
public function subparse($test, $drop_needle=false) | |||
{ | |||
$lineno = $this->stream->current->lineno; | |||
$rv = array(); | |||
while (!$this->stream->eof) { | |||
switch ($this->stream->current->type) { | |||
case Twig_Token::TEXT_TYPE: | |||
$token = $this->stream->next(); | |||
$rv[] = new Twig_Text($token->value, $token->lineno); | |||
break; | |||
case Twig_Token::VAR_START_TYPE: | |||
$token = $this->stream->next(); | |||
$expr = $this->parseExpression(); | |||
$this->stream->expect(Twig_Token::VAR_END_TYPE); | |||
$rv[] = new Twig_Print($expr, $token->lineno); | |||
break; | |||
case Twig_Token::BLOCK_START_TYPE: | |||
$this->stream->next(); | |||
$token = $this->stream->current; | |||
if ($token->type !== Twig_Token::NAME_TYPE) | |||
throw new Twig_SyntaxError('expected directive', | |||
$token->lineno); | |||
if (!is_null($test) && call_user_func($test, $token)) { | |||
if ($drop_needle) | |||
$this->stream->next(); | |||
return Twig_NodeList::fromArray($rv, $lineno); | |||
} | |||
if (!isset($this->handlers[$token->value])) | |||
throw new Twig_SyntaxError('unknown directive', | |||
$token->lineno); | |||
$this->stream->next(); | |||
$node = call_user_func($this->handlers[$token->value], | |||
$token); | |||
if (!is_null($node)) | |||
$rv[] = $node; | |||
break; | |||
default: | |||
assert(false, 'Lexer or parser ended up in ' . | |||
'unsupported state.'); | |||
} | |||
} | |||
return Twig_NodeList::fromArray($rv, $lineno); | |||
} | |||
public function parse() | |||
{ | |||
try { | |||
$body = $this->subparse(NULL); | |||
} | |||
catch (Twig_SyntaxError $e) { | |||
if (is_null($e->filename)) | |||
$e->filename = $this->stream->filename; | |||
throw $e; | |||
} | |||
if (!is_null($this->extends)) | |||
foreach ($this->blocks as $block) | |||
$block->parent = $this->extends; | |||
return new Twig_Module($body, $this->extends, $this->blocks, | |||
$this->stream->filename); | |||
} | |||
} |
@@ -0,0 +1,485 @@ | |||
<?php | |||
/** | |||
* Twig::Runtime | |||
* ~~~~~~~~~~~~~ | |||
* | |||
* The twig runtime environment. | |||
* | |||
* :copyright: 2008 by Armin Ronacher. | |||
* :license: BSD. | |||
*/ | |||
$twig_filters = array( | |||
// formatting filters | |||
'date' => 'twig_date_format_filter', | |||
'strftime' => 'twig_strftime_format_filter', | |||
'strtotime' => 'strtotime', | |||
'numberformat' => 'number_format', | |||
'moneyformat' => 'money_format', | |||
'filesizeformat' => 'twig_filesize_format_filter', | |||
'format' => 'sprintf', | |||
'relative' => 'relative_time', | |||
// numbers | |||
'even' => 'twig_is_even_filter', | |||
'odd' => 'twig_is_odd_filter', | |||
// escaping and encoding | |||
'escape' => 'twig_escape_filter', | |||
'e' => 'twig_escape_filter', | |||
'urlencode' => 'twig_urlencode_filter', | |||
'quotes' => 'twig_quotes_filter', | |||
'slashes' => 'addslashes', | |||
// string filters | |||
'title' => 'twig_title_string_filter', | |||
'capitalize' => 'twig_capitalize_string_filter', | |||
'upper' => 'strtoupper', | |||
'lower' => 'strtolower', | |||
'strip' => 'trim', | |||
'rstrip' => 'rtrim', | |||
'lstrip' => 'ltrim', | |||
'translate' => 'twig_translate_string_filter', | |||
'translate_plural' => 'twig_translate_plural_string_filter', | |||
'normalize' => 'normalize', | |||
'truncate' => 'twig_truncate_filter', | |||
'excerpt' => 'twig_excerpt_filter', | |||
'replace' => 'twig_replace_filter', | |||
'match' => 'twig_match_filter', | |||
'contains' => 'substr_count', | |||
'linebreaks' => 'nl2br', | |||
'camelize' => 'camelize', | |||
'strip_tags' => 'strip_tags', | |||
'pluralize' => 'twig_pluralize_string_filter', | |||
'depluralize' => 'twig_depluralize_string_filter', | |||
'sanitize' => 'sanitize', | |||
'repeat' => 'str_repeat', | |||
// array helpers | |||
'join' => 'twig_join_filter', | |||
'split' => 'twig_split_filter', | |||
'first' => 'twig_first_filter', | |||
'offset' => 'twig_offset_filter', | |||
'last' => 'twig_last_filter', | |||
'reverse' => 'array_reverse', | |||
'length' => 'twig_length_filter', | |||
'count' => 'count', | |||
'sort' => 'twig_sort_filter', | |||
// iteration and runtime | |||
'default' => 'twig_default_filter', | |||
'keys' => 'array_keys', | |||
'items' => 'twig_get_array_items_filter', | |||
// debugging | |||
'inspect' => 'twig_inspect_filter', | |||
'uploaded' => 'uploaded', | |||
'fallback' => 'oneof', | |||
'selected' => 'twig_selected_filter', | |||
'checked' => 'twig_checked_filter', | |||
'option_selected' => 'twig_option_selected_filter' | |||
); | |||
class Twig_LoopContextIterator implements Iterator | |||
{ | |||
public $context; | |||
public $seq; | |||
public $idx; | |||
public $length; | |||
public $parent; | |||
public function __construct(&$context, $seq, $parent) | |||
{ | |||
$this->context = $context; | |||
$this->seq = $seq; | |||
$this->idx = 0; | |||
$this->length = count($seq); | |||
$this->parent = $parent; | |||
} | |||
public function rewind() {} | |||
public function key() {} | |||
public function valid() | |||
{ | |||
return $this->idx < $this->length; | |||
} | |||
public function next() | |||
{ | |||
$this->idx++; | |||
} | |||
public function current() | |||
{ | |||
return $this; | |||
} | |||
} | |||
function unretarded_array_unshift(&$arr, &$val) { | |||
$arr = array_merge(array(&$val), $arr); | |||
} | |||
/** | |||
* This is called like an ordinary filter just with the name of the filter | |||
* as first argument. Currently we just raise an exception here but it | |||
* would make sense in the future to allow dynamic filter lookup for plugins | |||
* or something like that. | |||
*/ | |||
function twig_missing_filter($name) | |||
{ | |||
$args = func_get_args(); | |||
array_shift($args); | |||
$text = $args[0]; | |||
array_shift($args); | |||
array_unshift($args, $name); | |||
unretarded_array_unshift($args, $text); | |||
$trigger = Trigger::current(); | |||
if ($trigger->exists($name)) | |||
return call_user_func_array(array($trigger, "filter"), $args); | |||
return $text; | |||
} | |||
function twig_get_attribute($obj, $item, $function = true) | |||
{ | |||
if (is_array($obj) && isset($obj[$item])) | |||
return $obj[$item]; | |||
if (!is_object($obj)) | |||
return NULL; | |||
if ($function and method_exists($obj, $item)) | |||
return call_user_func(array($obj, $item)); | |||
if (property_exists($obj, $item)) { | |||
$tmp = get_object_vars($obj); | |||
return $tmp[$item]; | |||
} | |||
$method = 'get' . ucfirst($item); | |||
if ($function and method_exists($obj, $method)) | |||
return call_user_func(array($obj, $method)); | |||
if (is_object($obj)) { | |||
@$obj->$item; # Funky way of allowing __get to activate before returning the value. | |||
return @$obj->$item; | |||
} | |||
return NULL; | |||
} | |||
function twig_paginate(&$context, $as, $over, $per_page) | |||
{ | |||
$name = (in_array("page", Paginator::$names)) ? $as."_page" : "page" ; | |||
if (count($over) == 2 and $over[0] instanceof Model and is_string($over[1])) | |||
$context[$as] = $context["::parent"][$as] = new Paginator($over[0]->__getPlaceholders($over[1]), $per_page, $name); | |||
else | |||
$context[$as] = $context["::parent"][$as] = new Paginator($over, $per_page, $name); | |||
} | |||
function twig_iterate(&$context, $seq) | |||
{ | |||
$parent = isset($context['loop']) ? $context['loop'] : null; | |||
$seq = twig_make_array($seq); | |||
$context['loop'] = array('parent' => $parent, 'iterated' => false); | |||
return new Twig_LoopContextIterator($context, $seq, $parent); | |||
} | |||
function twig_set_loop_context(&$context, $iterator, $target) | |||
{ | |||
$context[$target] = $iterator->seq[$iterator->idx]; | |||
$context['loop'] = twig_make_loop_context($iterator); | |||
} | |||
function twig_set_loop_context_multitarget(&$context, $iterator, $targets) | |||
{ | |||
$values = $iterator->seq[$iterator->idx]; | |||
if (!is_array($values)) | |||
$values = array($values); | |||
$idx = 0; | |||
foreach ($values as $value) { | |||
if (!isset($targets[$idx])) | |||
break; | |||
$context[$targets[$idx++]] = $value; | |||
} | |||
$context['loop'] = twig_make_loop_context($iterator); | |||
} | |||
function twig_make_loop_context($iterator) | |||
{ | |||
return array( | |||
'parent' => $iterator->parent, | |||
'length' => $iterator->length, | |||
'index0' => $iterator->idx, | |||
'index' => $iterator->idx + 1, | |||
'revindex0' => $iterator->length - $iterator->idx - 1, | |||
'revindex '=> $iterator->length - $iterator->idx, | |||
'first' => $iterator->idx == 0, | |||
'last' => $iterator->idx + 1 == $iterator->length, | |||
'iterated' => true | |||
); | |||
} | |||
function twig_make_array($object) | |||
{ | |||
if (is_array($object)) | |||
return array_values($object); | |||
elseif (is_object($object)) { | |||
$result = array(); | |||
foreach ($object as $value) | |||
$result[] = $value; | |||
return $result; | |||
} | |||
return array(); | |||
} | |||
function twig_date_format_filter($timestamp, $format='F j, Y, G:i') | |||
{ | |||
return when($format, $timestamp); | |||
} | |||
function twig_strftime_format_filter($timestamp, $format='%x %X') | |||
{ | |||
return when($format, $timestamp, true); | |||
} | |||
function twig_urlencode_filter($url, $raw=false) | |||
{ | |||
if ($raw) | |||
return rawurlencode($url); | |||
return urlencode($url); | |||
} | |||
function twig_join_filter($value, $glue='') | |||
{ | |||
return implode($glue, (array) $value); | |||
} | |||
function twig_default_filter($value, $default='') | |||
{ | |||
return is_null($value) ? $default : $value; | |||
} | |||
function twig_get_array_items_filter($array) | |||
{ | |||
$result = array(); | |||
foreach ($array as $key => $value) | |||
$result[] = array($key, $value); | |||
return $result; | |||
} | |||
function twig_filesize_format_filter($value) | |||
{ | |||
$value = max(0, (int)$value); | |||
$places = strlen($value); | |||
if ($places <= 9 && $places >= 7) { | |||
$value = number_format($value / 1048576, 1); | |||
return "$value MB"; | |||
} | |||
if ($places >= 10) { | |||
$value = number_format($value / 1073741824, 1); | |||
return "$value GB"; | |||
} | |||
$value = number_format($value / 1024, 1); | |||
return "$value KB"; | |||
} | |||
function twig_is_even_filter($value) | |||
{ | |||
return $value % 2 == 0; | |||
} | |||
function twig_is_odd_filter($value) | |||
{ | |||
return $value % 2 == 1; | |||
} | |||
function twig_replace_filter($str, $search, $replace, $regex = false) | |||
{ | |||
if ($regex) | |||
return preg_replace($search, $replace, $str); | |||
else | |||
return str_replace($search, $replace, $str); | |||
} | |||
function twig_match_filter($str, $match) | |||
{ | |||
return preg_match($match, $str); | |||
} | |||
// add multibyte extensions if possible | |||
if (function_exists('mb_get_info')) { | |||
function twig_upper_filter($string) | |||
{ | |||
$template = twig_get_current_template(); | |||
if (!is_null($template->charset)) | |||
return mb_strtoupper($string, $template->charset); | |||
return strtoupper($string); | |||
} | |||
function twig_lower_filter($string) | |||
{ | |||
$template = twig_get_current_template(); | |||
if (!is_null($template->charset)) | |||
return mb_strtolower($string, $template->charset); | |||
return strtolower($string); | |||
} | |||
function twig_title_string_filter($string) | |||
{ | |||
$template = twig_get_current_template(); | |||
if (is_null($template->charset)) | |||
return ucwords(strtolower($string)); | |||
return mb_convert_case($string, MB_CASE_TITLE, $template->charset); | |||
} | |||
function twig_capitalize_string_filter($string) | |||
{ | |||
$template = twig_get_current_template(); | |||
if (is_null($template->charset)) | |||
return ucfirst(strtolower($string)); | |||
return mb_strtoupper(mb_substr($string, 0, 1, $template->charset)) . | |||
mb_strtolower(mb_substr($string, 1, null, $template->charset)); | |||
} | |||
// override the builtins | |||
$twig_filters['upper'] = 'twig_upper_filter'; | |||
$twig_filters['lower'] = 'twig_lower_filter'; | |||
} | |||
// and byte fallback | |||
else { | |||
function twig_title_string_filter($string) | |||
{ | |||
return ucwords(strtolower($string)); | |||
} | |||
function twig_capitalize_string_filter($string) | |||
{ | |||
return ucfirst(strtolower($string)); | |||
} | |||
} | |||
function twig_translate_string_filter($string, $domain = "theme") { | |||
$domain = ($domain == "theme" and ADMIN) ? "chyrp" : $domain ; | |||
return __($string, $domain); | |||
} | |||
function twig_translate_plural_string_filter($single, $plural, $number, $domain = "theme") { | |||
$domain = ($domain == "theme" and ADMIN) ? "chyrp" : $domain ; | |||
return _p($single, $plural, $number, $domain); | |||
} | |||
function twig_inspect_filter($thing) { | |||
if (ini_get("xdebug.var_display_max_depth") == -1) | |||
return var_dump($thing); | |||
else | |||
return '<pre class="chyrp_inspect"><code>' . | |||
fix(var_export($thing, true)) . | |||
'</code></pre>'; | |||
} | |||
function twig_split_filter($string, $cut = " ") { | |||
return explode($cut, $string); | |||
} | |||
function twig_first_filter($array) { | |||
foreach ($array as $key => &$val) | |||
return $val; # Return the first one. | |||
return false; | |||
} | |||
function twig_last_filter($array) { | |||
return $array[count($array) - 1]; | |||
} | |||
function twig_offset_filter($array, $offset = 0) { | |||
return $array[$offset]; | |||
} | |||
function twig_selected_filter($foo) { | |||
$try = func_get_args(); | |||
array_shift($try); | |||
$just_class = (end($try) === true); | |||
if ($just_class) | |||
array_pop($try); | |||
if (is_array($try[0])) { | |||
foreach ($try as $index => $it) | |||
if ($index) | |||
$try[0][] = $it; | |||
$try = $try[0]; | |||
} | |||
if (in_array($foo, $try)) | |||
return ($just_class) ? " selected" : ' class="selected"' ; | |||
} | |||
function twig_checked_filter($foo) { | |||
if ($foo) | |||
return ' checked="checked"'; | |||
} | |||
function twig_option_selected_filter($foo) { | |||
$try = func_get_args(); | |||
array_shift($try); | |||
if (in_array($foo, $try)) | |||
return ' selected="selected"'; | |||
} | |||
function twig_pluralize_string_filter($string, $number = null) { | |||
if ($number and $number == 1) | |||
return $string; | |||
else | |||
return pluralize($string); | |||
} | |||
function twig_depluralize_string_filter($string) { | |||
return depluralize($string); | |||
} | |||
function twig_quotes_filter($string) { | |||
return str_replace(array('"', "'"), array('\"', "\\'"), $string); | |||
} | |||
function twig_length_filter($thing) { | |||
if (is_string($thing)) | |||
return strlen($thing); | |||
else | |||
return count($thing); | |||
} | |||
function twig_escape_filter($string, $quotes = true, $decode = true) { | |||
if (!is_string($string)) # Certain post attributes might be parsed from YAML to an array, | |||
return $string; # in which case the module provides a value. However, the attr | |||
# is still passed to the "fallback" and "fix" filters when editing. | |||
$safe = fix($string, $quotes); | |||
return $decode ? preg_replace("/&(#?[A-Za-z0-9]+);/", "&\\1;", $safe) : $safe ; | |||
} | |||
function twig_truncate_filter($text, $length = 100, $ending = "...", $exact = false, $html = true) { | |||
return truncate($text, $length, $ending, $exact, $html); | |||
} | |||
function twig_excerpt_filter($text, $length = 200, $ending = "...", $exact = false, $html = true) { | |||
$paragraphs = preg_split("/(\r?\n\r?\n|\r\r)/", $text); | |||
if (count($paragraphs) > 1) | |||
return $paragraphs[0]; | |||
else | |||
return truncate($text, $length, $ending, $exact, $html); | |||
} | |||
function twig_sort_filter($array) { | |||
asort($array); | |||
return $array; | |||
} |
@@ -0,0 +1,807 @@ | |||
<?xml version="1.0" encoding="utf-8" ?> | |||
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> | |||
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en"> | |||
<head> | |||
<meta http-equiv="Content-Type" content="application/xhtml+xml; charset=utf-8" /> | |||
<meta name="generator" content="Docutils 0.4: http://docutils.sourceforge.net/" /> | |||
<title>Twig Template Engine Specification</title> | |||
<style type="text/css"> | |||
/* | |||
:Author: David Goodger | |||
:Contact: goodger@users.sourceforge.net | |||
:Date: $Date: 2005-12-18 01:56:14 +0100 (Sun, 18 Dec 2005) $ | |||
:Revision: $Revision: 4224 $ | |||
:Copyright: This stylesheet has been placed in the public domain. | |||
Default cascading style sheet for the HTML output of Docutils. | |||
See http://docutils.sf.net/docs/howto/html-stylesheets.html for how to | |||
customize this style sheet. | |||
*/ | |||
/* used to remove borders from tables and images */ | |||
.borderless, table.borderless td, table.borderless th { | |||
border: 0 } | |||
table.borderless td, table.borderless th { | |||
/* Override padding for "table.docutils td" with "! important". | |||
The right padding separates the table cells. */ | |||
padding: 0 0.5em 0 0 ! important } | |||
.first { | |||
/* Override more specific margin styles with "! important". */ | |||
margin-top: 0 ! important } | |||
.last, .with-subtitle { | |||
margin-bottom: 0 ! important } | |||
.hidden { | |||
display: none } | |||
a.toc-backref { | |||
text-decoration: none ; | |||
color: black } | |||
blockquote.epigraph { | |||
margin: 2em 5em ; } | |||
dl.docutils dd { | |||
margin-bottom: 0.5em } | |||
/* Uncomment (and remove this text!) to get bold-faced definition list terms | |||
dl.docutils dt { | |||
font-weight: bold } | |||
*/ | |||
div.abstract { | |||
margin: 2em 5em } | |||
div.abstract p.topic-title { | |||
font-weight: bold ; | |||
text-align: center } | |||
div.admonition, div.attention, div.caution, div.danger, div.error, | |||
div.hint, div.important, div.note, div.tip, div.warning { | |||
margin: 2em ; | |||
border: medium outset ; | |||
padding: 1em } | |||
div.admonition p.admonition-title, div.hint p.admonition-title, | |||
div.important p.admonition-title, div.note p.admonition-title, | |||
div.tip p.admonition-title { | |||
font-weight: bold ; | |||
font-family: sans-serif } | |||
div.attention p.admonition-title, div.caution p.admonition-title, | |||
div.danger p.admonition-title, div.error p.admonition-title, | |||
div.warning p.admonition-title { | |||
color: red ; | |||
font-weight: bold ; | |||
font-family: sans-serif } | |||
/* Uncomment (and remove this text!) to get reduced vertical space in | |||
compound paragraphs. | |||
div.compound .compound-first, div.compound .compound-middle { | |||
margin-bottom: 0.5em } | |||
div.compound .compound-last, div.compound .compound-middle { | |||
margin-top: 0.5em } | |||
*/ | |||
div.dedication { | |||
margin: 2em 5em ; | |||
text-align: center ; | |||
font-style: italic } | |||
div.dedication p.topic-title { | |||
font-weight: bold ; | |||
font-style: normal } | |||
div.figure { | |||
margin-left: 2em ; | |||
margin-right: 2em } | |||
div.footer, div.header { | |||
clear: both; | |||
font-size: smaller } | |||
div.line-block { | |||
display: block ; | |||
margin-top: 1em ; | |||
margin-bottom: 1em } | |||
div.line-block div.line-block { | |||
margin-top: 0 ; | |||
margin-bottom: 0 ; | |||
margin-left: 1.5em } | |||
div.sidebar { | |||
margin-left: 1em ; | |||
border: medium outset ; | |||
padding: 1em ; | |||
background-color: #ffffee ; | |||
width: 40% ; | |||
float: right ; | |||
clear: right } | |||
div.sidebar p.rubric { | |||
font-family: sans-serif ; | |||
font-size: medium } | |||
div.system-messages { | |||
margin: 5em } | |||
div.system-messages h1 { | |||
color: red } | |||
div.system-message { | |||
border: medium outset ; | |||
padding: 1em } | |||
div.system-message p.system-message-title { | |||
color: red ; | |||
font-weight: bold } | |||
div.topic { | |||
margin: 2em } | |||
h1.section-subtitle, h2.section-subtitle, h3.section-subtitle, | |||
h4.section-subtitle, h5.section-subtitle, h6.section-subtitle { | |||
margin-top: 0.4em } | |||
h1.title { | |||
text-align: center } | |||
h2.subtitle { | |||
text-align: center } | |||
hr.docutils { | |||
width: 75% } | |||
img.align-left { | |||
clear: left } | |||
img.align-right { | |||
clear: right } | |||
ol.simple, ul.simple { | |||
margin-bottom: 1em } | |||
ol.arabic { | |||
list-style: decimal } | |||
ol.loweralpha { | |||
list-style: lower-alpha } | |||
ol.upperalpha { | |||
list-style: upper-alpha } | |||
ol.lowerroman { | |||
list-style: lower-roman } | |||
ol.upperroman { | |||
list-style: upper-roman } | |||
p.attribution { | |||
text-align: right ; | |||
margin-left: 50% } | |||
p.caption { | |||
font-style: italic } | |||
p.credits { | |||
font-style: italic ; | |||
font-size: smaller } | |||
p.label { | |||
white-space: nowrap } | |||
p.rubric { | |||
font-weight: bold ; | |||
font-size: larger ; | |||
color: maroon ; | |||
text-align: center } | |||
p.sidebar-title { | |||
font-family: sans-serif ; | |||
font-weight: bold ; | |||
font-size: larger } | |||
p.sidebar-subtitle { | |||
font-family: sans-serif ; | |||
font-weight: bold } | |||
p.topic-title { | |||
font-weight: bold } | |||
pre.address { | |||
margin-bottom: 0 ; | |||
margin-top: 0 ; | |||
font-family: serif ; | |||
font-size: 100% } | |||
pre.literal-block, pre.doctest-block { | |||
margin-left: 2em ; | |||
margin-right: 2em ; | |||
background-color: #eeeeee } | |||
span.classifier { | |||
font-family: sans-serif ; | |||
font-style: oblique } | |||
span.classifier-delimiter { | |||
font-family: sans-serif ; | |||
font-weight: bold } | |||
span.interpreted { | |||
font-family: sans-serif } | |||
span.option { | |||
white-space: nowrap } | |||
span.pre { | |||
white-space: pre } | |||
span.problematic { | |||
color: red } | |||
span.section-subtitle { | |||
/* font-size relative to parent (h1..h6 element) */ | |||
font-size: 80% } | |||
table.citation { | |||
border-left: solid 1px gray; | |||
margin-left: 1px } | |||
table.docinfo { | |||
margin: 2em 4em } | |||
table.docutils { | |||
margin-top: 0.5em ; | |||
margin-bottom: 0.5em } | |||
table.footnote { | |||
border-left: solid 1px black; | |||
margin-left: 1px } | |||
table.docutils td, table.docutils th, | |||
table.docinfo td, table.docinfo th { | |||
padding-left: 0.5em ; | |||
padding-right: 0.5em ; | |||
vertical-align: top } | |||
table.docutils th.field-name, table.docinfo th.docinfo-name { | |||
font-weight: bold ; | |||
text-align: left ; | |||
white-space: nowrap ; | |||
padding-left: 0 } | |||
h1 tt.docutils, h2 tt.docutils, h3 tt.docutils, | |||
h4 tt.docutils, h5 tt.docutils, h6 tt.docutils { | |||
font-size: 100% } | |||
tt.docutils { | |||
background-color: #eeeeee } | |||
ul.auto-toc { | |||
list-style-type: none } | |||
</style> | |||
</head> | |||
<body> | |||
<div class="document" id="twig-template-engine-specification"> | |||
<h1 class="title">Twig Template Engine Specification</h1> | |||
<p>This specification specifies a simple cross-language template engine for at least | |||
PHP, Python and Ruby.</p> | |||
<div class="section"> | |||
<h1><a id="purpose" name="purpose">Purpose</a></h1> | |||
<p>A language independent and simple template engine is useful for applications that | |||
use code which is written in more than one programming language. Good Examples | |||
are portal systems which use a blog written in Ruby, a forum software written in | |||
PHP and a planet system written in Python.</p> | |||
</div> | |||
<div class="section"> | |||
<h1><a id="inspiration" name="inspiration">Inspiration</a></h1> | |||
<p>Twig uses a syntax similar to the Genshi text templates which in turn were | |||
inspired by django which also inspired Jinja (all three of them python template | |||
engines) which inspired the Twig runtime environment.</p> | |||
</div> | |||
<div class="section"> | |||
<h1><a id="undefined-behavior" name="undefined-behavior">Undefined Behavior</a></h1> | |||
<p>To simplify porting the template language to different platforms in a couple of | |||
situations the behavior is undefined. Template authors may never take advantage | |||
of such a situation!</p> | |||
</div> | |||
<div class="section"> | |||
<h1><a id="syntax" name="syntax">Syntax</a></h1> | |||
<p>I'm too lazy to write down the syntax as BNF diagram but the following snippet | |||
should explain the syntax elements:</p> | |||
<pre class="literal-block"> | |||
<!DOCTYPE HTML> | |||
{# This is a comment #} | |||
<title>{% block title %}Page Title Goes Here{% endblock %}</title> | |||
{% if show_navigation %} | |||
<nav> | |||
<ul> | |||
{% for item in navigation %} | |||
<li><a href="${item.href|e}">$item.caption</a></li> | |||
{% endfor %} | |||
</ul> | |||
</nav> | |||
{% endif %} | |||
<article>{% block body %}{% endblock %}</article> | |||
</pre> | |||
<div class="section"> | |||
<h2><a id="comments-and-whitespace" name="comments-and-whitespace">Comments and Whitespace</a></h2> | |||
<p>Everything between <tt class="docutils literal"><span class="pre">{#</span></tt> and <tt class="docutils literal"><span class="pre">#}</span></tt> is ignored by the lexer. Inside blocks and | |||
variable sections the Lexer has to remove whitespace too.</p> | |||
</div> | |||
<div class="section"> | |||
<h2><a id="output-expressions" name="output-expressions">Output Expressions</a></h2> | |||
<p>To output expressions two syntaxes exist. Simple variable output or full | |||
expression output:</p> | |||
<pre class="literal-block"> | |||
$this.is.a.variable.output | |||
${ expression | goes | here } | |||
</pre> | |||
<p>The former is what we call a variable expression, the second a full expression. | |||
Variable expressions must not contain whitespace, whereas a full expression | |||
must print the output of the full wrapped expression.</p> | |||
</div> | |||
<div class="section"> | |||
<h2><a id="expressions" name="expressions">Expressions</a></h2> | |||
<p>Expressions allow basic string manipulation and arithmetic calculations. It is | |||
an infix syntax with the following operators in this precedence:</p> | |||
<blockquote> | |||
<table border="1" class="docutils"> | |||
<colgroup> | |||
<col width="15%" /> | |||
<col width="85%" /> | |||
</colgroup> | |||
<thead valign="bottom"> | |||
<tr><th class="head">Operator</th> | |||
<th class="head">Description</th> | |||
</tr> | |||
</thead> | |||
<tbody valign="top"> | |||
<tr><td><tt class="docutils literal"><span class="pre">+</span></tt></td> | |||
<td>Convert both arguments into a number and add them up.</td> | |||
</tr> | |||
<tr><td><tt class="docutils literal"><span class="pre">-</span></tt></td> | |||
<td>Convert both arguments into a number and substract them.</td> | |||
</tr> | |||
<tr><td><tt class="docutils literal"><span class="pre">*</span></tt></td> | |||
<td>Convert both arguments into a number and multiply them.</td> | |||
</tr> | |||
<tr><td><tt class="docutils literal"><span class="pre">/</span></tt></td> | |||
<td>Convert both arguments into a number and divide them.</td> | |||
</tr> | |||
<tr><td><tt class="docutils literal"><span class="pre">%</span></tt></td> | |||
<td>Convert both arguments into a number and calculate the rest | |||
of the integer division.</td> | |||
</tr> | |||
<tr><td><tt class="docutils literal"><span class="pre">~</span></tt></td> | |||
<td>Convert both arguments into a string and concatenate them.</td> | |||
</tr> | |||
<tr><td><tt class="docutils literal"><span class="pre">or</span></tt></td> | |||
<td>True if the left or the right expression is true.</td> | |||
</tr> | |||
<tr><td><tt class="docutils literal"><span class="pre">and</span></tt></td> | |||
<td>True if the left and the right expression is true.</td> | |||
</tr> | |||
<tr><td><tt class="docutils literal"><span class="pre">not</span></tt></td> | |||
<td>negate the expression</td> | |||
</tr> | |||
</tbody> | |||
</table> | |||
</blockquote> | |||
<p>All number conversions have an undefined precision but the implementations | |||
should try to select the best possible type. For example, if the implementation | |||
sees an integer and a float that looks like an integer it may convert the | |||
latter into a long and add them.</p> | |||
<p>Use parentheses to group expressions.</p> | |||
<p>If an object cannot be compared the implementation might raise an error or fail | |||
silently. Template authors may never apply mathematical operators to untrusted | |||
data. This is especially true for the php implementation where the following | |||
outputs <tt class="docutils literal"><span class="pre">42</span></tt>:</p> | |||
<pre class="literal-block"> | |||
${ "foo41" + 1 } | |||
</pre> | |||
<p>This is undefined behavior and will break on different implementations or | |||
return <tt class="docutils literal"><span class="pre">0</span></tt> as <tt class="docutils literal"><span class="pre">"foo41"</span></tt> is not a valid number.</p> | |||
<div class="section"> | |||
<h3><a id="types" name="types">Types</a></h3> | |||
<p>The following types exist:</p> | |||
<blockquote> | |||
<table border="1" class="docutils"> | |||
<colgroup> | |||
<col width="15%" /> | |||
<col width="21%" /> | |||
<col width="64%" /> | |||
</colgroup> | |||
<thead valign="bottom"> | |||
<tr><th class="head">Type</th> | |||
<th class="head">Literal</th> | |||
<th class="head">Description</th> | |||
</tr> | |||
</thead> | |||
<tbody valign="top"> | |||
<tr><td><tt class="docutils literal"><span class="pre">integer</span></tt></td> | |||
<td><cite>d+</cite></td> | |||
<td>One of the two numeric types. Which of them | |||
is used and when is up to the implementation.</td> | |||
</tr> | |||
<tr><td><tt class="docutils literal"><span class="pre">float</span></tt></td> | |||
<td><cite>d+.d+</cite></td> | |||
<td>Floating point values.</td> | |||
</tr> | |||
<tr><td><tt class="docutils literal"><span class="pre">string</span></tt></td> | |||
<td>see below</td> | |||
<td>A unicode string. The PHP implementation has | |||
to use bytestrings here and may use mb_string.</td> | |||
</tr> | |||
<tr><td><tt class="docutils literal"><span class="pre">bool</span></tt></td> | |||
<td><cite>(true|false)</cite></td> | |||
<td>Represents boolean values.</td> | |||
</tr> | |||
<tr><td><tt class="docutils literal"><span class="pre">none</span></tt></td> | |||
<td><cite>none</cite></td> | |||
<td>This type is returned on missing variables or | |||
attributes.</td> | |||
</tr> | |||
</tbody> | |||
</table> | |||
</blockquote> | |||
<p>String regex:</p> | |||
<pre class="literal-block"> | |||
(?:"([^"\\\\]*(?:\\\\.[^"\\\\]*)*)"|\'([^\'\\\\]*(?:\\\\.[^\'\\\\]*)*)\')(?sm) | |||
</pre> | |||
</div> | |||
<div class="section"> | |||
<h3><a id="attribute-lookup" name="attribute-lookup">Attribute Lookup</a></h3> | |||
<p>There are two ways to look up attributes on objects. The dot and the | |||
subscript syntax, both inspired by JavaScript. Basically the following | |||
expressions do the very same:</p> | |||
<pre class="literal-block"> | |||
foo.name.0 | |||
foo['name'][0] | |||
</pre> | |||
<p>This is useful to dynamically get attributes from objects:</p> | |||
<pre class="literal-block"> | |||
foo[bar] | |||
</pre> | |||
<p>The underlaying implementation is free to specify on it's own what an attribute | |||
lookup means. The PHP reference implementation for example performs those | |||
actions on <tt class="docutils literal"><span class="pre">foo.bar</span></tt>:</p> | |||
<ul class="simple"> | |||
<li>try <tt class="docutils literal"><span class="pre">$foo['bar']</span></tt></li> | |||
<li>try <tt class="docutils literal"><span class="pre">$foo->bar()</span></tt></li> | |||
<li>try <tt class="docutils literal"><span class="pre">$foo->bar</span></tt></li> | |||
<li>try <tt class="docutils literal"><span class="pre">$foo->getBar()</span></tt></li> | |||
</ul> | |||
<p>The first match returns the object, attribute access to nonexisting attributes | |||
returns <cite>none</cite>.</p> | |||
</div> | |||
<div class="section"> | |||
<h3><a id="filtering" name="filtering">Filtering</a></h3> | |||
<p>The template language does not specify function calls, but filters can be used | |||
to further modify variables using functions the template engine provides.</p> | |||
<p>The following snippet shows how filters are translated to function calls:</p> | |||
<pre class="literal-block"> | |||
${ 42 | foo(1, 2) | bar | baz } | |||
-> baz(bar(foo(42, 1, 2))) | |||
</pre> | |||
<p>The following filters must be provided by the implementation:</p> | |||
<blockquote> | |||
<table border="1" class="docutils"> | |||
<colgroup> | |||
<col width="26%" /> | |||
<col width="74%" /> | |||
</colgroup> | |||
<thead valign="bottom"> | |||
<tr><th class="head">Name</th> | |||
<th class="head">Description</th> | |||
</tr> | |||
</thead> | |||
<tbody valign="top"> | |||
<tr><td><cite>date</cite></td> | |||
<td>Format the timestamp using the PHP date formatting | |||
rules. This may sound like a nonstandard way of | |||
formatting dates but it's a way very popular among | |||
template designers and also used by django.</td> | |||
</tr> | |||
<tr><td><cite>strftime</cite></td> | |||
<td>Format the timestamp using standard strftime rules.</td> | |||
</tr> | |||
<tr><td><cite>numberformat</cite></td> | |||
<td>Apply number formatting on the string. This may or | |||
may not use local specific rules.</td> | |||
</tr> | |||
<tr><td><cite>moneyformat</cite></td> | |||
<td>Like <cite>numberformat</cite> but for money.</td> | |||
</tr> | |||
<tr><td><cite>filesizeformat</cite></td> | |||
<td>Takes a number of bytes and displays it as KB/MB/GB</td> | |||
</tr> | |||
<tr><td><cite>format</cite></td> | |||
<td><dl class="first last docutils"> | |||
<dt>Applies <cite>sprintf</cite> formatting on the string::</dt> | |||
<dd>${ "%s %2f" | format(string, float) }</dd> | |||
</dl> | |||
</td> | |||
</tr> | |||
<tr><td><cite>even</cite></td> | |||
<td>Is the number even?</td> | |||
</tr> | |||
<tr><td><cite>odd</cite></td> | |||
<td>Is the number odd?</td> | |||
</tr> | |||
<tr><td><cite>escape</cite></td> | |||
<td>Apply HTML escaping on a string. This also has to | |||
convert <cite>"</cite> to <cite>&quot; but leave `'</cite> unmodified.</td> | |||
</tr> | |||
<tr><td><cite>e</cite></td> | |||
<td>Alias for <cite>escape</cite>.</td> | |||
</tr> | |||
<tr><td><cite>urlencode</cite></td> | |||
<td>URL encode the string. If the second parameter is | |||
true this function should encode for path sections, | |||
otherwise for query strings.</td> | |||
</tr> | |||
<tr><td><cite>quotes</cite></td> | |||
<td>Escape quotes (', ", etc.)</td> | |||
</tr> | |||
<tr><td><cite>title</cite></td> | |||
<td>Make the string lowercase and upper case the first | |||
characters of all words.</td> | |||
</tr> | |||
<tr><td><cite>capitalize</cite></td> | |||
<td>Like <cite>title</cite> but capitalizes only the first char of | |||
the whole string.</td> | |||
</tr> | |||
<tr><td><cite>upper</cite></td> | |||
<td>Convert the string to uppercase.</td> | |||
</tr> | |||
<tr><td><cite>lower</cite></td> | |||
<td>Convert the string to lowercase.</td> | |||
</tr> | |||
<tr><td><cite>strip</cite></td> | |||
<td>Trim leading and trailing whitespace.</td> | |||
</tr> | |||
<tr><td><cite>lstrip</cite></td> | |||
<td>Trim leading whitespace.</td> | |||
</tr> | |||
<tr><td><cite>rstrip</cite></td> | |||
<td>Trim trailing whitespace.</td> | |||
</tr> | |||
<tr><td><cite>translate</cite></td> | |||
<td>Translate the string using either the "theme" domain | |||
or the "chyrp" domain if in Admin. (Chyrp-specific)</td> | |||
</tr> | |||
<tr><td><cite>translate_plural</cite></td> | |||
<td>Translate the (singular) string, or the plural string | |||
if the number passed is not 1.</td> | |||
</tr> | |||
<tr><td><cite>normalize</cite></td> | |||
<td>Convert all excessive whitespace (including linebreaks) | |||
into a single space.</td> | |||
</tr> | |||
<tr><td><cite>truncate</cite></td> | |||
<td>Truncate a string, providing ellipsis, if it is longer | |||
than the passed length. Keeps words in tact by default, | |||
but with a second boolean parameter will be strict.</td> | |||
</tr> | |||
<tr><td><cite>replace</cite></td> | |||
<td>Replaces the occurrence of the first argument with the | |||
second argument in the string.</td> | |||
</tr> | |||
<tr><td><cite>linebreaks</cite></td> | |||
<td>Convert linebreaks to <br />'s.</td> | |||
</tr> | |||
<tr><td><cite>camelize</cite></td> | |||
<td>Convert string to camelcase.</td> | |||
</tr> | |||
<tr><td><cite>strip_tags</cite></td> | |||
<td>Strip HTML from the string.</td> | |||
</tr> | |||
<tr><td><cite>pluralize</cite></td> | |||
<td>Return the pluralization of a string, or if a number | |||
is passed and it is 1, don't pluralize.</td> | |||
</tr> | |||
<tr><td><cite>sanitize</cite></td> | |||
<td>Remove special characters from a string.</td> | |||
</tr> | |||
<tr><td><cite>join</cite></td> | |||
<td>Concatenate the array items and join them with the | |||
string provided (or commas by default).</td> | |||
</tr> | |||
<tr><td><cite>split</cite></td> | |||
<td>Split a string into an array at the given breakpoints.</td> | |||
</tr> | |||
<tr><td><cite>first</cite></td> | |||
<td>First entry of an Array.</td> | |||
</tr> | |||
<tr><td><cite>offset</cite></td> | |||
<td>Entry at Array[offset].</td> | |||
</tr> | |||
<tr><td><cite>last</cite></td> | |||
<td>Last entry of an Array.</td> | |||
</tr> | |||
<tr><td><cite>reverse</cite></td> | |||
<td>Reverse the Array items.</td> | |||
</tr> | |||
<tr><td><cite>count</cite></td> | |||
<td>Count the number of items in an array or string | |||
characters.</td> | |||
</tr> | |||
<tr><td><cite>length</cite></td> | |||
<td>Alias for <cite>count</cite>.</td> | |||
</tr> | |||
<tr><td><cite>default</cite></td> | |||
<td>If the value is <cite>none</cite> the first argument is returned</td> | |||
</tr> | |||
<tr><td><cite>keys</cite></td> | |||
<td>Keys of an Array.</td> | |||
</tr> | |||
<tr><td><cite>items</cite></td> | |||
<td>Items of an Array.</td> | |||
</tr> | |||
<tr><td><cite>inspect</cite></td> | |||
<td>Dumps the variable or value.</td> | |||
</tr> | |||
<tr><td><cite>fallback</cite></td> | |||
<td>If the value is empty or <cite>none</cite>, return this value.</td> | |||
</tr> | |||
<tr><td><cite>selected</cite></td> | |||
<td>If the first argument is the same as the value, output | |||
<cite>class="selected"</cite>, or <cite>selected</cite> if the second | |||
argument is <cite>true</cite>.</td> | |||
</tr> | |||
<tr><td><cite>option_selected</cite></td> | |||
<td>Same as <cite>selected</cite>, but for <cite>selected="selected"</cite>.</td> | |||
</tr> | |||
<tr><td><cite>checked</cite></td> | |||
<td>Same as <cite>selected</cite>, but for <cite>checked="checked"</cite>.</td> | |||
</tr> | |||
</tbody> | |||
</table> | |||
</blockquote> | |||
<p>Additionally, if a filter is missing (say, ${ foo | bar_filter }, in Chyrp it | |||
checks for an associated Trigger filter by that filter's name.</p> | |||
</div> | |||
</div> | |||
<div class="section"> | |||
<h2><a id="for-loops" name="for-loops">For Loops</a></h2> | |||
<p>Iteration works via for loops. Loops work a bit like their Python counterparts, | |||
except that they don't support multilevel tuple unpacking and that they add a new | |||
layer to the context. Thus at the end of the iteration all the modifications on | |||
the context disappear. Additionally, inside loops you have access to a special | |||
<cite>loop</cite> object which provides runtime information:</p> | |||
<blockquote> | |||
<table border="1" class="docutils"> | |||
<colgroup> | |||
<col width="30%" /> | |||
<col width="70%" /> | |||
</colgroup> | |||
<thead valign="bottom"> | |||
<tr><th class="head">Variable</th> | |||
<th class="head">Description</th> | |||
</tr> | |||
</thead> | |||
<tbody valign="top"> | |||
<tr><td><tt class="docutils literal"><span class="pre">loop.index</span></tt></td> | |||
<td>The current iteration of the loop (1-indexed)</td> | |||
</tr> | |||
<tr><td><tt class="docutils literal"><span class="pre">loop.index0</span></tt></td> | |||
<td>The current iteration of the loop (0-indexed)</td> | |||
</tr> | |||
<tr><td><tt class="docutils literal"><span class="pre">loop.revindex</span></tt></td> | |||
<td>The number of iterations from the end of the | |||
loop (1-indexed)</td> | |||
</tr> | |||
<tr><td><tt class="docutils literal"><span class="pre">loop.revindex0</span></tt></td> | |||
<td>The number of iterations from the end of the | |||
loop (0-indexed)</td> | |||
</tr> | |||
<tr><td><tt class="docutils literal"><span class="pre">loop.first</span></tt></td> | |||
<td>True if this is the first time through the loop</td> | |||
</tr> | |||
<tr><td><tt class="docutils literal"><span class="pre">loop.last</span></tt></td> | |||
<td>True if this is the last time through the loop</td> | |||
</tr> | |||
<tr><td><tt class="docutils literal"><span class="pre">loop.parent</span></tt></td> | |||
<td>For nested loops, this is the loop "above" the | |||
current one</td> | |||
</tr> | |||
</tbody> | |||
</table> | |||
</blockquote> | |||
<p>Additionally for loops can have an <cite>else</cite> section that is executed if no | |||
iteration took place.</p> | |||
<div class="section"> | |||
<h3><a id="example" name="example">Example</a></h3> | |||
<pre class="literal-block"> | |||
<ul> | |||
{% for user in users %} | |||
<li><a href="$user.href">${ user.username | escape }</a></li> | |||
{% else %} | |||
<li><em>No users found!</em></li> | |||
{% endfor %} | |||
</ul> | |||
</pre> | |||
</div> | |||
<div class="section"> | |||
<h3><a id="notes-on-iteration" name="notes-on-iteration">Notes on Iteration</a></h3> | |||
<p>Because we have to cope with PHP too, which has problematic arrays that are | |||
neither hashmaps nor lists, we have no support for associative array iteration | |||
at all. How do you iterate over associative arrays then? Using a filter:</p> | |||
<pre class="literal-block"> | |||
{% for key, value in array | items %} | |||
... | |||
{% endfor %} | |||
</pre> | |||
<p>To iterate over the keys only:</p> | |||
<pre class="literal-block"> | |||
{% for key in array | keys %} | |||
... | |||
{% endfor %} | |||
</pre> | |||
</div> | |||
</div> | |||
<div class="section"> | |||
<h2><a id="if-conditions" name="if-conditions">If Conditions</a></h2> | |||
<p>If conditions work like like Ruby, PHP and Python, just that we use PHP | |||
keywords. Also, use <cite>elseif</cite> and not <cite>else if</cite>:</p> | |||
<pre class="literal-block"> | |||
{% if expr1 %} | |||
... | |||
{% elseif expr2 %} | |||
... | |||
{% else %} | |||
... | |||
{% endif %} | |||
</pre> | |||
</div> | |||
<div class="section"> | |||
<h2><a id="inheritance" name="inheritance">Inheritance</a></h2> | |||
<p>Template inheritance allows you to build a base "skeleton" template that | |||
contains all the common elements of your site and defines <strong>blocks</strong> that | |||
child templates can override.</p> | |||
<p>Here a small template inheritance example:</p> | |||
<pre class="literal-block"> | |||
<!DOCTYPE HTML> | |||
<html lang="en"> | |||
<link rel="stylesheet" href="style.css"> | |||
<title>{% block title %}My site{% endblock %}</title> | |||
<div id="sidebar"> | |||
{% block sidebar %} | |||
<ul> | |||
<li><a href="/">Home</a></li> | |||
<li><a href="/blog/">Blog</a></li> | |||
</ul> | |||
{% endblock %} | |||
</div> | |||
<div id="content"> | |||
{% block content %}{% endblock %} | |||
</div> | |||
</html> | |||
</pre> | |||
<p>If we call that template "base.html" a "index.html" template could override | |||
it and fill in the blocks:</p> | |||
<pre class="literal-block"> | |||
{% extends "base.html" %} | |||
{% block title %}Foo &mdash; {% super %}{% endblock %} | |||
{% block content %} | |||
This is the content | |||
{% endblock %} | |||
</pre> | |||
<p>By using <cite>{% super %}</cite> you can render the parent's block. The template | |||
filenames must be constant strings (we don't support dynamic inheritance | |||
for simplicity) and are relative to the loader folder, not the current | |||
template.</p> | |||
</div> | |||
</div> | |||
</div> | |||
</body> | |||
</html> |
@@ -0,0 +1,363 @@ | |||
================================== | |||
Twig Template Engine Specification | |||
================================== | |||
This specification specifies a simple cross-language template engine for at least | |||
PHP, Python and Ruby. | |||
Purpose | |||
======= | |||
A language independent and simple template engine is useful for applications that | |||
use code which is written in more than one programming language. Good Examples | |||
are portal systems which use a blog written in Ruby, a forum software written in | |||
PHP and a planet system written in Python. | |||
Inspiration | |||
=========== | |||
Twig uses a syntax similar to the Genshi text templates which in turn were | |||
inspired by django which also inspired Jinja (all three of them python template | |||
engines) which inspired the Twig runtime environment. | |||
Undefined Behavior | |||
================== | |||
To simplify porting the template language to different platforms in a couple of | |||
situations the behavior is undefined. Template authors may never take advantage | |||
of such a situation! | |||
Syntax | |||
====== | |||
I'm too lazy to write down the syntax as BNF diagram but the following snippet | |||
should explain the syntax elements:: | |||
<!DOCTYPE HTML> | |||
{# This is a comment #} | |||
<title>{% block title %}Page Title Goes Here{% endblock %}</title> | |||
{% if show_navigation %} | |||
<nav> | |||
<ul> | |||
{% for item in navigation %} | |||
<li><a href="${item.href|e}">$item.caption</a></li> | |||
{% endfor %} | |||
</ul> | |||
</nav> | |||
{% endif %} | |||
<article>{% block body %}{% endblock %}</article> | |||
Comments and Whitespace | |||
----------------------- | |||
Everything between ``{#`` and ``#}`` is ignored by the lexer. Inside blocks and | |||
variable sections the Lexer has to remove whitespace too. | |||
Output Expressions | |||
------------------ | |||
To output expressions two syntaxes exist. Simple variable output or full | |||
expression output:: | |||
$this.is.a.variable.output | |||
${ expression | goes | here } | |||
The former is what we call a variable expression, the second a full expression. | |||
Variable expressions must not contain whitespace, whereas a full expression | |||
must print the output of the full wrapped expression. | |||
Expressions | |||
----------- | |||
Expressions allow basic string manipulation and arithmetic calculations. It is | |||
an infix syntax with the following operators in this precedence: | |||
=========== ============================================================== | |||
Operator Description | |||
=========== ============================================================== | |||
``+`` Convert both arguments into a number and add them up. | |||
``-`` Convert both arguments into a number and substract them. | |||
``*`` Convert both arguments into a number and multiply them. | |||
``/`` Convert both arguments into a number and divide them. | |||
``%`` Convert both arguments into a number and calculate the rest | |||
of the integer division. | |||
``~`` Convert both arguments into a string and concatenate them. | |||
``or`` True if the left or the right expression is true. | |||
``and`` True if the left and the right expression is true. | |||
``not`` negate the expression | |||
=========== ============================================================== | |||
All number conversions have an undefined precision but the implementations | |||
should try to select the best possible type. For example, if the implementation | |||
sees an integer and a float that looks like an integer it may convert the | |||
latter into a long and add them. | |||
Use parentheses to group expressions. | |||
If an object cannot be compared the implementation might raise an error or fail | |||
silently. Template authors may never apply mathematical operators to untrusted | |||
data. This is especially true for the php implementation where the following | |||
outputs ``42``:: | |||
${ "foo41" + 1 } | |||
This is undefined behavior and will break on different implementations or | |||
return ``0`` as ``"foo41"`` is not a valid number. | |||
Types | |||
~~~~~ | |||
The following types exist: | |||
=========== =============== ============================================== | |||
Type Literal Description | |||
=========== =============== ============================================== | |||
``integer`` `\d+` One of the two numeric types. Which of them | |||
is used and when is up to the implementation. | |||
``float`` `\d+\.\d+` Floating point values. | |||
``string`` see below A unicode string. The PHP implementation has | |||
to use bytestrings here and may use mb_string. | |||
``bool`` `(true|false)` Represents boolean values. | |||
``none`` `none` This type is returned on missing variables or | |||
attributes. | |||
=========== =============== ============================================== | |||
String regex:: | |||
(?:"([^"\\\\]*(?:\\\\.[^"\\\\]*)*)"|\'([^\'\\\\]*(?:\\\\.[^\'\\\\]*)*)\')(?sm) | |||
Attribute Lookup | |||
~~~~~~~~~~~~~~~~ | |||
There are two ways to look up attributes on objects. The dot and the | |||
subscript syntax, both inspired by JavaScript. Basically the following | |||
expressions do the very same:: | |||
foo.name.0 | |||
foo['name'][0] | |||
This is useful to dynamically get attributes from objects:: | |||
foo[bar] | |||
The underlaying implementation is free to specify on it's own what an attribute | |||
lookup means. The PHP reference implementation for example performs these | |||
actions on ``foo.bar``: | |||
- try ``$foo['bar']`` | |||
- try ``$foo->bar()`` (if they're using ``foo.bar`` and not ``foo[bar]``) | |||
- try ``$foo->bar`` | |||
- try ``$foo->getBar()`` (if they're using ``foo.bar`` and not ``foo[bar]``) | |||
The first match returns the object, attribute access to nonexisting attributes | |||
returns `none`. | |||
Filtering | |||
~~~~~~~~~ | |||
The template language does not specify function calls, but filters can be used | |||
to further modify variables using functions the template engine provides. | |||
The following snippet shows how filters are translated to function calls:: | |||
${ 42 | foo(1, 2) | bar | baz } | |||
-> baz(bar(foo(42, 1, 2))) | |||
The following filters must be provided by the implementation: | |||
=================== ====================================================== | |||
Name Description | |||
=================== ====================================================== | |||
`date` Format the timestamp using the PHP date formatting | |||
rules. This may sound like a nonstandard way of | |||
formatting dates but it's a way very popular among | |||
template designers and also used by django. | |||
`strftime` Format the timestamp using standard strftime rules. | |||
`numberformat` Apply number formatting on the string. This may or | |||
may not use local specific rules. | |||
`moneyformat` Like `numberformat` but for money. | |||
`filesizeformat` Takes a number of bytes and displays it as KB/MB/GB | |||
`format` Applies `sprintf` formatting on the string:: | |||
${ "%s %2f" | format(string, float) } | |||
`even` Is the number even? | |||
`odd` Is the number odd? | |||
`escape` Apply HTML escaping on a string. This also has to | |||
convert `"` to `" but leave `'` unmodified. | |||
`e` Alias for `escape`. | |||
`urlencode` URL encode the string. If the second parameter is | |||
true this function should encode for path sections, | |||
otherwise for query strings. | |||
`quotes` Escape quotes (\', \", etc.) | |||
`title` Make the string lowercase and upper case the first | |||
characters of all words. | |||
`capitalize` Like `title` but capitalizes only the first char of | |||
the whole string. | |||
`upper` Convert the string to uppercase. | |||
`lower` Convert the string to lowercase. | |||
`strip` Trim leading and trailing whitespace. | |||
`lstrip` Trim leading whitespace. | |||
`rstrip` Trim trailing whitespace. | |||
`translate` Translate the string using either the "theme" domain | |||
or the "chyrp" domain if in Admin. (Chyrp-specific) | |||
`translate_plural` Translate the (singular) string, or the plural string | |||
if the number passed is not 1. | |||
`normalize` Convert all excessive whitespace (including linebreaks) | |||
into a single space. | |||
`truncate` Truncate a string, providing ellipsis, if it is longer | |||
than the passed length. Keeps words in tact by default, | |||
but with a second boolean parameter will be strict. | |||
`replace` Replaces the occurrence of the first argument with the | |||
second argument in the string. | |||
`linebreaks` Convert linebreaks to <br />'s. | |||
`camelize` Convert string to camelcase. | |||
`strip_tags` Strip HTML from the string. | |||
`pluralize` Return the pluralization of a string, or if a number | |||
is passed and it is 1, don't pluralize. | |||
`sanitize` Remove special characters from a string. | |||
`join` Concatenate the array items and join them with the | |||
string provided (or commas by default). | |||
`split` Split a string into an array at the given breakpoints. | |||
`first` First entry of an Array. | |||
`offset` Entry at Array[offset]. | |||
`last` Last entry of an Array. | |||
`reverse` Reverse the Array items. | |||
`count` Count the number of items in an array or string | |||
characters. | |||
`length` Alias for `count`. | |||
`default` If the value is `none` the first argument is returned | |||
`keys` Keys of an Array. | |||
`items` Items of an Array. | |||
`inspect` Dumps the variable or value. | |||
`fallback` If the value is empty or `none`, return this value. | |||
`selected` If the first argument is the same as the value, output | |||
`class="selected"`, or `selected` if the second | |||
argument is `true`. | |||
`option_selected` Same as `selected`, but for `selected="selected"`. | |||
`checked` Same as `selected`, but for `checked="checked"`. | |||
=================== ====================================================== | |||
Additionally, if a filter is missing (say, ${ foo | bar_filter }, in Chyrp it | |||
checks for an associated Trigger filter by that filter's name. | |||
For Loops | |||
--------- | |||
Iteration works via for loops. Loops work a bit like their Python counterparts, | |||
except that they don't support multilevel tuple unpacking and that they add a new | |||
layer to the context. Thus at the end of the iteration all the modifications on | |||
the context disappear. Additionally, inside loops you have access to a special | |||
`loop` object which provides runtime information: | |||
====================== =================================================== | |||
Variable Description | |||
====================== =================================================== | |||
``loop.index`` The current iteration of the loop (1-indexed) | |||
``loop.index0`` The current iteration of the loop (0-indexed) | |||
``loop.revindex`` The number of iterations from the end of the | |||
loop (1-indexed) | |||
``loop.revindex0`` The number of iterations from the end of the | |||
loop (0-indexed) | |||
``loop.first`` True if this is the first time through the loop | |||
``loop.last`` True if this is the last time through the loop | |||
``loop.parent`` For nested loops, this is the loop "above" the | |||
current one | |||
====================== =================================================== | |||
Additionally for loops can have an `else` section that is executed if no | |||
iteration took place. | |||
Example | |||
~~~~~~~ | |||
:: | |||
<ul> | |||
{% for user in users %} | |||
<li><a href="$user.href">${ user.username | escape }</a></li> | |||
{% else %} | |||
<li><em>No users found!</em></li> | |||
{% endfor %} | |||
</ul> | |||
Notes on Iteration | |||
~~~~~~~~~~~~~~~~~~ | |||
Because we have to cope with PHP too, which has problematic arrays that are | |||
neither hashmaps nor lists, we have no support for associative array iteration | |||
at all. How do you iterate over associative arrays then? Using a filter:: | |||
{% for key, value in array | items %} | |||
... | |||
{% endfor %} | |||
To iterate over the keys only:: | |||
{% for key in array | keys %} | |||
... | |||
{% endfor %} | |||
If Conditions | |||
------------- | |||
If conditions work like like Ruby, PHP and Python, just that we use PHP | |||
keywords. Also, use `elseif` and not `else if`:: | |||
{% if expr1 %} | |||
... | |||
{% elseif expr2 %} | |||
... | |||
{% else %} | |||
... | |||
{% endif %} | |||
Inheritance | |||
----------- | |||
Template inheritance allows you to build a base "skeleton" template that | |||
contains all the common elements of your site and defines **blocks** that | |||
child templates can override. | |||
Here a small template inheritance example:: | |||
<!DOCTYPE HTML> | |||
<html lang="en"> | |||
<link rel="stylesheet" href="style.css"> | |||
<title>{% block title %}My site{% endblock %}</title> | |||
<div id="sidebar"> | |||
{% block sidebar %} | |||
<ul> | |||
<li><a href="/">Home</a></li> | |||
<li><a href="/blog/">Blog</a></li> | |||
</ul> | |||
{% endblock %} | |||
</div> | |||
<div id="content"> | |||
{% block content %}{% endblock %} | |||
</div> | |||
</html> | |||
If we call that template "base.html" a "index.html" template could override | |||
it and fill in the blocks:: | |||
{% extends "base.html" %} | |||
{% block title %}Foo — {% super %}{% endblock %} | |||
{% block content %} | |||
This is the content | |||
{% endblock %} | |||
By using `{% super %}` you can render the parent's block. The template | |||
filenames must be constant strings (we don't support dynamic inheritance | |||
for simplicity) and are relative to the loader folder, not the current | |||
template. |