Added KH 3.3.0 - extra modules

This commit is contained in:
Deon George 2013-03-19 14:46:14 +11:00
parent b140dbb1b6
commit a8f534b463
21 changed files with 1816 additions and 0 deletions

View File

@ -0,0 +1 @@
.DS_Store

View File

@ -0,0 +1,335 @@
<?php defined('SYSPATH') or die('No direct script access.');
/**
* Pagination links generator.
*
* @package Kohana/Pagination
* @category Base
* @author Kohana Team
* @copyright (c) 2008-2009 Kohana Team
* @license http://kohanaphp.com/license.html
*
* @method Pagination current_page()
* @method Pagination total_items()
* @method Pagination items_per_page()
* @method Pagination total_pages()
* @method Pagination current_first_item()
* @method Pagination current_last_item()
* @method Pagination previous_page()
* @method Pagination next_page()
* @method Pagination first_page()
* @method Pagination last_page()
* @method Pagination offset()
*/
class Kohana_Pagination {
/**
* @var array Merged configuration settings
*/
protected $config = array(
'current_page' => array('source' => 'query_string', 'key' => 'page'),
'total_items' => 0,
'items_per_page' => 10,
'view' => 'pagination/basic',
'auto_hide' => TRUE,
'first_page_in_url' => FALSE,
);
/**
* @var array Members that have access methods
*/
protected $_properties = array(
'current_page', 'total_items', 'items_per_page', 'total_pages', 'current_first_item', 'current_last_item',
'previous_page', 'next_page', 'first_page', 'last_page', 'offset',
);
// Current page number
protected $_current_page;
// Total item count
protected $_total_items;
// How many items to show per page
protected $_items_per_page;
// Total page count
protected $_total_pages;
// Item offset for the first item displayed on the current page
protected $_current_first_item;
// Item offset for the last item displayed on the current page
protected $_current_last_item;
// Previous page number; FALSE if the current page is the first one
protected $_previous_page;
// Next page number; FALSE if the current page is the last one
protected $_next_page;
// First page number; FALSE if the current page is the first one
protected $_first_page;
// Last page number; FALSE if the current page is the last one
protected $_last_page;
// Query offset
protected $_offset;
/**
* Creates a new Pagination object.
*
* @param array configuration
* @return Pagination
*/
public static function factory(array $config = array())
{
return new Pagination($config);
}
/**
* Creates a new Pagination object.
*
* @param array configuration
* @return void
*/
public function __construct(array $config = array())
{
// Overwrite system defaults with application defaults
$this->config = $this->config_group() + $this->config;
// Pagination setup
$this->setup($config);
}
/**
* Retrieves a pagination config group from the config file. One config group can
* refer to another as its parent, which will be recursively loaded.
*
* @param string pagination config group; "default" if none given
* @return array config settings
*/
public function config_group($group = 'default')
{
// Load the pagination config file
$config_file = Kohana::$config->load('pagination');
// Initialize the $config array
$config['group'] = (string) $group;
// Recursively load requested config groups
while (isset($config['group']) AND isset($config_file->$config['group']))
{
// Temporarily store config group name
$group = $config['group'];
unset($config['group']);
// Add config group values, not overwriting existing keys
$config += $config_file->$group;
}
// Get rid of possible stray config group names
unset($config['group']);
// Return the merged config group settings
return $config;
}
/**
* Loads configuration settings into the object and (re)calculates pagination if needed.
* Allows you to update config settings after a Pagination object has been constructed.
*
* @param array configuration
* @return object Pagination
*/
public function setup(array $config = array())
{
if (isset($config['group']))
{
// Recursively load requested config groups
$config += $this->config_group($config['group']);
}
// Overwrite the current config settings
$this->config = $config + $this->config;
// Only (re)calculate pagination when needed
if ($this->_current_page === NULL
OR isset($config['current_page'])
OR isset($config['total_items'])
OR isset($config['items_per_page']))
{
// Retrieve the current page number
if ( ! empty($this->config['current_page']['page']))
{
// The current page number has been set manually
$this->_current_page = (int) $this->config['current_page']['page'];
}
else
{
switch ($this->config['current_page']['source'])
{
case 'query_string':
$this->_current_page = isset($_GET[$this->config['current_page']['key']])
? (int) $_GET[$this->config['current_page']['key']]
: 1;
break;
case 'route':
$this->_current_page = (int) Request::current()->param($this->config['current_page']['key'], 1);
break;
}
}
// Calculate and clean all pagination variables
$this->_total_items = (int) max(0, $this->config['total_items']);
$this->_items_per_page = (int) max(1, $this->config['items_per_page']);
$this->_total_pages = (int) ceil($this->_total_items / $this->_items_per_page);
$this->_current_page = (int) min(max(1, $this->_current_page), max(1, $this->_total_pages));
$this->_current_first_item = (int) min((($this->_current_page - 1) * $this->_items_per_page) + 1, $this->_total_items);
$this->_current_last_item = (int) min($this->_current_first_item + $this->_items_per_page - 1, $this->_total_items);
$this->_previous_page = ($this->_current_page > 1) ? $this->_current_page - 1 : FALSE;
$this->_next_page = ($this->_current_page < $this->_total_pages) ? $this->_current_page + 1 : FALSE;
$this->_first_page = ($this->_current_page === 1) ? FALSE : 1;
$this->_last_page = ($this->_current_page >= $this->_total_pages) ? FALSE : $this->_total_pages;
$this->_offset = (int) (($this->_current_page - 1) * $this->_items_per_page);
}
// Chainable method
return $this;
}
/**
* Generates the full URL for a certain page.
*
* @param integer page number
* @return string page URL
*/
public function url($page = 1)
{
// Clean the page number
$page = max(1, (int) $page);
// No page number in URLs to first page
if ($page === 1 AND ! $this->config['first_page_in_url'])
{
$page = NULL;
}
switch ($this->config['current_page']['source'])
{
case 'query_string':
return URL::site(Request::current()->uri()).URL::query(array($this->config['current_page']['key'] => $page));
case 'route':
return URL::site(Request::current()->uri(array($this->config['current_page']['key'] => $page))).URL::query();
}
return '#';
}
/**
* Checks whether the given page number exists.
*
* @param integer page number
* @return boolean
* @since 3.0.7
*/
public function valid_page($page)
{
// Page number has to be a clean integer
if ( ! Valid::digit($page))
return FALSE;
return $page > 0 AND $page <= $this->_total_pages;
}
/**
* Renders the pagination links.
*
* @param mixed string of the view to use, or a Kohana_View object
* @return string pagination output (HTML)
*/
public function render($view = NULL)
{
// Automatically hide pagination whenever it is superfluous
if ($this->config['auto_hide'] === TRUE AND $this->_total_pages <= 1)
return '';
if ($view === NULL)
{
// Use the view from config
$view = $this->config['view'];
}
if ( ! $view instanceof View)
{
// Load the view file
$view = View::factory($view);
}
// Pass on the whole Pagination object
return $view->set(get_object_vars($this))->set('page', $this)->render();
}
/**
* Renders the pagination links.
*
* @return string pagination output (HTML)
*/
public function __toString()
{
try
{
return $this->render();
}
catch(Exception $e)
{
Kohana_Exception::handler($e);
return '';
}
}
/**
* Handles loading and setting properties.
*
* @param string $method Method name
* @param array $args Method arguments
* @return mixed
*/
public function __call($method, array $args)
{
if (in_array($method, $this->_properties))
{
if (!count($args))
{
return $this->{'_'.$method};
}
}
else
{
throw new Kohana_Exception('Invalid method :method called in :class',
array(':method' => $method, ':class' => get_class($this)));
}
}
/**
* Handles setting of property
*
* @param string $key Property name
* @param mixed $value Property value
* @return void
*/
public function __set($key, $value)
{
if (isset($this->{'_'.$key}))
{
$this->setup(array($key => $value));
}
else
{
throw new Kohana_Exception('The :property: property does not exist in the :class: class',
array(':property:' => $key, ':class:' => get_class($this)));
}
}
} // End Pagination

View File

@ -0,0 +1,3 @@
<?php defined('SYSPATH') or die('No direct script access.');
class Pagination extends Kohana_Pagination {}

View File

@ -0,0 +1,15 @@
<?php defined('SYSPATH') or die('No direct script access.');
return array(
// Application defaults
'default' => array(
'current_page' => array('source' => 'query_string', 'key' => 'page'), // source: "query_string" or "route"
'total_items' => 0,
'items_per_page' => 10,
'view' => 'pagination/basic',
'auto_hide' => TRUE,
'first_page_in_url' => FALSE,
),
);

View File

@ -0,0 +1,23 @@
<?php defined('SYSPATH') or die('No direct script access.');
return array(
// Leave this alone
'modules' => array(
// This should be the path to this modules userguide pages, without the 'guide/'. Ex: '/guide/modulename/' would be 'modulename'
'pagination' => array(
// Whether this modules userguide pages should be shown
'enabled' => TRUE,
// The name that should show up on the userguide index page
'name' => 'Pagination',
// A short description of this module, shown on the index page
'description' => 'Tool for creating paginated links and viewing pages of results.',
// Copyright message, shown in the footer for this module
'copyright' => '&copy; 20082010 Kohana Team',
)
)
);

View File

@ -0,0 +1,94 @@
# Pagination Configuration
[Pagination] uses 6 settings: `current_page`, `total_items`, `items_per_page`, `view`, `auto_hide` and `first_page_in_url`.
## Configuration Examples
This example shows the default configuration:
return array(
// Application defaults
'default' => array(
'current_page' => array('source' => 'query_string', 'key' => 'page'), // source: "query_string" or "route"
'total_items' => 0,
'items_per_page' => 10,
'view' => 'pagination/basic',
'auto_hide' => TRUE,
'first_page_in_url' => FALSE,
),
);
This is an example with multiple configurations:
return array(
// Application defaults
'default' => array(
'current_page' => array('source' => 'query_string', 'key' => 'page'),
'total_items' => 0,
'items_per_page' => 10,
'view' => 'pagination/basic',
'auto_hide' => TRUE,
'first_page_in_url' => FALSE,
),
// Second configuration
'pretty' => array(
'current_page' => array('source' => 'route', 'key' => 'page'),
'total_items' => 0,
'items_per_page' => 20,
'view' => 'pagination/pretty',
'auto_hide' => TRUE,
'first_page_in_url' => FALSE,
),
);
## Settings
### current_page
The `current_page` setting tells Pagination where to look to find the current page number.
There are two options for the `source` of the page number: `query_string` and `route`.
The `key` index in the configuration array tells Pagination what name to look for when it's searching in the query string or route.
This configuration informs Pagination to look in the query string for a value named `page`:
'current_page' => array('source' => 'query_string', 'key' => 'page'),
If you have a route setup with the page number in the actual URL like this:
Route::set('city_listings', '<city>listings(/<page_num>)', array('page_num' => '[0-9]+'))
->defaults(array(
'controller' => 'city',
'action' => 'listings'
));
then you would use a setting like this:
'current_page' => array('source' => 'route', 'key' => 'page_num'),
### total_items
`total_items` is a setting you will most likely pass in during runtime after figuring out exactly how many items you have. It can be set to zero in the configuration for now.
### items_per_page
Self explanatory. This is the maximum items to show on each page. Pagination determines the total number of pages based off of this number.
### view
The `view` setting should be a path to a Pagination view file.
### auto_hide
If `auto_hide` is set to `TRUE` then Pagination will automatically hide whenever there's only one page of items.
### first_page_in_url
If you want Pagination to add the page number to the first page's link then set this setting to `TRUE` otherwise leave it as `FALSE`.

View File

@ -0,0 +1,4 @@
## [Pagination]()
- [Config](config)
- [Usage](usage)
- [Examples](examples)

View File

@ -0,0 +1,37 @@
<p class="pagination">
<?php if ($page->first_page() !== FALSE): ?>
<a href="<?php echo HTML::chars($page->url($page->first_page())) ?>" rel="first"><?php echo __('First') ?></a>
<?php else: ?>
<?php echo __('First') ?>
<?php endif ?>
<?php if ($page->previous_page() !== FALSE): ?>
<a href="<?php echo HTML::chars($page->url($page->previous_page())) ?>" rel="prev"><?php echo __('Previous') ?></a>
<?php else: ?>
<?php echo __('Previous') ?>
<?php endif ?>
<?php for ($i = 1; $i <= $page->total_pages(); $i++): ?>
<?php if ($i == $page->current_page()): ?>
<strong><?php echo $i ?></strong>
<?php else: ?>
<a href="<?php echo HTML::chars($page->url($i)) ?>"><?php echo $i ?></a>
<?php endif ?>
<?php endfor ?>
<?php if ($page->next_page() !== FALSE): ?>
<a href="<?php echo HTML::chars($page->url($page->next_page())) ?>" rel="next"><?php echo __('Next') ?></a>
<?php else: ?>
<?php echo __('Next') ?>
<?php endif ?>
<?php if ($page->last_page() !== FALSE): ?>
<a href="<?php echo HTML::chars($page->url($page->last_page())) ?>" rel="last"><?php echo __('Last') ?></a>
<?php else: ?>
<?php echo __('Last') ?>
<?php endif ?>
</p><!-- .pagination -->

View File

@ -0,0 +1,94 @@
<?php
/*
First Previous 1 2 3 ... 22 23 24 25 26 [27] 28 29 30 31 32 ... 48 49 50 Next Last
*/
// Number of page links in the begin and end of whole range
$count_out = ( ! empty($config['count_out'])) ? (int) $config['count_out'] : 3;
// Number of page links on each side of current page
$count_in = ( ! empty($config['count_in'])) ? (int) $config['count_in'] : 5;
// Beginning group of pages: $n1...$n2
$n1 = 1;
$n2 = min($count_out, $page->total_pages());
// Ending group of pages: $n7...$n8
$n7 = max(1, $page->total_pages() - $count_out + 1);
$n8 = $page->total_pages();
// Middle group of pages: $n4...$n5
$n4 = max($n2 + 1, $page->current_page() - $count_in);
$n5 = min($n7 - 1, $page->current_page() + $count_in);
$use_middle = ($n5 >= $n4);
// Point $n3 between $n2 and $n4
$n3 = (int) (($n2 + $n4) / 2);
$use_n3 = ($use_middle && (($n4 - $n2) > 1));
// Point $n6 between $n5 and $n7
$n6 = (int) (($n5 + $n7) / 2);
$use_n6 = ($use_middle && (($n7 - $n5) > 1));
// Links to display as array(page => content)
$links = array();
// Generate links data in accordance with calculated numbers
for ($i = $n1; $i <= $n2; $i++)
{
$links[$i] = $i;
}
if ($use_n3)
{
$links[$n3] = '&hellip;';
}
for ($i = $n4; $i <= $n5; $i++)
{
$links[$i] = $i;
}
if ($use_n6)
{
$links[$n6] = '&hellip;';
}
for ($i = $n7; $i <= $n8; $i++)
{
$links[$i] = $i;
}
?>
<p class="pagination">
<?php if ($page->first_page() !== FALSE): ?>
<a href="<?php echo HTML::chars($page->url($page->first_page())) ?>" rel="first"><?php echo __('First') ?></a>
<?php else: ?>
<?php echo __('First') ?>
<?php endif ?>
<?php if ($page->previous_page() !== FALSE): ?>
<a href="<?php echo HTML::chars($page->url($page->previous_page())) ?>" rel="prev"><?php echo __('Previous') ?></a>
<?php else: ?>
<?php echo __('Previous') ?>
<?php endif ?>
<?php foreach ($links as $number => $content): ?>
<?php if ($number === $page->current_page()): ?>
<strong><?php echo $content ?></strong>
<?php else: ?>
<a href="<?php echo HTML::chars($page->url($number)) ?>"><?php echo $content ?></a>
<?php endif ?>
<?php endforeach ?>
<?php if ($page->next_page() !== FALSE): ?>
<a href="<?php echo HTML::chars($page->url($page->next_page())) ?>" rel="next"><?php echo __('Next') ?></a>
<?php else: ?>
<?php echo __('Next') ?>
<?php endif ?>
<?php if ($page->last_page() !== FALSE): ?>
<a href="<?php echo HTML::chars($page->url($page->last_page())) ?>" rel="last"><?php echo __('Last') ?></a>
<?php else: ?>
<?php echo __('Last') ?>
<?php endif ?>
</p><!-- .pagination -->

View File

@ -0,0 +1,22 @@
Copyright (c) 2010 Cédric de Saint Léger
Permission is hereby granted, free of charge, to any person
obtaining a copy of this software and associated documentation
files (the "Software"), to deal in the Software without
restriction, including without limitation the rights to use,
copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the
Software is furnished to do so, subject to the following
conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
OTHER DEALINGS IN THE SOFTWARE.

View File

@ -0,0 +1,31 @@
Kohana_XML is a XML modules to generate and read XML documents in Kohana.
It is build for KO3, but there are barely one or two lines that makes it KO3 specific,
so I guess it should work for KO2.x without much trouble.
## Notable Features
* **Extendible, configurable drivers** — You can use the XML class to write simple XML,
or use the Atom driver to generate Atom compliant XML, or write your own driver (extending XML
or another driver) to generate XML compliant to any specs you want. Driver support initial
configuration, which will be used when using native functions, and your own function.
Namespaces and prefix, value filters, default attributes, node name abstraction are all part
of driver configuration and are then used as such by native functions, so they are dealt with
on the fly. But you can also write your own function very easily in your drivers, and writing
an add_author($user_model) function in the Atom driver would take a second.
* **Dealing with objects of the same class whatever function you use** $xml→add_node(“test”);
generates another XML instance of the same driver you can add nodes to, import array or XML files
to, search in, modify, export, combine… The whole XML document becomes modular, easy to read and
to modify, and to run through with method chaining. Just play Lego with your XML.
* **Magic get and get()** — allows to easily run through the document. For instance
$atom→author→name will return an atom document authors name, this regardless of your driver
configuration. As another example of node name abstraction, if youve decided to abstract “pubDate”
with “updated” in your RSS2 driver configuration and “published” with “updated” in you Atom driver,
then $atom→updated will give you the same result as $rss→updated.
* **Jelly-style driver configuration** — I liked the way Jelly initializes its models, so you can
configure yours just the same way. Driver configuration then goes into a static meta class, which
improves performance.
* You can still use **DOM functions** if you wish and reintegrate in Kohana_XML

View File

@ -0,0 +1,13 @@
<?php defined('SYSPATH') or die('No direct script access.');
/**
* Document : xml.php
* Created on : 1 mai 2009, 13:03:03
* @author Cedric de Saint Leger <c.desaintleger@gmail.com>
*
* Description:
* XML class. Use this class to override XML_Core.
* Extend this class to make your own XML based driver (Atom, XRDS, GData, RSS, PodCast RSS, or your own brewed XML format)
*/
class XML extends XML_Core
{}

View File

