Commit 9b619571 authored by jepso's avatar jepso

- New feature in documentation: you can now link to other documentation...

- New feature in documentation: you can now link to other documentation sections with the following syntax:
    - [doc getting-started:installation], or
    - [doc getting-started:installation Custom link text]
- Updated Text_Wiki to 1.2.0
- Documentation should now pass XHTML validator
- Formatted DSN section so that it's easier on eyes
- The single quotes in <code type='php'> won't work anymore due to the Text_Wiki update. Use double quotes instead: <code type="php">. The single quotes have been converted to double quotes in documentation files.
- Modified the links in h1-h6 headings to use the same style as the headings.
- Some refactoring
parent 302503b8
......@@ -6,7 +6,7 @@
Initializing a new cache driver instance:
<code type='php'>
<code type="php">
$cache = new Doctrine_Cache_Memcache($options);
</code>
......@@ -14,7 +14,7 @@ $cache = new Doctrine_Cache_Memcache($options);
+++ Memcache
Memcache driver stores cache records into a memcached server. Memcached is a high-performance, distributed memory object caching system. In order to use this backend, you need a memcached daemon and the memcache PECL extension.
<code type='php'>
<code type="php">
// memcache allows multiple servers
$servers = array('host' => 'localhost',
'port' => 11211,
......@@ -35,7 +35,7 @@ The Alternative PHP Cache (APC) is a free and open opcode cache for PHP. It was
The APC cache driver of Doctrine stores cache records in shared memory.
<code type='php'>
<code type="php">
$cache = new Doctrine_Cache_Apc();
</code>
......@@ -46,7 +46,7 @@ Db caching backend stores cache records into given database. Usually some fast f
Initializing sqlite cache driver can be done as above:
<code type='php'>
<code type="php">
$conn = Doctrine_Manager::connection(new PDO('sqlite::memory:'));
$cache = new Doctrine_Cache_Sqlite(array('connection' => $conn));
......@@ -75,14 +75,14 @@ So not only does the DQL query cache skip the standard database query execution
You can set a connection or manager level cache driver by using Doctrine::ATTR_CACHE. Setting a connection level cache driver means that all queries executed with this connection use the specified cache driver whereas setting a manager level cache driver means that all connections (unless overridden at connection level) will use the given cache driver.
Setting a manager level cache driver:
<code type='php'>
<code type="php">
$manager = Doctrine_Manager::getInstance();
$manager->setAttribute(Doctrine::ATTR_CACHE, $cacheDriver);
</code>
Setting a connection level cache driver:
<code type='php'>
<code type="php">
$manager = Doctrine_Manager::getInstance();
$conn = $manager->openConnection('pgsql://user:pass@localhost/test');
......@@ -90,14 +90,14 @@ $conn->setAttribute(Doctrine::ATTR_CACHE, $cacheDriver);
</code>
Usually the cache entries are valid for only some time. You can set global value for how long the cache entries should be considered valid by using Doctrine::ATTR_CACHE_LIFESPAN.
<code type='php'>
<code type="php">
$manager = Doctrine_Manager::getInstance();
// set the lifespan as one hour (60 seconds * 60 minutes = 1 hour = 3600 secs)
$manager->setAttribute(Doctrine::ATTR_CACHE_LIFESPAN, 3600);
</code>
Now as we have set a cache driver for use we can make a DQL query to use it:
<code type='php'>
<code type="php">
$query = new Doctrine_Query();
// fetch blog titles and the number of comments
......@@ -114,7 +114,7 @@ $entries = $query->execute();
In the previous chapter we used global caching attributes. These attributes can be overriden at the query level. You can override the cache driver by calling useCache with a valid cacheDriver:
<code type='php'>
<code type="php">
$query = new Doctrine_Query();
$query->useCache(new Doctrine_Cache_Apc());
......@@ -122,7 +122,7 @@ $query->useCache(new Doctrine_Cache_Apc());
Also you can override the lifespan attribute by calling setCacheLifeSpan():
<code type='php'>
<code type="php">
$query = new Doctrine_Query();
// set the lifespan as half an hour
......
In order to connect to a database through Doctrine, you have to create a valid DSN - data source name.
Doctrine supports both PEAR DB/MDB2 like data source names as well as PDO style data source names. The following section deals with PEAR like data source names. If you need more info about the PDO-style data source names see http://www.php.net/manual/en/function.PDO-construct.php.
Doctrine supports both PEAR DB/MDB2 like data source names as well as PDO style data source names. The following section deals with PEAR like data source names. If you need more info about the PDO-style data source names see [[php PDO->__construct()]].
The DSN consists in the following parts:
**phptype**: Database backend used in PHP (i.e. mysql , pgsql etc.)
**dbsyntax**: Database used with regards to SQL syntax etc.
**protocol**: Communication protocol to use ( i.e. tcp, unix etc.)
**hostspec**: Host specification (hostname[:port])
**database**: Database to use on the DBMS server
**username**: User name for login
**password**: Password for login
**proto_opts**: Maybe used with protocol
**option**: Additional connection options in URI query string format. options get separated by &. The Following table shows a non complete list of options:
||~ DSN part ||~ Description ||
|| phptype || Database backend used in PHP (i.e. mysql , pgsql etc.) ||
|| dbsyntax || Database used with regards to SQL syntax etc. ||
|| protocol || Communication protocol to use ( i.e. tcp, unix etc.) ||
|| hostspec || Host specification (hostname[:port]) ||
|| database || Database to use on the DBMS server ||
|| username || User name for login ||
|| password || Password for login ||
|| proto_opts || Maybe used with protocol ||
|| option || Additional connection options in URI query string format. Options are separated by ampersand (&). The Following table shows a non complete list of options: ||
**List of options**
......@@ -22,10 +23,12 @@ The DSN consists in the following parts:
|| new_link || Some RDBMS do not create new connections when connecting to the same host multiple times. This option will attempt to force a new connection. ||
The DSN can either be provided as an associative array or as a string. The string format of the supplied DSN is in its fullest form:
`` phptype(dbsyntax)://username:password@protocol+hostspec/database?option=value ``
<code>phptype(dbsyntax)://username:password@protocol+hostspec/database?option=value</code>
Most variations are allowed:
<code>
phptype://username:password@protocol+hostspec:110//usr/db_file.db
phptype://username:password@hostspec/database
phptype://username:password@hostspec
......@@ -36,63 +39,73 @@ phptype:///database
phptype:///database?option=value&anotheroption=anothervalue
phptype(dbsyntax)
phptype
</code>
The currently supported database backends are:
||~ Driver name ||~ Supported databases ||
|| fbsql || FrontBase ||
|| ibase || InterBase / Firebird (requires PHP 5) ||
|| mssql || Microsoft SQL Server (NOT for Sybase. Compile PHP --with-mssql) ||
|| mysql || MySQL ||
|| mysqli || MySQL (supports new authentication protocol) (requires PHP 5) ||
|| oci8 || Oracle 7/8/9/10 ||
|| pgsql || PostgreSQL ||
|| querysim || QuerySim ||
|| sqlite || SQLite 2 ||
The currently supported database backends are:
A second DSN format supported is
<code>
phptype(syntax)://user:pass@protocol(proto_opts)/database
</code>
||//fbsql//|| -> FrontBase ||
||//ibase//|| -> InterBase / Firebird (requires PHP 5) ||
||//mssql//|| -> Microsoft SQL Server (NOT for Sybase. Compile PHP --with-mssql) ||
||//mysql//|| -> MySQL ||
||//mysqli//|| -> MySQL (supports new authentication protocol) (requires PHP 5) ||
||//oci8 //|| -> Oracle 7/8/9/10 ||
||//pgsql//|| -> PostgreSQL ||
||//querysim//|| -> QuerySim ||
||//sqlite//|| -> SQLite 2 ||
A second DSN format is supported phptype(syntax)://user:pass@protocol(proto_opts)/database
If your database, option values, username or password contain characters used to delineate DSN parts, you can escape them via URI hex encodings:
``: = %3a``
``/ = %2f``
``@ = %40``
``+ = %2b``
``( = %28``
``) = %29``
``? = %3f``
``= = %3d``
``& = %26``
||~ Character ||~ Hex Code ||
|| : || %3a ||
|| / || %2f ||
|| @ || %40 ||
|| + || %2b ||
|| ( || %28 ||
|| ) || %29 ||
|| ? || %3f ||
|| = || %3d ||
|| & || %26 ||
Warning
Please note, that some features may be not supported by all database backends.
Example
+++ Examples
**Example 1.** Connect to database through a socket
<code>
mysql://user@unix(/path/to/socket)/pear
</code>
**Example 2.** Connect to database on a non standard port
<code>
pgsql://user:pass@tcp(localhost:5555)/pear
</code>
**Example 3.** Connect to SQLite on a Unix machine using options
<code>
sqlite:////full/unix/path/to/file.db?mode=0666
</code>
**Example 4.** Connect to SQLite on a Windows machine using options
<code>
sqlite:///c:/full/windows/path/to/file.db?mode=0666
</code>
**Example 5.** Connect to MySQLi using SSL
<code>
mysqli://user:pass@localhost/pear?key=client-key.pem&cert=client-cert.pem
</code>
......@@ -11,14 +11,14 @@ people(id, name);
event_participants(event_id, person_id);
+++ Creating a database
<code type='php'>
<code type="php">
$conn->export->createDatabase('events_db');
</code>
+++ Creating tables
Now that the database is created, we can proceed with adding some tables. The method createTable() takes three parameters: the table name, an array of field definition and some extra options (optional and RDBMS-specific). Now lets create the events table:
<code type='php'>
<code type="php">
$definition = array (
'id' => array (
'type' => 'integer',
......@@ -54,7 +54,7 @@ The keys of the definition array are the names of the fields in the table. The v
Creating the people table:
<code type='php'>
<code type="php">
$options = array(
'comment' => 'Repository of people',
'character_set' => 'utf8',
......@@ -82,7 +82,7 @@ $conn->export->createTable('people', $definition, $options);
Creating the event_participants table with a foreign key:
<code type='php'>
<code type="php">
$options = array(
'foreignKeys' => array('local' => 'event_id',
'foreign' => 'id'
......@@ -110,7 +110,7 @@ $conn->export->createTable('event_participants', $definition, $options);
Now lets say we want to add foreign key on person_id too. This can be achieved as follows:
<code type='php'>
<code type="php">
$definition = array('local' => 'person_id',
'foreign' => 'id'
'foreignTable' => 'people'
......@@ -217,7 +217,7 @@ length, integer value
Not all RDBMS will support index sorting or length, in these cases the drivers will ignore them. In the test events database, we can assume that our application will show events occuring in a specific timeframe, so the selects will use the datetime field in WHERE conditions. It will help if there is an index on this field.
<code type='php'>
<code type="php">
$definition = array(
'fields' => array(
'datetime' => array()
......@@ -228,7 +228,7 @@ $conn->export->createIndex('events', 'event_timestamp', $definition);
+++ Deleting database elements
For every create*() method as shown above, there is a corresponding drop*() method to delete a database, a table, field, index or constraint. The drop*() methods do not check if the item to be deleted exists, so it's developer's responsibility to check for exceptions.
<code type='php'>
<code type="php">
// drop a sequence
try {
$conn->export->dropSequence('nonexisting');
......@@ -290,26 +290,26 @@ To see what's in the database, you can use the list*() family of functions in th
+++ Listing databases
<code type='php'>
<code type="php">
$dbs = $conn->import->listDatabases();
print_r($dbs);
</code>
+++ Listing sequences
<code type='php'>
<code type="php">
$seqs = $conn->export->listSequences('events_db');
print_r($seqs);
</code>
+++ Listing constraints
<code type='php'>
<code type="php">
$cons = $conn->export->listTableConstraints('event_participants');
</code>
+++ Listing table fields
<code type='php'>
<code type="php">
$fields = $conn->export->listTableFields('events');
print_r($fields);
/*
......@@ -324,7 +324,7 @@ Array
</code>
+++ Listing table indices
<code type='php'>
<code type="php">
$idx = $conn->export->listTableIndexes('events');
print_r($idx);
/*
......@@ -338,7 +338,7 @@ Array
+++ Listing tables
<code type='php'>
<code type="php">
$tables = $conn->export->listTables();
print_r($tables);
/*
......@@ -354,7 +354,7 @@ Array
+++ Listing views
<code type='php'>
<code type="php">
// currently there is no method to create a view,
// so let's do it "manually"
$sql = "CREATE VIEW names_only AS SELECT name FROM people";
......
......@@ -31,7 +31,7 @@ FROM User u LEFT JOIN u.Email e
In the following example we fetch all users and sort those users by the number of phonenumbers they have.
<code type='php'>
<code type="php">
$q = new Doctrine_Query();
$users = $q->select('u.*, COUNT(p.id) count')
......@@ -44,7 +44,7 @@ $users = $q->select('u.*, COUNT(p.id) count')
In the following example we use random in the ORDER BY clause in order to fetch random post.
<code type='php'>
<code type="php">
$q = new Doctrine_Query();
$posts = $q->select('p.*, RANDOM() rand')
......
......@@ -8,7 +8,7 @@ Doctrine provides flexible event listener architecture that not only allows list
There are several different listeners and hooks for various Doctrine components. Listeners are separate classes where as hooks are empty template methods which are defined in the base class.
An example of using Doctrine_Record hooks:
<code type='php'>
<code type="php">
class Blog extends Doctrine_Record
{
public function setTableDefinition()
......@@ -39,7 +39,7 @@ Connection listeners are used for listening the methods of Doctrine_Connection a
+++ Creating a new listener
There are three different ways of defining a listener. First you can create a listener by making a class that inherits Doctrine_EventListener:
<code type='php'>
<code type="php">
class MyListener extends Doctrine_EventListener
{
public function preExec(Doctrine_Event $event)
......@@ -53,7 +53,7 @@ Note that by declaring a class that extends Doctrine_EventListener you don't hav
Sometimes it may not be possible to define a listener that extends Doctrine_EventListener (you might have a listener that inherits some other base class). In this case you can make it implement Doctrine_EventListener_Interface.
<code type='php'>
<code type="php">
class MyListener implements Doctrine_EventListener_Interface
{
// notice: all listener methods must be defined here
......@@ -70,7 +70,7 @@ class MyListener implements Doctrine_EventListener_Interface
The third way of creating a listener is a very elegant one. You can make a class that implements Doctrine_Overloadable. This interface has only one method: __call(), which can be used for catching *all* the events.
<code type='php'>
<code type="php">
class MyDebugger implements Doctrine_Overloadable
{
public function __call($methodName, $args)
......@@ -84,13 +84,13 @@ class MyDebugger implements Doctrine_Overloadable
You can attach the listeners to a connection with setListener().
<code type='php'>
<code type="php">
$conn->setListener(new MyDebugger());
</code>
If you need to use multiple listeners you can use addListener().
<code type='php'>
<code type="php">
$conn->addListener(new MyDebugger());
$conn->addListener(new MyLogger());
</code>
......@@ -153,7 +153,7 @@ Here is a list of all availible listener methods:
|| postValidate() || Doctrine_Validator::validate() ||
Just like with connection listeners there are three ways of defining a record listener: by extending Doctrine_Record_Listener, by implement Doctrine_Record_Listener_Interface or by implementing Doctrine_Overloadable. In the following we'll create a global level listener by implementing Doctrine_Overloadable:
<code type='php'>
<code type="php">
class Logger extends Doctrine_Overloadable
{
public function __call($m, $a)
......@@ -167,13 +167,13 @@ class Logger extends Doctrine_Overloadable
Attaching the listener to manager is easy:
<code type='php'>
<code type="php">
$manager->addRecordListener(new Logger());
</code>
Note that by adding a manager level listener it affects on all connections and all tables / records within these connections. In the following we create a connection level listener:
<code type='php'>
<code type="php">
class Debugger extends Doctrine_Record_Listener
{
public function preInsert(Doctrine_Event $event)
......@@ -189,13 +189,13 @@ class Debugger extends Doctrine_Record_Listener
Attaching the listener to a connection is as easy as:
<code type='php'>
<code type="php">
$conn->addRecordListener(new Debugger());
</code>
Many times you want the listeners to be table specific so that they only apply on the actions on that given table. Here is an example:
<code type='php'>
<code type="php">
class Debugger extends Doctrine_Record_Listener
{
public function postDelete(Doctrine_Event $event)
......@@ -207,7 +207,7 @@ class Debugger extends Doctrine_Record_Listener
Attaching this listener to given table can be done as follows:
<code type='php'>
<code type="php">
class MyRecord extends Doctrine_Record
{
public function setTableDefinition()
......@@ -238,7 +238,7 @@ class MyRecord extends Doctrine_Record
|| postValidate() || Doctrine_Validator::validate() ||
Example 1. Using insert and update hooks
<code type='php'>
<code type="php">
class Blog extends Doctrine_Record
{
public function setTableDefinition()
......@@ -264,7 +264,7 @@ class Blog extends Doctrine_Record
All different event listeners in Doctrine allow chaining. This means that more than one listener can be attached for listening the same methods. The following example attaches two listeners for given connection:
<code type='php'>
<code type="php">
// here Debugger and Logger both inherit Doctrine_EventListener
$conn->addListener(new Debugger());
......@@ -274,7 +274,7 @@ $conn->addListener(new Logger());
++ The Event object
+++ Getting the invoker
You can get the object that invoked the event by calling getInvoker():
<code type='php'>
<code type="php">
class MyListener extends Doctrine_EventListener
{
public function preExec(Doctrine_Event $event)
......@@ -295,7 +295,7 @@ Doctrine_Event uses constants as event codes. Above is the list of all availible
* Doctrine_Event::STMT_FETCH
* Doctrine_Event::STMT_FETCHALL
<code type='php'>
<code type="php">
class MyListener extends Doctrine_EventListener
{
public function preExec(Doctrine_Event $event)
......@@ -318,7 +318,7 @@ class MyListener extends Doctrine_EventListener
* Doctrine_Event::RECORD_SERIALIZE
* Doctrine_Event::RECORD_UNSERIALIZE
<code type='php'>
<code type="php">
class MyRecord extends Doctrine_Record
{
public function preUpdate(Doctrine_Event $event)
......@@ -331,7 +331,7 @@ class MyRecord extends Doctrine_Record
The method getInvoker() returns the object that invoked the given event. For example for event Doctrine_Event::CONN_QUERY the invoker is a Doctrine_Connection object. Example:
<code type='php'>
<code type="php">
class MyRecord extends Doctrine_Record
{
public function preUpdate(Doctrine_Event $event)
......@@ -346,7 +346,7 @@ Doctrine_Event provides many methods for altering the execution of the listened
For some reason you may want to skip the execution of the listened method. It can be done as follows (note that preExec could be any listener method):
<code type='php'>
<code type="php">
class MyListener extends Doctrine_EventListener
{
public function preExec(Doctrine_Event $event)
......@@ -363,7 +363,7 @@ class MyListener extends Doctrine_EventListener
When using a chain of listeners you might want to skip the execution of the next listener. It can be achieved as follows:
<code type='php'>
<code type="php">
class MyListener extends Doctrine_EventListener
{
public function preExec(Doctrine_Event $event)
......
......@@ -2,7 +2,7 @@
Doctrine_Manager_Exception is thrown if something failed at the connection management
<code type='php'>
<code type="php">
try {
$manager->getConnection('unknown');
} catch (Doctrine_Manager_Exception) {
......@@ -18,7 +18,7 @@ thrown if something failed during the relation parsing
thrown if something failed at the database level
<code type='php'>
<code type="php">
try {
$conn->execute('SELECT * FROM unknowntable');
} catch (Doctrine_Connection_Exception) {
......
......@@ -9,7 +9,7 @@ Doctrine offers method called {{compile()}} to solve this issue. The compile met
Compiling is a method for making a single file of most used doctrine runtime components including the compiled file instead of multiple files (in worst cases dozens of files) can improve performance by an order of magnitude. In cases where this might fail, a {{Doctrine_Exception}} is throw detailing the error.
<code type='php'>
<code type="php">
Doctrine::compile();
// on some other script:
require_once('path_to_doctrine/Doctrine.compiled.php');
......@@ -23,7 +23,7 @@ Doctrine supports exporting record classes into database. This means that based
Lets say we have a classes called User and Phonenumber with the following definitions:
<code type='php'>
<code type="php">
// file User.php
class User extends Doctrine_Record
{
......@@ -56,7 +56,7 @@ class Phonenumber extends Doctrine_Record
Now lets say these classes are in directory 'models/'. We can make Doctrine to iterate through this directory and attach these classes into your database structure with the following script:
<code type='php'>
<code type="php">
require_once('path-to-doctrine/lib/Doctrine.php');
......@@ -86,7 +86,7 @@ Pay attention to the following things:
There might be situations where you don't want to execute the export queries immediately rather you want to get the query strings and maybe attach them to a build.sql file. This can be easily achieved as follows:
<code type='php'>
<code type="php">
require_once('path-to-doctrine/lib/Doctrine.php');
spl_autoload_register(array('Doctrine', 'autoload'));
......@@ -100,7 +100,7 @@ print_r($queries);
</code>
Notice that the methods Doctrine::export() and Doctrine::exportSql() are just short cuts for Doctrine_Export::export() and Doctrine_Export::exportSql(). So the following code is equivalent with the previous example:
<code type='php'>
<code type="php">
require_once('path-to-doctrine/lib/Doctrine.php');
spl_autoload_register(array('Doctrine', 'autoload'));
......@@ -118,7 +118,7 @@ print_r($queries);
+++ Convenience methods
In the previous example we exported all record classes within a directory. Sometimes you may want to export specific classes. It can be achieved by giving exportClasses() an array of class names:
<code type='php'>
<code type="php">
require_once('path-to-doctrine/lib/Doctrine.php');
spl_autoload_register(array('Doctrine', 'autoload'));
......@@ -135,7 +135,7 @@ Consider the same situation and you want to get the array of sql queries needed
+++ Export options
<code type='php'>
<code type="php">
// export everything, table definitions and constraints
$manager->setAttribute(Doctrine::ATTR_EXPORT, Doctrine::EXPORT_ALL);
......
......@@ -18,7 +18,7 @@ If you do not have a SVN client, chose one from the list below. Find the **Check
In order to use Doctrine in your project it must first be included.
<code type='php'>
<code type="php">
require_once('path-to-doctrine/lib/Doctrine.php');
</code>
......@@ -26,7 +26,7 @@ Doctrine support [http://www.php.net/autoload Autoloading] for including files s
If you do use the **__autoload** function for your own logic you can use it.
<code type='php'>
<code type="php">
function __autoload($class) {
Doctrine::autoload($class);
}
......
......@@ -6,7 +6,7 @@ An short example:
We want to create a database table called 'user' with columns id(primary key), name, username, password and created. Provided that you have already installed Doctrine these few lines of code are all you need:
<code type='php'>
<code type="php">
require_once('lib/Doctrine.php');
spl_autoload_register(array('Doctrine', 'autoload'));
......
......@@ -24,7 +24,7 @@ CREATE TABLE file (
Now we would like to convert it into Doctrine record class. It can be achieved easily with the following code snippet:
<code type='php'>
<code type="php">
require_once('lib/Doctrine.php');
spl_autoload_register(array('Doctrine', 'autoload'));
......@@ -38,7 +38,7 @@ $conn->import->import('myrecords');
That's it! Now there should be a file called File.php in your myrecords directory. The file should look like:
<code type='php'>
<code type="php">
/**
* This class has been auto-generated by the Doctrine ORM Framework
* Created: Saturday 10th of February 2007 01:03:15 PM
......
......@@ -344,7 +344,7 @@ The entity table has a column called {{type}} which tells whether an entity is a
The only thing we have to do is to create 3 records (the same as before) and add call the {{Doctrine_Table::setInheritanceMap()}} method inside the {{setUp()}} method.
<code type='php'>
<code type="php">
class Entity extends Doctrine_Record
{
public function setTableDefinition()
......@@ -379,7 +379,7 @@ class Group extends Entity
If we want to be able to fetch a record from the {{Entity}} table and automatically get a {{User}} record if the {{Entity}} we fetched is a user we have to do set the subclasses option in the parent class. The adjusted example:
<code type='php'>
<code type="php">
class Entity extends Doctrine_Record
{
public function setTableDefinition()
......@@ -415,7 +415,7 @@ class Group extends Entity
We can then do the following given the previous table mapping.
<code type='php'>
<code type="php">
$user = new User();
$user->name = 'Bjarte S. Karlsen';
$user->username = 'meus';
......@@ -444,7 +444,7 @@ A foreign key constraint specifies that the values in a column (or a group of co
Say you have the product table with the following definition:
<code type='php'>
<code type="php">
class Product extends Doctrine_Record
{
public function setTableDefinition()
......@@ -458,7 +458,7 @@ class Product extends Doctrine_Record
Let's also assume you have a table storing orders of those products. We want to ensure that the order table only contains orders of products that actually exist. So we define a foreign key constraint in the orders table that references the products table:
<code type='php'>
<code type="php">
class Order extends Doctrine_Record
{
public function setTableDefinition()
......@@ -512,7 +512,7 @@ Rejects the delete or update operation for the parent table. NO ACTION and RESTR
In the following example we define two classes, User and Phonenumber with their relation being one-to-many. We also add a foreign key constraint with onDelete cascade action. This means that everytime a users is being deleted its associated phonenumbers will also be deleted.
<code type='php'>
<code type="php">
class User extends Doctrine_Record
{
public function setTableDefinition()
......
......@@ -6,13 +6,13 @@ Using raw sql for fetching might be useful when you want to utilize database spe
Creating Doctrine_RawSql object is easy:
<code type='php'>
<code type="php">
$q = new Doctrine_RawSql();
</code>
Optionally a connection parameter can be given:
<code type='php'>
<code type="php">
$q = new Doctrine_RawSql($conn); // here $conn is an instance of Doctrine_Connection
</code>
......@@ -22,7 +22,7 @@ The first thing to notice when using Doctrine_RawSql is that you always have to
The following example should clarify the usage of these:
<code type='php'>
<code type="php">
$q = new Doctrine_RawSql();
$q->select('{u.*}')
......@@ -45,7 +45,7 @@ When fetching from multiple components the addComponent calls become a bit more
Consider the following model:
<code type='php'>
<code type="php">
// file User.php
class User extends Doctrine_Record
{
......@@ -78,7 +78,7 @@ class Phonenumber extends Doctrine_Record
In the following example we fetch all users and their phonenumbers:
<code type='php'>
<code type="php">
$q = new Doctrine_RawSql();
$q->select('{u.*}, {p.*}')
......
......@@ -104,7 +104,7 @@ The fields of this type should be able to handle 8 bit characters. Drivers take
By default Doctrine will use variable length character types. If fixed length types should be used can be controlled via the fixed modifier.
<code type='php'>
<code type="php">
class Test extends Doctrine_Record {
public function setTableDefinition() {
$this->hasColumn('stringtest', 'string', 200, array('fixed' => true));
......
......@@ -15,7 +15,7 @@ A not-null constraint simply specifies that a column must not assume the null va
The following definition uses a notnull constraint for column {{name}}. This means that the specified column doesn't accept null values.
<code type='php'>
<code type="php">
class User extends Doctrine_Record
{
public function setTableDefinition()
......@@ -45,7 +45,7 @@ In general, a unique constraint is violated when there are two or more rows in t
The following definition uses a unique constraint for column {{name}}.
<code type='php'>
<code type="php">
class User extends Doctrine_Record
{
public function setTableDefinition()
......@@ -59,7 +59,7 @@ class User extends Doctrine_Record
The following definition adds a unique constraint for columns {{name}} and {{age}}.
<code type='php'>
<code type="php">
class User extends Doctrine_Record
{
public function setTableDefinition()
......@@ -87,7 +87,7 @@ Doctrine provides the following simple check operators:
Consider the following example:
<code type='php'>
<code type="php">
class Product extends Doctrine_Record
{
public function setTableDefinition()
......
......@@ -5,7 +5,7 @@ Doctrine_Record::hasColumn() takes 4 arguments:
# **column length**
# **column constraints and validators**
<code type='php'>
<code type="php">
class Email extends Doctrine_Record {
public function setTableDefinition() {
// setting custom table name:
......
......@@ -7,7 +7,7 @@
{{Doctrine_Connection_Profiler}} can be enabled by adding it as an eventlistener for {{Doctrine_Connection}}.
<code type='php'>
<code type="php">
$conn = Doctrine_Manager::connection($dsn);
$profiler = new Doctrine_Connection_Profiler();
......@@ -18,7 +18,7 @@ $conn->setListener($profiler);
Perhaps some of your pages is loading slowly. The following shows how to build a complete profiler report from the connection:
<code type='php'>
<code type="php">
$totalTime = $profiler->getTotalElapsedSecs();
$queryCount = $profiler->getTotalNumQueries();
$longestTime = 0;
......@@ -43,7 +43,7 @@ echo 'Longest query: ' . $longestQuery . "\n";
++ AuditLog and versioning
Doctrine_AuditLog provides a full versioning solution. Lets say we have a NewsItem class that we want to be versioned. This functionality can be applied by simply adding $this->actAs('Versionable') into your record setup.
<code type='php'>
<code type="php">
class NewsItem extends Doctrine_Record
{
public function setTableDefinition()
......@@ -70,7 +70,7 @@ Now when we have defined this record to be versionable, Doctrine does internally
+++ Using versioning
<code type='php'>
<code type="php">
$newsItem = new NewsItem();
$newsItem->title = 'No news is good news';
$newsItem->content = 'All quiet on the western front';
......@@ -87,7 +87,7 @@ $newsItem->version; // 2
Doctrine_Record provides a method called revert() which can be used for reverting to specified version. Internally Doctrine queries the version table and fetches the data for given version. If the given version is not found a Doctrine_Record_Exception is being thrown.
<code type='php'>
<code type="php">
$newsItem->revert(1);
$newsItem->title; // No news is good news
......@@ -97,7 +97,7 @@ $newsItem->title; // No news is good news
There are many options for the versioning plugin. Sometimes you may want to use other version column than 'version'. This can be achieved by giving the options parameter to actAs() method.
<code type='php'>
<code type="php">
class NewsItem extends Doctrine_Record
{
public function setTableDefinition()
......@@ -124,7 +124,7 @@ Soft-delete is a very simple plugin for achieving the following behaviour: when
The following code snippet shows what you need in order to achieve this kind of behaviour. Notice how we define two event hooks: preDelete and postDelete. Also notice how the preDelete hook skips the actual delete-operation with skipOperation() call. For more info about the event hooks see the Event listener section.
<code type='php'>
<code type="php">
class SoftDeleteTest extends Doctrine_Record
{
public function setTableDefinition()
......@@ -146,7 +146,7 @@ class SoftDeleteTest extends Doctrine_Record
Now lets put the plugin in action:
<code type='php'>
<code type="php">
// save a new record
$record = new SoftDeleteTest();
......
......@@ -11,7 +11,7 @@ In the following example we make a user management system where
# When an entity is updated a current timestamp will be assigned to 'updated' field
# Entities will always be fetched in batches
<code type='php'>
<code type="php">
class Entity extends Doctrine_Record
{
public function setUp()
......
......@@ -4,7 +4,7 @@ Searching is a huge topic, hence an entire chapter has been devoted to a plugin
Consider we have a class called NewsItem with the following definition:
<code type='php'>
<code type="php">
class NewsItem extends Doctrine_Record
{
public function setTableDefinition()
......@@ -27,7 +27,7 @@ contains word 'framework') the database would have to traverse through each row
Doctrine solves this with its search component and inverse indexes. First lets alter our definition a bit:
<code type='php'>
<code type="php">
class NewsItem extends Doctrine_Record
{
public function setTableDefinition()
......
......@@ -7,7 +7,7 @@ Whenever you fetch an object that has not all of its fields loaded from database
Lets say we have a User class with the following definition:
<code type='php'>
<code type="php">
class User extends Doctrine_Record
{
public function setTableDefinition()
......@@ -21,7 +21,7 @@ class User extends Doctrine_Record
In the following example we fetch all the Users with the fields name and password loaded directly. Then we lazy-load a huge field called description for one user.
<code type='php'>
<code type="php">
$users = Doctrine_Query::create()->select('u.name, u.password')->from('User u');
// the following lazy-loads the description fields and executes one additional database query
......
......@@ -92,7 +92,7 @@ foreach($user as $key => $value) {
Updating objects is very easy, you just call the {{Doctrine_Record::save()}} method. The other way (perhaps even easier) is to call {{Doctrine_Connection::flush()}} which saves all objects. It should be noted though that flushing is a much heavier operation than just calling save method.
<code type='php'>
<code type="php">
$table = $conn->getTable('User');
......
<?php
error_reporting(E_ALL);
$includePath = dirname(dirname(dirname(__FILE__))) . DIRECTORY_SEPARATOR . 'vendor';
$includePath = dirname(dirname(dirname(__FILE__))) . DIRECTORY_SEPARATOR . 'vendor'
. PATH_SEPARATOR . dirname(__FILE__) . DIRECTORY_SEPARATOR . 'lib';
set_include_path($includePath);
......@@ -46,12 +47,6 @@ if ($cache->begin()) {
$tool = new DocTool('docs/en.txt');
// $tool->setOption('clean-url', true);
$supportedLangs = array('en', 'fi');
foreach ($supportedLangs as $language) {
include "lang/$language.php";
$tool->addLanguage($lang[$language], $language);
}
$baseUrl = '';
$title = 'Doctrine Manual';
$section = null;
......
<?php
$lang['en'] = array(
'Chapter %s' => 'Chapter %s',
'Contents' => 'Contents'
);
<?php
$lang['fi'] = array(
'Table of Contents' => 'Sisällysluettelo',
'Contents' => 'Sisältö',
'List of Chapters' => 'Luvut',
'Previous' => 'Edellinen',
'Next' => 'Seuraava'
);
......@@ -57,7 +57,7 @@ class Cache
*/
public function clear()
{
if ($handle = opendir($this->_dir)) {
if ($handle = @opendir($this->_dir)) {
while ($file = readdir($handle)) {
if ($file !== '.' && $file !== '..') {
@unlink($this->_dir . '/' . $file);
......
......@@ -8,8 +8,6 @@ class DocTool
private $_wiki;
private $_toc;
private $_options = array('max-level' => 1,
'lang' => 'en',
'default-lang' => 'en',
'one-page' => false,
'section' => null,
'clean-url' => false,
......@@ -18,9 +16,11 @@ class DocTool
public function __construct($filename)
{
$this->_wiki = new Text_Wiki();
$this->_wiki->disableRule('Wikilink');
$this->_toc = new Sensei_Doc_Toc($filename);
$this->_wiki = Text_Wiki::singleton('Doc');
$this->_wiki->setParseConf('Doclink', 'toc', $this->_toc);
$this->_wiki->setRenderConf('xhtml', 'Doclink', 'url_callback', array(&$this, 'makeUrl'));
}
public function getOption($option)
......@@ -32,23 +32,16 @@ class DocTool
{
switch ($option) {
case 'max-level':
if (!is_int($value)) {
throw new Exception('Value must be an integer.');
}
$value = (int) $value;
break;
case 'one-page':
case 'clean-url':
if (!is_bool($value)) {
throw new Exception('Value must be a boolean.');
}
$value = (bool) $value;
break;
case 'locale':
case 'base-url':
if (!is_string($value)) {
throw new Exception('Value must be a string.');
}
$value = (string) $value;
break;
case 'section':
......@@ -61,96 +54,81 @@ class DocTool
throw new Exception('Unknown option.');
}
$this->_wiki->setRenderConf('xhtml', 'Doclink', 'view_url', $this->getUrlPrefix());
$this->_options[$option] = $value;
}
public function addLanguage(array $translations, $lang)
public function renderToc($toc = null)
{
$this->_lang[$lang] = $translations;
}
if (!$toc) {
$toc = $this->_toc;
}
$classes = array();
if ($toc instanceof Sensei_Doc_Toc) {
$class = '';
if ($this->getOption('one-page')) {
$class = ' class="one-page"';
}
$classes[] = 'tree';
} else {
$isParent = false;
$section = $this->getOption('section');
if ($section !== null) {
$current = $section;
do {
if ($current === $toc) {
$isParent = true;
break;
}
} while (($current = $current->getParent()) !== null);
}
if (! $isParent) {
$classes[] = 'closed';
}
}
$classes = implode(' ', $classes);
if ($classes === '') {
echo "<ul>\n";
} else {
echo "<ul class=\"$classes\">\n";
}
for ($i = 0; $i < $toc->count(); $i++) {
$child = $toc->getChild($i);
if ($child === $this->getOption('section')) {
echo '<li class="current">';
} else {
echo '<li>';
}
public function translate($string)
{
$language = $this->getOption('lang');
if (array_key_exists($language, $this->_lang)
&& array_key_exists($string, $this->_lang[language])) {
return $this->_lang[$language][$string];
} else {
return $string;
}
}
public function renderToc($toc = null)
{
if (!$toc) {
$toc = $this->_toc;
}
$classes = array();
if ($toc instanceof Sensei_Doc_Toc) {
$class = '';
if ($this->getOption('one-page')) {
$class = ' class="one-page"';
}
$classes[] = 'tree';
} else {
$isParent = false;
$section = $this->getOption('section');
if ($section !== null) {
$current = $section;
do {
if ($current === $toc) {
$isParent = true;
break;
}
} while (($current = $current->getParent()) !== null);
}
if (! $isParent) {
$classes[] = 'closed';
}
}
$classes = implode(' ', $classes);
if ($classes === '') {
echo "<ul>\n";
} else {
echo "<ul class=\"$classes\">\n";
}
for ($i = 0; $i < $toc->count(); $i++) {
$child = $toc->getChild($i);
if ($child === $this->getOption('section')) {
echo '<li class="current">';
} else {
echo '<li>';
}
echo '<a href="' . $this->makeUrl($child->getPath()) . '">';
echo $child->getIndex() . ' ' . $child->getName() . '</a>';
if ($child->count() > 0) {
echo "\n";
$this->renderToc($child);
}
echo '</li>' . "\n";
}
echo '</ul>' . "\n";
echo '<a href="' . $this->makeUrl($child->getPath()) . '">';
echo $child->getIndex() . ' ' . $child->getName() . '</a>';
if ($child->count() > 0) {
echo "\n";
$this->renderToc($child);
}
echo '</li>' . "\n";
}
echo '</ul>' . "\n";
}
public function makeUrl($path)
public function getUrlPrefix()
{
$prefix = $this->getOption('base-url');
......@@ -160,12 +138,17 @@ class DocTool
} else {
$prefix .= '?chapter=';
}
}
}
return $prefix;
}
public function makeUrl($path)
{
$parts = explode(':', $path);
$firstPath = array_slice($parts, 0, $this->getOption('max-level'));
$href = $prefix . implode(':', $firstPath);
$href = $this->getUrlPrefix() . implode(':', $firstPath);
$anchorName = $this->makeAnchor($path);
if (!empty($anchorName)) {
......@@ -185,60 +168,57 @@ class DocTool
public function render()
{
if ($this->getOption('one-page')) {
for ($i = 0; $i < count($this->_toc); $i++) {
$this->renderSection($this->_toc->getChild($i));
}
} else {
$section = $this->getOption('section');
if (!$section) {
throw new Exception('Section has not been set.');
} else {
$this->renderSection($section);
}
}
if ($this->getOption('one-page')) {
for ($i = 0; $i < count($this->_toc); $i++) {
$this->renderSection($this->_toc->getChild($i));
}
} else {
$section = $this->getOption('section');
if (!$section) {
throw new Exception('Section has not been set.');
} else {
$this->renderSection($section);
}
}
}
protected function renderSection($section)
{
$level = $section->getLevel();
$name = $section->getName();
$index = $section->getIndex();
if ($section->getLevel() == 1) {
echo '<div class="chapter">' . "\n";
echo "<h$level>Chapter $index ";
} else {
echo '<div class="section">' . "\n";
echo "<h$level>$index ";
}
if ($section->getLevel() > $this->getOption('max-level')) {
echo '<a href="#'. $this->makeAnchor($section->getPath()) .'" id="' . $this->makeAnchor($section->getPath()) . '">';
echo $name;
echo '</a>';
} else {
echo $name;
}
echo "</h$level>\n";
if ($level === 1 && !$this->getOption('one-page')) {
//$this->renderToc($this->_toc);
}
echo $this->_wiki->transform($section->getText());
for ($i = 0; $i < count($section); $i++) {
$this->renderSection($section->getChild($i));
}
echo '</div>' . "\n";
}
protected function renderSection($section)
{
$level = $section->getLevel();
$name = $section->getName();
$index = $section->getIndex();
if ($section->getLevel() == 1) {
echo '<div class="chapter">' . "\n";
echo "<h$level>Chapter $index ";
} else {
echo '<div class="section">' . "\n";
echo "<h$level>$index ";
}
if ($section->getLevel() > $this->getOption('max-level')) {
echo '<a href="#'. $this->makeAnchor($section->getPath());
echo '" id="' . $this->makeAnchor($section->getPath()) . '">';
echo $name;
echo '</a>';
} else {
echo $name;
}
echo "</h$level>\n";
echo $this->_wiki->transform($section->getText());
for ($i = 0; $i < count($section); $i++) {
$this->renderSection($section->getChild($i));
}
echo '</div>' . "\n";
}
public function findByPath($path)
{
return $this->_toc->findByPath($path);
......
<?php
/**
* Parse structured wiki text and render into arbitrary formats such as XHTML.
*
* PHP versions 4 and 5
*
* @category Text
* @package Text_Wiki
* @author Justin Patrin <justinpatrin@php.net>
* @license http://www.gnu.org/copyleft/lesser.html LGPL License 2.1
* @version CVS: $Id: Default.php,v 1.1 2006/03/01 16:58:17 justinpatrin Exp $
* @link http://pear.php.net/package/Text_Wiki
*/
require_once('Text/Wiki.php');
/**
* This is the parser for the documentation
*
* @category Text
* @package Text_Wiki
* @author Janne Vanhala <jpvanhal@cc.hut.fi>
* @author Justin Patrin <justinpatrin@php.net>
*/
class Text_Wiki_Doc extends Text_Wiki {
var $rules = array(
'Prefilter',
'Delimiter',
'Code',
'Raw',
'Horiz',
'Break',
'Blockquote',
'List',
'Deflist',
'Table',
'Image',
'Phplookup',
'Newline',
'Paragraph',
'Url',
'Doclink',
'Colortext',
'Strong',
'Bold',
'Emphasis',
'Italic',
'Underline',
'Tt',
'Superscript',
'Subscript',
'Revise',
'Tighten'
);
function Text_Wiki_Doc($rules = null) {
parent::Text_Wiki($rules);
$this->addPath('parse', $this->fixPath(dirname(__FILE__)) . 'Parse/Doc');
$this->addPath('render', $this->fixPath(dirname(__FILE__)) . 'Render/');
$this->setRenderConf('Xhtml', 'Url', 'target', '');
$this->setRenderConf('Xhtml', 'charset', 'UTF-8');
}
}
......@@ -2,7 +2,7 @@
/**
*
* Parses for text marked as "raw" (i.e., to be rendered as-is).
* Parses for text marked as a code example block.
*
* @category Text
*
......@@ -12,17 +12,20 @@
*
* @license LGPL
*
* @version $Id: Raw.php,v 1.2 2006/02/15 10:20:08 toggg Exp $
* @version $Id: Code.php,v 1.11 2007/06/09 23:11:25 justinpatrin Exp $
*
*/
/**
*
* Parses for text marked as "raw" (i.e., to be rendered as-is).
* Parses for text marked as a code example block.
*
* This class implements a Text_Wiki rule to find sections of the source
* text that are not to be processed by Text_Wiki. These blocks of "raw"
* text will be rendered as they were found.
* This class implements a Text_Wiki_Parse to find sections marked as code
* examples. Blocks are marked as the string <code> on a line by itself,
* followed by the inline code example, and terminated with the string
* </code> on a line by itself. The code example is run through the
* native PHP highlight_string() function to colorize it, then surrounded
* with <pre>...</pre> tags when rendered as XHTML.
*
* @category Text
*
......@@ -32,7 +35,7 @@
*
*/
class Text_Wiki_Parse_Raw extends Text_Wiki_Parse {
class Text_Wiki_Parse_Code extends Text_Wiki_Parse {
/**
......@@ -45,15 +48,13 @@ class Text_Wiki_Parse_Raw extends Text_Wiki_Parse {
* @var string
*
*/
var $regex = "/<nowiki>(.*)<\/nowiki>/Ums";
var $regex = ';^<code(\s[^>]*)?>(?:\s*)(.*?)(?:\s*)</code>(\s|$);msi';
/**
*
* Generates a token entry for the matched text. Token options are:
*
* 'text' => The full matched text.
* 'text' => The full matched text, not including the <code></code> tags.
*
* @access public
*
......@@ -66,8 +67,31 @@ class Text_Wiki_Parse_Raw extends Text_Wiki_Parse {
function process(&$matches)
{
$options = array('text' => $matches[1]);
return $this->wiki->addToken($this->rule, $options);
// are there additional attribute arguments?
$args = trim($matches[1]);
if ($args == '') {
$options = array(
'text' => $matches[2],
'attr' => array('type' => '')
);
} else {
// get the attributes...
$attr = $this->getAttrs($args);
// ... and make sure we have a 'type'
if (! isset($attr['type'])) {
$attr['type'] = '';
}
// retain the options
$options = array(
'text' => $matches[2],
'attr' => $attr
);
}
return $this->wiki->addToken($this->rule, $options) . $matches[3];
}
}
?>
<?php
class Text_Wiki_Parse_Doclink extends Text_Wiki_Parse {
var $conf = array(
'toc' => null
);
var $regex = '/\[doc ([a-z0-9-]+(?::[a-z0-9-]+)*)(?: ([^\n\]]*))?]/';
function process(&$matches)
{
$toc = $this->getConf('toc');
if ($toc instanceof Sensei_Doc_Toc) {
$section = $toc->findByPath($matches[1]);
}
if (isset($section)) {
$options = array();
$options['path'] = $matches[1];
if (isset($matches[2])) {
$options['text'] = $matches[2];
} else {
$options['text'] = $section->getIndex() . ' ' . $section->getName(true);
}
return $this->wiki->addToken($this->rule, $options);
} else {
return $matches[0];
}
}
}
<?php
// vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4:
/**
* Blockquote rule end renderer for Xhtml
*
* PHP versions 4 and 5
*
* @category Text
* @package Text_Wiki
* @author Paul M. Jones <pmjones@php.net>
* @license http://www.gnu.org/copyleft/lesser.html LGPL License 2.1
* @version CVS: $Id: Blockquote.php,v 1.9 2007/05/26 18:25:23 mic Exp $
* @link http://pear.php.net/package/Text_Wiki
*/
/**
* This class renders a blockquote in XHTML.
*
* @category Text
* @package Text_Wiki
* @author Paul M. Jones <pmjones@php.net>
* @license http://www.gnu.org/copyleft/lesser.html LGPL License 2.1
* @version Release: @package_version@
* @link http://pear.php.net/package/Text_Wiki
*/
class Text_Wiki_Render_Xhtml_Blockquote extends Text_Wiki_Render {
var $conf = array(
'css' => null
);
/**
*
* Renders a token into text matching the requested format.
*
* @access public
*
* @param array $options The "options" portion of the token (second
* element).
*
* @return string The text rendered from the token options.
*
*/
function token($options)
{
$type = $options['type'];
$level = $options['level'];
// set up indenting so that the results look nice; we do this
// in two steps to avoid str_pad mathematics. ;-)
$pad = str_pad('', $level, "\t");
$pad = str_replace("\t", ' ', $pad);
// pick the css type
$css = $this->formatConf(' class="%s"', 'css');
if (isset($options['css'])) {
$css = ' class="' . $options['css']. '"';
}
// starting
if ($type == 'start') {
$output = $pad;
if ($level > 1) {
$output .= '</p>';
}
$output .= "<blockquote$css><p>";
return $output;
}
// ending
if ($type == 'end') {
$output = $pad . "</p></blockquote>\n";
if ($level > 1) {
$output .= '<p>';
}
return $output;
}
}
}
?>
<?php
// vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4:
/**
* Code rule end renderer for Xhtml
*
* PHP versions 4 and 5
*
* @category Text
* @package Text_Wiki
* @author Paul M. Jones <pmjones@php.net>
* @license http://www.gnu.org/copyleft/lesser.html LGPL License 2.1
* @version CVS: $Id: Code.php,v 1.13 2006/02/10 23:07:03 toggg Exp $
* @link http://pear.php.net/package/Text_Wiki
*/
/**
* This class renders code blocks in XHTML.
*
* @category Text
* @package Text_Wiki
* @author Paul M. Jones <pmjones@php.net>
* @license http://www.gnu.org/copyleft/lesser.html LGPL License 2.1
* @version Release: @package_version@
* @link http://pear.php.net/package/Text_Wiki
*/
class Text_Wiki_Render_Xhtml_Code extends Text_Wiki_Render {
var $conf = array(
'css' => null, // class for <pre>
'css_code' => null, // class for generic <code>
'css_php' => null, // class for PHP <code>
'css_html' => null, // class for HTML <code>
'css_filename' => null // class for optional filename <div>
);
/**
*
* Renders a token into text matching the requested format.
*
* @access public
*
* @param array $options The "options" portion of the token (second
* element).
*
* @return string The text rendered from the token options.
*
*/
function token($options)
{
$text = $options['text'];
$attr = $options['attr'];
$type = strtolower($attr['type']);
$css = $this->formatConf(' class="%s"', 'css');
$css_code = $this->formatConf(' class="%s"', 'css_code');
$css_php = $this->formatConf(' class="%s"', 'css_php');
$css_html = $this->formatConf(' class="%s"', 'css_html');
$css_filename = $this->formatConf(' class="%s"', 'css_filename');
if ($type == 'php') {
if (substr($options['text'], 0, 5) != '<?php') {
// PHP code example:
// add the PHP tags
$text = "<?php\n\n" . $options['text'] . "\n\n?>"; // <?php
}
// convert tabs to four spaces
$text = str_replace("\t", " ", $text);
// colorize the code block (also converts HTML entities and adds
// <pre>...</pre> tags)
$h = new PHP_Highlight(true);
$h->loadString($text);
$text = $h->toHtml(true);
$text = str_replace('&nbsp;', ' ', $text);
$text = str_replace('<pre>', "<pre><code$css_php>", $text);
$text = str_replace('</pre>', "</code></pre>", $text);
} elseif ($type == 'html' || $type == 'xhtml') {
// HTML code example:
// add <html> opening and closing tags,
// convert tabs to four spaces,
// convert entities.
$text = str_replace("\t", " ", $text);
$text = "<html>\n$text\n</html>";
$text = $this->textEncode($text);
$text = "<pre$css><code$css_html>$text</code></pre>";
} else {
// generic code example:
// convert tabs to four spaces,
// convert entities.
$text = str_replace("\t", " ", $text);
$text = $this->textEncode($text);
$text = "<pre$css><code$css_code>$text</code></pre>";
}
if ($css_filename && isset($attr['filename'])) {
$text = "<div$css_filename>" .
$attr['filename'] . '</div>' . $text;
}
return "\n$text\n\n";
}
}
?>
<?php
class Text_Wiki_Render_Xhtml_Doclink extends Text_Wiki_Render {
var $conf = array(
'url_callback' => null,
'css' => null
);
function token($options)
{
$callback = $this->getConf('url_callback');
if ($callback) {
$href = call_user_func($callback, $options['path']);
} else {
$href = $options['path'];
}
if ($this->getConf('css')) {
$css = ' class="' . $this->getConf('css') . '"';
} else {
$css = '';
}
$output = '<a href="' . $href . '">' . $options['text'] . '</a>';
return $output;
}
}
......@@ -92,9 +92,19 @@ a:visited:active {
background-color: inherit;
}
h1 a:link, h1 a:visited,
h2 a:link, h2 a:visited,
h3 a:link, h3 a:visited,
h4 a:link, h4 a:visited,
h5 a:link, h5 a:visited,
h6 a:link, h6 a:visited {
text-decoration: none !important;
color: black !important;
}
/*** Code blocks and highlighting**********************************************/
pre, tt {
pre, code, tt {
font-family: "Bitstream Vera Sans Mono", monospace;
font-size: small;
}
......
......@@ -9,7 +9,7 @@
* @package Text_Wiki
* @author Paul M. Jones <pmjones@php.net>
* @license http://www.gnu.org/copyleft/lesser.html LGPL License 2.1
* @version CVS: $Id: Wiki.php,v 1.48 2006/10/10 19:34:38 justinpatrin Exp $
* @version CVS: $Id: Wiki.php,v 1.51 2007/06/09 23:17:46 justinpatrin Exp $
* @link http://pear.php.net/package/Text_Wiki
*/
......@@ -33,7 +33,7 @@ require_once 'Text/Wiki/Render.php';
* @package Text_Wiki
* @author Paul M. Jones <pmjones@php.net>
* @license http://www.gnu.org/copyleft/lesser.html LGPL License 2.1
* @version Release: 1.2.0RC1
* @version Release: 1.2.0
* @link http://pear.php.net/package/Text_Wiki
*/
class Text_Wiki {
......@@ -256,6 +256,13 @@ class Text_Wiki {
var $source = '';
/**
* The output text
*
* @var string
*/
var $output = '';
/**
*
......@@ -337,9 +344,32 @@ class Text_Wiki {
/**
* Temporary configuration variable
*
* @var string
*/
var $renderingType = 'preg';
/**
* Stack of rendering callbacks
*
* @var Array
*/
var $_renderCallbacks = array();
/**
* Current output block
*
* @var string
*/
var $_block;
/**
* A stack of blocks
*
* @param Array
*/
var $_blocks;
/**
*
* Constructor.
......@@ -356,7 +386,10 @@ class Text_Wiki {
function Text_Wiki($rules = null)
{
if (is_array($rules)) {
$this->rules = $rules;
$this->rules = array();
foreach ($rules as $rule) {
$this->rules[] = ucfirst($rule);
}
}
$this->addPath(
......@@ -408,15 +441,15 @@ class Text_Wiki {
* in further calls it will be effectively ignored.
* @return &object a reference to the Text_Wiki unique instantiation.
*/
function singleton($parser = 'Default', $rules = null)
function &singleton($parser = 'Default', $rules = null)
{
static $only = array();
if (!isset($only[$parser])) {
$ret = Text_Wiki::factory($parser, $rules);
$ret = & Text_Wiki::factory($parser, $rules);
if (Text_Wiki::isError($ret)) {
return $ret;
}
$only[$parser] = $ret;
$only[$parser] =& $ret;
}
return $only[$parser];
}
......@@ -432,7 +465,7 @@ class Text_Wiki {
* {@see Text_Wiki::singleton} for a list of rules
* @return Text_Wiki a Parser object extended from Text_Wiki
*/
function factory($parser = 'Default', $rules = null)
function &factory($parser = 'Default', $rules = null)
{
$class = 'Text_Wiki_' . $parser;
$file = str_replace('_', '/', $class).'.php';
......@@ -445,7 +478,7 @@ class Text_Wiki {
}
}
$obj = new $class($rules);
$obj =& new $class($rules);
return $obj;
}
......@@ -920,7 +953,7 @@ class Text_Wiki {
// load may have failed; only parse if
// an object is in the array now
if (isset($this->parseObj[$name]) && is_object($this->parseObj[$name])) {
if (is_object($this->parseObj[$name])) {
$this->parseObj[$name]->parse();
}
}
......@@ -948,7 +981,7 @@ class Text_Wiki {
$format = ucwords(strtolower($format));
// the eventual output text
$output = '';
$this->output = '';
// when passing through the parsed source text, keep track of when
// we are in a delimited section
......@@ -965,7 +998,7 @@ class Text_Wiki {
// pre-rendering activity
if (is_object($this->formatObj[$format])) {
$output .= $this->formatObj[$format]->pre();
$this->output .= $this->formatObj[$format]->pre();
}
// load the render objects
......@@ -974,11 +1007,32 @@ class Text_Wiki {
}
if ($this->renderingType == 'preg') {
$output = preg_replace_callback('/'.$this->delim.'(\d+)'.$this->delim.'/',
$this->output = preg_replace_callback('/'.$this->delim.'(\d+)'.$this->delim.'/',
array(&$this, '_renderToken'),
$this->source);
/*
//Damn strtok()! Why does it "skip" empty parts of the string. It's useless now!
} elseif ($this->renderingType == 'strtok') {
echo '<pre>'.htmlentities($this->source).'</pre>';
$t = strtok($this->source, $this->delim);
$inToken = true;
$i = 0;
while ($t !== false) {
echo 'Token: '.$i.'<pre>"'.htmlentities($t).'"</pre><br/><br/>';
if ($inToken) {
//$this->output .= $this->renderObj[$this->tokens[$t][0]]->token($this->tokens[$t][1]);
} else {
$this->output .= $t;
}
$inToken = !$inToken;
$t = strtok($this->delim);
++$i;
}
*/
} else {
// pass through the parsed source text character by character
$this->_block = '';
$tokenStack = array();
$k = strlen($this->source);
for ($i = 0; $i < $k; $i++) {
......@@ -991,17 +1045,34 @@ class Text_Wiki {
// yes; are we ending the section?
if ($char == $this->delim) {
if (count($this->_renderCallbacks) == 0) {
$this->output .= $this->_block;
$this->_block = '';
}
if (isset($opts['type'])) {
if ($opts['type'] == 'start') {
array_push($tokenStack, $rule);
} elseif ($opts['type'] == 'end') {
if ($tokenStack[count($tokenStack) - 1] != $rule) {
return Text_Wiki::error('Unbalanced tokens, check your syntax');
} else {
array_pop($tokenStack);
}
}
}
// yes, get the replacement text for the delimited
// token number and unset the flag.
$key = (int)$key;
$rule = $this->tokens[$key][0];
$opts = $this->tokens[$key][1];
$output .= $this->renderObj[$rule]->token($opts);
$this->_block .= $this->renderObj[$rule]->token($opts);
$in_delim = false;
} else {
// no, add to the dlimited token key number
// no, add to the delimited token key number
$key .= $char;
}
......@@ -1015,21 +1086,35 @@ class Text_Wiki {
// set the flag.
$key = '';
$in_delim = true;
} else {
// no, add to the output as-is
$output .= $char;
$this->_block .= $char;
}
}
}
}
if (count($this->_renderCallbacks)) {
return $this->error('Render callbacks left over after processing finished');
}
/*
while (count($this->_renderCallbacks)) {
$this->popRenderCallback();
}
*/
if (strlen($this->_block)) {
$this->output .= $this->_block;
$this->_block = '';
}
// post-rendering activity
if (is_object($this->formatObj[$format])) {
$output .= $this->formatObj[$format]->post();
$this->output .= $this->formatObj[$format]->post();
}
// return the rendered source text.
return $output;
return $this->output;
}
/**
......@@ -1043,6 +1128,28 @@ class Text_Wiki {
return $this->renderObj[$this->tokens[$matches[1]][0]]->token($this->tokens[$matches[1]][1]);
}
function registerRenderCallback($callback) {
$this->_blocks[] = $this->_block;
$this->_block = '';
$this->_renderCallbacks[] = $callback;
}
function popRenderCallback() {
if (count($this->_renderCallbacks) == 0) {
return Text_Wiki::error('Render callback popped when no render callbacks in stack');
} else {
$callback = array_pop($this->_renderCallbacks);
$this->_block = call_user_func($callback, $this->_block);
if (count($this->_blocks)) {
$parentBlock = array_pop($this->_blocks);
$this->_block = $parentBlock.$this->_block;
}
if (count($this->_renderCallbacks) == 0) {
$this->output .= $this->_block;
$this->_block = '';
}
}
}
/**
*
......@@ -1222,7 +1329,7 @@ class Text_Wiki {
}
}
$this->parseObj[$rule] = new $class($this);
$this->parseObj[$rule] =& new $class($this);
}
......@@ -1258,7 +1365,7 @@ class Text_Wiki {
}
}
$this->renderObj[$rule] = new $class($this);
$this->renderObj[$rule] =& new $class($this);
}
......@@ -1291,7 +1398,7 @@ class Text_Wiki {
}
}
$this->formatObj[$format] = new $class($this);
$this->formatObj[$format] =& new $class($this);
}
......
<?php
// vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4:
/**
* Parse structured wiki text and render into arbitrary formats such as XHTML.
* This is the Text_Wiki extension for Mediawiki markup
*
* PHP versions 4 and 5
*
* @category Text
* @package Text_Wiki
* @author Bertrand Gugger <bertrand@toggg.com>
* @author Paul M. Jones <pmjones@php.net>
* @license http://www.gnu.org/copyleft/lesser.html LGPL License 2.1
* @version CVS: $Id: Mediawiki.php,v 1.8 2006/02/25 09:59:34 toggg Exp $
* @link http://pear.php.net/package/Text_Wiki
*/
/**
* "master" class for handling the management and convenience
*/
require_once('Text/Wiki.php');
/**
* Base Text_Wiki handler class extension for Mediawiki markup
*
* @category Text
* @package Text_Wiki
* @author Bertrand Gugger <bertrand@toggg.com>
* @license http://www.gnu.org/copyleft/lesser.html LGPL License 2.1
* @version Release: @package_version@
* @link http://pear.php.net/package/Text_Wiki
* @see Text_Wiki::Text_Wiki()
*/
class Text_Wiki_Mediawiki extends Text_Wiki {
var $rules = array(
'Prefilter',
'Delimiter',
// 'Code',
// 'Plugin',
// 'Function',
// 'Html',
'Raw',
// 'Preformatted',
// 'Include',
// 'Embed',
// 'Page',
// 'Anchor',
'Heading',
'Toc',
// 'Titlebar',
'Horiz',
'Break',
// 'Blockquote',
'List',
'Deflist',
'Table',
// 'Box',
// 'Image', // done by Wikilink but still possible to disable/configure
// 'Phplookup',
// 'Center',
'Newline',
'Paragraph',
'Url',
// 'Freelink',
// 'Colortext',
'Wikilink',
// 'Strong', ** will be only fake inserted by Emphasis if needed for render
// 'Bold',
'Emphasis',
// 'Italic',
// 'Underline',
// 'Tt',
// 'Superscript',
// 'Subscript',
// 'Specialchar',
// 'Revise',
// 'Interwiki', // done by Wikilink but still possible to disable/configure
'Tighten'
);
/**
* Constructor: just adds the path to Mediawiki rules
*
* @access public
* @param array $rules The set of rules to load for this object.
*/
function Text_Wiki_Mediawiki($rules = null) {
parent::Text_Wiki($rules);
$this->addPath('parse', $this->fixPath(dirname(__FILE__)).'Parse/Mediawiki');
}
}
?>
......@@ -233,12 +233,6 @@ class Text_Wiki_Parse {
{
// find the =" sections;
$tmp = explode('="', trim($text));
$mark = '"';
if (count($tmp) == 1) {
$tmp = explode("='", trim($text));
$mark = "'";
}
// basic setup
$k = count($tmp) - 1;
......@@ -257,11 +251,12 @@ class Text_Wiki_Parse {
// find the last double-quote in the value.
// the part to the left is the value for the last key,
// the part to the right is the next key name
$pos = strrpos($val, $mark);
$pos = strrpos($val, '"');
$attrs[$key] = stripslashes(substr($val, 0, $pos));
$key = trim(substr($val, $pos+1));
}
return $attrs;
}
......
......@@ -12,7 +12,7 @@
*
* @license LGPL
*
* @version $Id: Blockquote.php,v 1.3 2005/02/23 17:38:29 pmjones Exp $
* @version $Id: Blockquote.php,v 1.4 2006/10/21 05:56:28 justinpatrin Exp $
*
*/
......@@ -76,7 +76,7 @@ class Text_Wiki_Parse_Blockquote extends Text_Wiki_Parse {
function process(&$matches)
{
// the replacement text we will return to parse()
$return = '';
$return = "\n";
// the list of post-processing matches
$list = array();
......@@ -91,10 +91,8 @@ class Text_Wiki_Parse_Blockquote extends Text_Wiki_Parse {
PREG_SET_ORDER
);
// a stack of starts and ends; we keep this so that we know what
// indent level we're at.
$stack = array();
$curLevel = 0;
// loop through each list-item element.
foreach ($list as $key => $val) {
......@@ -105,75 +103,78 @@ class Text_Wiki_Parse_Blockquote extends Text_Wiki_Parse {
// we number levels starting at 1, not zero
$level = strlen($val[1]);
// get the text of the line
$text = $val[2];
// add a level to the list?
while ($level > count($stack)) {
while ($level > $curLevel) {
// the current indent level is greater than the number
// of stack elements, so we must be starting a new
// level. push the new level onto the stack with a
// dummy value (boolean true)...
array_push($stack, true);
++$curLevel;
$return .= "\n";
//$return .= "\n";
// ...and add a start token to the return.
$return .= $this->wiki->addToken(
$this->rule,
array(
'type' => 'start',
'level' => $level - 1
'level' => $curLevel
)
);
$return .= "\n\n";
//$return .= "\n\n";
}
// remove a level?
while (count($stack) > $level) {
while ($curLevel > $level) {
// as long as the stack count is greater than the
// current indent level, we need to end list types.
// continue adding end-list tokens until the stack count
// and the indent level are the same.
array_pop($stack);
$return .= "\n\n";
//$return .= "\n\n";
$return .= $this->wiki->addToken(
$this->rule,
array (
'type' => 'end',
'level' => count($stack)
'level' => $curLevel
)
);
$return .= "\n";
//$return .= "\n";
--$curLevel;
}
// add the line text.
$return .= $text;
$return .= $val[2];
}
// the last char of the matched pattern must be \n but we don't
// want this to be inside the tokens
$return = substr($return, 0, -1);
// the last line may have been indented. go through the stack
// and create end-tokens until the stack is empty.
$return .= "\n";
while (count($stack) > 0) {
array_pop($stack);
//$return .= "\n";
while ($curLevel > 0) {
$return .= $this->wiki->addToken(
$this->rule,
array (
'type' => 'end',
'level' => count($stack)
'level' => $curLevel
)
);
--$curLevel;
}
// put back the trailing \n
$return .= "\n";
// we're done! send back the replacement text.
return "\n$return\n\n";
return $return;
}
}
?>
\ No newline at end of file
......@@ -12,7 +12,7 @@
*
* @license LGPL
*
* @version $Id: Code.php,v 1.10 2006/02/21 14:33:53 toggg Exp $
* @version $Id: Code.php,v 1.11 2007/06/09 23:11:25 justinpatrin Exp $
*
*/
......@@ -50,8 +50,7 @@ class Text_Wiki_Parse_Code extends Text_Wiki_Parse {
*/
/* var $regex = '/^(\<code( .+)?\>)\n(.+)\n(\<\/code\>)(\s|$)/Umsi';*/
/* var $regex = ';^<code(\s[^>]*)?>((?:(?R)|.)*?)\n</code>(\s|$);msi';*/
var $regex = ';^<code(\s[^>]*)?>\n(.+?)\n</code>(\s|$);msi';
var $regex = ';^<code(\s[^>]*)?>((?:(?R)|.*?)*)\n</code>(\s|$);msi';
/**
*
......@@ -72,7 +71,7 @@ class Text_Wiki_Parse_Code extends Text_Wiki_Parse {
{
// are there additional attribute arguments?
$args = trim($matches[1]);
if ($args == '') {
$options = array(
'text' => $matches[2],
......@@ -81,7 +80,7 @@ class Text_Wiki_Parse_Code extends Text_Wiki_Parse {
} else {
// get the attributes...
$attr = $this->getAttrs($args);
// ... and make sure we have a 'type'
if (! isset($attr['type'])) {
$attr['type'] = '';
......
......@@ -12,7 +12,7 @@
*
* @license LGPL
*
* @version $Id: Freelink.php,v 1.4 2005/10/19 23:43:53 toggg Exp $
* @version $Id: Freelink.php,v 1.8 2006/12/08 08:23:51 justinpatrin Exp $
*
*/
......@@ -41,6 +41,9 @@
class Text_Wiki_Parse_Freelink extends Text_Wiki_Parse {
var $conf = array (
'utf-8' => false
);
/**
*
......@@ -56,16 +59,20 @@ class Text_Wiki_Parse_Freelink extends Text_Wiki_Parse {
function Text_Wiki_Parse_Freelink(&$obj)
{
parent::Text_Wiki_Parse($obj);
if ($this->getConf('utf-8')) {
$any = '\p{L}';
} else {
$any = '';
}
$this->regex =
'/' . // START regex
"\\(\\(" . // double open-parens
"(" . // START freelink page patter
"[-A-Za-z0-9 _+\\/.,;:!?'\"\\[\\]\\{\\}&\xc0-\xff]+" . // 1 or more of just about any character
"[-A-Za-z0-9 _+\\/.,;:!?'\"\\[\\]\\{\\}&".$any."\xc0-\xff]+" . // 1 or more of just about any character
")" . // END freelink page pattern
"(" . // START display-name
"\|" . // a pipe to start the display name
"[-A-Za-z0-9 _+\\/.,;:!?'\"\\[\\]\\{\\}&\xc0-\xff]+" . // 1 or more of just about any character
"[-A-Za-z0-9 _+\\/.,;:!?'\"\\[\\]\\{\\}&".$any."\xc0-\xff]+" . // 1 or more of just about any character
")?" . // END display-name pattern 0 or 1
"(" . // START pattern for named anchors
"\#" . // a hash mark
......@@ -73,7 +80,7 @@ class Text_Wiki_Parse_Freelink extends Text_Wiki_Parse {
"[-A-Za-z0-9_:.]*" . // 0 or more alpha, digit, underscore
")?" . // END named anchors pattern 0 or 1
"()\\)\\)" . // double close-parens
'/'; // END regex
'/'.($this->getConf('utf-8') ? 'u' : ''); // END regex
}
......
......@@ -12,7 +12,7 @@
*
* @license LGPL
*
* @version $Id: Prefilter.php,v 1.3 2005/02/23 17:38:29 pmjones Exp $
* @version $Id: Prefilter.php,v 1.4 2006/12/08 08:30:37 justinpatrin Exp $
*
*/
......@@ -65,6 +65,12 @@ class Text_Wiki_Parse_Prefilter extends Text_Wiki_Parse {
// add extra newlines at the top and end; this
// seems to help many rules.
$this->wiki->source = "\n" . $this->wiki->source . "\n\n";
$this->wiki->source = str_replace("\n----\n","\n\n----\n\n",
$this->wiki->source);
$this->wiki->source = preg_replace("/\n(\\+{1,6})(.*)\n/m",
"\n\n\\1 \\2\n\n",
$this->wiki->source);
// finally, compress all instances of 3 or more newlines
// down to two newlines.
......
......@@ -12,7 +12,7 @@
*
* @license LGPL
*
* @version $Id: Wikilink.php,v 1.5 2005/09/14 14:29:38 toggg Exp $
* @version $Id: Wikilink.php,v 1.9 2006/12/08 08:23:51 justinpatrin Exp $
*
*/
......@@ -45,7 +45,8 @@
class Text_Wiki_Parse_Wikilink extends Text_Wiki_Parse {
var $conf = array (
'ext_chars' => false
'ext_chars' => false,
'utf-8' => false
);
/**
......@@ -65,13 +66,17 @@ class Text_Wiki_Parse_Wikilink extends Text_Wiki_Parse {
{
parent::Text_Wiki_Parse($obj);
if ($this->getConf('ext_chars')) {
if ($this->getConf('utf-8')) {
$upper = 'A-Z\p{Lu}';
$lower = 'a-z0-9\p{Ll}';
$either = 'A-Za-z0-9\p{L}';
} else if ($this->getConf('ext_chars')) {
// use an extended character set; this should
// allow for umlauts and so on. taken from the
// Tavi project defaults.php file.
$upper = "A-Z\xc0-\xde";
$lower = "a-z0-9\xdf-\xfe";
$either = "A-Za-z0-9\xc0-\xfe";
$upper = 'A-Z\xc0-\xde';
$lower = 'a-z0-9\xdf-\xfe';
$either = 'A-Za-z0-9\xc0-\xfe';
} else {
// the default character set, should be fine
// for most purposes.
......@@ -111,7 +116,7 @@ class Text_Wiki_Parse_Wikilink extends Text_Wiki_Parse {
function parse()
{
// described wiki links
$tmp_regex = '/\[' . $this->regex . ' (.+?)\]/';
$tmp_regex = '/\[' . $this->regex . ' (.+?)\]/'.($this->getConf('utf-8') ? 'u' : '');
$this->wiki->source = preg_replace_callback(
$tmp_regex,
array(&$this, 'processDescr'),
......@@ -119,13 +124,15 @@ class Text_Wiki_Parse_Wikilink extends Text_Wiki_Parse {
);
// standalone wiki links
if ($this->getConf('ext_chars')) {
if ($this->getConf('utf-8')) {
$either = 'A-Za-z0-9\p{L}';
} else if ($this->getConf('ext_chars')) {
$either = "A-Za-z0-9\xc0-\xfe";
} else {
$either = "A-Za-z0-9";
}
$tmp_regex = "/(^|[^{$either}\-_]){$this->regex}/";
$tmp_regex = "/(^|[^{$either}\-_]){$this->regex}/".($this->getConf('utf-8') ? 'u' : '');
$this->wiki->source = preg_replace_callback(
$tmp_regex,
array(&$this, 'process'),
......
<?php
// vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4:
/**
*
* Mediawiki: Parse for definition lists.
*
* @category Text
* @package Text_Wiki
* @author Justin Patrin <papercrane@reversefold.com>
* @author Paul M. Jones <pmjones@php.net>
* @author Moritz Venn <ritzmo@php.net>
* @license LGPL
* @version $Id: Deflist.php,v 1.1 2006/03/29 18:41:43 ritzmo Exp $
*
*/
/**
*
* Parses for definition lists.
*
* This class implements a Text_Wiki_Parse to find source text marked as a
* definition list.
* If a line starts with ';' or ':' it is considered a part of a definition
* list. ';' indicates the term to be defined and ':' indicates its definition.
* As in Mediawiki we also allow definition lists to only consist of one
* item-type.
*
* @category Text
* @package Text_Wiki
*
* @author Justin Patrin <papercrane@reversefold.com>
* @author Paul M. Jones <pmjones@php.net>
* @author Moritz Venn <ritzmo@php.net>
*
*/
class Text_Wiki_Parse_Deflist extends Text_Wiki_Parse {
/**
*
* The regular expression used to parse the source text and find
* matches conforming to this rule. Used by the parse() method.
*
* @access public
*
* @var string
*
* @see parse()
*
*/
var $regex = '/\n((?:\;|\:)+.*?\n(?!(?:\;|\:)+))/s';
/**
*
* Generates a replacement for the matched text. Token options are:
*
* 'type' =>
* 'list_start' : the start of a definition list
* 'list_end' : the end of a definition list
* 'term_start' : the start of a definition term
* 'term_end' : the end of a definition term
* 'narr_start' : the start of definition narrative
* 'narr_end' : the end of definition narrative
* 'unknown' : unknown type of definition portion
*
* 'level' => the indent level (0 for the first level, 1 for the
* second, etc)
*
* 'count' => the list item number at this level. not needed for
* xhtml, but very useful for PDF and RTF.
*
* @access public
*
* @param array &$matches The array of matches from parse().
*
* @return A series of text and delimited tokens marking the different
* list text and list elements.
*
*/
function process(&$matches)
{
// the replacement text we will return
$return = '';
// the list of post-processing matches
$list = array();
// a stack of list-start and list-end types; we keep this
// so that we know what kind of list we're working with
// (bullet or number) and what indent level we're at.
$stack = array();
// the item count is the number of list items for any
// given list-type on the stack
$itemcount = array();
// have we processed the very first list item?
$pastFirst = false;
// populate $list with this set of matches. $matches[1] is the
// text matched as a list set by parse().
preg_match_all(
'/^((;|:)+)(.*?)$/ms',
$matches[1],
$list,
PREG_SET_ORDER
);
// loop through each list-item element.
foreach ($list as $key => $val) {
// $val[0] is the full matched list-item line
// $val[1] is the type (* or #)
// $val[2] is the level (number)
// $val[3] is the list item text
// how many levels are we indented? (1 means the "root"
// list level, no indenting.)
$level = strlen($val[1]);
// get the list item type
if ($val[2] == ';') {
$type = 'term';
} elseif ($val[2] == ':') {
$type = 'narr';
} else {
$type = 'unknown';
}
// get the text of the list item
$text = $val[3];
// add a level to the list?
if ($level > count($stack)) {
// the current indent level is greater than the
// number of stack elements, so we must be starting
// a new list. push the new list type onto the
// stack...
array_push($stack, $type);
// The new list has to be opened in an item (use current type)
if ($level > 1) {
$return .= $this->wiki->addToken(
$this->rule,
array(
'type' => $type . '_start',
'level' => $level - 1
)
);
}
// ...and add a list-start token to the return.
$return .= $this->wiki->addToken(
$this->rule,
array(
'type' => 'list_start',
'level' => $level - 1
)
);
}
// remove a level from the list?
while (count($stack) > $level) {
echo ".";
// so we don't keep counting the stack, we set up a temp
// var for the count. -1 becuase we're going to pop the
// stack in the next command. $tmp will then equal the
// current level of indent.
$tmp = count($stack) - 1;
// as long as the stack count is greater than the
// current indent level, we need to end list types.
// continue adding end-list tokens until the stack count
// and the indent level are the same.
$return .= $this->wiki->addToken(
$this->rule,
array (
'type' => 'list_end',
'level' => $tmp
)
);
array_pop($stack);
// reset to the current (previous) list type so that
// the new list item matches the proper list type.
$type = $stack[$tmp - 1];
// Close the previously opened List item
$return .= $this->wiki->addToken(
$this->rule,
array (
'type' => $type . '_end',
'level' => $tmp
)
);
// reset the item count for the popped indent level
unset($itemcount[$tmp + 1]);
}
// add to the item count for this list (taking into account
// which level we are at).
if (! isset($itemcount[$level])) {
// first count
$itemcount[$level] = 0;
} else {
// increment count
$itemcount[$level]++;
}
// is this the very first item in the list?
if (! $pastFirst) {
$first = true;
$pastFirst = true;
} else {
$first = false;
}
// create a list-item starting token.
$start = $this->wiki->addToken(
$this->rule,
array(
'type' => $type . '_start',
'level' => $level,
'count' => $itemcount[$level],
'first' => $first
)
);
// create a list-item ending token.
$end = $this->wiki->addToken(
$this->rule,
array(
'type' => $type . '_end',
'level' => $level,
'count' => $itemcount[$level]
)
);
// add the starting token, list-item text, and ending token
// to the return.
$return .= $start . $text . $end;
}
// the last list-item may have been indented. go through the
// list-type stack and create end-list tokens until the stack
// is empty.
$level = count($stack);
while ($level > 0) {
array_pop($stack);
$return .= $this->wiki->addToken(
$this->rule,
array (
'type' => 'list_end',
'level' => $level - 1
)
);
// if we are higher than level 1 we need to close fake items
if ($level > 1) {
$return .= $this->wiki->addToken(
$this->rule,
array (
'type' => $stack[$level - 2] . '_end',
'level' => $level - 2
)
);
}
$level = count($stack);
}
// we're done! send back the replacement text.
return "\n" . $return . "\n\n";
}
}
?>
<?php
// vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4:
/**
* Mediawiki: Parses for emphazised text.
*
* Text_Wiki rule parser to find source text emphazised
* as defined by text surrounded by repeated single quotes ''...'' and more
* Translated are ''emphasis'' , '''strong''' or '''''both''''' ...
*
* PHP versions 4 and 5
*
* @category Text
* @package Text_Wiki
* @author Bertrand Gugger <bertrand@toggg.com>
* @author Paul M. Jones <pmjones@php.net>
* @copyright 2005 bertrand Gugger
* @license http://www.gnu.org/copyleft/lesser.html LGPL License 2.1
* @version CVS: $Id: Emphasis.php,v 1.4 2006/02/15 12:27:40 toggg Exp $
* @link http://pear.php.net/package/Text_Wiki
*/
/**
* Emphazised text rule parser class for Mediawiki. Makes Emphasis, Strong or both
* This class implements a Text_Wiki_Parse to find source text marked for
* emphasis, stronger and very as defined by text surrounded by 2,3 or 5 single-quotes.
* On parsing, the text itself is left in place, but the starting and ending
* instances of the single-quotes are replaced with tokens.
*
* @category Text
* @package Text_Wiki
* @author Bertrand Gugger <bertrand@toggg.com>
* @copyright 2005 bertrand Gugger
* @license http://www.gnu.org/copyleft/lesser.html LGPL License 2.1
* @version Release: @package_version@
* @link http://pear.php.net/package/Text_Wiki
* @see Text_Wiki_Parse::Text_Wiki_Parse()
*/
class Text_Wiki_Parse_Emphasis extends Text_Wiki_Parse {
/**
* The regular expression used to parse the source text and find
* matches conforming to this rule. Used by the parse() method.
* We match '' , ''' or ''''' embeded texts
*
* @access public
* @var string
* @see Text_Wiki_Parse::parse()
*/
var $regex = "/(?<!')'('{1,4})(.*?)\\1'(?!')/";
/**
* Generates a replacement for the matched text. Token options are:
* - 'type' => ['start'|'end'] The starting or ending point of the emphasized text.
* Generated tokens are Emphasis (this rule), Strong or Emphasis / Strong
* The text itself is left in the source but may content bested blocks
*
* @access public
* @param array &$matches The array of matches from parse().
* @return string Delimited by start/end tokens to be used as
* placeholder in the source text surrounding the text to be emphasized.
*/
function process(&$matches)
{
$embeded = $matches[2];
switch (strlen($matches[1])) {
case 1:
$start = $this->wiki->addToken($this->rule, array('type' => 'start'));
$end = $this->wiki->addToken($this->rule, array('type' => 'end'));
break;
case 3:
$embeded = "'" . $embeded . "'";
case 2:
$start = $this->wiki->addToken('Strong', array('type' => 'start'));
$end = $this->wiki->addToken('Strong', array('type' => 'end'));
break;
case 4:
$start = $this->wiki->addToken($this->rule, array('type' => 'start'))
. $this->wiki->addToken('Strong', array('type' => 'start'));
$end = $this->wiki->addToken('Strong', array('type' => 'end'))
. $this->wiki->addToken($this->rule, array('type' => 'end'));
break;
}
return $start . $embeded . $end;
}
}
?>
<?php
/**
*
* Parses for heading text.
*
* @category Text
*
* @package Text_Wiki
*
* @author Paul M. Jones <pmjones@php.net>
*
* @author Moritz Venn <moritz.venn@freaque.net>
*
* @license LGPL
*
* @version $Id: Heading.php,v 1.3 2006/02/03 19:47:02 toggg Exp $
*
*/
/**
*
* Parses for heading text.
*
* This class implements a Text_Wiki_Parse to find source text marked to
* be a heading element, as defined by text on a line by itself prefixed
* with a number of plus signs (+). The heading text itself is left in
* the source, but is prefixed and suffixed with delimited tokens marking
* the start and end of the heading.
*
* @category Text
*
* @package Text_Wiki
*
* @author Moritz Venn <moritz.venn@freaque.net>
*
*/
class Text_Wiki_Parse_Heading extends Text_Wiki_Parse {
/**
*
* The regular expression used to parse the source text and find
* matches conforming to this rule. Used by the parse() method.
*
* @access public
*
* @var string
*
* @see parse()
*
*/
var $regex = '/^(={2,6})(.*?)\1(?:\s|$)/m';
var $conf = array(
'id_prefix' => 'toc'
);
/**
*
* Generates a replacement for the matched text. Token options are:
*
* 'type' => ['start'|'end'] The starting or ending point of the
* heading text. The text itself is left in the source.
*
* @access public
*
* @param array &$matches The array of matches from parse().
*
* @return string A pair of delimited tokens to be used as a
* placeholder in the source text surrounding the heading text.
*
*/
function process(&$matches)
{
// keep a running count for header IDs. we use this later
// when constructing TOC entries, etc.
static $id;
if (! isset($id)) {
$id = 0;
}
$prefix = htmlspecialchars($this->getConf('id_prefix'));
$start = $this->wiki->addToken(
$this->rule,
array(
'type' => 'start',
'level' => strlen($matches[1]),
'text' => trim($matches[2]),
'id' => $prefix . $id ++
)
);
$end = $this->wiki->addToken(
$this->rule,
array(
'type' => 'end',
'level' => strlen($matches[1])
)
);
return $start . trim($matches[2]) . $end . "\n";
}
}
?>
<?php
/**
*
* Parses for bulleted and numbered lists.
*
* @category Text
*
* @package Text_Wiki
*
* @author Justin Patrin <papercrane@reversefold.com>
* @author Paul M. Jones <pmjones@php.net>
*
* @license LGPL
*
* @version $Id: List.php,v 1.1 2006/03/28 04:46:09 ritzmo Exp $
*
*/
/**
*
* Parses for bulleted and numbered lists.
*
* This class implements a Text_Wiki_Parse to find source text marked as
* a bulleted or numbered list. In short, if a line starts with '* ' then
* it is a bullet list item; if a line starts with '# ' then it is a
* number list item. Spaces in front of the * or # indicate an indented
* sub-list. The list items must be on sequential lines, and may be
* separated by blank lines to improve readability. Using a non-* non-#
* non-whitespace character at the beginning of a line ends the list.
*
* @category Text
*
* @package Text_Wiki
*
* @author Justin Patrin <papercrane@reversefold.com>
* @author Paul M. Jones <pmjones@php.net>
*
*/
class Text_Wiki_Parse_List extends Text_Wiki_Parse {
/**
*
* The regular expression used to parse the source text and find
* matches conforming to this rule. Used by the parse() method.
*
* @access public
*
* @var string
*
* @see parse()
*
*/
//TODO: add text continuations (any number of + signs) and expandable areas (- after *s ot #s)
var $regex = '/\n((?:\*|#)+.*?\n(?!(?:\*|#)+))/s';
/**
*
* Generates a replacement for the matched text. Token options are:
*
* 'type' =>
* 'bullet_start' : the start of a bullet list
* 'bullet_end' : the end of a bullet list
* 'number_start' : the start of a number list
* 'number_end' : the end of a number list
* 'item_start' : the start of item text (bullet or number)
* 'item_end' : the end of item text (bullet or number)
* 'unknown' : unknown type of list or item
*
* 'level' => the indent level (0 for the first level, 1 for the
* second, etc)
*
* 'count' => the list item number at this level. not needed for
* xhtml, but very useful for PDF and RTF.
*
* @access public
*
* @param array &$matches The array of matches from parse().
*
* @return A series of text and delimited tokens marking the different
* list text and list elements.
*
*/
function process(&$matches)
{
// the replacement text we will return
$return = '';
// the list of post-processing matches
$list = array();
// a stack of list-start and list-end types; we keep this
// so that we know what kind of list we're working with
// (bullet or number) and what indent level we're at.
$stack = array();
// the item count is the number of list items for any
// given list-type on the stack
$itemcount = array();
// have we processed the very first list item?
$pastFirst = false;
// populate $list with this set of matches. $matches[1] is the
// text matched as a list set by parse().
preg_match_all(
'/^((\*|#)+)(.*?)$/ms',
$matches[1],
$list,
PREG_SET_ORDER
);
// loop through each list-item element.
foreach ($list as $key => $val) {
// $val[0] is the full matched list-item line
// $val[1] is the type (* or #)
// $val[2] is the level (number)
// $val[3] is the list item text
// how many levels are we indented? (1 means the "root"
// list level, no indenting.)
$level = strlen($val[1]);
// get the list item type
if ($val[2] == '*') {
$type = 'bullet';
} elseif ($val[2] == '#') {
$type = 'number';
} else {
$type = 'unknown';
}
// get the text of the list item
$text = $val[3];
// add a level to the list?
if ($level > count($stack)) {
// the current indent level is greater than the
// number of stack elements, so we must be starting
// a new list. push the new list type onto the
// stack...
array_push($stack, $type);
// ...and add a list-start token to the return.
$return .= $this->wiki->addToken(
$this->rule,
array(
'type' => $type . '_list_start',
'level' => $level - 1
)
);
}
// remove a level from the list?
while (count($stack) > $level) {
// so we don't keep counting the stack, we set up a temp
// var for the count. -1 becuase we're going to pop the
// stack in the next command. $tmp will then equal the
// current level of indent.
$tmp = count($stack) - 1;
// as long as the stack count is greater than the
// current indent level, we need to end list types.
// continue adding end-list tokens until the stack count
// and the indent level are the same.
$return .= $this->wiki->addToken(
$this->rule,
array (
'type' => array_pop($stack) . '_list_end',
'level' => $tmp
)
);
// reset to the current (previous) list type so that
// the new list item matches the proper list type.
$type = $stack[$tmp - 1];
// reset the item count for the popped indent level
unset($itemcount[$tmp + 1]);
}
// add to the item count for this list (taking into account
// which level we are at).
if (! isset($itemcount[$level])) {
// first count
$itemcount[$level] = 0;
} else {
// increment count
$itemcount[$level]++;
}
// is this the very first item in the list?
if (! $pastFirst) {
$first = true;
$pastFirst = true;
} else {
$first = false;
}
// create a list-item starting token.
$start = $this->wiki->addToken(
$this->rule,
array(
'type' => $type . '_item_start',
'level' => $level,
'count' => $itemcount[$level],
'first' => $first
)
);
// create a list-item ending token.
$end = $this->wiki->addToken(
$this->rule,
array(
'type' => $type . '_item_end',
'level' => $level,
'count' => $itemcount[$level]
)
);
// add the starting token, list-item text, and ending token
// to the return.
$return .= $start . $text . $end;
}
// the last list-item may have been indented. go through the
// list-type stack and create end-list tokens until the stack
// is empty.
while (count($stack) > 0) {
$return .= $this->wiki->addToken(
$this->rule,
array (
'type' => array_pop($stack) . '_list_end',
'level' => count($stack)
)
);
}
// we're done! send back the replacement text.
return "\n" . $return . "\n\n";
}
}
?>
\ No newline at end of file
<?php
// vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4:
/**
* Mediawiki: Parses for tables.
*
* This class implements a Text_Wiki_Rule to find tables in pipe syntax
* {| ... |- ... | ... |}
* On parsing, the text itself is left in place, but the starting and ending
* tags for table, rows and cells are replaced with tokens. (nested tables enabled)
*
* PHP versions 4 and 5
*
* @category Text
* @package Text_Wiki
* @author Bertrand Gugger <bertrand@toggg.com>
* @copyright 2005 bertrand Gugger
* @license http://www.gnu.org/copyleft/lesser.html LGPL License 2.1
* @version CVS: $Id: Table.php,v 1.7 2005/12/06 15:54:56 ritzmo Exp $
* @link http://pear.php.net/package/Text_Wiki
*/
/**
* Table rule parser class for Mediawiki.
*
* @category Text
* @package Text_Wiki
* @author Bertrand Gugger <bertrand@toggg.com>
* @copyright 2005 bertrand Gugger
* @license http://www.gnu.org/copyleft/lesser.html LGPL License 2.1
* @version Release: @package_version@
* @link http://pear.php.net/package/Text_Wiki
* @see Text_Wiki_Parse::Text_Wiki_Parse()
*/
class Text_Wiki_Parse_Table extends Text_Wiki_Parse {
/**
* The regular expression used to parse the source text and find
* matches conforming to this rule. Used by the parse() method.
*
* @access public
* @var string
* @see parse()
*/
var $regex = '#^\{\|(.*?)(?:^\|\+(.*?))?(^(?:((?R))|.)*?)^\|}#msi';
/**
* The regular expression used in second stage to find table's rows
* used by process() to call back processRows()
*
* @access public
* @var string
* @see process()
* @see processRows()
*/
var $regexRows = '#(?:^(\||!)-|\G)(.*?)^(.*?)(?=^(?:\|-|!-|\z))#msi';
/**
* The regular expression used in third stage to find rows's cells
* used by processRows() to call back processCells()
*
* @access public
* @var string
* @see process()
* @see processCells()
*/
var $regexCells =
'#((?:^\||^!|\|\||!!|\G))(?:([^|\n]*?) \|(?!\|))?(.+?)(?=^\||^!|\|\||!!|\z)#msi';
/**
* The current table nesting depth, starts by zero
*
* @access private
* @var int
*/
var $_level = 0;
/**
* The count of rows for this level
*
* @access private
* @var array of int
*/
var $_countRows = array();
/**
* The max count of cells for this level
*
* @access private
* @var array of int
*/
var $_maxCells = array();
/**
* The count of cells for each row
*
* @access private
* @var array of int
*/
var $_countCells = array();
/**
* The count of spanned cells from previous rowspans for each column
*
* @access private
* @var array of int
*/
var $_spanCells = array();
/**
* Generates a replacement for the matched text. Returned token options are:
* 'type' =>
* 'table_start' : the start of a bullet list
* 'table_end' : the end of a bullet list
* 'row_start' : the start of a number list
* 'row_end' : the end of a number list
* 'cell_start' : the start of item text (bullet or number)
* 'cell_end' : the end of item text (bullet or number)
* 'caption_start' : the start of associated caption
* 'caption_end' : the end of associated caption
*
* 'level' => the table nesting level (starting zero) ('table_start')
*
* 'rows' => the number of rows in the table ('table_start')
*
* 'cols' => the number of columns in the table or rows
* ('table_start' and 'row_start')
*
* 'span' => column span ('cell_start')
*
* 'row_span' => row span ('cell_start')
*
* 'attr' => header optional attribute flag ('row_start' or 'cell_start')
*
* 'format' => table, row or cell optional styling ('xxx_start')
*
* @param array &$matches The array of matches from parse().
* @return string the original text with tags replaced by delimited tokens
* which point to the the token array containing their type and definition
* @access public
*/
function process(&$matches)
{
if (array_key_exists(4, $matches)) {
$this->_level++;
$expsub = preg_replace_callback(
$this->regex,
array(&$this, 'process'),
$matches[3]
);
$this->_level--;
} else {
$expsub = $matches[3];
}
$this->_countRows[$this->_level] = $this->_maxCells[$this->_level] = 0;
$this->_countCells[$this->_level] = $this->_spanCells[$this->_level] = array();
$sub = preg_replace_callback(
$this->regexRows,
array(&$this, 'processRows'),
$expsub
);
$param = array(
'type' => 'table_start',
'level' => $this->_level,
'rows' => $this->_countRows[$this->_level],
'cols' => $this->_maxCells[$this->_level]
);
if ($format = trim($matches[1])) {
$param['format'] = $format;
}
$ret = $this->wiki->addToken($this->rule, $param );
if ($matches[2]) {
$ret .= $this->wiki->addToken($this->rule, array(
'type' => 'caption_start',
'level' => $this->_level ) ) . $matches[2] .
$this->wiki->addToken($this->rule, array(
'type' => 'caption_end',
'level' => $this->_level ) );
}
$param['type'] = 'table_end';
return $ret . $sub . $this->wiki->addToken($this->rule, $param );
}
/**
* Generates a replacement for the matched rows. Token options are:
* 'type' =>
* 'row_start' : the start of a row
* 'row_end' : the end of a row
*
* 'order' => the row order in the table
*
* 'cols' => the count of cells in the row
*
* 'attr' => header optional attribute flag
*
* 'format' => row optional styling
*
* @param array &$matches The array of matches from process() callback.
* @return string 2 delimited tokens pointing the row params
* and containing the cells-parsed block of text between the tags
* @access public
*/
function processRows(&$matches)
{
$this->_countCells[$this->_level][$this->_countRows[$this->_level]] = 0;
$sub = preg_replace_callback(
$this->regexCells,
array(&$this, 'processCells'),
$matches[3]
);
$param = array(
'type' => 'row_start',
'order' => $this->_countRows[$this->_level],
'cols' => $this->_countCells[$this->_level][$this->_countRows[$this->_level]++]
);
if ($matches[1] == '!') {
$param['attr'] = 'header';
}
if ($format = trim($matches[2])) {
$param['format'] = $format;
}
if ($this->_maxCells[$this->_level] < $param['cols']) {
$this->_maxCells[$this->_level] = $param['cols'];
}
$ret = $this->wiki->addToken($this->rule, $param );
$param['type'] = 'row_end';
return $ret . $sub . $this->wiki->addToken($this->rule, $param );
}
/**
* Generates a replacement for the matched cells. Token options are:
* 'type' =>
* 'cell_start' : the start of a row
* 'cell_end' : the end of a row
*
* 'order' => the cell order in the row
*
* 'cols' => the count of cells in the row
*
* 'span' => column span
*
* 'row_span' => row span
*
* 'attr' => header optional attribute flag
*
* 'format' => cell optional styling
*
* @param array &$matches The array of matches from processRows() callback.
* @return string 2 delimited tokens pointing the cell params
* and containing the block of text between the tags
* @access public
*/
function processCells(&$matches)
{
$order = & $this->_countCells[$this->_level][$this->_countRows[$this->_level]];
while (isset($this->_spanCells[$this->_level][$order])) {
if (--$this->_spanCells[$this->_level][$order] < 2) {
unset($this->_spanCells[$this->_level][$order]);
}
$order++;
}
$param = array(
'type' => 'cell_start',
'attr' => $matches[1] && ($matches[1]{0} == '!') ? 'header': null,
'span' => 1,
'rowspan' => 1,
'order' => $order
);
if ($format = trim($matches[2])) {
if (preg_match('#(.*)colspan=("|\')?(\d+)(?(2)\2)(.*)#i', $format, $pieces)) {
$param['span'] = (int)$pieces[3];
$format = $pieces[1] . $pieces[4];
}
if (preg_match('#(.*)rowspan=("|\')?(\d+)(?(2)\2)(.*)#i', $format, $pieces)) {
$this->_spanCells[$this->_level][$order] =
$param['rowspan'] = (int)$pieces[3];
$format = $pieces[1] . $pieces[4];
}
$param['format'] = $format;
}
$this->_countCells[$this->_level][$this->_countRows[$this->_level]] += $param['span'];
$ret = $this->wiki->addToken($this->rule, $param);
$param['type'] = 'cell_end';
return $ret . $matches[3] . $this->wiki->addToken($this->rule, $param );
}
}
?>
<?php
/**
*
* Parse for URLS in the source text.
*
* @category Text
*
* @package Text_Wiki
*
* @author Paul M. Jones <pmjones@php.net>
*
* @author Moritz Venn <moritz.venn@freaque.net>
*
* @license LGPL
*
* @version $Id: Url.php,v 1.1 2005/12/06 15:54:56 ritzmo Exp $
*
*/
/**
*
* Parse for URLS in the source text.
*
* Various URL markings are supported: inline (the URL by itself),
* inline (where the URL is enclosed in square brackets), and named
* reference (where the URL is enclosed in square brackets and has a
* name included inside the brackets). E.g.:
*
* inline -- http://example.com
* undescribed -- [http://example.com]
* described -- [http://example.com Example Description]
* described -- [http://www.example.com|Example Description]
*
* When rendering a URL token, this will convert URLs pointing to a .gif,
* .jpg, or .png image into an inline <img /> tag (for the 'xhtml'
* format).
*
* Token options are:
*
* 'type' => ['inline'|'footnote'|'descr'] the type of URL
*
* 'href' => the URL link href portion
*
* 'text' => the displayed text of the URL link
*
* @category Text
*
* @package Text_Wiki
*
* @author Paul M. Jones <pmjones@php.net>
*
* @author Moritz Venn <moritz.venn@freaque.net>
*
*/
class Text_Wiki_Parse_Url extends Text_Wiki_Parse {
/**
*
* Keeps a running count of numbered-reference URLs.
*
* @access public
*
* @var int
*
*/
var $footnoteCount = 0;
/**
*
* URL schemes recognized by this rule.
*
* @access public
*
* @var array
*
*/
var $conf = array(
'schemes' => array(
'http://',
'https://',
'ftp://',
'gopher://',
'news://',
'mailto:'
)
);
/**
*
* Constructor.
*
* We override the constructor so we can comment the regex nicely.
*
* @access public
*
*/
function Text_Wiki_Parse_Url(&$obj)
{
parent::Text_Wiki_Parse($obj);
// convert the list of recognized schemes to a regex-safe string,
// where the pattern delim is a slash
$tmp = array();
$list = $this->getConf('schemes', array());
foreach ($list as $val) {
$tmp[] = preg_quote($val, '/');
}
$schemes = implode('|', $tmp);
// build the regex
$this->regex =
"($schemes)" . // allowed schemes
"(" . // start pattern
"[^ \\/\"\'{$this->wiki->delim}]*\\/" . // no spaces, backslashes, slashes, double-quotes, single quotes, or delimiters;
")*" . // end pattern
"[^ \\t\\n\\/\"\'{$this->wiki->delim}]*" .
"[A-Za-z0-9\\/?=&~_]";
}
/**
*
* Find three different kinds of URLs in the source text.
*
* @access public
*
*/
function parse()
{
// -------------------------------------------------------------
//
// Described-reference (named) URLs.
//
// the regular expression for this kind of URL
$tmp_regex = '/\[(' . $this->regex . ')[ |]([^\]]+)\]/';
// use a custom callback processing method to generate
// the replacement text for matches.
$this->wiki->source = preg_replace_callback(
$tmp_regex,
array(&$this, 'processDescr'),
$this->wiki->source
);
// -------------------------------------------------------------
//
// Unnamed-reference ('Ordinary'-style) URLs.
//
// the regular expression for this kind of URL
$tmp_regex = '/\[(' . $this->regex . ')\]/U';
// use a custom callback processing method to generate
// the replacement text for matches.
$this->wiki->source = preg_replace_callback(
$tmp_regex,
//array(&$this, 'processFootnote'),
array(&$this, 'processOrdinary'),
$this->wiki->source
);
// -------------------------------------------------------------
//
// Normal inline URLs.
//
// the regular expression for this kind of URL
$tmp_regex = '/(^|[^A-Za-z])(' . $this->regex . ')(.*?)/';
// use the standard callback for inline URLs
$this->wiki->source = preg_replace_callback(
$tmp_regex,
array(&$this, 'process'),
$this->wiki->source
);
//$tmp_regex = '/(^|[^A-Za-z])([a-zA-Z])(.*?)/';
$tmp_regex = '/(^|\s)([a-zA-Z0-9\-]+\.[a-zA-Z0-9\-]+(\.[a-zA-Z0-9\-]+)+)($|\s)/';
// use the standard callback for inline URLs
$this->wiki->source = preg_replace_callback(
$tmp_regex,
array(&$this, 'processWithoutProtocol'),
$this->wiki->source
);
$tmp_regex = '/(^|\s|'.$this->wiki->delim.')<([a-zA-Z0-9\-\.%_\+\!\*\'\(\)\,]+@[a-zA-Z0-9\-]+(\.[a-zA-Z0-9\-]+)+)>(\s|'.$this->wiki->delim.'|$)/';
// use the standard callback for inline URLs
$this->wiki->source = preg_replace_callback(
$tmp_regex,
array(&$this, 'processInlineEmail'),
$this->wiki->source
);
}
/**
*
* Process inline URLs.
*
* @param array &$matches
*
* @param array $matches An array of matches from the parse() method
* as generated by preg_replace_callback. $matches[0] is the full
* matched string, $matches[1] is the first matched pattern,
* $matches[2] is the second matched pattern, and so on.
*
* @return string The processed text replacement.
*
*/
function process(&$matches)
{
// set options
$options = array(
'type' => 'inline',
'href' => $matches[2],
'text' => $matches[2]
);
// tokenize
return $matches[1] . $this->wiki->addToken($this->rule, $options) . $matches[5];
}
function processWithoutProtocol(&$matches)
{
// set options
$options = array(
'type' => 'inline',
'href' => 'http://'.$matches[2],
'text' => $matches[2]
);
// tokenize
return $matches[1] . $this->wiki->addToken($this->rule, $options) . $matches[4];
}
function processInlineEmail(&$matches)
{
// set options
$options = array(
'type' => 'inline',
'href' => 'mailto://'.$matches[2],
'text' => $matches[2]
);
// tokenize
return $matches[1] . $this->wiki->addToken($this->rule, $options) . $matches[4];
}
/**
*
* Process numbered (footnote) URLs.
*
* Token options are:
* @param array &$matches
*
* @param array $matches An array of matches from the parse() method
* as generated by preg_replace_callback. $matches[0] is the full
* matched string, $matches[1] is the first matched pattern,
* $matches[2] is the second matched pattern, and so on.
*
* @return string The processed text replacement.
*
*/
function processFootnote(&$matches)
{
// keep a running count for footnotes
$this->footnoteCount++;
// set options
$options = array(
'type' => 'footnote',
'href' => $matches[1],
'text' => $this->footnoteCount
);
// tokenize
return $this->wiki->addToken($this->rule, $options);
}
function processOrdinary(&$matches)
{
// keep a running count for footnotes
$this->footnoteCount++;
// set options
$options = array(
'type' => 'descr',
'href' => $matches[1],
'text' => $matches[1]
);
// tokenize
return $this->wiki->addToken($this->rule, $options);
}
/**
*
* Process described-reference (named-reference) URLs.
*
* Token options are:
* 'type' => ['inline'|'footnote'|'descr'] the type of URL
* 'href' => the URL link href portion
* 'text' => the displayed text of the URL link
*
* @param array &$matches
*
* @param array $matches An array of matches from the parse() method
* as generated by preg_replace_callback. $matches[0] is the full
* matched string, $matches[1] is the first matched pattern,
* $matches[2] is the second matched pattern, and so on.
*
* @return string The processed text replacement.
*
*/
function processDescr(&$matches)
{
// set options
$options = array(
'type' => 'descr',
'href' => $matches[1],
'text' => $matches[4]
);
// tokenize
return $this->wiki->addToken($this->rule, $options);
}
}
?>
\ No newline at end of file
This diff is collapsed.
......@@ -2,34 +2,40 @@
class Text_Wiki_Render_Latex_Url extends Text_Wiki_Render {
var $conf = array(
'target' => false,
'images' => true,
'img_ext' => array('jpg', 'jpeg', 'gif', 'png')
);
/**
*
*
* Renders a token into text matching the requested format.
*
*
* @access public
*
*
* @param array $options The "options" portion of the token (second
* element).
*
*
* @return string The text rendered from the token options.
*
*
*/
function token($options)
{
// create local variables from the options array (text,
// href, type)
extract($options);
return $text . '\footnote{' . $href . '}';
if ($options['type'] == 'start') {
return '';
} else if ($options['type'] == 'end') {
return '\footnote{' . $href . '}';
} else {
return $text . '\footnote{' . $href . '}';
}
}
}
?>
......@@ -2,24 +2,28 @@
class Text_Wiki_Render_Plain_Url extends Text_Wiki_Render {
/**
*
*
* Renders a token into text matching the requested format.
*
*
* @access public
*
*
* @param array $options The "options" portion of the token (second
* element).
*
*
* @return string The text rendered from the token options.
*
*
*/
function token($options)
{
return $options['text'];
if ($options['type'] == 'start' || $options['type'] == 'end') {
return '';
} else {
return $options['text'];
}
}
}
?>
\ No newline at end of file
<?php
/**
*
* Address rule end renderer for Xhtml
*
* PHP versions 4 and 5
*
* @category Text
*
* @package Text_Wiki
*
* @author Michele Tomaiuolo <tomamic@yahoo.it>
*
* @license http://www.gnu.org/copyleft/lesser.html LGPL License 2.1
*
* @version CVS: $Id: Address.php,v 1.1 2007/02/01 09:33:00 mic Exp $
*
* @link http://pear.php.net/package/Text_Wiki
*
*/
class Text_Wiki_Render_Xhtml_Address extends Text_Wiki_Render {
var $conf = array(
'css' => null
);
/**
*
* Renders a token into text matching the requested format.
*
* @access public
*
* @param array $options The "options" portion of the token (second
* element).
*
* @return string The text rendered from the token options.
*
*/
function token($options)
{
if ($options['type'] == 'start') {
$css = $this->formatConf(' class="%s"', 'css');
return "<address$css>";
}
if ($options['type'] == 'end') {
return '</address>';
}
}
}
?>
......@@ -9,7 +9,7 @@
* @package Text_Wiki
* @author Paul M. Jones <pmjones@php.net>
* @license http://www.gnu.org/copyleft/lesser.html LGPL License 2.1
* @version CVS: $Id: Blockquote.php,v 1.7 2005/07/30 08:03:28 toggg Exp $
* @version CVS: $Id: Blockquote.php,v 1.9 2007/05/26 18:25:23 mic Exp $
* @link http://pear.php.net/package/Text_Wiki
*/
......@@ -55,6 +55,9 @@ class Text_Wiki_Render_Xhtml_Blockquote extends Text_Wiki_Render {
// pick the css type
$css = $this->formatConf(' class="%s"', 'css');
if (isset($options['css'])) {
$css = ' class="' . $options['css']. '"';
}
// starting
if ($type == 'start') {
return "$pad<blockquote$css>";
......
......@@ -46,11 +46,11 @@ class Text_Wiki_Render_Xhtml_Bold extends Text_Wiki_Render {
{
if ($options['type'] == 'start') {
$css = $this->formatConf(' class="%s"', 'css');
return "<strong$css>";
return "<b$css>";
}
if ($options['type'] == 'end') {
return '</strong>';
return '</b>';
}
}
}
......
......@@ -9,7 +9,7 @@
* @package Text_Wiki
* @author Paul M. Jones <pmjones@php.net>
* @license http://www.gnu.org/copyleft/lesser.html LGPL License 2.1
* @version CVS: $Id: Box.php,v 1.2 2005/07/30 08:03:28 toggg Exp $
* @version CVS: $Id: Box.php,v 1.3 2007/03/03 23:00:54 mic Exp $
* @link http://pear.php.net/package/Text_Wiki
*/
......@@ -26,7 +26,7 @@
class Text_Wiki_Render_Xhtml_Box extends Text_Wiki_Render {
var $conf = array(
'css' => null
'css' => 'simplebox'
);
/**
......@@ -45,8 +45,13 @@ class Text_Wiki_Render_Xhtml_Box extends Text_Wiki_Render {
function token($options)
{
if ($options['type'] == 'start') {
$css = $this->formatConf(' class="%s"', 'css');
return "<div class='simplebox'$css>";
if ($options['css']) {
$css = ' class="' . $options['css']. '"';
}
else {
$css = $this->formatConf(' class="%s"', 'css');
}
return "<div $css>";
}
if ($options['type'] == 'end') {
......
......@@ -9,7 +9,7 @@
* @package Text_Wiki
* @author Paul M. Jones <pmjones@php.net>
* @license http://www.gnu.org/copyleft/lesser.html LGPL License 2.1
* @version CVS: $Id: Center.php,v 1.7 2005/07/30 08:03:28 toggg Exp $
* @version CVS: $Id: Center.php,v 1.8 2007/02/19 08:51:19 mic Exp $
* @link http://pear.php.net/package/Text_Wiki
*/
......@@ -25,6 +25,10 @@
*/
class Text_Wiki_Render_Xhtml_Center extends Text_Wiki_Render {
var $conf = array(
'css' => null
);
/**
*
* Renders a token into text matching the requested format.
......@@ -41,7 +45,13 @@ class Text_Wiki_Render_Xhtml_Center extends Text_Wiki_Render {
function token($options)
{
if ($options['type'] == 'start') {
return '<div style="text-align: center;">';
$css = $this->getConf('css');
if ($css) {
return "<div class=\"$css\">";
}
else {
return '<div style="text-align: center;">';
}
}
if ($options['type'] == 'end') {
......
......@@ -59,38 +59,33 @@ class Text_Wiki_Render_Xhtml_Code extends Text_Wiki_Render {
$css_filename = $this->formatConf(' class="%s"', 'css_filename');
if ($type == 'php') {
if (substr($options['text'], 0, 5) != '<?php') {
// PHP code example:
// add the PHP tags
$text = "<?php \n\n"
. $options['text'] . "\n\n"
. "?>";
$text = "<?php\n" . $options['text'] . "\n?>"; // <?php
}
// convert tabs to four spaces
$text = str_replace("\t", "&nbsp;&nbsp;&nbsp;&nbsp;", $text);
$text = str_replace("\t", " ", $text);
// colorize the code block (also converts HTML entities and adds
// <code>...</code> tags)
$h = new PHP_Highlight(true);
$h->loadString($text);
$text = $h->toHtml(true);
ob_start();
highlight_string($text);
$text = ob_get_contents();
ob_end_clean();
// replace <br /> tags with simple newlines.
// replace non-breaking space with simple spaces.
// translate HTML <font> and color to XHTML <span> and style.
// courtesy of research by A. Kalin :-).
$map = array(
'&nbsp;' => ' '
//'<br />' => "\n",
//"\n" => "<br \>",
//'<font' => '<span',
//'</font>' => '</span>',
//'color="' => 'style="color:'
'<br />' => "\n",
'&nbsp;' => ' ',
'<font' => '<span',
'</font>' => '</span>',
'color="' => 'style="color:'
);
$text = strtr($text, $map);
// get rid of the last newline inside the code block
......@@ -105,10 +100,7 @@ class Text_Wiki_Render_Xhtml_Code extends Text_Wiki_Render {
}
// done
/*$text = "<table border=1 class='dashed' cellpadding=0 cellspacing=0>
<tr><td><b>$text
</b></td></tr>
</table>";*/
$text = "<pre$css>$text</pre>";
} elseif ($type == 'html' || $type == 'xhtml') {
......@@ -117,25 +109,17 @@ class Text_Wiki_Render_Xhtml_Code extends Text_Wiki_Render {
// convert tabs to four spaces,
// convert entities.
$text = str_replace("\t", " ", $text);
//$text = "<html>\n$text\n</html>";
$text = "<html>\n$text\n</html>";
$text = $this->textEncode($text);
$text = "<pre$css>$text</pre>";
$text = "<pre$css><code$css_html>$text</code></pre>";
} elseif ($type == 'sql') {
// HTML code example:
// add <html> opening and closing tags,
// convert tabs to four spaces,
// convert entities.
$text = str_replace("\t", " ", $text);
$text = $this->textEncode($text);
$text = "<pre class=\"sql\">$text</pre>";
} else {
// generic code example:
// convert tabs to four spaces,
// convert entities.
$text = str_replace("\t", " ", $text);
$text = $this->textEncode($text);
$text = "<pre$css>$text</pre>";
$text = "<pre$css><code$css_code>$text</code></pre>";
}
if ($css_filename && isset($attr['filename'])) {
......
......@@ -9,7 +9,7 @@
* @package Text_Wiki
* @author Paul M. Jones <pmjones@php.net>
* @license http://www.gnu.org/copyleft/lesser.html LGPL License 2.1
* @version CVS: $Id: Image.php,v 1.16 2006/02/10 23:07:03 toggg Exp $
* @version CVS: $Id: Image.php,v 1.17 2007/03/15 15:04:50 justinpatrin Exp $
* @link http://pear.php.net/package/Text_Wiki
*/
......@@ -82,15 +82,10 @@ class Text_Wiki_Render_Xhtml_Image extends Text_Wiki_Render {
unset($options['attr']['link']);
// stephane@metacites.net -- 25/07/2004
// we make up an align="center" value for the <img> tag.
if (isset($options['attr']['align']) &&
$options['attr']['align'] == 'center') {
// unset so it won't show up as an attribute
unset($options['attr']['align']);
// use CSS for all alignment
if (isset($options['attr']['align'])) {
// make sure we have a style attribute
if (! isset($options['attr']['style'])) {
if (!isset($options['attr']['style'])) {
// no style, set up a blank one
$options['attr']['style'] = '';
} else {
......@@ -98,9 +93,18 @@ class Text_Wiki_Render_Xhtml_Image extends Text_Wiki_Render {
$options['attr']['style'] .= ' ';
}
// add a "center" style to the existing style.
$options['attr']['style'] .=
'display: block; margin-left: auto; margin-right: auto;';
if ($options['attr']['align'] == 'center') {
// add a "center" style to the existing style.
$options['attr']['style'] .=
'display: block; margin-left: auto; margin-right: auto;';
} else {
// add a float style to the existing style
$options['attr']['style'] .=
'float: '.$options['attr']['align'];
}
// unset so it won't show up as an attribute
unset($options['attr']['align']);
}
// stephane@metacites.net -- 25/07/2004
......
......@@ -9,7 +9,7 @@
* @package Text_Wiki
* @author Paul M. Jones <pmjones@php.net>
* @license http://www.gnu.org/copyleft/lesser.html LGPL License 2.1
* @version CVS: $Id: Interwiki.php,v 1.14 2006/02/25 05:03:13 toggg Exp $
* @version CVS: $Id: Interwiki.php,v 1.15 2007/03/15 00:08:47 justinpatrin Exp $
* @link http://pear.php.net/package/Text_Wiki
*/
......@@ -86,7 +86,7 @@ class Text_Wiki_Render_Xhtml_Interwiki extends Text_Wiki_Render {
$output = "<a$css href=\"$href\"";
// are we targeting a specific window?
if ($target) {
if ($target && $target != '_self') {
// this is XHTML compliant, suggested by Aaron Kalin.
// code tip is actually from youngpup.net, and it
// uses the $target as the new window name.
......
......@@ -46,11 +46,11 @@ class Text_Wiki_Render_Xhtml_Italic extends Text_Wiki_Render {
{
if ($options['type'] == 'start') {
$css = $this->formatConf(' class="%s"', 'css');
return "<em$css>";
return "<i$css>";
}
if ($options['type'] == 'end') {
return '</em>';
return '</i>';
}
}
}
......
......@@ -9,7 +9,7 @@
* @package Text_Wiki
* @author Paul M. Jones <pmjones@php.net>
* @license http://www.gnu.org/copyleft/lesser.html LGPL License 2.1
* @version CVS: $Id: Phplookup.php,v 1.11 2006/02/10 23:07:03 toggg Exp $
* @version CVS: $Id: Phplookup.php,v 1.12 2007/03/15 00:08:47 justinpatrin Exp $
* @link http://pear.php.net/package/Text_Wiki
*/
......@@ -54,7 +54,7 @@ class Text_Wiki_Render_Xhtml_Phplookup extends Text_Wiki_Render {
// are we targeting another window?
$target = $this->getConf('target', '');
if ($target) {
if ($target && $target != '_self') {
// use a "popup" window. this is XHTML compliant, suggested by
// Aaron Kalin. uses the $target as the new window name.
$target = $this->textEncode($target);
......
......@@ -9,7 +9,7 @@
* @package Text_Wiki
* @author Paul M. Jones <pmjones@php.net>
* @license http://www.gnu.org/copyleft/lesser.html LGPL License 2.1
* @version CVS: $Id: Preformatted.php,v 1.2 2005/07/30 08:03:29 toggg Exp $
* @version CVS: $Id: Preformatted.php,v 1.3 2007/02/07 13:40:44 mic Exp $
* @link http://pear.php.net/package/Text_Wiki
*/
......@@ -40,7 +40,8 @@ class Text_Wiki_Render_Xhtml_Preformatted extends Text_Wiki_Render {
function token($options)
{
return '<pre>'.$options['text'].'</pre>';
$text = $this->textEncode($options['text']);
return '<pre>'.$text.'</pre>';
}
}
?>
......@@ -46,11 +46,11 @@ class Text_Wiki_Render_Xhtml_Underline extends Text_Wiki_Render {
{
if ($options['type'] == 'start') {
$css = $this->formatConf(' class="%s"', 'css');
return "<span style=\"text-decoration: underline;\"$css>";
return "<u$css>";
}
if ($options['type'] == 'end') {
return '</span>';
return '</u>';
}
}
}
......
......@@ -9,7 +9,7 @@
* @package Text_Wiki
* @author Paul M. Jones <pmjones@php.net>
* @license http://www.gnu.org/copyleft/lesser.html LGPL License 2.1
* @version CVS: $Id: Url.php,v 1.13 2006/02/10 23:07:03 toggg Exp $
* @version CVS: $Id: Url.php,v 1.18 2007/05/26 17:15:41 mic Exp $
* @link http://pear.php.net/package/Text_Wiki
*/
......@@ -73,46 +73,58 @@ class Text_Wiki_Render_Xhtml_Url extends Text_Wiki_Render {
// generate an image tag
$css = $this->formatConf(' class="%s"', 'css_img');
$output = "<img$css src=\"$href\" alt=\"$text\" />";
$start = "<img$css src=\"$href\" alt=\"$text\" title=\"$text\" /><!-- ";
$end = " -->";
} else {
// should we build a target clause?
if ($href{0} == '#' ||
strtolower(substr($href, 0, 7)) == 'mailto:') {
// targets not allowed for on-page anchors
// and mailto: links.
strtolower(substr($href, 0, 7)) == 'mailto:') {
// targets not allowed for on-page anchors
// and mailto: links.
$target = '';
} else {
// allow targets on non-anchor non-mailto links
// allow targets on non-anchor non-mailto links
$target = $this->getConf('target');
}
// generate a regular link (not an image)
$text = $this->textEncode($text);
$css = $this->formatConf(' class="%s"', "css_$type");
$output = "<a$css href=\"$href\"";
$start = "<a$css href=\"$href\"";
/*
if ($target) {
if ($target && $target != '_self') {
// use a "popup" window. this is XHTML compliant, suggested by
// Aaron Kalin. uses the $target as the new window name.
$target = $this->textEncode($target);
$output .= " onclick=\"window.open(this.href, '$target');";
$output .= " return false;\"";
$start .= " onclick=\"window.open(this.href, '$target');";
$start .= " return false;\"";
}
if (isset($name)) {
$start .= " id=\"$name\"";
}
*/
// finish up output
$output .= ">$text</a>";
$start .= ">";
$end = "</a>";
// make numbered references look like footnotes when no
// CSS class specified, make them superscript by default
if ($type == 'footnote' && ! $css) {
$output = '<sup>' . $output . '</sup>';
$start = '<sup>' . $start;
$end = $end . '</sup>';
}
}
if ($options['type'] == 'start') {
$output = $start;
} else if ($options['type'] == 'end') {
$output = $end;
} else {
$output = $start . $text . $end;
}
return $output;
}
}
......
......@@ -9,7 +9,7 @@
* @package Text_Wiki
* @author Paul M. Jones <pmjones@php.net>
* @license http://www.gnu.org/copyleft/lesser.html LGPL License 2.1
* @version CVS: $Id: Wikilink.php,v 1.21 2006/07/28 14:52:52 justinpatrin Exp $
* @version CVS: $Id: Wikilink.php,v 1.22 2006/12/08 21:25:24 justinpatrin Exp $
* @link http://pear.php.net/package/Text_Wiki
*/
......@@ -102,7 +102,7 @@ class Text_Wiki_Render_Xhtml_Wikilink extends Text_Wiki_Render {
}
// get the CSS class and generate output
$css = sprintf(' class="%s"', $this->textEncode($this->getConf('css')));
$css = ' class="'.$this->textEncode($this->getConf('css')).'"';
$start = '<a'.$css.' href="'.$this->textEncode($href).'">';
$end = '</a>';
......@@ -136,7 +136,7 @@ class Text_Wiki_Render_Xhtml_Wikilink extends Text_Wiki_Render {
}
// get the appropriate CSS class and new-link text
$css = sprintf(' class="%s"', $this->textEncode($this->getConf('css_new')));
$css = ' class="'.$this->textEncode($this->getConf('css_new')).'"';
$new = $this->getConf('new_text');
// what kind of linking are we doing?
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment