*/ /* This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ /* Code notes: * - Local, non-boolean variables start with "the" as in $the_output * and $the_version. Local boolean variables start with words such * as "is" or "has" to indicate their meaning as in $is_installed * and $has_title. Short-lived loop variables use standard, short * names as in $i and $x. * * - Function parameters are prefixed with "in", "out", or "io" to * indicate their use as input, output, or input-output, * respectively, as in $in_title. * * - All functions are prefixed with "textile_" and words are * separated with underscores. * * - I make extensive use of heredoc syntax and multiline strings. * It looks much nicer than dozens of "$var .= ..." lines and is * easier to read and format -- especially the HTML tags. It helps * in outputting HTML pages that are well-formatted and readable. */ // update automatically _textile_install(); require_once(drupal_get_path('module','textile') . '/textilephp/Textile.php'); require_once(drupal_get_path('module','textile') . '/smartypants-php/SmartyPants-PHP.inc'); /******************************************************************** * Drupal Hooks ********************************************************************/ /** * Implementation of hook_filter(). */ function textile_filter($in_op, $in_delta = 0, $in_format = -1, $in_text = '') { switch ($in_op) { case 'list': return array(t("Textile")); case 'description': return t('Allows content to be submitted using Textile, a simple, plain text syntax that is filtered into valid XHTML.'); case 'process': if (variable_get("textile_tags_$in_format", 1)) { return preg_replace_callback('{\[textile\](.*?)(\[/textile\]|$)}is', '_textile_process', $in_text); } else { return _textile_process(array(NULL, $in_text)); } case 'settings': return form_group(t('Textile filter'), form_checkbox(t('Use tags'), "textile_tags_$in_format", 1, variable_get("textile_tags_$in_format", 1), t('If enabled, only text between [textile] and optional [/textile] tags will be processed. Otherwise, all text will be processed as Textile markup.'))); default: return $in_text; } } // function textile_filter /** * Implementation of hook_filter_tips(). */ function textile_filter_tips($in_delta, $in_format, $in_is_long = FALSE) { if ($in_is_long) { return t('

Textile Help

Block modifier syntax:

CSS attributes can be applied to blocks (paragraphes, headers, etc.). CSS classes are specifed with "(class)"; CSS IDs are specified with "(#id)"; both can be specified with "(class#id)". An arbtirary CSS style can be applied by using "{style}". Finally, language attributes are applied using "[language]".

Additionally, alignment and indentation shorthands are provided. To left-align, right-align, center, and justify text, use "<", ">", "=", and "<>", respectively. "(" left-indents a block 1em for each occurrence, and ")" right-indents similarly.

Tables have additional options. "^", "-", and "~" specify top, middle, and bottom vertical alignment. The "_" attribute on a cell indicates that it is a table header.

The examples below illustrate these attributes.

textile input output

Headings

hx. (where x is 1 - 6)
h1. Heading

Heading

h2(class). Heading with class

Heading with class

Paragraphs

p=. Centered text

Centered text

p())(#id). Indented text with ID

Indented text with ID

Block quotes

bq(class#id). Quote with class and ID
Quote with class and ID
bq[en]. English quote
English quote

Ordered lists

{color: blue}# Attributes specified
# before the first item
# affect the whole list
  1. Attributes specified
  2. before the first item
  3. affect the whole list

Unordered lists

* Lists can have
## subitems or
## sublists
* too
  • Lists can have
    1. subitems or
    2. sublists
  • too

Footnotes

fnx. (where x is 1 - 100)
fn17. Footnote

17 Footnote

Tables

|_. A|_. B|_. C|
(dark). |very|simple|table|
|<. left|=. center|>. right|
|^{height:3em}. top|-. middle|~. bottom|
A B C
very simple table
left center right
top middle bottom

Phrase modifier syntax:

The class, ID, style, and language attributes described above also apply to the span phrase modifier as shown below.

textile input output
_emphasis_ emphasis
__italic__ italic
*strong* strong
**bold** bold
??citation?? citation
-delete text- deleted text
+inserted text+ inserted text
^superscript^ superscript
~subscript~ subscript
@code@ code
%(class)span% span
%{color:red;}span% span
==no textile== no textile
"link text":url link text
"link text(title)":url link text
!imageurl!
!imageurl(alt text)! alt text
!imageurl!:url
ABC(Always Be Closing) ABC
Footnote reference[17] Footnote reference17
'); } elseif (variable_get("textile_tags_$in_format", 1)) { return t('You can use Textile markup to format text between the [textile] and (optional) [/textile] tags.'); } else { return t('You can use Textile markup to format text.'); } } // function textile_filter_tips /** * Implementation of hook_help(). */ function textile_help($in_section = 'admin/help#textile') { switch ($in_section) { case 'admin/modules#description': return textile_filter('description'); case 'admin/help#textile': return t('

The Textile module allows users to enter content using Textile, a simple, plain text syntax that is filtered into valid XHTML. The filter tips page provides syntax descriptions and examples.

'); } } // function textile_help /******************************************************************** * Module Functions ********************************************************************/ /** * Returns the version of this release of the Textile module. * * @return * An array with keys 'text' and 'build' containing the * text version and build ID of this release, respectively. * * @private */ function _textile_version() { /* Why text and an ID? Well, the text is easier for the user to * read and understand while the build ID, being a number (a date * with a serial, specifically), is easier for the developer to use * to determine newer/older versions for upgrade and installation * purposes. */ return array('text' => '2.1.1', 'build' => 2004092301); } // function _textile_version /** * Indicates whether or not the textile module is installed and * up-to-date, transparently upgrading compatible versions when * possible. * * @return * TRUE if the module is up to date; * FALSE, otherwise. * * @private */ function _textile_is_installed() { $the_current_ver = _textile_version(); $the_installed_ver = variable_get('textile_version', NULL); // Handle versions that can be safely/transparently updated. if ($the_installed_ver && ($the_installed_ver['build'] < $the_current_ver['build'])) { variable_set('textile_version', $the_current_ver); return TRUE; } return ($the_current_ver['build'] == $the_installed_ver['build']); } // function _textile_is_installed /** * Installs the textile module, updating existing Textile-formatted * nodes if needed and creating the textile_version * variable. * * @private */ function _textile_install() { if (_textile_is_installed()) { return; } // get version information $the_current_ver = _textile_version(); $the_installed_ver = variable_get('textile_version', NULL); if ($the_installed_ver['build'] > $the_current_ver['build']) { // don't install an older version return; } // update old tags db_query("UPDATE {node} SET body = REPLACE(body, '\001textile1\001\n', '[textile]\n\n') WHERE body LIKE '%%\001textile1\001\n%%'"); db_query("UPDATE {node} SET teaser = REPLACE(teaser, '\001textile1\001\n', '[textile]\n\n') WHERE teaser LIKE '%%\001textile1\001\n%%'"); db_query("UPDATE {node} SET body = REPLACE(body, '\001textile2\001\n', '[textile]\n\n') WHERE body LIKE '%%\001textile2\001\n%%'"); db_query("UPDATE {node} SET teaser = REPLACE(teaser, '\001textile2\001\n', '[textile]\n\n') WHERE teaser LIKE '%%\001textile2\001\n%%'"); // installed successfully variable_set('textile_version', $the_current_ver); } // function _textile_install /** * Performs the appropriate Textile filtering on the provided text. * * @param $in_matches * The array specifying the text to be filtered at * index 1. * * @return * A string containing the filtered text. * * @private */ function _textile_process($in_matches) { static $s_textile = NULL; if ($s_textile === NULL) { $s_textile = new DrupalTextile(array('smarty_mode' => DEFAULT_OPERATION_MODE)); } return $s_textile->process($in_matches[1]); } // function _textile_process /** * DrupalTextile is a subclass of MTLikeTextile that properly handles * Textile footnote URL fragments in a Drupal context. */ class DrupalTextile extends MTLikeTextile { /** * Creates a new DrupalTextile object using the provided options. * * @param $in_options * The array specifying the options for this object. */ function DrupalTextile($in_options = array()) { parent::MTLikeTextile($in_options); } // function DrupalTextile /** * Processes the provided Textile-formatted text, properly handling * URL fragments in a Drupal context. * * @param $in_string * The string specifying the text to be processed. * * @return * A string containing the processed text. */ function process($in_string) { $this->charset('utf8'); // Changed as described in http://drupal.org/node/11357 return preg_replace('{(