@ -0,0 +1,732 @@
<?php defined('SYSPATH') OR die('No direct access allowed.');
/**
* Document : core.php
* Created on : 1 mai 2009, 13:03:03
* @author Cedric de Saint Leger <c.desaintleger@gmail.com>
*
* Description:
* XML_Core class.
*/
class XML_Core
{
/**
* @var string XML document version
*/
public static $xml_version = "1.0";
/**
* @var string Root Node name
*/
public $root_node;
/**
* The DOM_Element corresponding to this XML instance
* This is made public to use DOM functions directly if desired.
* @var DOM_Element XML instance DOM node.
*/
public $dom_node;
/**
* Basically a handy shortcut of $this->dom_node->ownerDocument
* All XML instance belonging to the same document will have this attribute in common
* @var DOM_Document XML instance DOM document, owner of dom_node
*/
public $dom_doc;
/**
* @var array Array of XML_Meta, containing metadata about XML drivers config
*/
protected static $_metas = array();
/**
* This creates an XML object from the specified driver.
* Specify the driver name, or if there is no specific driver, the root node name
* @param string $driver [optional] Driver Name
* @param string $root_node [optional] Root Node name. Force the root node name. Must be used if no driver nor element is specified.
* @param string $element [optional] XML string or file to generate XML from. Must be used if no driver nor root_node is specified.
* @return XML XML object
*/
public static function factory($driver = NULL, $root_node = NULL, $element = NULL)
{
if ($driver)
{
// Let's attempt to generate a new instance of the subclass corresponding to the driver provided
$class = 'XML_Driver_'.ucfirst($driver);
// Register a new meta object
XML::$_metas[strtolower($class)] = $meta = new XML_Meta;
// Override the meta with driver-specific attributes
call_user_func(array($class, "initialize"), $meta);
// Set content type to default if it is not already set, and report it as initialized
$meta->content_type("text/xml")->set_initialized();
return new $class($element, $root_node);
}
else
{
// Register a new meta object in the root node
XML::$_metas["xml"] = $meta = new XML_Meta;
// Set content type to default if it is not already set, and report it as initialized
$meta->content_type("text/xml")->set_initialized();
return new XML($element, $root_node);
}
}
/**
* Class constructor. You should use the factory instead.
* @param string $element [optional] What to construct from. Could be some xml string, a file name, or a DOMNode
* @param string $root_node [optional] The root node name. To be specified if no driver are used.
* @return XML XML object instance
*/
public function __construct($element = NULL, $root_node = NULL)
{
// Create the initial DOMDocument
$this->dom_doc = new DOMDocument(XML::$xml_version, Kohana::$charset);
if ($root_node)
{
// If a root node is specified, overwrite the current_one
$this->root_node = $root_node;
}
// Initialize the document with the given element
if (is_string($element))
{
if (is_file($element) OR Valid::url($element))
{
// Generate XML from a file
$this->dom_doc->load($element);
}
else
{
// Generate XML from a string
$this->dom_doc->loadXML($element);
}
// Node is the root node of the document, containing the whole tree
$this->dom_node = $this->dom_doc->documentElement;
}
elseif ($element instanceof DOMNode)
{
// This is called from another XML instance ( through add_node, or else...)
// Let's add that node to the new object node
$this->dom_node = $element;
// And overwrite the document with that node's owner document
$this->dom_doc = $this->dom_node->ownerDocument;
}
elseif ( ! is_null($this->root_node))
{
// Create the Root Element from the driver attributes
if ($this->meta()->get("namespace", $this->root_node))
{
$root_node_name = $this->meta()->get("prefix", $this->root_node) ? $this->meta()->get("prefix", $this->root_node).":$this->root_node" : $this->root_node;
// Create the root node in its prefixed namespace
$root_node = $this->dom_doc->createElementNS($this->meta()->get("namespace", $this->root_node), $root_node_name);
}
else
{
// Create the root node
$root_node = $this->dom_doc->createElement($this->root_node);
}
// Append the root node to the object DOMDocument, and set the resulting DOMNode as it's node
$this->dom_node = $this->dom_doc->appendChild($root_node);
// Add other attributes
$this->add_attributes($this->dom_node);
}
else
{
throw new Kohana_Exception("You have to specify a root_node, either in your driver or in the constructor if you're not using any.");
}
}
/**
* Adds a node to the document
* @param string $name Name of the node. Prefixed namespaces are handled automatically.
* @param value $value [optional] value of the node (will be filtered). If value is not valid CDATA,
* it will be wrapped into a CDATA section
* @param array $attributes [optional] array of attributes. Prefixed namespaces are handled automatically.
* @return XML instance for the node that's been added.
*/
public function add_node($name, $value = NULL, $attributes = array())
{
// Trim the name
$name = trim($name);
// Create the element
$node = $this->create_element($name);
// Add the attributes
$this->add_attributes($node, $attributes);
// Add the value if provided
if ($value !== NULL)
{
$value = strval($this->filter($name, $value, $node));
if (str_replace(array('<', '>', '&'), "", $value) === $value)
{
// Value is valid CDATA, let's add it as a new text node
$value = $this->dom_doc->createTextNode($value);
}
else
{
// We shall create a CDATA section to wrap the text provided
$value = $this->dom_doc->createCDATASection($value);
}
$node->appendChild($value);
}
// return a new XML instance of the same class from the child node
$class = get_class($this);
return new $class($this->dom_node->appendChild($node));
}
/**
* Magic get returns the first child node matching the value
* @param string $node_name
* @return mixed If trying to get a node:
* NULL will be return if nothing is matched,
* A string value is returned if it a text/cdata node is matched
* An XML instance is returned otherwise, allowing chaining.
*/
public function __get($value)
{
if ( ! isset($this->$value))
{
$node = current($this->get($value));
if ($node instanceof XML)
{
// Return the whole XML document
return $node;
}
// We did not match any child nodes
return NULL;
}
parent::__get($value);
}
/**
* Gets all nodes matching a name and returns them as an array.
* Can also be used to get a pointer to a particular node and then deal with that node as an XML instance.
* @param string $value name of the nodes desired
* @param bool $as_array [optional] whether or not the nodes should be returned as an array
* @return array Multi-dimensional array or array of XML instances
*/
public function get($value, $as_array = FALSE)
{
$return = array();
$value = $this->meta()->alias($value);
foreach ($this->dom_node->getElementsByTagName($value) as $item)
{
if ($as_array)
{
// Return as array but ignore root node
$array = $this->_as_array($item);
foreach ($array as $val)
{
$return[] = $val;
}
}
else
{
$class = get_class($this);
$return[] = new $class($item);
}
}
return $return;
}
/**
* Queries the document with an XPath query
* @param string $query XPath query
* @param bool $as_array [optional] whether or not the nodes should be returned as an array
* @return array Multi-dimensional array or array of XML instances
*/
public function xpath($query, $as_array = TRUE)
{
$return = array();
$xpath = new DOMXPath($this->dom_doc);
foreach ($xpath->query($query) as $item)
{
if ($as_array)
{
$array = $this->_as_array($item);
foreach ($array as $val)
{
$return[] = $val;
}
}
else
{
$class = get_class($this);
$return[] = new $class($item);
}
}
return $return;
}
/**
* Exports the document as a multi-dimensional array.
* Handles element with the same name.
*
* Root node is ignored, as it is known and available in the driver.
* Example :
* <node_name attr_name="val">
* <child_node_name>
* value1
* </child_node_name>
* <child_node_name>
* value2
* </child_node_name>
* </node_name>
*
* Here's the resulting array structure :
* array ("node_name" => array(
* // array of nodes called "node_name"
* 0 => array(
* // Attributes of that node
* "xml_attributes" => array(
* "attr_name" => "val",
* )
* // node contents
* "child_node_name" => array(
* // array of nodes called "child_node_name"
* 0 => value1,
* 1 => value2,
* )
* The output is retro-actively convertible to XML using from_array().
* @return array
*/
public function as_array()
{
$dom_element = $this->dom_node;
$return = array();
// This function is run on a whole XML document and this is the root node.
// That root node shall be ignored in the array as it driven by the driver and handles document namespaces.
foreach($dom_element->childNodes as $dom_child)
{
if ($dom_child->nodeType == XML_ELEMENT_NODE)
{
// Let's run through the child nodes
$child = $this->_as_array($dom_child);
foreach ($child as $key => $val)
{
$return[$key][]=$val;
}
}
}
return $return;
}
/**
* Recursive as_array for child nodes
* @param DOMNode $dom_node
* @return Array
*/
private function _as_array(DOMNode $dom_node)
{
// All other nodes shall be parsed normally : attributes then text value and child nodes, running through the XML tree
$object_element = array();
// Get the desired node name for this node
$node_name = $this->meta()->key($dom_node->tagName);
// Get children, run through XML tree
if ($dom_node->hasChildNodes())
{
if (!$dom_node->firstChild->hasChildNodes())
{
// Get text value
$object_element[$node_name] = trim($dom_node->firstChild->nodeValue);
}
foreach($dom_node->childNodes as $dom_child)
{
if ($dom_child->nodeType === XML_ELEMENT_NODE)
{
$child = $this->_as_array($dom_child);
foreach ($child as $key=>$val)
{
$object_element[$node_name][$key][]=$val;
}
}
}
}
// Get attributes
if ($dom_node->hasAttributes())
{
$object_element[$dom_node->nodeName]['xml_attributes'] = array();
foreach($dom_node->attributes as $att_name => $dom_attribute)
{
// Get the desired name for this attribute
$att_name = $this->meta()->key($att_name);
$object_element[$node_name]['xml_attributes'][$att_name] = $dom_attribute->value;
}
}
return $object_element;
}
/**
* Converts an array to XML. Expected structure is given in as_array().
* However, from_array() is essentially more flexible regarding to the input array structure,
* as we don't have to bother about nodes having the same name.
* Try something logical, that should work as expected.
* @param object $mixed
* @return XML
*/
public function from_array($array)
{
$this->_from_array($array, $this->dom_node);
return $this;
}
/**
* Array shall be like : array('element_name' => array( 0 => text, 'xml_attributes' => array()));
* @param object $mixed
* @param DOMElement $dom_element
* @return
*/
protected function _from_array($mixed, DOMElement $dom_element)
{
if (is_array($mixed))
{
foreach( $mixed as $index => $mixed_element )
{
if ( is_numeric($index) )
{
// If we have numeric keys, we're having multiple children of the same node.
// Append the new node to the current node's parent
// If this is the first node to add, $node = $dom_element
$node = $dom_element;
if ( $index != 0 )
{
// If not, lets create a copy of the node with the same name
$node = $this->create_element($dom_element->tagName);
// And append it to the parent node
$node = $dom_element->parentNode->appendChild($node);
}
$this->_from_array($mixed_element, $node);
}
elseif ($index == "xml_attributes")
{
// Add attributes to the node
$this->add_attributes($dom_element, $mixed_element);
}
else
{
// Create a new element with the key as the element name.
// Create the element corresponding to the key
$node = $this->create_element($index);
// Add the driver attributes
$this->add_attributes($node);
// Append it
$dom_element->appendChild($node);
// Treat the array by recursion
$this->_from_array($mixed_element, $node);
}
}
}
elseif ($mixed)
{
// This is a string value that shall be appended as such
$mixed = $this->filter($dom_element->tagName, $mixed, $dom_element);
$dom_element->appendChild($this->dom_doc->createTextNode($mixed));
}
}
/**
* This function is used to import another XML instance, or whatever we can construct XML from (string, filename, DOMNode...)
*
* $xml1 = XML::factory("atom", "<feed><bla>bla</bla></feed>");
* $xml2 = XML::factory("rss", "<test></test>");
* $node_xml2 = $xml2->add_node("key");
*
* // outputs "<test><key><feed><bla>bla</bla></feed></key></test>"
* $node_xml2->import($xml1)->render();
*
* // outputs "<feed><bla>bla</bla></feed><key><feed><bla>bla</bla></feed></key>"
* $xml1->import($xml2->get("key"))->render();
*
* @param object $xml XML instance or DOMNode
* @return object $this Chainable function
*/
public function import($xml)
{
if (! $xml instanceof XML)
{
// Attempt to construct XML from the input
$class = get_class($this);
$xml = new $class($xml);
}
// Import the node, and all its children, to the document
$node = $this->dom_doc->importNode($xml->dom_node, TRUE);
$this->dom_node->appendChild($node);
return $this;
}
/**
* Creates an element, sorts out namespaces (default / prefixed)
* @param string $name element name
* @return DOMElement
*/
private function create_element($name)
{
$name = $this->meta()->alias($name);
// Let's check if the element name has a namespace, and if this prefix is defined in our driver
if ($namespace_uri = $this->meta()->get("namespace", $name))
{
if (stristr($name, ":"))
{
// Separate the namespace prefix and the name
list($prefix, $name) = explode(":", $name);
// Register the prefixed namespace in the document root
$this->dom_doc->documentElement->setAttributeNS("http://www.w3.org/2000/xmlns/" ,"xmlns:".$prefix, $namespace_uri);
// Create the prefixed element within that namespace
$node = $this->dom_doc->createElementNS($namespace_uri, $prefix.":".$name);
}
else
{
// Create the element normally
$node = $this->dom_doc->createElement($name);
// Add the new default namespace as an attribute.
$node->setAttribute("xmlns", $namespace_uri);
}
}
else
{
// Simply create the element
$node = $this->dom_doc->createElement($name);
}
return $node;
}
/**
* Applies attributes to a node
* @param DOMNode $node
* @param array $attributes as key => value
* @return DOMNode
*/
private function add_attributes(DOMNode $node, $attributes = array())
{
$node_name = $this->meta()->alias($node->tagName);
if ($this->meta()->get("attributes", $node_name))
{
$attributes = array_merge($this->meta()->get("attributes", $node_name), $attributes);
}
foreach ($attributes as $key => $val)
{
// Trim elements
$key = $this->meta()->alias(trim($key));
$val = $this->filter($key, trim($val), $node);
// Set the attribute
// Let's check if the attribute name has a namespace prefix, and if this prefix is defined in our driver
if ($namespace_uri = $this->meta()->get("namespace", $key)
AND stristr($name, ":"))
{
// Separate the namespace prefix and the name
list($prefix, $name) = explode(":", $name);
// Register the prefixed namespace
$this->dom_node->setAttributeNS("http://www.w3.org/2000/xmlns/" ,"xmlns:".$prefix, $namespace_uri);
// Add the prefixed attribute within that namespace
$node->setAttributeNS($namespace_uri, $key, $val);
}
else
{
// Simply add the attribute
$node->setAttribute($key, $val);
}
}
return $node;
}
/**
* Applies filter on a value.
* These filters are callbacks usually defined in the driver.
* They allow to format dates, links, standard stuff, and play
* as you wish with the value before it is added to the document.
*
* You could even extend it and modify the node name.
*
* @param string $name
* @param string $value
* @return string $value formatted value
*/
protected function filter($name, $value, &$node)
{
$name = $this->meta()->alias($name);
if ($this->meta()->get("filter", $name))
{
return call_user_func(array($this, $this->meta()->get("filter", $name)), $value, $node);
}
return $value;
}
/**
* This is a classic filter that takes a uri and makes a proper link
* @param object $value
* @return $value
*/
public function normalize_uri($value, $node)
{
if (strpos($value, '://') === FALSE)
{
if (strlen(URL::base()) > 1 AND stristr($value, URL::base()))
{
// Make sure the path is not base related
$value = str_replace(URL::base(), '', $value);
}
// Convert URIs to URLs
$value = URL::site($value, TRUE);
}
return $value;
}
/**
* Another classic filter to deal with boolean
* @param boolean $value
* @return string $value, true or false
*/
public function normalize_bool($value)
{
return $value ? "true" : "false";
}
/**
* Returns this drivers XML metadata
* @return XML_Meta
*/
public function meta()
{
return XML::$_metas[strtolower(get_class($this))];
}
/**
* Outputs nicely formatted XML when converting as string
* @return string
*/
public function __toString()
{
return $this->render(TRUE);
}
/**
* Render the XML.
* @param boolean $formatted [optional] Should the output be formatted and indented ?
* @return string
*/
public function render($formatted = FALSE)
{
$this->dom_doc->formatOutput = $formatted;
return $this->dom_doc->saveXML();
}
/**
* Outputs the XML in a file
* @param string filename
* @return
*/
public function export($file)
{
return $this->dom_doc->save($file);
}
/**
* Returns this instance node value, if the dom_node is a text node
*
* @return string
*/
public function value()
{
if ($this->dom_node->hasChildNodes() AND $this->dom_node->firstChild->nodeType === XML_TEXT_NODE)
{
return $this->dom_node->nodeValue;
}
return NULL;
}
/**
* Returns this instance node value
*
* @return string|array attributes as array of attribute value if a name is specified
*/
public function attributes($attribute_name = NULL)
{
if ($attribute_name === NULL)
{
// Return an array of attributes
$attributes = array();
if ($this->dom_node->hasAttributes())
{
foreach ($this->dom_node->attributes as $attribute)
{
$attributes[$attribute->name] = $attribute->value;
}
}
return $attributes;
}
// Simply return the attribute value
return $this->dom_node->getAttribute($attribute_name);
}
} // End XML_Core

View File

@ -0,0 +1,101 @@
<?php defined('SYSPATH') or die('No direct script access.');
/**
* Document : atom.php
* Created on : 1 mai 2009, 13:03:03
* @author Cedric de Saint Leger <c.desaintleger@gmail.com>
*
* Description:
* Atom driver
*/
class XML_Driver_Atom extends XML
{
public $root_node = 'feed';
protected static function initialize(XML_Meta $meta)
{
$meta ->content_type("application/atom+xml")
->nodes (
array(
"feed" => array("namespace" => "http://www.w3.org/2005/Atom"),
// "entry" => array("namespace" => "http://www.w3.org/2005/Atom"),
"href" => array("filter" => "normalize_uri"),
"link" => array("filter" => "normalize_uri"),
"logo" => array("filter" => "normalize_uri"),
"icon" => array("filter" => "normalize_uri"),
"id" => array("filter" => "normalize_uri"),
"updated" => array("filter" => "normalize_datetime"),
"published" => array("filter" => "normalize_datetime"),
"startDate" => array("filter" => "normalize_date"),
'endDate' => array("filter" => "normalize_date"),
"summary" => array("filter" => "normalize_text"),
"subtitle" => array("filter" => "normalize_text"),
"title" => array("filter" => "normalize_text"),
"content" => array("filter" => "normalize_text")
)
);
}
public function add_person($type, $name, $email = NULL, $uri = NULL)
{
$author = $this->add_node($type);
$author->add_node("name", $name);
if ($email)
{
$author->add_node("email", $email);
}
if ($uri)
{
$author->add_node("uri", $uri);
}
return $this;
}
public function add_content(XML $xml_document)
{
$this->add_node("content", NULL, array("type" => $xml_document->meta()->content_type()))->import($xml_document);
return $this;
}
public function normalize_text($value, $node)
{
if (strpos($value, "<") >= 0 AND strpos($value, ">") > 0)
{
// Assume type = html
$node->setAttribute("type", "html");
}
else
{
$node->setAttribute("type", "text");
}
return $value;
}
public function normalize_datetime($value)
{
if ( ! is_numeric($value))
{
$value = strtotime($value);
}
// Convert timestamps to RFC 3339 formatted datetime
return date(DATE_RFC3339, $value);
}
public function normalize_date($value)
{
if ( ! is_numeric($value))
{
$value = strtotime($value);
}
// Convert timestamps to RFC 3339 formatted dates
return date("Y-m-d", $value);
}
}

View File

@ -0,0 +1,57 @@
<?php defined('SYSPATH') or die('No direct script access.');
/**
* Document : rss2.php
* Created on : 1 mai 2009, 13:03:03
* @author Cedric de Saint Leger <c.desaintleger@gmail.com>
*
* Description:
* RSS2 driver
*/
class XML_Driver_Rss2 extends XML
{
public $root_node = 'rss';
protected static function initialize(XML_Meta $meta)
{
$meta ->content_type("application/rss+xml")
->nodes (
array(
"rss" => array("attributes" => array("version" => "2.0")),
"title" => array("filter" => "normalize_text"),
"description" => array("filter" => "normalize_text"),
"link" => array("filter" => "normalize_uri"),
"atom:link" => array("attributes" => array(
"rel" => "self",
"type" => "application/rss+xml",
// "href" => URL::site(Request::initial()->uri(), TRUE)
),
"namespace" => "http://www.w3.org/2005/Atom"),
"href" => array("filter" => "normalize_uri"),
"docs" => array("filter" => "normalize_uri"),
"guid" => array("filter" => "normalize_uri"),
"pubDate" => array("filter" => "normalize_date"),
"lastBuildDate" => array("filter" => "normalize_date")
)
);
}
public function normalize_date($value)
{
if ( ! is_numeric($value))
{
$value = strtotime($value);
}
// Convert timestamps to RFC 822 formatted dates, with 4 digits year
return date(DATE_RSS, $value);
}
public function normalize_text($value)
{
// Strip HTML tags
return strip_tags($value);
}
}

View File

@ -0,0 +1,55 @@
<?php defined('SYSPATH') or die('No direct script access.');
/**
* Document : xrds.php
* Created on : 1 mai 2009, 13:03:03
* @author Cedric de Saint Leger <c.desaintleger@gmail.com>
*
* Description:
* XRDS driver. For Service Discovery.
*/
class XML_Driver_XRDS extends XML
{
public $root_node = 'xrds:XRDS';
protected static function initialize(XML_Meta $meta)
{
$meta ->content_type("application/xrds+xml")
->nodes (
array(
"xrds:XRDS" => array("namespace" => 'xri://$xrds', "attributes" => array("xmlns" => 'xri://$xrd*($v*2.0)')),
"LocalID" => array("filter" => "normalize_uri"),
"openid:Delegate" => array("filter" => "normalize_uri", "namespace" => "http://openid.net/xmlns/1.0"),
"URI" => array("filter" => "normalize_uri"),
)
);
}
public function add_service($type, $uri, $priority = NULL)
{
if (! is_null($priority))
{
$priority = array("priority" => $priority);
}
else
{
$priority = array();
}
$service_node = $this->add_node("Service", NULL, $priority);
if (! is_array($type))
{
$type = array($type);
}
foreach ($type as $t)
{
$service_node->add_node("Type", $t);
}
$service_node->add_node("URI", $uri);
return $service_node;
}
}

View File

@ -0,0 +1,12 @@
<?php defined('SYSPATH') or die('No direct script access.');
/**
* Document : meta.php
* Created on : 1 mai 2009, 13:03:03
* @author Cedric de Saint Leger <c.desaintleger@gmail.com>
*
* Description:
* XML_Meta class. Use this to override XML_Meta_Core
*/
class XML_Meta extends XML_Meta_Core
{}

View File

@ -0,0 +1,187 @@
<?php defined('SYSPATH') or die('No direct script access.');
/**
* Document : meta.php
* Created on : 1 mai 2009, 13:03:03
* @author Cedric de Saint Leger <c.desaintleger@gmail.com>
*
* Description:
* XML_Meta_Core class. This class contains XML drivers metadata
*/
class XML_Meta_Core
{
/**
* @var array assoc array alias => $node_name
* This is used to abstract the node names
*/
protected $nodes = array();
/**
* @var array whole node configuration array
* array("node_name" => array(
* // Effective node name in the XML document.
* // This name is abstracted and "node_name" is always used when dealing with the object.
* "node" => "effective_node_name",
* // Defines a namespace URI for this node
* "namespace" => "http://www.namespace.uri",
* // Defines a prefix for the namespace above. If not defined, namespace is interpreted as a default namespace
* "prefix" => "ns",
* // Defines a callback function to filter/normalize the value
* "filter" => "filter_function_name",
* // Array of attributes
* "attributes" => array("default_attribute1" => "value")
* ),
* "alias" => "node_name",
* )
*/
protected $nodes_config = array();
/**
* @var string content type for HTML headers
*/
protected $content_type;
/**
* @var boolean whether the object is initialized
*/
protected $_initialized = FALSE;
/**
* Returns the name of a node, sort out aliases
* @param string $name
* @return string $node_name
*/
public function alias($name)
{
if (isset($this->nodes_config[$name]))
{
if ( ! is_array($this->nodes_config[$name]))
{
$name = $this->nodes_config[$name];
}
}
return Arr::get($this->nodes, $name, $name);
}
/**
* Returns the value of a meta key for a given node name
* exemple $this->get('attributes', 'feed') will return all the attributes set up in the meta
* for the node feed.
* @param object $key meta key
* @param object $name node name
* @return meta value or NULL if not set
*/
public function get($key, $name)
{
$name = $this->alias($name);
if (isset($this->nodes_config[$name]) AND is_array($this->nodes_config[$name]) AND array_key_exists($key, $this->nodes_config[$name]))
{
return $this->nodes_config[$name][$key];
}
return NULL;
}
/**
* Set nodes config attribute
* Use it this way :
* nodes(array("node_name" => array("namespace" => "http://www.namespace.uri", "prefix" => "ns", "filter" => "filter_function_name", "attributes" => array("default_attribute1" => "value")))),
* OR to set up node alias names :
* nodes(array("alias" => "node_name"));
*
* @param array $nodes array formatted as mentionned above
* @param bool $overwrite [optional] Overwrite current values if they are set ?
* @return object $this
*/
public function nodes(Array $nodes)
{
$this->nodes_config = $this->_initialized ?
array_merge($nodes, $this->nodes_config) :
array_merge($this->nodes_config, $nodes);
$this->generate_nodes_map();
return $this;
}
/**
* Sets the content type for headers
* @param string $type
* @return object $this
*/
public function content_type($type = NULL)
{
if ($type)
{
$this->content_type = $this->_initialized ?
$type :
$this->content_type ?
$this->content_type :
$type;
}
else
{
return $this->content_type;
}
return $this;
}
/**
* Returns the key name corresponding to a node name
* This is used when using as_array(), to return array keys corresponding to the node names
* @param object $node_name
* @return
*/
public function key($node_name)
{
// Extract the name if it is prefixed
$expl = explode(":", $node_name);
$node_name = count($expl) > 1 ? end($expl) : current($expl);
if (in_array($node_name, $this->nodes))
{
return current(array_keys($this->nodes, $node_name));
}
return $node_name;
}
/**
* Generates - or re-generates the node map
* @return object $this
*/
public function generate_nodes_map()
{
$map = array();
foreach ($this->nodes_config as $key => $config)
{
if (is_array($config))
{
if (isset ($config["node"]))
{
$map[$key] = $config["node"];
}
}
}
$this->nodes = $map;
return $this;
}
/**
* Reports the Meta as initialized.
* This basically allows Meta methods to overwrite existing value, if they are called explicitely
* @return object $this
*/
public function set_initialized()
{
$this->_initialized = TRUE;
return $this;
}
}