diff options
51 files changed, 4326 insertions, 223 deletions
@@ -27,7 +27,8 @@ BUGS TODO MISC ========= - when the plugins are not used in the piwik.php logging script, don't load the related files - +- Archive::build must have the same parameters order as all the API calls that use idSite, Period, Date +- change all .png images to GIF CHANGES DONE TO LIBRARIES diff --git a/config/config.ini.php b/config/config.ini.php index ca0dd57dae..bc34f5c60a 100755 --- a/config/config.ini.php +++ b/config/config.ini.php @@ -15,15 +15,19 @@ profiler = true dbname = piwiktests tables_prefix = piwiktests_ +[Language] +current = en +default = en + [Plugins] enabled[] = UserSettings enabled[] = Actions -;enabled[] = UserCountry -;enabled[] = Provider -;enabled[] = Referers -;enabled[] = VisitFrequency -;enabled[] = VisitTime -;enabled[] = VisitorInterest +enabled[] = Provider +enabled[] = UserCountry +enabled[] = Referers +enabled[] = VisitFrequency +enabled[] = VisitTime +enabled[] = VisitorInterest [Plugins_LogStats] enabled[] = Provider @@ -5,6 +5,8 @@ error_reporting(E_ALL|E_NOTICE); date_default_timezone_set('Europe/London'); define('PIWIK_INCLUDE_PATH', '.'); +define('PIWIK_PLUGINS_PATH', PIWIK_INCLUDE_PATH . '/plugins'); +define('PIWIK_DATAFILES_INCLUDE_PATH', PIWIK_INCLUDE_PATH . "/modules/DataFiles"); set_include_path(PIWIK_INCLUDE_PATH . PATH_SEPARATOR . PIWIK_INCLUDE_PATH . '/libs/' @@ -16,12 +18,6 @@ assert_options(ASSERT_ACTIVE, 1); assert_options(ASSERT_WARNING, 1); assert_options(ASSERT_BAIL, 1); -//ini_set('xdebug.collect_vars', 'on'); -//ini_set('xdebug.collect_params', '4'); -//ini_set('xdebug.dump_globals', 'on'); -//ini_set('xdebug.dump.SERVER', 'REQUEST_URI'); -//ini_set('xdebug.show_local_vars', 'on'); - /** * Error / exception handling functions */ @@ -51,6 +47,7 @@ require_once "Access.php"; require_once "Auth.php"; require_once "API/Proxy.php"; require_once "Site.php"; +require_once "Translate.php"; //move into a init() method Piwik::createConfigObject(); @@ -103,41 +100,6 @@ Zend_Registry::get('access')->loadAccess(); Zend_Loader::loadClass('Piwik_Archive'); Zend_Loader::loadClass('Piwik_Date'); -Piwik::printMemoryUsage('Before archiving'); -//$test = new Piwik_Archive; -//$period = new Piwik_Period_Day( Piwik_Date::today() ); -//$site = new Piwik_Site(1); -//$test->setPeriod($period); -//$test->setSite($site); -// -// -//$test = new Piwik_Archive; -//$period = new Piwik_Period_Month(Piwik_Date::today()); -//$site = new Piwik_Site(1); -//$test->setPeriod($period); -//$test->setSite($site); -//Piwik::log("visits=".$test->get('nb_visits')); -//Piwik::log("max_actions=".$test->get('max_actions')); -//Piwik::log("UserSettings_resolution = ".$test->getDataTable('UserSettings_resolution')); - - -$test = new Piwik_Archive; -//Piwik::printMemoryUsage('after archive instanciation'); -$period = new Piwik_Period_Week(Piwik_Date::today()); -//Piwik::printMemoryUsage('after period'); -$site = new Piwik_Site(1); -//Piwik::printMemoryUsage('after site'); -$test->setPeriod($period); -$test->setSite($site); -$test->get('nb_visits'); -//Piwik::printMemoryUsage('after first get'); -//$test->get('nb_visits'); -//$test->get('nb_visits'); -//$test->get('nb_visits'); -//$test->get('toto12'); -//Piwik::log("Referers_keywordBySearchEngine = ". $test->getDataTableExpanded('Referers_keywordBySearchEngine')); -//Piwik::log("Referers_keywordBySearchEngine = ". $test->getDataTable('Referers_keywordBySearchEngine')); - main(); displayProfiler(); Piwik::printMemoryUsage(); @@ -173,8 +135,8 @@ function displayProfiler() function main() { Piwik::log( - '<a href="http://localhost/dev/piwiktrunk/?method=UserSettings.getResolution&idSite=1&date=yesterday&period=week&format=console&filter_limit=&filter_offset=&filter_column=label&filter_pattern=12"> - http://localhost/dev/piwiktrunk/?method=UserSettings.getResolution&idSite=1&date=yesterday&period=week&format=console&filter_limit=&filter_offset=&filter_column=label&filter_pattern=12 + '<a href="http://localhost/dev/piwiktrunk/?method=UserSettings.getResolution&idSite=1&date=yesterday&period=week&format=xml&filter_limit=&filter_offset=&filter_column=label&filter_pattern=12"> + http://localhost/dev/piwiktrunk/?method=UserSettings.getResolution&idSite=1&date=yesterday&period=week&format=xml&filter_limit=&filter_offset=&filter_column=label&filter_pattern=12 </a> <br>' ); @@ -197,32 +159,102 @@ function main() require_once "API/Request.php"; Piwik::log("getResolution"); - $request = new Piwik_API_Request('method=UserSettings.getResolution&idSite=1&date=yesterday&period=week&format=console&filter_limit=&filter_offset=&filter_column=label&filter_pattern='); - echo $return = $request->process(); + $request = new Piwik_API_Request(' + method=UserSettings.getResolution + &idSite=1 + &date=yesterday + &period=week + &format=console + &filter_limit= + &filter_offset= + &filter_column=label + &filter_pattern= + '); + print(($request->process())); Piwik::log("getOS"); - $request = new Piwik_API_Request('filter_sort_column=1&filter_sort_order=asc&method=UserSettings.getOS&idSite=1&date=yesterday&period=week&format=html&filter_limit=&filter_offset=&filter_column=label&filter_pattern='); - echo $return = $request->process(); + $request = new Piwik_API_Request('method=UserSettings.getOS + + &idSite=1 + &date=yesterday + &period=week + &format=xml + &filter_limit= + &filter_offset= + &filter_column=label + &filter_pattern= + '); + dump(htmlentities($request->process())); Piwik::log("getConfiguration"); - $request = new Piwik_API_Request('method=UserSettings.getConfiguration&idSite=1&date=yesterday&period=week&format=console&filter_limit=10&filter_offset=0&filter_column=label&filter_pattern='); - echo $return = $request->process(); + $request = new Piwik_API_Request(' + method=UserSettings.getConfiguration + &idSite=1 + &date=yesterday + &period=week + &format=xml + &filter_limit=10 + &filter_offset=0 + &filter_column=label + &filter_pattern= + '); + dump(htmlentities($request->process())); Piwik::log("getBrowser"); - $request = new Piwik_API_Request('method=UserSettings.getBrowser&idSite=1&date=yesterday&period=week&format=console&filter_limit=10&filter_offset=0&filter_column=label&filter_pattern='); - echo $return = $request->process(); + $request = new Piwik_API_Request(' + method=UserSettings.getBrowser + &idSite=1 + &date=yesterday + &period=week + &format=xml + &filter_limit= + &filter_offset= + &filter_column=label + &filter_pattern= + '); + dump(htmlentities($request->process())); Piwik::log("getBrowserType"); - $request = new Piwik_API_Request('method=UserSettings.getBrowserType&idSite=1&date=yesterday&period=week&format=console&filter_limit=10&filter_offset=0&filter_column=label&filter_pattern='); - echo $return = $request->process(); + $request = new Piwik_API_Request(' + method=UserSettings.getBrowserType + &idSite=1 + &date=yesterday + &period=week + &format=xml + &filter_limit= + &filter_offset= + &filter_column=label + &filter_pattern= + '); + dump(htmlentities($request->process())); Piwik::log("getWideScreen"); - $request = new Piwik_API_Request('method=UserSettings.getWideScreen&idSite=1&date=yesterday&period=week&format=console&filter_limit=10&filter_offset=0&filter_column=label&filter_pattern='); - echo $return = $request->process(); + $request = new Piwik_API_Request(' + method=UserSettings.getWideScreen + &idSite=1 + &date=yesterday + &period=week + &format=xml + &filter_limit= + &filter_offset= + &filter_column=label + &filter_pattern= + '); + dump(htmlentities($request->process())); Piwik::log("getPlugin"); - $request = new Piwik_API_Request('method=UserSettings.getPlugin&idSite=1&date=yesterday&period=week&format=console&filter_limit=10&filter_offset=0&filter_column=label&filter_pattern='); - echo $return = $request->process(); + $request = new Piwik_API_Request(' + method=UserSettings.getPlugin + &idSite=1 + &date=yesterday + &period=week + &format=xml + &filter_limit= + &filter_offset= + &filter_column=label + &filter_pattern= + '); + dump(htmlentities($request->process())); Piwik::log("getActions"); $request = new Piwik_API_Request( @@ -231,16 +263,205 @@ function main() &date=yesterday &period=month &format=html - &filter_limit=20 + &filter_limit=10 &filter_offset=0 + ' + ); +// echo(($request->process())); + + Piwik::log("getActions EXPANDED"); + $request = new Piwik_API_Request( + 'method=Actions.getActions + &idSite=1 + &date=yesterday + &period=month + &format=html + &expanded=true &filter_column=label - &filter_pattern= + &filter_pattern=a + &filter_limit=10 + &filter_offset=0 + + ' + ); +// echo(($request->process())); + + Piwik::log("getActions EXPANDED SUBTABLE"); + $request = new Piwik_API_Request( + 'method=Actions.getActions + &idSubtable=5477 + &idSite=1 + &date=yesterday + &period=month + &format=html + &expanded=false + ' ); - echo $return = $request->process(); +// echo(($request->process())); + + Piwik::log("getDownloads"); + $request = new Piwik_API_Request( + 'method=Actions.getDownloads + &idSite=1 + &date=yesterday + &period=month + &format=xml + ' + ); +// dump(htmlentities($request->process())); + Piwik::log("getOutlinks"); + $request = new Piwik_API_Request( + 'method=Actions.getOutlinks + &idSite=1 + &date=yesterday + &period=month + &format=xml + ' + ); +// dump(htmlentities($request->process())); + Piwik::log("getProvider"); + $request = new Piwik_API_Request( + 'method=Provider.getProvider + &idSite=1 + &date=yesterday + &period=month + &format=xml + ' + ); + dump(htmlentities($request->process())); + + Piwik::log("getCountry"); + $request = new Piwik_API_Request( + 'method=UserCountry.getCountry + &idSite=1 + &date=yesterday + &period=month + &format=xml + &filter_limit=10 + &filter_offset=0 + ' + ); + dump(htmlentities($request->process())); + + Piwik::log("getContinent"); + $request = new Piwik_API_Request( + 'method=UserCountry.getContinent + &idSite=1 + &date=yesterday + &period=month + &format=xml + ' + ); + dump(htmlentities($request->process())); + + + Piwik::log("getContinent"); + $request = new Piwik_API_Request( + 'method=VisitFrequency.getSummary + &idSite=1 + &date=yesterday + &period=month + &format=xml + ' + ); + dump(htmlentities($request->process())); + + Piwik::log("getNumberOfVisitsPerVisitDuration"); + $request = new Piwik_API_Request( + 'method=VisitorInterest.getNumberOfVisitsPerVisitDuration + &idSite=1 + &date=yesterday + &period=month + &format=xml + ' + ); + dump(htmlentities($request->process())); + + Piwik::log("getNumberOfVisitsPerPage"); + $request = new Piwik_API_Request( + 'method=VisitorInterest.getNumberOfVisitsPerPage + &idSite=1 + &date=yesterday + &period=month + &format=xml + ' + ); + dump(htmlentities($request->process())); + + + + Piwik::log("getVisitInformationPerServerTime"); + $request = new Piwik_API_Request( + 'method=VisitTime.getVisitInformationPerServerTime + &idSite=1 + &date=yesterday + &period=week + &format=xml + ' + ); + dump(htmlentities($request->process())); + + + Piwik::log("getRefererType"); + $request = new Piwik_API_Request( + 'method=Referers.getRefererType + &idSite=1 + &date=yesterday + &period=week + &format=xml + ' + ); + dump(htmlentities($request->process())); + + Piwik::log("getKeywords"); + $request = new Piwik_API_Request( + 'method=Referers.getKeywords + &idSite=1 + &date=yesterday + &period=week + &format=xml + &filter_limit=10 + &filter_offset=0 + ' + ); + dump(htmlentities($request->process())); + Piwik::log("getSearchEnginesFromKeywordId"); + $request = new Piwik_API_Request( + 'method=Referers.getSearchEnginesFromKeywordId + &idSite=1 + &date=yesterday + &period=week + &format=xml + &idSubtable=1886 + &filter_limit=10 + &filter_offset=0 + ' + ); + dump(htmlentities($request->process())); + + Piwik::log("getSearchEngines"); + $request = new Piwik_API_Request( + 'method=Referers.getSearchEngines + &idSite=1 + &date=yesterday + &period=week + &format=xml + &filter_limit=10 + &filter_offset=0 + ' + ); + dump(htmlentities($request->process())); + } +function dump($var) +{ + print("<pre>"); + var_export($var); + print("</pre>"); +} ?> diff --git a/lang/en.php b/lang/en.php new file mode 100644 index 0000000000..ffc7e0e69e --- /dev/null +++ b/lang/en.php @@ -0,0 +1,5 @@ +<?php +$translations = array( + 'General_Unknown' => 'Unknown', +); +?> diff --git a/libs/PEAR.php b/libs/PEAR.php index fc879a0a78..f14d8a06f1 100755 --- a/libs/PEAR.php +++ b/libs/PEAR.php @@ -227,7 +227,7 @@ class PEAR * @return mixed A reference to the variable. If not set it will be * auto initialised to NULL. */ - function &getStaticProperty($class, $var) + static function &getStaticProperty($class, $var) { static $properties; if (!isset($properties[$class])) { @@ -278,7 +278,7 @@ class PEAR */ function isError($data, $code = null) { - if (is_a($data, 'PEAR_Error')) { + if ($data instanceof PEAR_Error) { if (is_null($code)) { return true; } elseif (is_string($code)) { @@ -521,7 +521,7 @@ class PEAR * @see PEAR::setErrorHandling * @since PHP 4.0.5 */ - function &raiseError($message = null, + static function &raiseError($message = null, $code = null, $mode = null, $options = null, @@ -566,10 +566,10 @@ class PEAR $ec = 'PEAR_Error'; } if ($skipmsg) { - $a = &new $ec($code, $mode, $options, $userinfo); + $a = new $ec($code, $mode, $options, $userinfo); return $a; } else { - $a = &new $ec($message, $code, $mode, $options, $userinfo); + $a = new $ec($message, $code, $mode, $options, $userinfo); return $a; } } diff --git a/libs/XML/Serializer.php b/libs/XML/Serializer.php new file mode 100644 index 0000000000..9b2e9f6cfb --- /dev/null +++ b/libs/XML/Serializer.php @@ -0,0 +1,1025 @@ +<?PHP +/* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: */ + +/** + * XML_Serializer + * + * Creates XML documents from PHP data structures like arrays, objects or scalars. + * + * PHP versions 4 and 5 + * + * LICENSE: This source file is subject to version 3.0 of the PHP license + * that is available through the world-wide-web at the following URI: + * http://www.php.net/license/3_0.txt. If you did not receive a copy of + * the PHP License and are unable to obtain it through the web, please + * send a note to license@php.net so we can mail you a copy immediately. + * + * @category XML + * @package XML_Serializer + * @author Stephan Schmidt <schst@php.net> + * @copyright 1997-2005 The PHP Group + * @license http://www.php.net/license/3_0.txt PHP License 3.0 + * @version CVS: $Id: Serializer.php,v 1.47 2005/09/30 13:40:30 schst Exp $ + * @link http://pear.php.net/package/XML_Serializer + * @see XML_Unserializer + */ + +/** + * uses PEAR error management + */ +require_once 'PEAR.php'; + +/** + * uses XML_Util to create XML tags + */ +require_once 'XML/Util.php'; + +/** + * option: string used for indentation + * + * Possible values: + * - any string (default is any string) + */ +define('XML_SERIALIZER_OPTION_INDENT', 'indent'); + +/** + * option: string used for linebreaks + * + * Possible values: + * - any string (default is \n) + */ +define('XML_SERIALIZER_OPTION_LINEBREAKS', 'linebreak'); + +/** + * option: enable type hints + * + * Possible values: + * - true + * - false + */ +define('XML_SERIALIZER_OPTION_TYPEHINTS', 'typeHints'); + +/** + * option: add an XML declaration + * + * Possible values: + * - true + * - false + */ +define('XML_SERIALIZER_OPTION_XML_DECL_ENABLED', 'addDecl'); + +/** + * option: encoding of the document + * + * Possible values: + * - any valid encoding + * - null (default) + */ +define('XML_SERIALIZER_OPTION_XML_ENCODING', 'encoding'); + +/** + * option: default name for tags + * + * Possible values: + * - any string (XML_Serializer_Tag is default) + */ +define('XML_SERIALIZER_OPTION_DEFAULT_TAG', 'defaultTagName'); + +/** + * option: use classname for objects in indexed arrays + * + * Possible values: + * - true (default) + * - false + */ +define('XML_SERIALIZER_OPTION_CLASSNAME_AS_TAGNAME', 'classAsTagName'); + +/** + * option: attribute where original key is stored + * + * Possible values: + * - any string (default is _originalKey) + */ +define('XML_SERIALIZER_OPTION_ATTRIBUTE_KEY', 'keyAttribute'); + +/** + * option: attribute for type (only if typeHints => true) + * + * Possible values: + * - any string (default is _type) + */ +define('XML_SERIALIZER_OPTION_ATTRIBUTE_TYPE', 'typeAttribute'); + +/** + * option: attribute for class (only if typeHints => true) + * + * Possible values: + * - any string (default is _class) + */ +define('XML_SERIALIZER_OPTION_ATTRIBUTE_CLASS', 'classAttribute'); + +/** + * option: scalar values (strings, ints,..) will be serialized as attribute + * + * Possible values: + * - true + * - false (default) + * - array which sets this option on a per-tag basis + */ +define('XML_SERIALIZER_OPTION_SCALAR_AS_ATTRIBUTES', 'scalarAsAttributes'); + +/** + * option: prepend string for attributes + * + * Possible values: + * - any string (default is any string) + */ +define('XML_SERIALIZER_OPTION_PREPEND_ATTRIBUTES', 'prependAttributes'); + +/** + * option: indent the attributes, if set to '_auto', it will indent attributes so they all start at the same column + * + * Possible values: + * - true + * - false (default) + */ +define('XML_SERIALIZER_OPTION_INDENT_ATTRIBUTES', 'indentAttributes'); + +/** + * option: use 'simplexml' to use parent name as tagname if transforming an indexed array + * + * Possible values: + * - XML_SERIALIZER_MODE_DEFAULT (default) + * - XML_SERIALIZER_MODE_SIMPLEXML + */ +define('XML_SERIALIZER_OPTION_MODE', 'mode'); + +/** + * option: add a doctype declaration + * + * Possible values: + * - true + * - false (default) + */ +define('XML_SERIALIZER_OPTION_DOCTYPE_ENABLED', 'addDoctype'); + +/** + * option: supply a string or an array with id and uri ({@see XML_Util::getDoctypeDeclaration()} + * + * Possible values: + * - string + * - array + */ +define('XML_SERIALIZER_OPTION_DOCTYPE', 'doctype'); + +/** + * option: name of the root tag + * + * Possible values: + * - string + * - null (default) + */ +define('XML_SERIALIZER_OPTION_ROOT_NAME', 'rootName'); + +/** + * option: attributes of the root tag + * + * Possible values: + * - array + */ +define('XML_SERIALIZER_OPTION_ROOT_ATTRIBS', 'rootAttributes'); + +/** + * option: all values in this key will be treated as attributes + * + * Possible values: + * - array + */ +define('XML_SERIALIZER_OPTION_ATTRIBUTES_KEY', 'attributesArray'); + +/** + * option: this value will be used directly as content, instead of creating a new tag, may only be used in conjuction with attributesArray + * + * Possible values: + * - string + * - null (default) + */ +define('XML_SERIALIZER_OPTION_CONTENT_KEY', 'contentName'); + +/** + * option: this value will be used in a comment, instead of creating a new tag + * + * Possible values: + * - string + * - null (default) + */ +define('XML_SERIALIZER_OPTION_COMMENT_KEY', 'commentName'); + +/** + * option: tag names that will be changed + * + * Possible values: + * - array + */ +define('XML_SERIALIZER_OPTION_TAGMAP', 'tagMap'); + +/** + * option: function that will be applied before serializing + * + * Possible values: + * - any valid PHP callback + */ +define('XML_SERIALIZER_OPTION_ENCODE_FUNC', 'encodeFunction'); + +/** + * option: function that will be applied before serializing + * + * Possible values: + * - string + * - null (default) + */ +define('XML_SERIALIZER_OPTION_NAMESPACE', 'namespace'); + +/** + * option: type of entities to replace + * + * Possible values: + * - XML_SERIALIZER_ENTITIES_NONE + * - XML_SERIALIZER_ENTITIES_XML (default) + * - XML_SERIALIZER_ENTITIES_XML_REQUIRED + * - XML_SERIALIZER_ENTITIES_HTML + */ +define('XML_SERIALIZER_OPTION_ENTITIES', 'replaceEntities'); + +/** + * option: whether to return the result of the serialization from serialize() + * + * Possible values: + * - true + * - false (default) + */ +define('XML_SERIALIZER_OPTION_RETURN_RESULT', 'returnResult'); + +/** + * option: whether to ignore properties that are set to null + * + * Possible values: + * - true + * - false (default) + */ +define('XML_SERIALIZER_OPTION_IGNORE_NULL', 'ignoreNull'); + +/** + * option: whether to use cdata sections for character data + * + * Possible values: + * - true + * - false (default) + */ +define('XML_SERIALIZER_OPTION_CDATA_SECTIONS', 'cdata'); + + +/** + * default mode + */ +define('XML_SERIALIZER_MODE_DEFAULT', 'default'); + +/** + * SimpleXML mode + * + * When serializing indexed arrays, the key of the parent value is used as a tagname. + */ +define('XML_SERIALIZER_MODE_SIMPLEXML', 'simplexml'); + +/** + * error code for no serialization done + */ +define('XML_SERIALIZER_ERROR_NO_SERIALIZATION', 51); + +/** + * do not replace entitites + */ +define('XML_SERIALIZER_ENTITIES_NONE', XML_UTIL_ENTITIES_NONE); + +/** + * replace all XML entitites + * This setting will replace <, >, ", ' and & + */ +define('XML_SERIALIZER_ENTITIES_XML', XML_UTIL_ENTITIES_XML); + +/** + * replace only required XML entitites + * This setting will replace <, " and & + */ +define('XML_SERIALIZER_ENTITIES_XML_REQUIRED', XML_UTIL_ENTITIES_XML_REQUIRED); + +/** + * replace HTML entitites + * @link http://www.php.net/htmlentities + */ +define('XML_SERIALIZER_ENTITIES_HTML', XML_UTIL_ENTITIES_HTML); + +/** + * Creates XML documents from PHP data structures like arrays, objects or scalars. + * + * this class can be used in two modes: + * + * 1. create an XML document from an array or object that is processed by other + * applications. That means, you can create a RDF document from an array in the + * following format: + * + * $data = array( + * 'channel' => array( + * 'title' => 'Example RDF channel', + * 'link' => 'http://www.php-tools.de', + * 'image' => array( + * 'title' => 'Example image', + * 'url' => 'http://www.php-tools.de/image.gif', + * 'link' => 'http://www.php-tools.de' + * ), + * array( + * 'title' => 'Example item', + * 'link' => 'http://example.com' + * ), + * array( + * 'title' => 'Another Example item', + * 'link' => 'http://example.org' + * ) + * ) + * ); + * + * to create a RDF document from this array do the following: + * + * require_once 'XML/Serializer.php'; + * + * $options = array( + * XML_SERIALIZER_OPTION_INDENT => "\t", // indent with tabs + * XML_SERIALIZER_OPTION_LINEBREAKS => "\n", // use UNIX line breaks + * XML_SERIALIZER_OPTION_ROOT_NAME => 'rdf:RDF', // root tag + * XML_SERIALIZER_OPTION_DEFAULT_TAG => 'item' // tag for values with numeric keys + * ); + * + * $serializer = new XML_Serializer($options); + * $rdf = $serializer->serialize($data); + * + * You will get a complete XML document that can be processed like any RDF document. + * + * 2. this classes can be used to serialize any data structure in a way that it can + * later be unserialized again. + * XML_Serializer will store the type of the value and additional meta information + * in attributes of the surrounding tag. This meat information can later be used + * to restore the original data structure in PHP. If you want XML_Serializer + * to add meta information to the tags, add + * + * XML_SERIALIZER_OPTION_TYPEHINTS => true + * + * to the options array in the constructor. + * + * @category XML + * @package XML_Serializer + * @author Stephan Schmidt <schst@php.net> + * @copyright 1997-2005 The PHP Group + * @license http://www.php.net/license/3_0.txt PHP License 3.0 + * @version Release: @package_version@ + * @link http://pear.php.net/package/XML_Serializer + * @see XML_Unserializer + */ +class XML_Serializer extends PEAR +{ + /** + * list of all available options + * + * @access private + * @var array + */ + var $_knownOptions = array( + XML_SERIALIZER_OPTION_INDENT, + XML_SERIALIZER_OPTION_LINEBREAKS, + XML_SERIALIZER_OPTION_TYPEHINTS, + XML_SERIALIZER_OPTION_XML_DECL_ENABLED, + XML_SERIALIZER_OPTION_XML_ENCODING, + XML_SERIALIZER_OPTION_DEFAULT_TAG, + XML_SERIALIZER_OPTION_CLASSNAME_AS_TAGNAME, + XML_SERIALIZER_OPTION_ATTRIBUTE_KEY, + XML_SERIALIZER_OPTION_ATTRIBUTE_TYPE, + XML_SERIALIZER_OPTION_ATTRIBUTE_CLASS, + XML_SERIALIZER_OPTION_SCALAR_AS_ATTRIBUTES, + XML_SERIALIZER_OPTION_PREPEND_ATTRIBUTES, + XML_SERIALIZER_OPTION_INDENT_ATTRIBUTES, + XML_SERIALIZER_OPTION_MODE, + XML_SERIALIZER_OPTION_DOCTYPE_ENABLED, + XML_SERIALIZER_OPTION_DOCTYPE, + XML_SERIALIZER_OPTION_ROOT_NAME, + XML_SERIALIZER_OPTION_ROOT_ATTRIBS, + XML_SERIALIZER_OPTION_ATTRIBUTES_KEY, + XML_SERIALIZER_OPTION_CONTENT_KEY, + XML_SERIALIZER_OPTION_COMMENT_KEY, + XML_SERIALIZER_OPTION_TAGMAP, + XML_SERIALIZER_OPTION_ENCODE_FUNC, + XML_SERIALIZER_OPTION_NAMESPACE, + XML_SERIALIZER_OPTION_ENTITIES, + XML_SERIALIZER_OPTION_RETURN_RESULT, + XML_SERIALIZER_OPTION_IGNORE_NULL, + XML_SERIALIZER_OPTION_CDATA_SECTIONS + ); + + /** + * default options for the serialization + * + * @access private + * @var array + */ + var $_defaultOptions = array( + XML_SERIALIZER_OPTION_INDENT => '', // string used for indentation + XML_SERIALIZER_OPTION_LINEBREAKS => "\n", // string used for newlines + XML_SERIALIZER_OPTION_TYPEHINTS => false, // automatically add type hin attributes + XML_SERIALIZER_OPTION_XML_DECL_ENABLED => false, // add an XML declaration + XML_SERIALIZER_OPTION_XML_ENCODING => null, // encoding specified in the XML declaration + XML_SERIALIZER_OPTION_DEFAULT_TAG => 'XML_Serializer_Tag', // tag used for indexed arrays or invalid names + XML_SERIALIZER_OPTION_CLASSNAME_AS_TAGNAME => false, // use classname for objects in indexed arrays + XML_SERIALIZER_OPTION_ATTRIBUTE_KEY => '_originalKey', // attribute where original key is stored + XML_SERIALIZER_OPTION_ATTRIBUTE_TYPE => '_type', // attribute for type (only if typeHints => true) + XML_SERIALIZER_OPTION_ATTRIBUTE_CLASS => '_class', // attribute for class of objects (only if typeHints => true) + XML_SERIALIZER_OPTION_SCALAR_AS_ATTRIBUTES => false, // scalar values (strings, ints,..) will be serialized as attribute + XML_SERIALIZER_OPTION_PREPEND_ATTRIBUTES => '', // prepend string for attributes + XML_SERIALIZER_OPTION_INDENT_ATTRIBUTES => false, // indent the attributes, if set to '_auto', it will indent attributes so they all start at the same column + XML_SERIALIZER_OPTION_MODE => XML_SERIALIZER_MODE_DEFAULT, // use XML_SERIALIZER_MODE_SIMPLEXML to use parent name as tagname if transforming an indexed array + XML_SERIALIZER_OPTION_DOCTYPE_ENABLED => false, // add a doctype declaration + XML_SERIALIZER_OPTION_DOCTYPE => null, // supply a string or an array with id and uri ({@see XML_Util::getDoctypeDeclaration()} + XML_SERIALIZER_OPTION_ROOT_NAME => null, // name of the root tag + XML_SERIALIZER_OPTION_ROOT_ATTRIBS => array(), // attributes of the root tag + XML_SERIALIZER_OPTION_ATTRIBUTES_KEY => null, // all values in this key will be treated as attributes + XML_SERIALIZER_OPTION_CONTENT_KEY => null, // this value will be used directly as content, instead of creating a new tag, may only be used in conjuction with attributesArray + XML_SERIALIZER_OPTION_COMMENT_KEY => null, // this value will be used directly as comment, instead of creating a new tag, may only be used in conjuction with attributesArray + XML_SERIALIZER_OPTION_TAGMAP => array(), // tag names that will be changed + XML_SERIALIZER_OPTION_ENCODE_FUNC => null, // function that will be applied before serializing + XML_SERIALIZER_OPTION_NAMESPACE => null, // namespace to use + XML_SERIALIZER_OPTION_ENTITIES => XML_SERIALIZER_ENTITIES_XML, // type of entities to replace, + XML_SERIALIZER_OPTION_RETURN_RESULT => false, // serialize() returns the result of the serialization instead of true + XML_SERIALIZER_OPTION_IGNORE_NULL => false, // ignore properties that are set to null + XML_SERIALIZER_OPTION_CDATA_SECTIONS => false // Whether to use cdata sections for plain character data + ); + + /** + * options for the serialization + * + * @access public + * @var array + */ + var $options = array(); + + /** + * current tag depth + * + * @access private + * @var integer + */ + var $_tagDepth = 0; + + /** + * serilialized representation of the data + * + * @access private + * @var string + */ + var $_serializedData = null; + + /** + * constructor + * + * @access public + * @param mixed $options array containing options for the serialization + */ + function XML_Serializer( $options = null ) + { + $this->PEAR(); + if (is_array($options)) { + $this->options = array_merge($this->_defaultOptions, $options); + } else { + $this->options = $this->_defaultOptions; + } + } + + /** + * return API version + * + * @access public + * @static + * @return string $version API version + */ + function apiVersion() + { + return '@package_version@'; + } + + /** + * reset all options to default options + * + * @access public + * @see setOption(), XML_Serializer() + */ + function resetOptions() + { + $this->options = $this->_defaultOptions; + } + + /** + * set an option + * + * You can use this method if you do not want to set all options in the constructor + * + * @access public + * @see resetOption(), XML_Serializer() + */ + function setOption($name, $value) + { + $this->options[$name] = $value; + } + + /** + * sets several options at once + * + * You can use this method if you do not want to set all options in the constructor + * + * @access public + * @see resetOption(), XML_Unserializer(), setOption() + */ + function setOptions($options) + { + $this->options = array_merge($this->options, $options); + } + + /** + * serialize data + * + * @access public + * @param mixed $data data to serialize + * @return boolean true on success, pear error on failure + */ + function serialize($data, $options = null) + { + // if options have been specified, use them instead + // of the previously defined ones + if (is_array($options)) { + $optionsBak = $this->options; + if (isset($options['overrideOptions']) && $options['overrideOptions'] == true) { + $this->options = array_merge($this->_defaultOptions, $options); + } else { + $this->options = array_merge($this->options, $options); + } + } else { + $optionsBak = null; + } + + // start depth is zero + $this->_tagDepth = 0; + + $rootAttributes = $this->options[XML_SERIALIZER_OPTION_ROOT_ATTRIBS]; + if (isset($this->options[XML_SERIALIZER_OPTION_NAMESPACE]) && is_array($this->options[XML_SERIALIZER_OPTION_NAMESPACE])) { + $rootAttributes['xmlns:'.$this->options[XML_SERIALIZER_OPTION_NAMESPACE][0]] = $this->options[XML_SERIALIZER_OPTION_NAMESPACE][1]; + } + + $this->_serializedData = ''; + // serialize an array + if (is_array($data)) { + if (isset($this->options[XML_SERIALIZER_OPTION_ROOT_NAME])) { + $tagName = $this->options[XML_SERIALIZER_OPTION_ROOT_NAME]; + } else { + $tagName = 'array'; + } + + $this->_serializedData .= $this->_serializeArray($data, $tagName, $rootAttributes); + } elseif (is_object($data)) { + // serialize an object + if (isset($this->options[XML_SERIALIZER_OPTION_ROOT_NAME])) { + $tagName = $this->options[XML_SERIALIZER_OPTION_ROOT_NAME]; + } else { + $tagName = get_class($data); + } + $this->_serializedData .= $this->_serializeObject($data, $tagName, $rootAttributes); + } else { + $tag = array(); + if (isset($this->options[XML_SERIALIZER_OPTION_ROOT_NAME])) { + $tag['qname'] = $this->options[XML_SERIALIZER_OPTION_ROOT_NAME]; + } else { + $tag['qname'] = gettype($data); + } + if ($this->options[XML_SERIALIZER_OPTION_TYPEHINTS] === true) { + $rootAttributes[$this->options[XML_SERIALIZER_OPTION_ATTRIBUTE_TYPE]] = gettype($data); + } + @settype($data, 'string'); + $tag['content'] = $data; + $tag['attributes'] = $rootAttributes; + $this->_serializedData = $this->_createXMLTag($tag); + } + + // add doctype declaration + if ($this->options[XML_SERIALIZER_OPTION_DOCTYPE_ENABLED] === true) { + $this->_serializedData = XML_Util::getDoctypeDeclaration($tagName, $this->options[XML_SERIALIZER_OPTION_DOCTYPE]) + . $this->options[XML_SERIALIZER_OPTION_LINEBREAKS] + . $this->_serializedData; + } + + // build xml declaration + if ($this->options[XML_SERIALIZER_OPTION_XML_DECL_ENABLED]) { + $atts = array(); + $this->_serializedData = XML_Util::getXMLDeclaration('1.0', $this->options[XML_SERIALIZER_OPTION_XML_ENCODING]) + . $this->options[XML_SERIALIZER_OPTION_LINEBREAKS] + . $this->_serializedData; + } + + if ($this->options[XML_SERIALIZER_OPTION_RETURN_RESULT] === true) { + $result = $this->_serializedData; + } else { + $result = true; + } + + if ($optionsBak !== null) { + $this->options = $optionsBak; + } + + return $result; + } + + /** + * get the result of the serialization + * + * @access public + * @return string serialized XML + */ + function getSerializedData() + { + if ($this->_serializedData == null) { + return $this->raiseError('No serialized data available. Use XML_Serializer::serialize() first.', XML_SERIALIZER_ERROR_NO_SERIALIZATION); + } + return $this->_serializedData; + } + + /** + * serialize any value + * + * This method checks for the type of the value and calls the appropriate method + * + * @access private + * @param mixed $value + * @param string $tagName + * @param array $attributes + * @return string + */ + function _serializeValue($value, $tagName = null, $attributes = array()) + { + if (is_array($value)) { + $xml = $this->_serializeArray($value, $tagName, $attributes); + } elseif (is_object($value)) { + $xml = $this->_serializeObject($value, $tagName); + } else { + $tag = array( + 'qname' => $tagName, + 'attributes' => $attributes, + 'content' => $value + ); + $xml = $this->_createXMLTag($tag); + } + return $xml; + } + + /** + * serialize an array + * + * @access private + * @param array $array array to serialize + * @param string $tagName name of the root tag + * @param array $attributes attributes for the root tag + * @return string $string serialized data + * @uses XML_Util::isValidName() to check, whether key has to be substituted + */ + function _serializeArray(&$array, $tagName = null, $attributes = array()) + { + $_content = null; + $_comment = null; + + // check for comment + if ($this->options[XML_SERIALIZER_OPTION_COMMENT_KEY] !== null) { + if (isset($array[$this->options[XML_SERIALIZER_OPTION_COMMENT_KEY]])) { + $_comment = $array[$this->options[XML_SERIALIZER_OPTION_COMMENT_KEY]]; + unset($array[$this->options[XML_SERIALIZER_OPTION_COMMENT_KEY]]); + } + } + + /** + * check for special attributes + */ + if ($this->options[XML_SERIALIZER_OPTION_ATTRIBUTES_KEY] !== null) { + if (isset($array[$this->options[XML_SERIALIZER_OPTION_ATTRIBUTES_KEY]])) { + $attributes = $array[$this->options[XML_SERIALIZER_OPTION_ATTRIBUTES_KEY]]; + unset($array[$this->options[XML_SERIALIZER_OPTION_ATTRIBUTES_KEY]]); + } + /** + * check for special content + */ + if ($this->options[XML_SERIALIZER_OPTION_CONTENT_KEY] !== null) { + if (isset($array[$this->options[XML_SERIALIZER_OPTION_CONTENT_KEY]])) { + $_content = $array[$this->options[XML_SERIALIZER_OPTION_CONTENT_KEY]]; + unset($array[$this->options[XML_SERIALIZER_OPTION_CONTENT_KEY]]); + } + } + } + + if ($this->options[XML_SERIALIZER_OPTION_IGNORE_NULL] === true) { + foreach (array_keys($array) as $key) { + if (is_null($array[$key])) { + unset($array[$key]); + } + } + } + + /* + * if mode is set to simpleXML, check whether + * the array is associative or indexed + */ + if (is_array($array) && !empty($array) && $this->options[XML_SERIALIZER_OPTION_MODE] == XML_SERIALIZER_MODE_SIMPLEXML) { + $indexed = true; + foreach ($array as $key => $val) { + if (!is_int($key)) { + $indexed = false; + break; + } + } + + if ($indexed && $this->options[XML_SERIALIZER_OPTION_MODE] == XML_SERIALIZER_MODE_SIMPLEXML) { + $string = ''; + foreach ($array as $key => $val) { + $string .= $this->_serializeValue( $val, $tagName, $attributes); + + $string .= $this->options[XML_SERIALIZER_OPTION_LINEBREAKS]; + // do indentation + if ($this->options[XML_SERIALIZER_OPTION_INDENT]!==null && $this->_tagDepth>0) { + $string .= str_repeat($this->options[XML_SERIALIZER_OPTION_INDENT], $this->_tagDepth); + } + } + return rtrim($string); + } + } + + $scalarAsAttributes = false; + if (is_array($this->options[XML_SERIALIZER_OPTION_SCALAR_AS_ATTRIBUTES]) && isset($this->options[XML_SERIALIZER_OPTION_SCALAR_AS_ATTRIBUTES][$tagName])) { + $scalarAsAttributes = $this->options[XML_SERIALIZER_OPTION_SCALAR_AS_ATTRIBUTES][$tagName]; + } elseif ($this->options[XML_SERIALIZER_OPTION_SCALAR_AS_ATTRIBUTES] === true) { + $scalarAsAttributes = true; + } + + if ($scalarAsAttributes === true) { + $this->expectError('*'); + foreach ($array as $key => $value) { + if (is_scalar($value) && (XML_Util::isValidName($key) === true)) { + unset($array[$key]); + $attributes[$this->options[XML_SERIALIZER_OPTION_PREPEND_ATTRIBUTES].$key] = $value; + } + } + $this->popExpect(); + } elseif (is_array($scalarAsAttributes)) { + $this->expectError('*'); + foreach ($scalarAsAttributes as $key) { + if (!isset($array[$key])) { + continue; + } + $value = $array[$key]; + if (is_scalar($value) && (XML_Util::isValidName($key) === true)) { + unset($array[$key]); + $attributes[$this->options[XML_SERIALIZER_OPTION_PREPEND_ATTRIBUTES].$key] = $value; + } + } + $this->popExpect(); + } + + // check for empty array => create empty tag + if (empty($array)) { + $tag = array( + 'qname' => $tagName, + 'content' => $_content, + 'attributes' => $attributes + ); + } else { + $this->_tagDepth++; + $tmp = $this->options[XML_SERIALIZER_OPTION_LINEBREAKS]; + foreach ($array as $key => $value) { + // do indentation + if ($this->options[XML_SERIALIZER_OPTION_INDENT]!==null && $this->_tagDepth>0) { + $tmp .= str_repeat($this->options[XML_SERIALIZER_OPTION_INDENT], $this->_tagDepth); + } + + if (isset($this->options[XML_SERIALIZER_OPTION_TAGMAP][$key])) { + $key = $this->options[XML_SERIALIZER_OPTION_TAGMAP][$key]; + } + + // copy key + $origKey = $key; + $this->expectError('*'); + // key cannot be used as tagname => use default tag + $valid = XML_Util::isValidName($key); + $this->popExpect(); + if (PEAR::isError($valid)) { + if ($this->options[XML_SERIALIZER_OPTION_CLASSNAME_AS_TAGNAME] && is_object($value)) { + $key = get_class($value); + } else { + $key = $this->_getDefaultTagname($tagName); + } + } + $atts = array(); + if ($this->options[XML_SERIALIZER_OPTION_TYPEHINTS] === true) { + $atts[$this->options[XML_SERIALIZER_OPTION_ATTRIBUTE_TYPE]] = gettype($value); + if ($key !== $origKey) { + $atts[$this->options[XML_SERIALIZER_OPTION_ATTRIBUTE_KEY]] = (string)$origKey; + } + } + + $tmp .= $this->_createXMLTag(array( + 'qname' => $key, + 'attributes' => $atts, + 'content' => $value ) + ); + $tmp .= $this->options[XML_SERIALIZER_OPTION_LINEBREAKS]; + } + + $this->_tagDepth--; + if ($this->options[XML_SERIALIZER_OPTION_INDENT]!==null && $this->_tagDepth>0) { + $tmp .= str_repeat($this->options[XML_SERIALIZER_OPTION_INDENT], $this->_tagDepth); + } + + if (trim($tmp) === '') { + $tmp = null; + } + + $tag = array( + 'qname' => $tagName, + 'content' => $tmp, + 'attributes' => $attributes + ); + } + if ($this->options[XML_SERIALIZER_OPTION_TYPEHINTS] === true) { + if (!isset($tag['attributes'][$this->options[XML_SERIALIZER_OPTION_ATTRIBUTE_TYPE]])) { + $tag['attributes'][$this->options[XML_SERIALIZER_OPTION_ATTRIBUTE_TYPE]] = 'array'; + } + } + + $string = ''; + if (!is_null($_comment)) { + $string .= XML_Util::createComment($_comment); + $string .= $this->options[XML_SERIALIZER_OPTION_LINEBREAKS]; + if ($this->options[XML_SERIALIZER_OPTION_INDENT]!==null && $this->_tagDepth>0) { + $string .= str_repeat($this->options[XML_SERIALIZER_OPTION_INDENT], $this->_tagDepth); + } + } + $string .= $this->_createXMLTag($tag, false); + return $string; + } + + /** + * get the name of the default tag. + * + * The name of the parent tag needs to be passed as the + * default name can depend on the context. + * + * @param string name of the parent tag + * @return string default tag name + */ + function _getDefaultTagname($parent) + { + if (is_string($this->options[XML_SERIALIZER_OPTION_DEFAULT_TAG])) { + return $this->options[XML_SERIALIZER_OPTION_DEFAULT_TAG]; + } + if (isset($this->options[XML_SERIALIZER_OPTION_DEFAULT_TAG][$parent])) { + return $this->options[XML_SERIALIZER_OPTION_DEFAULT_TAG][$parent]; + } elseif (isset($this->options[XML_SERIALIZER_OPTION_DEFAULT_TAG]['#default'])) { + return $this->options[XML_SERIALIZER_OPTION_DEFAULT_TAG]['#default']; + } elseif (isset($this->options[XML_SERIALIZER_OPTION_DEFAULT_TAG]['__default'])) { + // keep this for BC + return $this->options[XML_SERIALIZER_OPTION_DEFAULT_TAG]['__default']; + } + return 'XML_Serializer_Tag'; + } + + /** + * serialize an object + * + * @access private + * @param object $object object to serialize + * @return string $string serialized data + */ + function _serializeObject(&$object, $tagName = null, $attributes = array()) + { + // check for magic function + if (method_exists($object, '__sleep')) { + $propNames = $object->__sleep(); + if (is_array($propNames)) { + $properties = array(); + foreach ($propNames as $propName) { + $properties[$propName] = $object->$propName; + } + } else { + $properties = get_object_vars($object); + } + } else { + $properties = get_object_vars($object); + } + + if (empty($tagName)) { + $tagName = get_class($object); + } + + // typehints activated? + if ($this->options[XML_SERIALIZER_OPTION_TYPEHINTS] === true) { + $attributes[$this->options[XML_SERIALIZER_OPTION_ATTRIBUTE_TYPE]] = 'object'; + $attributes[$this->options[XML_SERIALIZER_OPTION_ATTRIBUTE_CLASS]] = get_class($object); + } + $string = $this->_serializeArray($properties, $tagName, $attributes); + return $string; + } + + /** + * create a tag from an array + * this method awaits an array in the following format + * array( + * 'qname' => $tagName, + * 'attributes' => array(), + * 'content' => $content, // optional + * 'namespace' => $namespace // optional + * 'namespaceUri' => $namespaceUri // optional + * ) + * + * @access private + * @param array $tag tag definition + * @param boolean $replaceEntities whether to replace XML entities in content or not + * @return string $string XML tag + */ + function _createXMLTag($tag, $firstCall = true) + { + // build fully qualified tag name + if ($this->options[XML_SERIALIZER_OPTION_NAMESPACE] !== null) { + if (is_array($this->options[XML_SERIALIZER_OPTION_NAMESPACE])) { + $tag['qname'] = $this->options[XML_SERIALIZER_OPTION_NAMESPACE][0] . ':' . $tag['qname']; + } else { + $tag['qname'] = $this->options[XML_SERIALIZER_OPTION_NAMESPACE] . ':' . $tag['qname']; + } + } + + // attribute indentation + if ($this->options[XML_SERIALIZER_OPTION_INDENT_ATTRIBUTES] !== false) { + $multiline = true; + $indent = str_repeat($this->options[XML_SERIALIZER_OPTION_INDENT], $this->_tagDepth); + + if ($this->options[XML_SERIALIZER_OPTION_INDENT_ATTRIBUTES] == '_auto') { + $indent .= str_repeat(' ', (strlen($tag['qname'])+2)); + + } else { + $indent .= $this->options[XML_SERIALIZER_OPTION_INDENT_ATTRIBUTES]; + } + } else { + $multiline = false; + $indent = false; + } + + if (is_array($tag['content'])) { + if (empty($tag['content'])) { + $tag['content'] = ''; + } + } elseif(is_scalar($tag['content']) && (string)$tag['content'] == '') { + $tag['content'] = ''; + } + + // replace XML entities (only needed, if this is not a nested call) + if ($firstCall === true) { + if ($this->options[XML_SERIALIZER_OPTION_CDATA_SECTIONS] === true) { + $replaceEntities = XML_UTIL_CDATA_SECTION; + } else { + $replaceEntities = $this->options[XML_SERIALIZER_OPTION_ENTITIES]; + } + } else { + $replaceEntities = XML_SERIALIZER_ENTITIES_NONE; + } + if (is_scalar($tag['content']) || is_null($tag['content'])) { + if ($this->options[XML_SERIALIZER_OPTION_ENCODE_FUNC]) { + if ($firstCall === true) { + $tag['content'] = call_user_func($this->options[XML_SERIALIZER_OPTION_ENCODE_FUNC], $tag['content']); + } + $tag['attributes'] = array_map($this->options[XML_SERIALIZER_OPTION_ENCODE_FUNC], $tag['attributes']); + } + $tag = XML_Util::createTagFromArray($tag, $replaceEntities, $multiline, $indent, $this->options[XML_SERIALIZER_OPTION_LINEBREAKS]); + } elseif (is_array($tag['content'])) { + $tag = $this->_serializeArray($tag['content'], $tag['qname'], $tag['attributes']); + } elseif (is_object($tag['content'])) { + $tag = $this->_serializeObject($tag['content'], $tag['qname'], $tag['attributes']); + } elseif (is_resource($tag['content'])) { + settype($tag['content'], 'string'); + if ($this->options[XML_SERIALIZER_OPTION_ENCODE_FUNC]) { + if ($replaceEntities === true) { + $tag['content'] = call_user_func($this->options[XML_SERIALIZER_OPTION_ENCODE_FUNC], $tag['content']); + } + $tag['attributes'] = array_map($this->options[XML_SERIALIZER_OPTION_ENCODE_FUNC], $tag['attributes']); + } + $tag = XML_Util::createTagFromArray($tag, $replaceEntities); + } + return $tag; + } +} +?>
\ No newline at end of file diff --git a/libs/XML/Unserializer.php b/libs/XML/Unserializer.php new file mode 100644 index 0000000000..a5a03dcfc6 --- /dev/null +++ b/libs/XML/Unserializer.php @@ -0,0 +1,856 @@ +<?PHP +/* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: */ + +/** + * XML_Unserializer + * + * Parses any XML document into PHP data structures. + * + * PHP versions 4 and 5 + * + * LICENSE: This source file is subject to version 3.0 of the PHP license + * that is available through the world-wide-web at the following URI: + * http://www.php.net/license/3_0.txt. If you did not receive a copy of + * the PHP License and are unable to obtain it through the web, please + * send a note to license@php.net so we can mail you a copy immediately. + * + * @category XML + * @package XML_Serializer + * @author Stephan Schmidt <schst@php.net> + * @copyright 1997-2005 The PHP Group + * @license http://www.php.net/license/3_0.txt PHP License 3.0 + * @version CVS: $Id: Unserializer.php,v 1.39 2005/09/28 11:19:56 schst Exp $ + * @link http://pear.php.net/package/XML_Serializer + * @see XML_Unserializer + */ + +/** + * uses PEAR error managemt + */ +require_once 'PEAR.php'; + +/** + * uses XML_Parser to unserialize document + */ +require_once 'XML/Parser.php'; + +/** + * option: Convert nested tags to array or object + * + * Possible values: + * - array + * - object + * - associative array to define this option per tag name + */ +define('XML_UNSERIALIZER_OPTION_COMPLEXTYPE', 'complexType'); + +/** + * option: Name of the attribute that stores the original key + * + * Possible values: + * - any string + */ +define('XML_UNSERIALIZER_OPTION_ATTRIBUTE_KEY', 'keyAttribute'); + +/** + * option: Name of the attribute that stores the type + * + * Possible values: + * - any string + */ +define('XML_UNSERIALIZER_OPTION_ATTRIBUTE_TYPE', 'typeAttribute'); + +/** + * option: Name of the attribute that stores the class name + * + * Possible values: + * - any string + */ +define('XML_UNSERIALIZER_OPTION_ATTRIBUTE_CLASS', 'classAttribute'); + +/** + * option: Whether to use the tag name as a class name + * + * Possible values: + * - true or false + */ +define('XML_UNSERIALIZER_OPTION_TAG_AS_CLASSNAME', 'tagAsClass'); + +/** + * option: Name of the default class + * + * Possible values: + * - any string + */ +define('XML_UNSERIALIZER_OPTION_DEFAULT_CLASS', 'defaultClass'); + +/** + * option: Whether to parse attributes + * + * Possible values: + * - true or false + */ +define('XML_UNSERIALIZER_OPTION_ATTRIBUTES_PARSE', 'parseAttributes'); + +/** + * option: Key of the array to store attributes (if any) + * + * Possible values: + * - any string + * - false (disabled) + */ +define('XML_UNSERIALIZER_OPTION_ATTRIBUTES_ARRAYKEY', 'attributesArray'); + +/** + * option: string to prepend attribute name (if any) + * + * Possible values: + * - any string + * - false (disabled) + */ +define('XML_UNSERIALIZER_OPTION_ATTRIBUTES_PREPEND', 'prependAttributes'); + +/** + * option: key to store the content, if XML_UNSERIALIZER_OPTION_ATTRIBUTES_PARSE is used + * + * Possible values: + * - any string + */ +define('XML_UNSERIALIZER_OPTION_CONTENT_KEY', 'contentName'); + +/** + * option: map tag names + * + * Possible values: + * - associative array + */ +define('XML_UNSERIALIZER_OPTION_TAG_MAP', 'tagMap'); + +/** + * option: list of tags that will always be enumerated + * + * Possible values: + * - indexed array + */ +define('XML_UNSERIALIZER_OPTION_FORCE_ENUM', 'forceEnum'); + +/** + * option: Encoding of the XML document + * + * Possible values: + * - UTF-8 + * - ISO-8859-1 + */ +define('XML_UNSERIALIZER_OPTION_ENCODING_SOURCE', 'encoding'); + +/** + * option: Desired target encoding of the data + * + * Possible values: + * - UTF-8 + * - ISO-8859-1 + */ +define('XML_UNSERIALIZER_OPTION_ENCODING_TARGET', 'targetEncoding'); + +/** + * option: Callback that will be applied to textual data + * + * Possible values: + * - any valid PHP callback + */ +define('XML_UNSERIALIZER_OPTION_DECODE_FUNC', 'decodeFunction'); + +/** + * option: whether to return the result of the unserialization from unserialize() + * + * Possible values: + * - true + * - false (default) + */ +define('XML_UNSERIALIZER_OPTION_RETURN_RESULT', 'returnResult'); + +/** + * option: set the whitespace behaviour + * + * Possible values: + * - XML_UNSERIALIZER_WHITESPACE_KEEP + * - XML_UNSERIALIZER_WHITESPACE_TRIM + * - XML_UNSERIALIZER_WHITESPACE_NORMALIZE + */ +define('XML_UNSERIALIZER_OPTION_WHITESPACE', 'whitespace'); + +/** + * Keep all whitespace + */ +define('XML_UNSERIALIZER_WHITESPACE_KEEP', 'keep'); + +/** + * remove whitespace from start and end of the data + */ +define('XML_UNSERIALIZER_WHITESPACE_TRIM', 'trim'); + +/** + * normalize whitespace + */ +define('XML_UNSERIALIZER_WHITESPACE_NORMALIZE', 'normalize'); + +/** + * option: whether to ovverride all options that have been set before + * + * Possible values: + * - true + * - false (default) + */ +define('XML_UNSERIALIZER_OPTION_OVERRIDE_OPTIONS', 'overrideOptions'); + +/** + * option: list of tags, that will not be used as keys + */ +define('XML_UNSERIALIZER_OPTION_IGNORE_KEYS', 'ignoreKeys'); + +/** + * option: whether to use type guessing for scalar values + */ +define('XML_UNSERIALIZER_OPTION_GUESS_TYPES', 'guessTypes'); + +/** + * error code for no serialization done + */ +define('XML_UNSERIALIZER_ERROR_NO_UNSERIALIZATION', 151); + +/** + * XML_Unserializer + * + * class to unserialize XML documents that have been created with + * XML_Serializer. To unserialize an XML document you have to add + * type hints to the XML_Serializer options. + * + * If no type hints are available, XML_Unserializer will guess how + * the tags should be treated, that means complex structures will be + * arrays and tags with only CData in them will be strings. + * + * <code> + * require_once 'XML/Unserializer.php'; + * + * // be careful to always use the ampersand in front of the new operator + * $unserializer = &new XML_Unserializer(); + * + * $unserializer->unserialize($xml); + * + * $data = $unserializer->getUnserializedData(); + * <code> + * + * + * @category XML + * @package XML_Serializer + * @author Stephan Schmidt <schst@php.net> + * @copyright 1997-2005 The PHP Group + * @license http://www.php.net/license/3_0.txt PHP License 3.0 + * @version Release: @package_version@ + * @link http://pear.php.net/package/XML_Serializer + * @see XML_Serializer + */ +class XML_Unserializer extends PEAR +{ + /** + * list of all available options + * + * @access private + * @var array + */ + var $_knownOptions = array( + XML_UNSERIALIZER_OPTION_COMPLEXTYPE, + XML_UNSERIALIZER_OPTION_ATTRIBUTE_KEY, + XML_UNSERIALIZER_OPTION_ATTRIBUTE_TYPE, + XML_UNSERIALIZER_OPTION_ATTRIBUTE_CLASS, + XML_UNSERIALIZER_OPTION_TAG_AS_CLASSNAME, + XML_UNSERIALIZER_OPTION_DEFAULT_CLASS, + XML_UNSERIALIZER_OPTION_ATTRIBUTES_PARSE, + XML_UNSERIALIZER_OPTION_ATTRIBUTES_ARRAYKEY, + XML_UNSERIALIZER_OPTION_ATTRIBUTES_PREPEND, + XML_UNSERIALIZER_OPTION_CONTENT_KEY, + XML_UNSERIALIZER_OPTION_TAG_MAP, + XML_UNSERIALIZER_OPTION_FORCE_ENUM, + XML_UNSERIALIZER_OPTION_ENCODING_SOURCE, + XML_UNSERIALIZER_OPTION_ENCODING_TARGET, + XML_UNSERIALIZER_OPTION_DECODE_FUNC, + XML_UNSERIALIZER_OPTION_RETURN_RESULT, + XML_UNSERIALIZER_OPTION_WHITESPACE, + XML_UNSERIALIZER_OPTION_IGNORE_KEYS, + XML_UNSERIALIZER_OPTION_GUESS_TYPES + ); + /** + * default options for the serialization + * + * @access private + * @var array + */ + var $_defaultOptions = array( + XML_UNSERIALIZER_OPTION_COMPLEXTYPE => 'array', // complex types will be converted to arrays, if no type hint is given + XML_UNSERIALIZER_OPTION_ATTRIBUTE_KEY => '_originalKey', // get array key/property name from this attribute + XML_UNSERIALIZER_OPTION_ATTRIBUTE_TYPE => '_type', // get type from this attribute + XML_UNSERIALIZER_OPTION_ATTRIBUTE_CLASS => '_class', // get class from this attribute (if not given, use tag name) + XML_UNSERIALIZER_OPTION_TAG_AS_CLASSNAME => true, // use the tagname as the classname + XML_UNSERIALIZER_OPTION_DEFAULT_CLASS => 'stdClass', // name of the class that is used to create objects + XML_UNSERIALIZER_OPTION_ATTRIBUTES_PARSE => false, // parse the attributes of the tag into an array + XML_UNSERIALIZER_OPTION_ATTRIBUTES_ARRAYKEY => false, // parse them into sperate array (specify name of array here) + XML_UNSERIALIZER_OPTION_ATTRIBUTES_PREPEND => '', // prepend attribute names with this string + XML_UNSERIALIZER_OPTION_CONTENT_KEY => '_content', // put cdata found in a tag that has been converted to a complex type in this key + XML_UNSERIALIZER_OPTION_TAG_MAP => array(), // use this to map tagnames + XML_UNSERIALIZER_OPTION_FORCE_ENUM => array(), // these tags will always be an indexed array + XML_UNSERIALIZER_OPTION_ENCODING_SOURCE => null, // specify the encoding character of the document to parse + XML_UNSERIALIZER_OPTION_ENCODING_TARGET => null, // specify the target encoding + XML_UNSERIALIZER_OPTION_DECODE_FUNC => null, // function used to decode data + XML_UNSERIALIZER_OPTION_RETURN_RESULT => false, // unserialize() returns the result of the unserialization instead of true + XML_UNSERIALIZER_OPTION_WHITESPACE => XML_UNSERIALIZER_WHITESPACE_TRIM, // remove whitespace around data + XML_UNSERIALIZER_OPTION_IGNORE_KEYS => array(), // List of tags that will automatically be added to the parent, instead of adding a new key + XML_UNSERIALIZER_OPTION_GUESS_TYPES => false // Whether to use type guessing + ); + + /** + * current options for the serialization + * + * @access public + * @var array + */ + var $options = array(); + + /** + * unserialized data + * + * @access private + * @var string + */ + var $_unserializedData = null; + + /** + * name of the root tag + * + * @access private + * @var string + */ + var $_root = null; + + /** + * stack for all data that is found + * + * @access private + * @var array + */ + var $_dataStack = array(); + + /** + * stack for all values that are generated + * + * @access private + * @var array + */ + var $_valStack = array(); + + /** + * current tag depth + * + * @access private + * @var int + */ + var $_depth = 0; + + /** + * XML_Parser instance + * + * @access private + * @var object XML_Parser + */ + var $_parser = null; + + /** + * constructor + * + * @access public + * @param mixed $options array containing options for the unserialization + */ + function XML_Unserializer($options = null) + { + if (is_array($options)) { + $this->options = array_merge($this->_defaultOptions, $options); + } else { + $this->options = $this->_defaultOptions; + } + } + + /** + * return API version + * + * @access public + * @static + * @return string $version API version + */ + function apiVersion() + { + return '@package_version@'; + } + + /** + * reset all options to default options + * + * @access public + * @see setOption(), XML_Unserializer(), setOptions() + */ + function resetOptions() + { + $this->options = $this->_defaultOptions; + } + + /** + * set an option + * + * You can use this method if you do not want to set all options in the constructor + * + * @access public + * @see resetOption(), XML_Unserializer(), setOptions() + */ + function setOption($name, $value) + { + $this->options[$name] = $value; + } + + /** + * sets several options at once + * + * You can use this method if you do not want to set all options in the constructor + * + * @access public + * @see resetOption(), XML_Unserializer(), setOption() + */ + function setOptions($options) + { + $this->options = array_merge($this->options, $options); + } + + /** + * unserialize data + * + * @access public + * @param mixed $data data to unserialize (string, filename or resource) + * @param boolean $isFile data should be treated as a file + * @param array $options options that will override the global options for this call + * @return boolean $success + */ + function unserialize($data, $isFile = false, $options = null) + { + $this->_unserializedData = null; + $this->_root = null; + + // if options have been specified, use them instead + // of the previously defined ones + if (is_array($options)) { + $optionsBak = $this->options; + if (isset($options[XML_UNSERIALIZER_OPTION_OVERRIDE_OPTIONS]) && $options[XML_UNSERIALIZER_OPTION_OVERRIDE_OPTIONS] == true) { + $this->options = array_merge($this->_defaultOptions, $options); + } else { + $this->options = array_merge($this->options, $options); + } + } else { + $optionsBak = null; + } + + $this->_valStack = array(); + $this->_dataStack = array(); + $this->_depth = 0; + + $this->_createParser(); + + if (is_string($data)) { + if ($isFile) { + $result = $this->_parser->setInputFile($data); + if (PEAR::isError($result)) { + return $result; + } + $result = $this->_parser->parse(); + } else { + $result = $this->_parser->parseString($data,true); + } + } else { + $this->_parser->setInput($data); + $result = $this->_parser->parse(); + } + + if ($this->options[XML_UNSERIALIZER_OPTION_RETURN_RESULT] === true) { + $return = $this->_unserializedData; + } else { + $return = true; + } + + if ($optionsBak !== null) { + $this->options = $optionsBak; + } + + if (PEAR::isError($result)) { + return $result; + } + + return $return; + } + + /** + * get the result of the serialization + * + * @access public + * @return string $serializedData + */ + function getUnserializedData() + { + if ($this->_root === null) { + return $this->raiseError('No unserialized data available. Use XML_Unserializer::unserialize() first.', XML_UNSERIALIZER_ERROR_NO_UNSERIALIZATION); + } + return $this->_unserializedData; + } + + /** + * get the name of the root tag + * + * @access public + * @return string $rootName + */ + function getRootName() + { + if ($this->_root === null) { + return $this->raiseError('No unserialized data available. Use XML_Unserializer::unserialize() first.', XML_UNSERIALIZER_ERROR_NO_UNSERIALIZATION); + } + return $this->_root; + } + + /** + * Start element handler for XML parser + * + * @access private + * @param object $parser XML parser object + * @param string $element XML element + * @param array $attribs attributes of XML tag + * @return void + */ + function startHandler($parser, $element, $attribs) + { + if (isset($attribs[$this->options[XML_UNSERIALIZER_OPTION_ATTRIBUTE_TYPE]])) { + $type = $attribs[$this->options[XML_UNSERIALIZER_OPTION_ATTRIBUTE_TYPE]]; + $guessType = false; + } else { + $type = 'string'; + if ($this->options[XML_UNSERIALIZER_OPTION_GUESS_TYPES] === true) { + $guessType = true; + } else { + $guessType = false; + } + } + + if ($this->options[XML_UNSERIALIZER_OPTION_DECODE_FUNC] !== null) { + $attribs = array_map($this->options[XML_UNSERIALIZER_OPTION_DECODE_FUNC], $attribs); + } + + $this->_depth++; + $this->_dataStack[$this->_depth] = null; + + if (is_array($this->options[XML_UNSERIALIZER_OPTION_TAG_MAP]) && isset($this->options[XML_UNSERIALIZER_OPTION_TAG_MAP][$element])) { + $element = $this->options[XML_UNSERIALIZER_OPTION_TAG_MAP][$element]; + } + + $val = array( + 'name' => $element, + 'value' => null, + 'type' => $type, + 'guessType' => $guessType, + 'childrenKeys' => array(), + 'aggregKeys' => array() + ); + + if ($this->options[XML_UNSERIALIZER_OPTION_ATTRIBUTES_PARSE] == true && (count($attribs) > 0)) { + $val['children'] = array(); + $val['type'] = $this->_getComplexType($element); + $val['class'] = $element; + + if ($this->options[XML_UNSERIALIZER_OPTION_GUESS_TYPES] === true) { + $attribs = $this->_guessAndSetTypes($attribs); + } + if ($this->options[XML_UNSERIALIZER_OPTION_ATTRIBUTES_ARRAYKEY] != false) { + $val['children'][$this->options[XML_UNSERIALIZER_OPTION_ATTRIBUTES_ARRAYKEY]] = $attribs; + } else { + foreach ($attribs as $attrib => $value) { + $val['children'][$this->options[XML_UNSERIALIZER_OPTION_ATTRIBUTES_PREPEND].$attrib] = $value; + } + } + } + + $keyAttr = false; + + if (is_string($this->options[XML_UNSERIALIZER_OPTION_ATTRIBUTE_KEY])) { + $keyAttr = $this->options[XML_UNSERIALIZER_OPTION_ATTRIBUTE_KEY]; + } elseif (is_array($this->options[XML_UNSERIALIZER_OPTION_ATTRIBUTE_KEY])) { + if (isset($this->options[XML_UNSERIALIZER_OPTION_ATTRIBUTE_KEY][$element])) { + $keyAttr = $this->options[XML_UNSERIALIZER_OPTION_ATTRIBUTE_KEY][$element]; + } elseif (isset($this->options[XML_UNSERIALIZER_OPTION_ATTRIBUTE_KEY]['#default'])) { + $keyAttr = $this->options[XML_UNSERIALIZER_OPTION_ATTRIBUTE_KEY]['#default']; + } elseif (isset($this->options[XML_UNSERIALIZER_OPTION_ATTRIBUTE_KEY]['__default'])) { + // keep this for BC + $keyAttr = $this->options[XML_UNSERIALIZER_OPTION_ATTRIBUTE_KEY]['__default']; + } + } + + if ($keyAttr !== false && isset($attribs[$keyAttr])) { + $val['name'] = $attribs[$keyAttr]; + } + + if (isset($attribs[$this->options[XML_UNSERIALIZER_OPTION_ATTRIBUTE_CLASS]])) { + $val['class'] = $attribs[$this->options[XML_UNSERIALIZER_OPTION_ATTRIBUTE_CLASS]]; + } + + array_push($this->_valStack, $val); + } + + /** + * Try to guess the type of several values and + * set them accordingly + * + * @access private + * @param array array, containing the values + * @return array array, containing the values with their correct types + */ + function _guessAndSetTypes($array) + { + foreach ($array as $key => $value) { + $array[$key] = $this->_guessAndSetType($value); + } + return $array; + } + + /** + * Try to guess the type of a value and + * set it accordingly + * + * @access private + * @param string character data + * @return mixed value with the best matching type + */ + function _guessAndSetType($value) + { + if ($value === 'true') { + return true; + } + if ($value === 'false') { + return false; + } + if ($value === 'NULL') { + return null; + } + if (preg_match('/^[-+]?[0-9]{1,}$/', $value)) { + return intval($value); + } + if (preg_match('/^[-+]?[0-9]{1,}\.[0-9]{1,}$/', $value)) { + return doubleval($value); + } + return (string)$value; + } + + /** + * End element handler for XML parser + * + * @access private + * @param object XML parser object + * @param string + * @return void + */ + function endHandler($parser, $element) + { + $value = array_pop($this->_valStack); + switch ($this->options[XML_UNSERIALIZER_OPTION_WHITESPACE]) { + case XML_UNSERIALIZER_WHITESPACE_KEEP: + $data = $this->_dataStack[$this->_depth]; + break; + case XML_UNSERIALIZER_WHITESPACE_NORMALIZE: + $data = trim(preg_replace('/\s\s+/m', ' ', $this->_dataStack[$this->_depth])); + break; + case XML_UNSERIALIZER_WHITESPACE_TRIM: + default: + $data = trim($this->_dataStack[$this->_depth]); + break; + } + + // adjust type of the value + switch(strtolower($value['type'])) { + + // unserialize an object + case 'object': + if (isset($value['class'])) { + $classname = $value['class']; + } else { + $classname = ''; + } + // instantiate the class + if ($this->options[XML_UNSERIALIZER_OPTION_TAG_AS_CLASSNAME] === true && class_exists($classname)) { + $value['value'] = &new $classname; + } else { + $value['value'] = &new $this->options[XML_UNSERIALIZER_OPTION_DEFAULT_CLASS]; + } + if (trim($data) !== '') { + if ($value['guessType'] === true) { + $data = $this->_guessAndSetType($data); + } + $value['children'][$this->options[XML_UNSERIALIZER_OPTION_CONTENT_KEY]] = $data; + } + + // set properties + foreach ($value['children'] as $prop => $propVal) { + // check whether there is a special method to set this property + $setMethod = 'set'.$prop; + if (method_exists($value['value'], $setMethod)) { + call_user_func(array(&$value['value'], $setMethod), $propVal); + } else { + $value['value']->$prop = $propVal; + } + } + // check for magic function + if (method_exists($value['value'], '__wakeup')) { + $value['value']->__wakeup(); + } + break; + + // unserialize an array + case 'array': + if (trim($data) !== '') { + if ($value['guessType'] === true) { + $data = $this->_guessAndSetType($data); + } + $value['children'][$this->options[XML_UNSERIALIZER_OPTION_CONTENT_KEY]] = $data; + } + if (isset($value['children'])) { + $value['value'] = $value['children']; + } else { + $value['value'] = array(); + } + break; + + // unserialize a null value + case 'null': + $data = null; + break; + + // unserialize a resource => this is not possible :-( + case 'resource': + $value['value'] = $data; + break; + + // unserialize any scalar value + default: + if ($value['guessType'] === true) { + $data = $this->_guessAndSetType($data); + } else { + settype($data, $value['type']); + } + + $value['value'] = $data; + break; + } + $parent = array_pop($this->_valStack); + if ($parent === null) { + $this->_unserializedData = &$value['value']; + $this->_root = &$value['name']; + return true; + } else { + // parent has to be an array + if (!isset($parent['children']) || !is_array($parent['children'])) { + $parent['children'] = array(); + if (!in_array($parent['type'], array('array', 'object'))) { + $parent['type'] = $this->_getComplexType($parent['name']); + if ($parent['type'] == 'object') { + $parent['class'] = $parent['name']; + } + } + } + + if (in_array($element, $this->options[XML_UNSERIALIZER_OPTION_IGNORE_KEYS])) { + $ignoreKey = true; + } else { + $ignoreKey = false; + } + + if (!empty($value['name']) && $ignoreKey === false) { + // there already has been a tag with this name + if (in_array($value['name'], $parent['childrenKeys']) || in_array($value['name'], $this->options[XML_UNSERIALIZER_OPTION_FORCE_ENUM])) { + // no aggregate has been created for this tag + if (!in_array($value['name'], $parent['aggregKeys'])) { + if (isset($parent['children'][$value['name']])) { + $parent['children'][$value['name']] = array($parent['children'][$value['name']]); + } else { + $parent['children'][$value['name']] = array(); + } + array_push($parent['aggregKeys'], $value['name']); + } + array_push($parent['children'][$value['name']], $value['value']); + } else { + $parent['children'][$value['name']] = &$value['value']; + array_push($parent['childrenKeys'], $value['name']); + } + } else { + array_push($parent['children'], $value['value']); + } + array_push($this->_valStack, $parent); + } + + $this->_depth--; + } + + /** + * Handler for character data + * + * @access private + * @param object XML parser object + * @param string CDATA + * @return void + */ + function cdataHandler($parser, $cdata) + { + if ($this->options[XML_UNSERIALIZER_OPTION_DECODE_FUNC] !== null) { + $cdata = call_user_func($this->options[XML_UNSERIALIZER_OPTION_DECODE_FUNC], $cdata); + } + $this->_dataStack[$this->_depth] .= $cdata; + } + + /** + * get the complex type, that should be used for a specified tag + * + * @access private + * @param string name of the tag + * @return string complex type ('array' or 'object') + */ + function _getComplexType($tagname) + { + if (is_string($this->options[XML_UNSERIALIZER_OPTION_COMPLEXTYPE])) { + return $this->options[XML_UNSERIALIZER_OPTION_COMPLEXTYPE]; + } + if (isset($this->options[XML_UNSERIALIZER_OPTION_COMPLEXTYPE][$tagname])) { + return $this->options[XML_UNSERIALIZER_OPTION_COMPLEXTYPE][$tagname]; + } + if (isset($this->options[XML_UNSERIALIZER_OPTION_COMPLEXTYPE]['#default'])) { + return $this->options[XML_UNSERIALIZER_OPTION_COMPLEXTYPE]['#default']; + } + return 'array'; + } + + /** + * create the XML_Parser instance + * + * @access private + * @return boolean + */ + function _createParser() + { + if (is_object($this->_parser)) { + $this->_parser->free(); + unset($this->_parser); + } + $this->_parser = &new XML_Parser($this->options[XML_UNSERIALIZER_OPTION_ENCODING_SOURCE], 'event', $this->options[XML_UNSERIALIZER_OPTION_ENCODING_TARGET]); + $this->_parser->folding = false; + $this->_parser->setHandlerObj($this); + return true; + } +} +?>
\ No newline at end of file diff --git a/libs/XML/Util.php b/libs/XML/Util.php new file mode 100644 index 0000000000..133de882c9 --- /dev/null +++ b/libs/XML/Util.php @@ -0,0 +1,752 @@ +<?PHP
+/* vim: set expandtab tabstop=4 shiftwidth=4: */
+// +----------------------------------------------------------------------+
+// | PHP Version 4 |
+// +----------------------------------------------------------------------+
+// | Copyright (c) 1997-2002 The PHP Group |
+// +----------------------------------------------------------------------+
+// | This source file is subject to version 2.0 of the PHP license, |
+// | that is bundled with this package in the file LICENSE, and is |
+// | available at through the world-wide-web at |
+// | http://www.php.net/license/2_02.txt. |
+// | If you did not receive a copy of the PHP license and are unable to |
+// | obtain it through the world-wide-web, please send a note to |
+// | license@php.net so we can mail you a copy immediately. |
+// +----------------------------------------------------------------------+
+// | Authors: Stephan Schmidt <schst@php-tools.net> |
+// +----------------------------------------------------------------------+
+//
+// $Id: Util.php,v 1.28 2006/12/16 09:42:56 schst Exp $
+
+/**
+ * error code for invalid chars in XML name
+ */
+define("XML_UTIL_ERROR_INVALID_CHARS", 51);
+
+/**
+ * error code for invalid chars in XML name
+ */
+define("XML_UTIL_ERROR_INVALID_START", 52);
+
+/**
+ * error code for non-scalar tag content
+ */
+define("XML_UTIL_ERROR_NON_SCALAR_CONTENT", 60);
+
+/**
+ * error code for missing tag name
+ */
+define("XML_UTIL_ERROR_NO_TAG_NAME", 61);
+
+/**
+ * replace XML entities
+ */
+define("XML_UTIL_REPLACE_ENTITIES", 1);
+
+/**
+ * embedd content in a CData Section
+ */
+define("XML_UTIL_CDATA_SECTION", 5);
+
+/**
+ * do not replace entitites
+ */
+define("XML_UTIL_ENTITIES_NONE", 0);
+
+/**
+ * replace all XML entitites
+ * This setting will replace <, >, ", ' and &
+ */
+define("XML_UTIL_ENTITIES_XML", 1);
+
+/**
+ * replace only required XML entitites
+ * This setting will replace <, " and &
+ */
+define("XML_UTIL_ENTITIES_XML_REQUIRED", 2);
+
+/**
+ * replace HTML entitites
+ * @link http://www.php.net/htmlentities
+ */
+define("XML_UTIL_ENTITIES_HTML", 3);
+
+/**
+ * Collapse all empty tags.
+ */
+define("XML_UTIL_COLLAPSE_ALL", 1);
+
+/**
+ * Collapse only empty XHTML tags that have no end tag.
+ */
+define("XML_UTIL_COLLAPSE_XHTML_ONLY", 2);
+
+/**
+ * utility class for working with XML documents
+ *
+ * @category XML
+ * @package XML_Util
+ * @version 1.1.0
+ * @author Stephan Schmidt <schst@php.net>
+ */
+class XML_Util {
+
+ /**
+ * return API version
+ *
+ * @access public
+ * @static
+ * @return string $version API version
+ */
+ static function apiVersion()
+ {
+ return '1.1';
+ }
+
+ /**
+ * replace XML entities
+ *
+ * With the optional second parameter, you may select, which
+ * entities should be replaced.
+ *
+ * <code>
+ * require_once 'XML/Util.php';
+ *
+ * // replace XML entites:
+ * $string = XML_Util::replaceEntities("This string contains < & >.");
+ * </code>
+ *
+ * @access public
+ * @static
+ * @param string string where XML special chars should be replaced
+ * @param integer setting for entities in attribute values (one of XML_UTIL_ENTITIES_XML, XML_UTIL_ENTITIES_XML_REQUIRED, XML_UTIL_ENTITIES_HTML)
+ * @return string string with replaced chars
+ * @see reverseEntities()
+ */
+ static function replaceEntities($string, $replaceEntities = XML_UTIL_ENTITIES_XML)
+ {
+ switch ($replaceEntities) {
+ case XML_UTIL_ENTITIES_XML:
+ return strtr($string,array(
+ '&' => '&',
+ '>' => '>',
+ '<' => '<',
+ '"' => '"',
+ '\'' => ''' ));
+ break;
+ case XML_UTIL_ENTITIES_XML_REQUIRED:
+ return strtr($string,array(
+ '&' => '&',
+ '<' => '<',
+ '"' => '"' ));
+ break;
+ case XML_UTIL_ENTITIES_HTML:
+ return htmlentities($string);
+ break;
+ }
+ return $string;
+ }
+
+ /**
+ * reverse XML entities
+ *
+ * With the optional second parameter, you may select, which
+ * entities should be reversed.
+ *
+ * <code>
+ * require_once 'XML/Util.php';
+ *
+ * // reverse XML entites:
+ * $string = XML_Util::reverseEntities("This string contains < & >.");
+ * </code>
+ *
+ * @access public
+ * @static
+ * @param string string where XML special chars should be replaced
+ * @param integer setting for entities in attribute values (one of XML_UTIL_ENTITIES_XML, XML_UTIL_ENTITIES_XML_REQUIRED, XML_UTIL_ENTITIES_HTML)
+ * @return string string with replaced chars
+ * @see replaceEntities()
+ */
+ static function reverseEntities($string, $replaceEntities = XML_UTIL_ENTITIES_XML)
+ {
+ switch ($replaceEntities) {
+ case XML_UTIL_ENTITIES_XML:
+ return strtr($string,array(
+ '&' => '&',
+ '>' => '>',
+ '<' => '<',
+ '"' => '"',
+ ''' => '\'' ));
+ break;
+ case XML_UTIL_ENTITIES_XML_REQUIRED:
+ return strtr($string,array(
+ '&' => '&',
+ '<' => '<',
+ '"' => '"' ));
+ break;
+ case XML_UTIL_ENTITIES_HTML:
+ $arr = array_flip(get_html_translation_table(HTML_ENTITIES));
+ return strtr($string, $arr);
+ break;
+ }
+ return $string;
+ }
+
+ /**
+ * build an xml declaration
+ *
+ * <code>
+ * require_once 'XML/Util.php';
+ *
+ * // get an XML declaration:
+ * $xmlDecl = XML_Util::getXMLDeclaration("1.0", "UTF-8", true);
+ * </code>
+ *
+ * @access public
+ * @static
+ * @param string $version xml version
+ * @param string $encoding character encoding
+ * @param boolean $standAlone document is standalone (or not)
+ * @return string $decl xml declaration
+ * @uses XML_Util::attributesToString() to serialize the attributes of the XML declaration
+ */
+ static function getXMLDeclaration($version = "1.0", $encoding = null, $standalone = null)
+ {
+ $attributes = array(
+ "version" => $version,
+ );
+ // add encoding
+ if ($encoding !== null) {
+ $attributes["encoding"] = $encoding;
+ }
+ // add standalone, if specified
+ if ($standalone !== null) {
+ $attributes["standalone"] = $standalone ? "yes" : "no";
+ }
+
+ return sprintf("<?xml%s?>", XML_Util::attributesToString($attributes, false));
+ }
+
+ /**
+ * build a document type declaration
+ *
+ * <code>
+ * require_once 'XML/Util.php';
+ *
+ * // get a doctype declaration:
+ * $xmlDecl = XML_Util::getDocTypeDeclaration("rootTag","myDocType.dtd");
+ * </code>
+ *
+ * @access public
+ * @static
+ * @param string $root name of the root tag
+ * @param string $uri uri of the doctype definition (or array with uri and public id)
+ * @param string $internalDtd internal dtd entries
+ * @return string $decl doctype declaration
+ * @since 0.2
+ */
+ static function getDocTypeDeclaration($root, $uri = null, $internalDtd = null)
+ {
+ if (is_array($uri)) {
+ $ref = sprintf( ' PUBLIC "%s" "%s"', $uri["id"], $uri["uri"] );
+ } elseif (!empty($uri)) {
+ $ref = sprintf( ' SYSTEM "%s"', $uri );
+ } else {
+ $ref = "";
+ }
+
+ if (empty($internalDtd)) {
+ return sprintf("<!DOCTYPE %s%s>", $root, $ref);
+ } else {
+ return sprintf("<!DOCTYPE %s%s [\n%s\n]>", $root, $ref, $internalDtd);
+ }
+ }
+
+ /**
+ * create string representation of an attribute list
+ *
+ * <code>
+ * require_once 'XML/Util.php';
+ *
+ * // build an attribute string
+ * $att = array(
+ * "foo" => "bar",
+ * "argh" => "tomato"
+ * );
+ *
+ * $attList = XML_Util::attributesToString($att);
+ * </code>
+ *
+ * @access public
+ * @static
+ * @param array $attributes attribute array
+ * @param boolean|array $sort sort attribute list alphabetically, may also be an assoc array containing the keys 'sort', 'multiline', 'indent', 'linebreak' and 'entities'
+ * @param boolean $multiline use linebreaks, if more than one attribute is given
+ * @param string $indent string used for indentation of multiline attributes
+ * @param string $linebreak string used for linebreaks of multiline attributes
+ * @param integer $entities setting for entities in attribute values (one of XML_UTIL_ENTITIES_NONE, XML_UTIL_ENTITIES_XML, XML_UTIL_ENTITIES_XML_REQUIRED, XML_UTIL_ENTITIES_HTML)
+ * @return string string representation of the attributes
+ * @uses XML_Util::replaceEntities() to replace XML entities in attribute values
+ * @todo allow sort also to be an options array
+ */
+ static function attributesToString($attributes, $sort = true, $multiline = false, $indent = ' ', $linebreak = "\n", $entities = XML_UTIL_ENTITIES_XML)
+ {
+ /**
+ * second parameter may be an array
+ */
+ if (is_array($sort)) {
+ if (isset($sort['multiline'])) {
+ $multiline = $sort['multiline'];
+ }
+ if (isset($sort['indent'])) {
+ $indent = $sort['indent'];
+ }
+ if (isset($sort['linebreak'])) {
+ $multiline = $sort['linebreak'];
+ }
+ if (isset($sort['entities'])) {
+ $entities = $sort['entities'];
+ }
+ if (isset($sort['sort'])) {
+ $sort = $sort['sort'];
+ } else {
+ $sort = true;
+ }
+ }
+ $string = '';
+ if (is_array($attributes) && !empty($attributes)) {
+ if ($sort) {
+ ksort($attributes);
+ }
+ if( !$multiline || count($attributes) == 1) {
+ foreach ($attributes as $key => $value) {
+ if ($entities != XML_UTIL_ENTITIES_NONE) {
+ if ($entities === XML_UTIL_CDATA_SECTION) {
+ $entities = XML_UTIL_ENTITIES_XML;
+ }
+ $value = XML_Util::replaceEntities($value, $entities);
+ }
+ $string .= ' '.$key.'="'.$value.'"';
+ }
+ } else {
+ $first = true;
+ foreach ($attributes as $key => $value) {
+ if ($entities != XML_UTIL_ENTITIES_NONE) {
+ $value = XML_Util::replaceEntities($value, $entities);
+ }
+ if ($first) {
+ $string .= " ".$key.'="'.$value.'"';
+ $first = false;
+ } else {
+ $string .= $linebreak.$indent.$key.'="'.$value.'"';
+ }
+ }
+ }
+ }
+ return $string;
+ }
+
+ /**
+ * Collapses empty tags.
+ *
+ * @access public
+ * @static
+ * @param string $xml XML
+ * @param integer $mode Whether to collapse all empty tags (XML_UTIL_COLLAPSE_ALL) or only XHTML (XML_UTIL_COLLAPSE_XHTML_ONLY) ones.
+ * @return string $xml XML
+ */
+ static function collapseEmptyTags($xml, $mode = XML_UTIL_COLLAPSE_ALL) {
+ if ($mode == XML_UTIL_COLLAPSE_XHTML_ONLY) {
+ return preg_replace(
+ '/<(area|base|br|col|hr|img|input|link|meta|param)([^>]*)><\/\\1>/s',
+ '<\\1\\2 />',
+ $xml
+ );
+ } else {
+ return preg_replace(
+ '/<(\w+)([^>]*)><\/\\1>/s',
+ '<\\1\\2 />',
+ $xml
+ );
+ }
+ }
+
+ /**
+ * create a tag
+ *
+ * This method will call XML_Util::createTagFromArray(), which
+ * is more flexible.
+ *
+ * <code>
+ * require_once 'XML/Util.php';
+ *
+ * // create an XML tag:
+ * $tag = XML_Util::createTag("myNs:myTag", array("foo" => "bar"), "This is inside the tag", "http://www.w3c.org/myNs#");
+ * </code>
+ *
+ * @access public
+ * @static
+ * @param string $qname qualified tagname (including namespace)
+ * @param array $attributes array containg attributes
+ * @param mixed $content
+ * @param string $namespaceUri URI of the namespace
+ * @param integer $replaceEntities whether to replace XML special chars in content, embedd it in a CData section or none of both
+ * @param boolean $multiline whether to create a multiline tag where each attribute gets written to a single line
+ * @param string $indent string used to indent attributes (_auto indents attributes so they start at the same column)
+ * @param string $linebreak string used for linebreaks
+ * @param boolean $sortAttributes Whether to sort the attributes or not
+ * @return string $string XML tag
+ * @see XML_Util::createTagFromArray()
+ * @uses XML_Util::createTagFromArray() to create the tag
+ */
+ static function createTag($qname, $attributes = array(), $content = null, $namespaceUri = null, $replaceEntities = XML_UTIL_REPLACE_ENTITIES, $multiline = false, $indent = "_auto", $linebreak = "\n", $sortAttributes = true)
+ {
+ $tag = array(
+ "qname" => $qname,
+ "attributes" => $attributes
+ );
+
+ // add tag content
+ if ($content !== null) {
+ $tag["content"] = $content;
+ }
+
+ // add namespace Uri
+ if ($namespaceUri !== null) {
+ $tag["namespaceUri"] = $namespaceUri;
+ }
+
+ return XML_Util::createTagFromArray($tag, $replaceEntities, $multiline, $indent, $linebreak, $sortAttributes);
+ }
+
+ /**
+ * create a tag from an array
+ * this method awaits an array in the following format
+ * <pre>
+ * array(
+ * "qname" => $qname // qualified name of the tag
+ * "namespace" => $namespace // namespace prefix (optional, if qname is specified or no namespace)
+ * "localpart" => $localpart, // local part of the tagname (optional, if qname is specified)
+ * "attributes" => array(), // array containing all attributes (optional)
+ * "content" => $content, // tag content (optional)
+ * "namespaceUri" => $namespaceUri // namespaceUri for the given namespace (optional)
+ * )
+ * </pre>
+ *
+ * <code>
+ * require_once 'XML/Util.php';
+ *
+ * $tag = array(
+ * "qname" => "foo:bar",
+ * "namespaceUri" => "http://foo.com",
+ * "attributes" => array( "key" => "value", "argh" => "fruit&vegetable" ),
+ * "content" => "I'm inside the tag",
+ * );
+ * // creating a tag with qualified name and namespaceUri
+ * $string = XML_Util::createTagFromArray($tag);
+ * </code>
+ *
+ * @access public
+ * @static
+ * @param array $tag tag definition
+ * @param integer $replaceEntities whether to replace XML special chars in content, embedd it in a CData section or none of both
+ * @param boolean $multiline whether to create a multiline tag where each attribute gets written to a single line
+ * @param string $indent string used to indent attributes (_auto indents attributes so they start at the same column)
+ * @param string $linebreak string used for linebreaks
+ * @param boolean $sortAttributes Whether to sort the attributes or not
+ * @return string $string XML tag
+ * @see XML_Util::createTag()
+ * @uses XML_Util::attributesToString() to serialize the attributes of the tag
+ * @uses XML_Util::splitQualifiedName() to get local part and namespace of a qualified name
+ */
+ static function createTagFromArray($tag, $replaceEntities = XML_UTIL_REPLACE_ENTITIES, $multiline = false, $indent = "_auto", $linebreak = "\n", $sortAttributes = true)
+ {
+ if (isset($tag['content']) && !is_scalar($tag['content'])) {
+ return XML_Util::raiseError( 'Supplied non-scalar value as tag content', XML_UTIL_ERROR_NON_SCALAR_CONTENT );
+ }
+
+ if (!isset($tag['qname']) && !isset($tag['localPart'])) {
+ return XML_Util::raiseError( 'You must either supply a qualified name (qname) or local tag name (localPart).', XML_UTIL_ERROR_NO_TAG_NAME );
+ }
+
+ // if no attributes hav been set, use empty attributes
+ if (!isset($tag["attributes"]) || !is_array($tag["attributes"])) {
+ $tag["attributes"] = array();
+ }
+
+ if (isset($tag['namespaces'])) {
+ foreach ($tag['namespaces'] as $ns => $uri) {
+ $tag['attributes']['xmlns:'.$ns] = $uri;
+ }
+ }
+
+ // qualified name is not given
+ if (!isset($tag["qname"])) {
+ // check for namespace
+ if (isset($tag["namespace"]) && !empty($tag["namespace"])) {
+ $tag["qname"] = $tag["namespace"].":".$tag["localPart"];
+ } else {
+ $tag["qname"] = $tag["localPart"];
+ }
+ // namespace URI is set, but no namespace
+ } elseif (isset($tag["namespaceUri"]) && !isset($tag["namespace"])) {
+ $parts = XML_Util::splitQualifiedName($tag["qname"]);
+ $tag["localPart"] = $parts["localPart"];
+ if (isset($parts["namespace"])) {
+ $tag["namespace"] = $parts["namespace"];
+ }
+ }
+
+ if (isset($tag["namespaceUri"]) && !empty($tag["namespaceUri"])) {
+ // is a namespace given
+ if (isset($tag["namespace"]) && !empty($tag["namespace"])) {
+ $tag["attributes"]["xmlns:".$tag["namespace"]] = $tag["namespaceUri"];
+ } else {
+ // define this Uri as the default namespace
+ $tag["attributes"]["xmlns"] = $tag["namespaceUri"];
+ }
+ }
+
+ // check for multiline attributes
+ if ($multiline === true) {
+ if ($indent === "_auto") {
+ $indent = str_repeat(" ", (strlen($tag["qname"])+2));
+ }
+ }
+
+ // create attribute list
+ $attList = XML_Util::attributesToString($tag['attributes'], $sortAttributes, $multiline, $indent, $linebreak, $replaceEntities );
+ if (!isset($tag['content']) || (string)$tag['content'] == '') {
+ $tag = sprintf('<%s%s />', $tag['qname'], $attList);
+ } else {
+ switch ($replaceEntities) {
+ case XML_UTIL_ENTITIES_NONE:
+ break;
+ case XML_UTIL_CDATA_SECTION:
+ $tag['content'] = XML_Util::createCDataSection($tag['content']);
+ break;
+ default:
+ $tag['content'] = XML_Util::replaceEntities($tag['content'], $replaceEntities);
+ break;
+ }
+ $tag = sprintf('<%s%s>%s</%s>', $tag['qname'], $attList, $tag['content'], $tag['qname'] );
+ }
+ return $tag;
+ }
+
+ /**
+ * create a start element
+ *
+ * <code>
+ * require_once 'XML/Util.php';
+ *
+ * // create an XML start element:
+ * $tag = XML_Util::createStartElement("myNs:myTag", array("foo" => "bar") ,"http://www.w3c.org/myNs#");
+ * </code>
+ *
+ * @access public
+ * @static
+ * @param string $qname qualified tagname (including namespace)
+ * @param array $attributes array containg attributes
+ * @param string $namespaceUri URI of the namespace
+ * @param boolean $multiline whether to create a multiline tag where each attribute gets written to a single line
+ * @param string $indent string used to indent attributes (_auto indents attributes so they start at the same column)
+ * @param string $linebreak string used for linebreaks
+ * @param boolean $sortAttributes Whether to sort the attributes or not
+ * @return string $string XML start element
+ * @see XML_Util::createEndElement(), XML_Util::createTag()
+ */
+ static function createStartElement($qname, $attributes = array(), $namespaceUri = null, $multiline = false, $indent = '_auto', $linebreak = "\n", $sortAttributes = true)
+ {
+ // if no attributes hav been set, use empty attributes
+ if (!isset($attributes) || !is_array($attributes)) {
+ $attributes = array();
+ }
+
+ if ($namespaceUri != null) {
+ $parts = XML_Util::splitQualifiedName($qname);
+ }
+
+ // check for multiline attributes
+ if ($multiline === true) {
+ if ($indent === "_auto") {
+ $indent = str_repeat(" ", (strlen($qname)+2));
+ }
+ }
+
+ if ($namespaceUri != null) {
+ // is a namespace given
+ if (isset($parts["namespace"]) && !empty($parts["namespace"])) {
+ $attributes["xmlns:".$parts["namespace"]] = $namespaceUri;
+ } else {
+ // define this Uri as the default namespace
+ $attributes["xmlns"] = $namespaceUri;
+ }
+ }
+
+ // create attribute list
+ $attList = XML_Util::attributesToString($attributes, $sortAttributes, $multiline, $indent, $linebreak);
+ $element = sprintf("<%s%s>", $qname, $attList);
+ return $element;
+ }
+
+ /**
+ * create an end element
+ *
+ * <code>
+ * require_once 'XML/Util.php';
+ *
+ * // create an XML start element:
+ * $tag = XML_Util::createEndElement("myNs:myTag");
+ * </code>
+ *
+ * @access public
+ * @static
+ * @param string $qname qualified tagname (including namespace)
+ * @return string $string XML end element
+ * @see XML_Util::createStartElement(), XML_Util::createTag()
+ */
+ static function createEndElement($qname)
+ {
+ $element = sprintf("</%s>", $qname);
+ return $element;
+ }
+
+ /**
+ * create an XML comment
+ *
+ * <code>
+ * require_once 'XML/Util.php';
+ *
+ * // create an XML start element:
+ * $tag = XML_Util::createComment("I am a comment");
+ * </code>
+ *
+ * @access public
+ * @static
+ * @param string $content content of the comment
+ * @return string $comment XML comment
+ */
+ static function createComment($content)
+ {
+ $comment = sprintf("<!-- %s -->", $content);
+ return $comment;
+ }
+
+ /**
+ * create a CData section
+ *
+ * <code>
+ * require_once 'XML/Util.php';
+ *
+ * // create a CData section
+ * $tag = XML_Util::createCDataSection("I am content.");
+ * </code>
+ *
+ * @access public
+ * @static
+ * @param string $data data of the CData section
+ * @return string $string CData section with content
+ */
+ static function createCDataSection($data)
+ {
+ return sprintf("<![CDATA[%s]]>", $data);
+ }
+
+ /**
+ * split qualified name and return namespace and local part
+ *
+ * <code>
+ * require_once 'XML/Util.php';
+ *
+ * // split qualified tag
+ * $parts = XML_Util::splitQualifiedName("xslt:stylesheet");
+ * </code>
+ * the returned array will contain two elements:
+ * <pre>
+ * array(
+ * "namespace" => "xslt",
+ * "localPart" => "stylesheet"
+ * );
+ * </pre>
+ *
+ * @access public
+ * @static
+ * @param string $qname qualified tag name
+ * @param string $defaultNs default namespace (optional)
+ * @return array $parts array containing namespace and local part
+ */
+ static function splitQualifiedName($qname, $defaultNs = null)
+ {
+ if (strstr($qname, ':')) {
+ $tmp = explode(":", $qname);
+ return array(
+ "namespace" => $tmp[0],
+ "localPart" => $tmp[1]
+ );
+ }
+ return array(
+ "namespace" => $defaultNs,
+ "localPart" => $qname
+ );
+ }
+
+ /**
+ * check, whether string is valid XML name
+ *
+ * <p>XML names are used for tagname, attribute names and various
+ * other, lesser known entities.</p>
+ * <p>An XML name may only consist of alphanumeric characters,
+ * dashes, undescores and periods, and has to start with a letter
+ * or an underscore.
+ * </p>
+ *
+ * <code>
+ * require_once 'XML/Util.php';
+ *
+ * // verify tag name
+ * $result = XML_Util::isValidName("invalidTag?");
+ * if (XML_Util::isError($result)) {
+ * print "Invalid XML name: " . $result->getMessage();
+ * }
+ * </code>
+ *
+ * @access public
+ * @static
+ * @param string $string string that should be checked
+ * @return mixed $valid true, if string is a valid XML name, PEAR error otherwise
+ * @todo support for other charsets
+ */
+ static function isValidName($string)
+ {
+ // check for invalid chars
+ if (!preg_match('/^[[:alpha:]_]$/', $string{0})) {
+ return XML_Util::raiseError('XML names may only start with letter or underscore', XML_UTIL_ERROR_INVALID_START);
+ }
+
+ // check for invalid chars
+ if (!preg_match('/^([[:alpha:]_]([[:alnum:]\-\.]*)?:)?[[:alpha:]_]([[:alnum:]\_\-\.]+)?$/', $string)) {
+ return XML_Util::raiseError('XML names may only contain alphanumeric chars, period, hyphen, colon and underscores', XML_UTIL_ERROR_INVALID_CHARS);
+ }
+ // XML name is valid
+ return true;
+ }
+
+ /**
+ * replacement for XML_Util::raiseError
+ *
+ * Avoids the necessity to always require
+ * PEAR.php
+ *
+ * @access public
+ * @param string error message
+ * @param integer error code
+ * @return object PEAR_Error
+ */
+ static function raiseError($msg, $code)
+ {
+ require_once 'PEAR.php';
+ return PEAR::raiseError($msg, $code);
+ }
+}
+?>
\ No newline at end of file diff --git a/modules/API/Proxy.php b/modules/API/Proxy.php index d1c2c70a3b..f051024eee 100755 --- a/modules/API/Proxy.php +++ b/modules/API/Proxy.php @@ -4,6 +4,8 @@ class Piwik_API_Proxy static $classCalled = null; protected $alreadyRegistered = array(); private $api = null; + + const NO_DEFAULT_VALUE = null; static private $instance = null; protected function __construct() @@ -85,7 +87,7 @@ class Piwik_API_Proxy { $nameVariable = $parameter->getName(); - $defaultValue = ''; + $defaultValue = Piwik_API_Proxy::NO_DEFAULT_VALUE; if($parameter->isDefaultValueAvailable()) { $defaultValue = $parameter->getDefaultValue(); @@ -94,7 +96,7 @@ class Piwik_API_Proxy $aParameters[$nameVariable] = $defaultValue; } $this->api[$class][$name]['parameters'] = $aParameters; - $this->api[$class][$name]['numberOfRequiredParameters'] = $method->getNumberOfRequiredParameters(); + $this->api[$class][$name]['numberOfRequiredParameters'] = $method->getNumberOfParameters(); Piwik::log("- $name is public ".$this->getStrListParameters($class, $name)); } diff --git a/modules/API/Request.php b/modules/API/Request.php index 1127362e97..96211e1bc3 100644 --- a/modules/API/Request.php +++ b/modules/API/Request.php @@ -31,7 +31,11 @@ class Piwik_API_Request $moduleMethod = Piwik_Common::getRequestVar('method', null, null, $this->requestToUse); list($module, $method) = $this->extractModuleAndMethod($moduleMethod); - + + if(!Piwik_PluginsManager::getInstance()->isPluginEnabled($module)) + { + throw new Exception("The plugin '$module' is not enabled."); + } // call the method via the PublicAPI class $api = Piwik_Api_Proxy::getInstance(); $api->registerClass($module); @@ -47,15 +51,19 @@ class Piwik_API_Request $finalParameters = array(); foreach($parameters as $name => $defaultValue) { - if(!empty($defaultValue)) - { - $requestValue = Piwik_Common::getRequestVar($name, $defaultValue, null, $this->requestToUse); - } - else - { - $requestValue = Piwik_Common::getRequestVar($name, null, null, $this->requestToUse); - } - + try{ + // there is a default value specified + if($defaultValue !== Piwik_API_Proxy::NO_DEFAULT_VALUE) + { + $requestValue = Piwik_Common::getRequestVar($name, $defaultValue, null, $this->requestToUse); + } + else + { + $requestValue = Piwik_Common::getRequestVar($name, null, null, $this->requestToUse); + } + } catch(Exception $e) { + Piwik::error("The required variable '$name' is not correct or has not been found in the API Request. <br>\n ".var_export($this->requestToUse, true)); + } $finalParameters[] = $requestValue; } @@ -71,6 +79,7 @@ class Piwik_API_Request $dataTable = $returnedValue; $this->applyDataTableGenericFilters($dataTable); + $dataTable->applyQueuedFilters(); $toReturn = $this->getRenderedDataTable($dataTable); } @@ -94,22 +103,28 @@ class Piwik_API_Request // Generic filters // PatternFileName => Parameter names to match to constructor parameters + /* + * Order to apply the filters: + * 1 - Filter that remove filtered rows + * 2 - Filter that sort the remaining rows + * 3 - Filter that keep only a subset of the results + */ $genericFilters = array( - 'Limit' => array( - 'filter_offset' => 'integer', - 'filter_limit' => 'integer', - ), 'Pattern' => array( 'filter_column' => 'string', 'filter_pattern' => 'string', ), + 'ExcludeLowPopulation' => array( + 'filter_excludelowpop' => 'string', + 'filter_excludelowpop_value' => 'float', + ), 'Sort' => array( 'filter_sort_column' => 'string', 'filter_sort_order' => 'string', ), - 'ExcludeLowPopulation' => array( - 'filter_excludelowpop' => 'string', - 'filter_excludelowpop_value' => 'float', + 'Limit' => array( + 'filter_offset' => 'integer', + 'filter_limit' => 'integer', ), ); diff --git a/modules/Archive.php b/modules/Archive.php index ce883e1da1..b4dfcd737c 100644 --- a/modules/Archive.php +++ b/modules/Archive.php @@ -206,7 +206,7 @@ class Piwik_Archive } - public function loadSubDataTables($name, Piwik_DataTable $dataTableToLoad) + public function loadSubDataTables($name, Piwik_DataTable $dataTableToLoad, $addDetailSubtableId = false) { // we have to recursively load all the subtables associated to this table's rows // and update the subtableID so that it matches the newly instanciated table @@ -220,16 +220,24 @@ class Piwik_Archive $this->loadSubDataTables($name, $subDataTableLoaded); + // we edit the subtable ID so that it matches the newly table created in memory + // NB: + // we dont do that in the case we are displaying the table expanded. + // in this case we wan't the user to see the REAL dataId in the database + if($addDetailSubtableId) + { + $row->addDetail('databaseSubtableId', $row->getIdSubDataTable()); + } $row->setSubtable( $subDataTableLoaded ); } } } - public function getDataTableExpanded($name) + public function getDataTableExpanded($name, $idSubTable = null) { $this->preFetchBlob($name); - $dataTableToLoad = $this->getDataTable($name); - $this->loadSubDataTables($name, $dataTableToLoad); + $dataTableToLoad = $this->getDataTable($name, $idSubTable); + $this->loadSubDataTables($name, $dataTableToLoad, $addDetailSubtableId = true); return $dataTableToLoad; } @@ -258,6 +266,26 @@ class Piwik_Archive return $table; } + public function getDataTableFromNumeric( $fields ) + { + require_once "DataTable/Simple.php"; + if(!is_array($fields)) + { + $fields = array($fields); + } + + $values = array(); + foreach($fields as $field) + { + $values[$field] = $this->getNumeric($field); + } + + $table = new Piwik_DataTable_Simple; + $table->loadFromArray($values); + + return $table; + } + public function getNumeric( $name ) { diff --git a/modules/ArchiveProcessing.php b/modules/ArchiveProcessing.php index fa7d122561..46b8628870 100644 --- a/modules/ArchiveProcessing.php +++ b/modules/ArchiveProcessing.php @@ -188,7 +188,7 @@ abstract class Piwik_ArchiveProcessing protected function insertRecord($record) { // table to use to save the data - if(is_numeric($record->value)) + if(Piwik::isNumeric($record->value)) { $table = $this->tableArchiveNumeric; } diff --git a/modules/ArchiveProcessing/Period.php b/modules/ArchiveProcessing/Period.php index 7a7a6e6519..f08726daba 100644 --- a/modules/ArchiveProcessing/Period.php +++ b/modules/ArchiveProcessing/Period.php @@ -104,11 +104,10 @@ class Piwik_ArchiveProcessing_Period extends Piwik_ArchiveProcessing $archive->loadSubDataTables($name, $datatableToSum); -// echo $datatableToSum; $table->addDataTable($datatableToSum); + $archive->freeBlob($name); } -// echo $table; return $table; } diff --git a/modules/DataFiles/SearchEngines.php b/modules/DataFiles/SearchEngines.php index 94e61df515..f88a8ad7d3 100644 --- a/modules/DataFiles/SearchEngines.php +++ b/modules/DataFiles/SearchEngines.php @@ -51,6 +51,7 @@ if(!isset($GLOBALS['Piwik_SearchEngines'] )) "all.by" => array("All.by", "query"), // Altavista + "www.altavista.com" => array("AltaVista", "q"), "listings.altavista.com" => array("AltaVista", "q"), "www.altavista.de" => array("AltaVista", "q"), "altavista.fr" => array("AltaVista", "q"), @@ -67,18 +68,17 @@ if(!isset($GLOBALS['Piwik_SearchEngines'] )) "us.altavista.com" => array("AltaVista", "q"), "nl.altavista.com" => array("Altavista", "q"), "ch.altavista.com" => array("AltaVista", "q"), - "www.altavista.com" => array("AltaVista", "q"), // APOLLO7 "www.apollo7.de" => array("Apollo7", "query"), "apollo7.de" => array("Apollo7", "query"), // AOL + "aolsearch.aol.com" => array("AOL", "query"), "www.aolrecherche.aol.fr" => array("AOL", "q"), "www.aolrecherches.aol.fr" => array("AOL", "query"), "www.aolimages.aol.fr" => array("AOL", "query"), "www.recherche.aol.fr" => array("AOL", "q"), - "aolsearch.aol.com" => array("AOL", "query"), "aolsearcht.aol.com" => array("AOL", "query"), "find.web.aol.com" => array("AOL", "query"), "recherche.aol.ca" => array("AOL", "query"), @@ -101,6 +101,7 @@ if(!isset($GLOBALS['Piwik_SearchEngines'] )) "arianna.libero.it" => array("Arianna", "query"), // Ask + "www.ask.com" => array("Ask", "ask"), "web.ask.com" => array("Ask", "ask"), "www.ask.co.uk" => array("Ask", "q"), "uk.ask.com" => array("Ask", "q"), @@ -110,7 +111,6 @@ if(!isset($GLOBALS['Piwik_SearchEngines'] )) "it.ask.com" => array("Ask", "q"), "nl.ask.com" => array("Ask", "q"), "ask.jp" => array("Ask", "q"), - "www.ask.com" => array("Ask", "ask"), // Atlas "search.atlas.cz" => array("Atlas", "q", "windows-1250"), @@ -195,10 +195,10 @@ if(!isset($GLOBALS['Piwik_SearchEngines'] )) "fr.dir.com" => array("dir.com", "req"), // dmoz + "dmoz.org" => array("dmoz", "search"), "editors.dmoz.org" => array("dmoz", "search"), "search.dmoz.org" => array("dmoz", "search"), "www.dmoz.org" => array("dmoz", "search"), - "dmoz.org" => array("dmoz", "search"), // Dogpile "search.dogpile.com" => array("Dogpile", "q"), @@ -241,8 +241,8 @@ if(!isset($GLOBALS['Piwik_SearchEngines'] )) "www.feedster.com" => array("Feedster", ""), // Francite - "antisearch.francite.com" => array("Francite", "KEYWORDS"), "recherche.francite.com" => array("Francite", "name"), + "antisearch.francite.com" => array("Francite", "KEYWORDS"), // Fireball "suche.fireball.de" => array("Fireball", "query"), @@ -266,8 +266,8 @@ if(!isset($GLOBALS['Piwik_SearchEngines'] )) "suche.freenet.de" => array("Freenet", "query"), //Froogle - "froogle.google.de" => array("Google (Froogle)", "q"), "froogle.google.com" => array("Google (Froogle)", "q"), + "froogle.google.de" => array("Google (Froogle)", "q"), "froogle.google.co.uk" => array("Google (Froogle)", "q"), //GAIS @@ -315,6 +315,7 @@ if(!isset($GLOBALS['Piwik_SearchEngines'] )) "search.sweetim.com" => array("Google", "q"), // Google + "www.google.com" => array("Google", "q"), "gogole.fr" => array("Google", "q"), "www.gogole.fr" => array("Google", "q"), "wwwgoogle.fr" => array("Google", "q"), @@ -468,9 +469,9 @@ if(!isset($GLOBALS['Piwik_SearchEngines'] )) "www.google.co.za" => array("Google", "q"), "www.google.co.ma" => array("Google", "q"), "www.goggle.com" => array("Google", "q"), - "www.google.com" => array("Google", "q"), //Google Blogsearch + "blogsearch.google.com" => array("Google Blogsearch", "q"), "blogsearch.google.de" => array("Google Blogsearch", "q"), "blogsearch.google.fr" => array("Google Blogsearch", "q"), "blogsearch.google.co.uk" => array("Google Blogsearch", "q"), @@ -483,7 +484,6 @@ if(!isset($GLOBALS['Piwik_SearchEngines'] )) "blogsearch.google.at" => array("Google Blogsearch", "q"), "blogsearch.google.ch" => array("Google Blogsearch", "q"), "blogsearch.google.pl" => array("Google Blogsearch", "q"), - "blogsearch.google.com" => array("Google Blogsearch", "q"), // Google translation @@ -493,6 +493,7 @@ if(!isset($GLOBALS['Piwik_SearchEngines'] )) "directory.google.com" => array("Google Directory", " "), // Google Images + "images.google.com" => array("Google Images", "q"), "images.google.fr" => array("Google Images", "q"), "images.google.be" => array("Google Images", "q"), "images.google.ca" => array("Google Images", "q"), @@ -545,9 +546,9 @@ if(!isset($GLOBALS['Piwik_SearchEngines'] )) "images.google.ru" => array("Google Images", "q"), "images.google.se" => array("Google Images", "q"), "images.google.sk" => array("Google Images", "q"), - "images.google.com" => array("Google Images", "q"), // Google News + "news.google.com" => array("Google News", "q"), "news.google.se" => array("Google News", "q"), "news.google.com" => array("Google News", "q"), "news.google.es" => array("Google News", "q"), @@ -574,7 +575,6 @@ if(!isset($GLOBALS['Piwik_SearchEngines'] )) "news.google.com.ly" => array("Google News", "q"), "news.google.it" => array("Google News", "q"), "news.google.sm" => array("Google News", "q"), - "news.google.com" => array("Google News", "q"), // Goyellow.de "www.goyellow.de" => array("GoYellow.de", "MDN"), @@ -704,6 +704,7 @@ if(!isset($GLOBALS['Piwik_SearchEngines'] )) "www.mozbot.com" => array("mozbot", "q"), // MSN + "search.msn.com" => array("MSN", "q"), "beta.search.msn.fr" => array("MSN", "q"), "search.msn.fr" => array("MSN", "q"), "search.msn.es" => array("MSN", "q"), @@ -736,7 +737,6 @@ if(!isset($GLOBALS['Piwik_SearchEngines'] )) "search.ninemsn.com.au" => array("MSN", "q"), "search.msn.dk" => array("MSN", "q"), "search.arabia.msn.com" => array("MSN", "q"), - "search.msn.com" => array("MSN", "q"), "search.prodigy.msn.com" => array("MSN", "q"), // El Mundo @@ -890,10 +890,10 @@ if(!isset($GLOBALS['Piwik_SearchEngines'] )) "search.supereva.com" => array("Supereva", "q"), // Sympatico + "search.sympatico.msn.ca" => array("Sympatico", "q"), "search.sli.sympatico.ca" => array("Sympatico", "q"), "search.fr.sympatico.msn.ca" => array("Sympatico", "q"), "sea.search.fr.sympatico.msn.ca"=> array("Sympatico", "q"), - "search.sympatico.msn.ca" => array("Sympatico", "q"), // Suchmaschine.com "www.suchmaschine.com" => array("Suchmaschine.com", "suchstr"), @@ -931,11 +931,11 @@ if(!isset($GLOBALS['Piwik_SearchEngines'] )) "search.virgilio.it" => array("Virgilio", "qs"), // Voila + "search.voila.com" => array("Voila", "kw"), "search.ke.voila.fr" => array("Voila", "rdata"), "moteur.voila.fr" => array("Voila", "kw"), "search.voila.fr" => array("Voila", "kw"), "beta.voila.fr" => array("Voila", "kw"), - "search.voila.com" => array("Voila", "kw"), // Volny "web.volny.cz" => array("Volny", "search", "windows-1250"), @@ -955,6 +955,7 @@ if(!isset($GLOBALS['Piwik_SearchEngines'] )) "www.x-recherche.com" => array("X-Recherche", "mots"), // Yahoo + "search.yahoo.com" => array("Yahoo!", "p"), "ink.yahoo.com" => array("Yahoo!", "p"), "ink.yahoo.fr" => array("Yahoo!", "p"), "fr.ink.yahoo.com" => array("Yahoo!", "p"), @@ -977,7 +978,6 @@ if(!isset($GLOBALS['Piwik_SearchEngines'] )) "cade.search.yahoo.com" => array("Yahoo!", "p"), "tw.search.yahoo.com" => array("Yahoo!", "p"), "www.yahoo.com.cn" => array("Yahoo!", "p"), - "search.yahoo.com" => array("Yahoo!", "p"), "de.dir.yahoo.com" => array("Yahoo! Webverzeichnis", ""), "cf.dir.yahoo.com" => array("Yahoo! Directory", ""), @@ -1036,5 +1036,15 @@ if(!isset($GLOBALS['Piwik_SearchEngines'] )) // Zoznam "www.zoznam.sk" => array("Zoznam", "s"), ); + + $GLOBALS['Piwik_SearchEngines_NameToUrl'] = array(); + foreach($GLOBALS['Piwik_SearchEngines'] as $url => $info) + { + if(!isset($GLOBALS['Piwik_SearchEngines_NameToUrl'][$info[0]])) + { + $GLOBALS['Piwik_SearchEngines_NameToUrl'][$info[0]] = $url; + } + } + } ?> diff --git a/modules/DataTable.php b/modules/DataTable.php index e92451ad91..c0cb2c312c 100644 --- a/modules/DataTable.php +++ b/modules/DataTable.php @@ -120,6 +120,7 @@ class Piwik_DataTable protected $currentId; protected $depthLevel = 0; protected $indexNotUpToDate = false; + protected $queuedFilters = array(); const MAXIMUM_DEPTH_LEVEL_ALLOWED = 20; @@ -134,6 +135,31 @@ class Piwik_DataTable usort(&$this->rows, $functionCallback); } + public function queueFilter( $className, $parameters = array() ) + { + if(!is_array($parameters)) + { + $parameters = array($parameters); + } + $this->queuedFilters[] = array('className' => $className, 'parameters' => $parameters); + } + public function applyQueuedFilters() + { + foreach($this->queuedFilters as $filter) + { + // make a reflection object + $reflectionObj = new ReflectionClass($filter['className']); + + // the first parameter of a filter is the DataTable + // we add the current datatable as the parameter + $filter['parameters'] = array_merge(array($this), $filter['parameters']); + + // use Reflection to create a new instance, using the $args + $filter = $reflectionObj->newInstanceArgs($filter['parameters']); + } + $this->queuedFilters = array(); + } + public function rebuildIndex() { foreach($this->getRows() as $id => $row) diff --git a/modules/DataTable/Filter.php b/modules/DataTable/Filter.php index cd71b8378b..d3a09464d2 100644 --- a/modules/DataTable/Filter.php +++ b/modules/DataTable/Filter.php @@ -23,4 +23,6 @@ require_once "DataTable/Filter/Empty.php"; require_once "DataTable/Filter/ColumnCallback.php"; require_once "DataTable/Filter/ColumnCallbackReplace.php"; require_once "DataTable/Filter/ColumnCallbackAddDetail.php"; +require_once "DataTable/Filter/DetailCallbackAddDetail.php"; require_once "DataTable/Filter/ExcludeLowPopulation.php"; +require_once "DataTable/Filter/ReplaceColumnNames.php"; diff --git a/modules/DataTable/Filter/ColumnCallbackAddDetail.php b/modules/DataTable/Filter/ColumnCallbackAddDetail.php index 2e367d635b..4668303f1f 100644 --- a/modules/DataTable/Filter/ColumnCallbackAddDetail.php +++ b/modules/DataTable/Filter/ColumnCallbackAddDetail.php @@ -9,15 +9,16 @@ */ class Piwik_DataTable_Filter_ColumnCallbackAddDetail extends Piwik_DataTable_Filter { - private $columnToFilter; + private $columnToRead; private $functionToApply; + private $detailToAdd; - public function __construct( $table, $columnToRead, $columnToAdd, $functionToApply ) + public function __construct( $table, $columnToRead, $detailToAdd, $functionToApply ) { parent::__construct($table); $this->functionToApply = $functionToApply; $this->columnToRead = $columnToRead; - $this->columnToAdd = $columnToAdd; + $this->detailToAdd = $detailToAdd; $this->filter(); } @@ -27,7 +28,7 @@ class Piwik_DataTable_Filter_ColumnCallbackAddDetail extends Piwik_DataTable_Fil { $oldValue = $row->getColumn($this->columnToRead); $newValue = call_user_func( $this->functionToApply, $oldValue); - $row->addDetail($this->columnToAdd, $newValue); + $row->addDetail($this->detailToAdd, $newValue); } } } diff --git a/modules/DataTable/Filter/DetailCallbackAddDetail.php b/modules/DataTable/Filter/DetailCallbackAddDetail.php new file mode 100644 index 0000000000..67a53d0983 --- /dev/null +++ b/modules/DataTable/Filter/DetailCallbackAddDetail.php @@ -0,0 +1,36 @@ +<?php + +/** + * Add a new detail to the table based on the value resulting + * from a callback function with the parameter being another detail's value + * + * For example for the searchEngine we have a "details" information that gives + * the URL of the search engine. We use this URL to add a new "details" that gives + * the path of the logo for this search engine URL. + */ +class Piwik_DataTable_Filter_DetailCallbackAddDetail extends Piwik_DataTable_Filter +{ + private $detailToRead; + private $functionToApply; + private $detailToAdd; + + public function __construct( $table, $detailToRead, $detailToAdd, $functionToApply ) + { + parent::__construct($table); + $this->functionToApply = $functionToApply; + $this->detailToRead = $detailToRead; + $this->detailToAdd = $detailToAdd; + $this->filter(); + } + + protected function filter() + { + foreach($this->table->getRows() as $key => $row) + { + $oldValue = $row->getDetail($this->detailToRead); + $newValue = call_user_func( $this->functionToApply, $oldValue); + $row->addDetail($this->detailToAdd, $newValue); + } + } +} + diff --git a/modules/DataTable/Filter/ExcludeLowPopulation.php b/modules/DataTable/Filter/ExcludeLowPopulation.php index bd5e681521..d7630ea1ed 100644 --- a/modules/DataTable/Filter/ExcludeLowPopulation.php +++ b/modules/DataTable/Filter/ExcludeLowPopulation.php @@ -13,21 +13,18 @@ class Piwik_DataTable_Filter_ExcludeLowPopulation extends Piwik_DataTable_Filter function filter() { $function = array("Piwik_DataTable_Filter_ExcludeLowPopulation","excludeLowPopulation"); -// echo "AVANT LOW FILTER".$this->table; + $filter = new Piwik_DataTable_Filter_ColumnCallback( $this->table, $this->columnToFilter, $function ); -// echo "APRES LOW FILTER".$this->table; } static public function excludeLowPopulation($value) { - $test = self::$minimumValue; - $return = $value >= $test; - return $return; + return $value >= self::$minimumValue; } } ?> diff --git a/modules/DataTable/Filter/Limit.php b/modules/DataTable/Filter/Limit.php index 3a3adecd7d..6ec00244a5 100644 --- a/modules/DataTable/Filter/Limit.php +++ b/modules/DataTable/Filter/Limit.php @@ -21,7 +21,7 @@ class Piwik_DataTable_Filter_Limit extends Piwik_DataTable_Filter // - from 0 to offset // at this point the array has offset less elements - // - from limit - offset to the end - offset + // - from limit to the end $table->deleteRowsOffset( 0, $this->offset ); $table->deleteRowsOffset( $this->limit ); } diff --git a/modules/DataTable/Filter/ReplaceColumnNames.php b/modules/DataTable/Filter/ReplaceColumnNames.php new file mode 100644 index 0000000000..6309b16938 --- /dev/null +++ b/modules/DataTable/Filter/ReplaceColumnNames.php @@ -0,0 +1,51 @@ +<?php + +/** + * Delete all rows of when a given function returns false for a given column + */ +class Piwik_DataTable_Filter_ReplaceColumnNames extends Piwik_DataTable_Filter +{ + /* + * old column name => new column name + */ + protected $mappingToApply = array( + Piwik_Archive::INDEX_NB_UNIQ_VISITORS => 'nb_unique_visitors', + Piwik_Archive::INDEX_NB_VISITS => 'nb_visits', + Piwik_Archive::INDEX_NB_ACTIONS => 'nb_actions', + Piwik_Archive::INDEX_MAX_ACTIONS => 'max_actions', + Piwik_Archive::INDEX_SUM_VISIT_LENGTH => 'sum_visit_length', + Piwik_Archive::INDEX_BOUNCE_COUNT => 'bounce_count', + ); + + public function __construct( $table, $mappingToApply = null ) + { + parent::__construct($table); + if(!is_null($mappingToApply)) + { + $this->mappingToApply = $mappingToApply; + } + + $this->filter(); + } + + protected function filter() + { + foreach($this->table->getRows() as $key => $row) + { + $columns = $row->getColumns(); + + foreach($this->mappingToApply as $oldName => $newName) + { + // if the old column is there + if(isset($columns[$oldName])) + { + $columns[$newName] = $columns[$oldName]; + unset($columns[$oldName]); + } + } + + $row->setColumns($columns); + } + } +} + diff --git a/modules/DataTable/Filter/Sort.php b/modules/DataTable/Filter/Sort.php index 7f2e0c595b..a5c2463505 100644 --- a/modules/DataTable/Filter/Sort.php +++ b/modules/DataTable/Filter/Sort.php @@ -5,7 +5,7 @@ class Piwik_DataTable_Filter_Sort extends Piwik_DataTable_Filter private $columnToSort; private $order; - public function __construct( $table, $columnToSort, $order ) + public function __construct( $table, $columnToSort, $order = 'desc' ) { parent::__construct($table); $this->columnToSort = $columnToSort; diff --git a/modules/DataTable/Renderer/HTML.php b/modules/DataTable/Renderer/HTML.php index 9ee991006f..0e27fc25bd 100644 --- a/modules/DataTable/Renderer/HTML.php +++ b/modules/DataTable/Renderer/HTML.php @@ -64,6 +64,16 @@ class Piwik_DataTable_Renderer_HTML extends Piwik_DataTable_Renderer } $i++; } + + /* + // to keep the same columns order as the columns labelled with strings + ksort($allColumns); + //replace the label first + if(isset($allColumns['label'])) + { + $allColumns = array_merge(array('label'=>true),$allColumns); + } + */ $allColumns['_details'] = true; $allColumns['_idSubtable'] = true; diff --git a/modules/DataTable/Renderer/PHP.php b/modules/DataTable/Renderer/PHP.php index b4f7c1437d..228275fea0 100644 --- a/modules/DataTable/Renderer/PHP.php +++ b/modules/DataTable/Renderer/PHP.php @@ -1,6 +1,4 @@ <?php - - class Piwik_DataTable_Renderer_PHP extends Piwik_DataTable_Renderer { protected $serialize; @@ -12,7 +10,21 @@ class Piwik_DataTable_Renderer_PHP extends Piwik_DataTable_Renderer function render() { - return $this->renderTable($this->table); + if($this->table instanceof Piwik_DataTable_Simple) + { + $array = $this->renderSimpleTable($this->table); + } + else + { + $array = $this->renderTable($this->table); + } + + if($this->serialize) + { + $array = serialize($array); + } + + return $array; } function renderTable($table) @@ -28,14 +40,17 @@ class Piwik_DataTable_Renderer_PHP extends Piwik_DataTable_Renderer ); $array[] = $newRow; } - - if($this->serialize) - { - serialize($array); - } - else + return $array; + } + + function renderSimpleTable($table) + { + $array = array(); + + foreach($table->getRows() as $row) { - return $array; + $array[$row->getColumn('label')] = $row->getColumn('value'); } + return $array; } }
\ No newline at end of file diff --git a/modules/DataTable/Renderer/XML.php b/modules/DataTable/Renderer/XML.php index 51ea618536..0d6ebef305 100644 --- a/modules/DataTable/Renderer/XML.php +++ b/modules/DataTable/Renderer/XML.php @@ -17,32 +17,33 @@ class Piwik_DataTable_Renderer_XML extends Piwik_DataTable_Renderer { $renderer = new Piwik_DataTable_Renderer_PHP($table, $serialize = false); $array = $renderer->render(); - $xmlStr = $this->array_to_simplexml($array)->asXML(); - $xmlStr = str_replace("<","\n<",$xmlStr); + require_once 'XML/Serializer.php'; + + $options = array( + XML_SERIALIZER_OPTION_INDENT => ' ', + XML_SERIALIZER_OPTION_LINEBREAKS => "\n", + XML_SERIALIZER_OPTION_ROOT_NAME => 'row', + XML_SERIALIZER_OPTION_MODE => XML_SERIALIZER_MODE_SIMPLEXML + ); + + $serializer = new XML_Serializer($options); + + if($table instanceof Piwik_DataTable_Simple) + { + $serializer->setOption(XML_SERIALIZER_OPTION_ROOT_NAME, 'result'); + } + + $result = $serializer->serialize($array); + + $xmlStr = $serializer->getSerializedData(); + + if(get_class($table) == 'Piwik_DataTable') + { + $xmlStr = "<result>\n".$xmlStr."\n</result>"; + $xmlStr = str_replace(">\n", ">\n\t",$xmlStr); + $xmlStr = str_replace("\t</result>", "</result>",$xmlStr); + } return $xmlStr; - } - - // from http://uk3.php.net/simplexml - function array_to_simplexml($array, $name="result" ,&$xml=null ) - { - if(is_null($xml)) - { - $xml = new SimpleXMLElement("<{$name}/>"); - } - - foreach($array as $key => $value) - { - if(is_array($value)) - { - $xml->addChild($key); - $this->array_to_simplexml($value, $name, $xml->$key); - } - else - { - $xml->addChild($key, $value); - } - } - return $xml; } }
\ No newline at end of file diff --git a/modules/DataTable/Row.php b/modules/DataTable/Row.php index f4143904e2..adc3c20665 100644 --- a/modules/DataTable/Row.php +++ b/modules/DataTable/Row.php @@ -80,6 +80,15 @@ class Piwik_DataTable_Row return $this->c[self::DETAILS]; } + public function getDetail( $name ) + { + if(!isset($this->c[self::DETAILS][$name])) + { + return false; + } + return $this->c[self::DETAILS][$name]; + } + /** * @return int|null */ @@ -128,6 +137,11 @@ class Piwik_DataTable_Row $this->c[self::DATATABLE_ASSOCIATED] = $subTable->getId(); } + public function setColumns( $columns ) + { + $this->c[self::COLUMNS] = $columns; + } + public function setColumn($name, $value) { $this->c[self::COLUMNS][$name] = $value; @@ -161,12 +175,6 @@ class Piwik_DataTable_Row */ public function sumRow( $rowToSum ) { -// if( $rowToSum->getIdSubDataTable() != null xor $this->getIdSubDataTable() != null ) -// { -// throw new Exception("Only one of either \$this or \$rowToSum -// has a subTable associated. Not expected."); -// } -// foreach($rowToSum->getColumns() as $name => $value) { if($name != 'label' diff --git a/modules/DataTable/Simple.php b/modules/DataTable/Simple.php new file mode 100644 index 0000000000..4c86186024 --- /dev/null +++ b/modules/DataTable/Simple.php @@ -0,0 +1,20 @@ +<?php +class Piwik_DataTable_Simple extends Piwik_DataTable +{ + public function __construct() + { + parent::__construct(); + } + + function loadFromArray($array) + { + foreach($array as $label => $value) + { + $row = new Piwik_DataTable_Row; + $row->addColumn('label', $label); + $row->addColumn('value', $value); + $this->addRow($row); + } + } +} +?> diff --git a/modules/ErrorHandler.php b/modules/ErrorHandler.php index cb746380e6..7ae1fc9d9d 100755 --- a/modules/ErrorHandler.php +++ b/modules/ErrorHandler.php @@ -6,5 +6,29 @@ function Piwik_ErrorHandler($errno, $errstr, $errfile, $errline) $backtrace = ob_get_contents(); ob_end_clean(); Zend_Registry::get('logger_error')->log($errno, $errstr, $errfile, $errline, $backtrace); + + switch($errno) + { + case E_ERROR: + case E_PARSE: + case E_CORE_ERROR: + case E_CORE_WARNING: + case E_COMPILE_ERROR: + case E_COMPILE_WARNING: + case E_USER_ERROR: + case E_EXCEPTION: + exit; + break; + + case E_WARNING: + case E_NOTICE: + case E_USER_WARNING: + case E_USER_NOTICE: + case E_STRICT: + case E_RECOVERABLE_ERROR: + default: + // do not exit + break; + } } ?> diff --git a/modules/Period.php b/modules/Period.php index b2d0d899fd..53b7a570b7 100644 --- a/modules/Period.php +++ b/modules/Period.php @@ -75,6 +75,10 @@ abstract class Piwik_Period //TODO test getDateEnd public function getDateEnd() { + if(!$this->subperiodsProcessed) + { + $this->generate(); + } if(count($this->subperiods) == 0) { return $this->getDate(); diff --git a/modules/Piwik.php b/modules/Piwik.php index 45c240ec8e..96a33bde85 100755 --- a/modules/Piwik.php +++ b/modules/Piwik.php @@ -17,12 +17,18 @@ class Piwik 'year' =>4, ); - static public function log($message = '', $priority = Zend_Log::NOTICE) + static public function log($message = '') { Zend_Registry::get('logger_message')->log($message); Zend_Registry::get('logger_message')->log( "<br>" . PHP_EOL); } + + static public function error($message = '') + { + trigger_error($message, E_USER_ERROR); + } + //TODO TEST secureDiv static public function secureDiv( $i1, $i2 ) { @@ -68,6 +74,7 @@ class Piwik static public function loadPlugins() { + Piwik_PluginsManager::getInstance()->setLanguageToLoad( Piwik_Translate::getInstance()->getLanguageToLoad() ); Piwik_PluginsManager::getInstance()->setPluginsToLoad( Zend_Registry::get('config')->Plugins->enabled ); } diff --git a/modules/Plugin.php b/modules/Plugin.php index caf7527e33..d9b72adbd0 100644 --- a/modules/Plugin.php +++ b/modules/Plugin.php @@ -10,6 +10,43 @@ abstract class Piwik_Plugin { } + public function registerTranslation( $langCode ) + { + $infos = $this->getInformation(); + if(!isset($infos['translationAvailable'])) + { + $infos['translationAvailable'] = false; + } + $translationAvailable = $infos['translationAvailable']; + + if(!$translationAvailable) + { + return; + } + + $name = $infos['name']; + $path = PIWIK_INCLUDE_PATH . "/plugins/" . $name ."/lang/%s.php"; + + $defaultLangPath = sprintf($path, $langCode); + $defaultEnglishLangPath = sprintf($path, 'en'); + + $translations = array(); + if(is_readable($defaultLangPath)) + { + require $defaultLangPath; + } + elseif(is_readable($defaultEnglishLangPath)) + { + require $defaultEnglishLangPath; + } + else + { + throw new Exception("The language file couldn't be find for this plugin '$name'."); + } + + Piwik_Translate::getInstance()->addTranslationArray($translations); + } + /** * Returns the plugin details */ diff --git a/modules/PluginsManager.php b/modules/PluginsManager.php index 295597e1cc..bf7caeca8f 100644 --- a/modules/PluginsManager.php +++ b/modules/PluginsManager.php @@ -33,6 +33,7 @@ class Piwik_PluginsManager protected $pluginsToLoad = array(); protected $installPlugins = false; protected $doLoadPlugins = true; + protected $languageToLoad = null; static private $instance = null; @@ -54,6 +55,11 @@ class Piwik_PluginsManager $this->dispatcher = Event_Dispatcher::getInstance(); } + public function isPluginEnabled($name) + { + return in_array( $name, $this->pluginsToLoad->toArray()); + } + public function setPluginsToLoad( $array ) { $this->pluginsToLoad = $array; @@ -124,11 +130,19 @@ class Piwik_PluginsManager if($this->doLoadPlugins) { + + $newPlugin->registerTranslation( $this->languageToLoad ); $this->addPluginObservers( $newPlugin ); + } } } + public function setLanguageToLoad( $code ) + { + $this->languageToLoad = $code; + } + /** * For the given plugin, add all the observers of this plugin. */ diff --git a/modules/Translate.php b/modules/Translate.php new file mode 100644 index 0000000000..3c8e7706dd --- /dev/null +++ b/modules/Translate.php @@ -0,0 +1,52 @@ +<?php +class Piwik_Translate +{ + static private $instance = null; + + static public function getInstance() + { + if (self::$instance == null) + { + $c = __CLASS__; + self::$instance = new $c(); + } + return self::$instance; + } + + private function __construct() + { + $GLOBALS['Piwik_translations'] = array(); + + $language = $this->getLanguageToLoad(); + + $translations = array(); + require_once PIWIK_INCLUDE_PATH . "/lang/" . $language .".php"; + + $this->addTranslationArray($translations); + } + + public function addTranslationArray($translation) + { + //TODO check that no string overlap? + $GLOBALS['Piwik_translations'] = array_merge($GLOBALS['Piwik_translations'], $translation); + } + + public function getLanguageToLoad() + { + $language = Zend_Registry::get('config')->Language->current; + + //TODO checker that it is safe + + return $language; + } +} + +function Piwik_Translate($index) +{ + if(isset($GLOBALS['Piwik_translations'][$index])) + { + return $GLOBALS['Piwik_translations'][$index]; + } + throw new Exception("Translation string '$index' not available."); +} +?> diff --git a/plugins/Actions.php b/plugins/Actions.php index 74bd8ceeea..48c55d7d9d 100644 --- a/plugins/Actions.php +++ b/plugins/Actions.php @@ -275,23 +275,28 @@ class Piwik_Actions extends Piwik_Plugin return $rowsProcessed; } + static protected $nameToIdMapping = array( + 'nb_visits' => 1, + 'nb_hits' => 2, + 'entry_nb_unique_visitor' => 3, + 'entry_nb_visits' => 4, + 'entry_nb_actions' => 5, + 'entry_sum_visit_length' => 6, + 'entry_bounce_count' => 7, + 'exit_nb_unique_visitor' => 8, + 'exit_nb_visits' => 9, + 'exit_bounce_count' => 10, + 'sum_time_spent' => 11, + + ); + static public function getColumnsMap() + { + return array_flip(self::$nameToIdMapping); + } + protected function getIdColumn( $name ) { - $map = array( - 'nb_visits' => 0, - 'nb_hits' => 1, - 'entry_nb_unique_visitor' => 2, - 'entry_nb_visits' => 3, - 'entry_nb_actions' => 4, - 'entry_sum_visit_length' => 5, - 'entry_bounce_count' => 6, - 'exit_nb_unique_visitor' => 7, - 'exit_nb_visits' => 8, - 'exit_bounce_count' => 9, - 'sum_time_spent' => 10, - ); - - return $map[$name]; + return self::$nameToIdMapping[$name]; } } diff --git a/plugins/Actions/API.php b/plugins/Actions/API.php index 1eac19b8f7..6ef2bd7c95 100644 --- a/plugins/Actions/API.php +++ b/plugins/Actions/API.php @@ -2,6 +2,7 @@ require_once "DataFiles/Browsers.php"; require_once "DataFiles/OS.php"; +require_once "Actions.php"; class Piwik_Actions_API extends Piwik_Apiable { @@ -21,11 +22,44 @@ class Piwik_Actions_API extends Piwik_Apiable return self::$instance; } - public function getActions( $idSite, $period, $date ) + protected function getDataTable($name, $idSite, $period, $date, $expanded, $idSubtable ) { Piwik::checkUserHasViewAccess( $idSite ); $archive = Piwik_Archive::build($idSite, $date, $period ); - $dataTable = $archive->getDataTableExpanded('Actions_actions'); + + if($idSubtable === false) + { + $idSubtable = null; + } + + if($expanded) + { + $dataTable = $archive->getDataTableExpanded($name, $idSubtable); + } + else + { + $dataTable = $archive->getDataTable($name, $idSubtable); + } + + $dataTable->queueFilter( 'Piwik_DataTable_Filter_ReplaceColumnNames', + array(Piwik_Actions::getColumnsMap()) + ); return $dataTable; } -}
\ No newline at end of file + + public function getActions( $idSite, $period, $date, $expanded = false, $idSubtable = false ) + { + return $this->getDataTable('Actions_actions', $idSite, $period, $date, $expanded, $idSubtable ); + } + + public function getDownloads( $idSite, $period, $date, $expanded = false, $idSubtable = false ) + { + return $this->getDataTable('Actions_downloads', $idSite, $period, $date, $expanded, $idSubtable ); + } + + public function getOutlinks( $idSite, $period, $date, $expanded = false, $idSubtable = false ) + { + return $this->getDataTable('Actions_outlink', $idSite, $period, $date, $expanded, $idSubtable ); + } +} + diff --git a/plugins/ExamplePlugin.php b/plugins/ExamplePlugin.php index 4358f674b9..101a174e06 100644 --- a/plugins/ExamplePlugin.php +++ b/plugins/ExamplePlugin.php @@ -1,6 +1,6 @@ <?php -class Piwik_Example extends Piwik_Plugin +class Piwik_ExamplePlugin extends Piwik_Plugin { public function __construct() { @@ -10,11 +10,13 @@ class Piwik_Example extends Piwik_Plugin public function getInformation() { $info = array( - 'name' => 'Name', + // name must be the className prefix! + 'name' => 'ExamplePlugin', 'description' => 'Description', 'author' => 'Piwik', 'homepage' => 'http://piwik.org/', 'version' => '0.1', + 'translationAvailable' => false, ); return $info; @@ -40,4 +42,5 @@ class Piwik_Example extends Piwik_Plugin { $objectPassed = $notification->getNotificationObject(); } -}
\ No newline at end of file +} + diff --git a/plugins/ExamplePlugin/API.php b/plugins/ExamplePlugin/API.php new file mode 100644 index 0000000000..06d00e30d3 --- /dev/null +++ b/plugins/ExamplePlugin/API.php @@ -0,0 +1,32 @@ +<?php + +class Piwik_ExamplePlugin_API extends Piwik_Apiable +{ + static private $instance = null; + protected function __construct() + { + parent::__construct(); + } + + static public function getInstance() + { + if (self::$instance == null) + { + $c = __CLASS__; + self::$instance = new $c(); + } + return self::$instance; + } + + public function getAnswerToLife() + { + return 42; + } + + public function getMoreInformationAnswerToLife() + { + return "Check http://en.wikipedia.org/wiki/The_Answer_to_Life,_the_Universe,_and_Everything"; + } + +} + diff --git a/plugins/Provider/API.php b/plugins/Provider/API.php new file mode 100644 index 0000000000..811805dc60 --- /dev/null +++ b/plugins/Provider/API.php @@ -0,0 +1,67 @@ +<?php + +require_once "DataFiles/Browsers.php"; +require_once "DataFiles/OS.php"; +require_once "Actions.php"; + +class Piwik_Provider_API extends Piwik_Apiable +{ + static private $instance = null; + protected function __construct() + { + parent::__construct(); + } + + static public function getInstance() + { + if (self::$instance == null) + { + $c = __CLASS__; + self::$instance = new $c(); + } + return self::$instance; + } + + public function getProvider( $idSite, $period, $date ) + { + Piwik::checkUserHasViewAccess( $idSite ); + $archive = Piwik_Archive::build($idSite, $date, $period ); + $dataTable = $archive->getDataTable('Provider_hostnameExt'); + $dataTable->queueFilter('Piwik_DataTable_Filter_ColumnCallbackAddDetail', array('label', 'url', 'Piwik_getHostnameUrl')); + $dataTable->queueFilter('Piwik_DataTable_Filter_ColumnCallbackReplace', array('label', 'Piwik_getHostnameName')); + $dataTable->queueFilter('Piwik_DataTable_Filter_ReplaceColumnNames'); + return $dataTable; + } +} + + +function Piwik_getHostnameName($in) +{ + if(empty($in)) + { + return "Unknown"; + } + elseif(strtolower($in) === 'ip') + { + return "IP"; + } + else + { + return ucfirst(substr($in, 0, strpos($in, '.'))); + } + +} + + +function Piwik_getHostnameUrl($in) +{ + if(empty($in) + || strtolower($in) === 'ip') + { + return "http://piwik.org/"; + } + else + { + return "http://www.".$in."/"; + } +} diff --git a/plugins/Referers.php b/plugins/Referers.php index ad918f923a..9bc242b92f 100644 --- a/plugins/Referers.php +++ b/plugins/Referers.php @@ -21,6 +21,7 @@ class Piwik_Referers extends Piwik_Plugin 'author' => 'Piwik', 'homepage' => 'http://piwik.org/', 'version' => '0.1', + 'translationAvailable' => true, ); return $info; diff --git a/plugins/Referers/API.php b/plugins/Referers/API.php new file mode 100644 index 0000000000..3047972707 --- /dev/null +++ b/plugins/Referers/API.php @@ -0,0 +1,160 @@ +<?php + +require_once "DataFiles/Browsers.php"; +require_once "DataFiles/OS.php"; +require_once "Actions.php"; + +class Piwik_Referers_API extends Piwik_Apiable +{ + static private $instance = null; + protected function __construct() + { + parent::__construct(); + } + + static public function getInstance() + { + if (self::$instance == null) + { + $c = __CLASS__; + self::$instance = new $c(); + } + return self::$instance; + } + + function getDataTable($name, $idSite, $period, $date, $idSubtable = null) + { + Piwik::checkUserHasViewAccess( $idSite ); + $archive = Piwik_Archive::build($idSite, $date, $period ); + + $dataTable = $archive->getDataTable($name, $idSubtable); + $dataTable->queueFilter('Piwik_DataTable_Filter_ReplaceColumnNames'); + return $dataTable; + } + function getRefererType($idSite, $period, $date) + { + $dataTable = $this->getDataTable('Referers_type',$idSite, $period, $date); + $dataTable->queueFilter('Piwik_DataTable_Filter_ColumnCallbackReplace', array('label', 'Piwik_getRefererTypeLabel')); + return $dataTable; + } + + function getKeywords($idSite, $period, $date) + { + $dataTable = $this->getDataTable('Referers_searchEngineByKeyword',$idSite, $period, $date); + return $dataTable; + } + + function getSearchEnginesFromKeywordId($idSite, $period, $date, $idSubtable) + { + require PIWIK_DATAFILES_INCLUDE_PATH . "/SearchEngines.php"; + $dataTable = $this->getDataTable('Referers_searchEngineByKeyword',$idSite, $period, $date, $idSubtable); + $dataTable->queueFilter('Piwik_DataTable_Filter_ColumnCallbackAddDetail', array( 'label', 'url', 'Piwik_getSearchEngineUrlFromName') ); + $dataTable->queueFilter('Piwik_DataTable_Filter_DetailCallbackAddDetail', array( 'url', 'logo', 'Piwik_getSearchEngineLogoFromName') ); + return $dataTable; + } + + function getSearchEngines($idSite, $period, $date) + { + $dataTable = $this->getDataTable('Referers_keywordBySearchEngine',$idSite, $period, $date); + $dataTable->queueFilter('Piwik_DataTable_Filter_ColumnCallbackAddDetail', array( 'label', 'url', 'Piwik_getSearchEngineUrlFromName') ); + $dataTable->queueFilter('Piwik_DataTable_Filter_DetailCallbackAddDetail', array( 'url', 'logo', 'Piwik_getSearchEngineLogoFromName') ); + return $dataTable; + } + + function getKeywordsFromSearchEngineId($idSite, $period, $date, $idSubtable) + {} + + function getCampaigns($idSite, $period, $date) + {} + function getKeywordsFromCampaignId($idSite, $period, $date, $idSubtable) + {} + + function getWebsites($idSite, $period, $date) + {} + function getUrlsFromWebsiteId($idSite, $period, $date, $idSubtable) + {} + + function getPartners($idSite, $period, $date) + {} + function getUrlsFromPartnerId($idSite, $period, $date, $idSubtable) + {} + + + function getNumberOfDistinctSearchEngines($idSite, $period, $date) + {} + function getNumberOfDistinctKeywords($idSite, $period, $date) + {} + function getNumberOfDistinctCampaigns($idSite, $period, $date) + {} + function getNumberOfDistinctWebsites($idSite, $period, $date) + {} + function getNumberOfDistinctWebsitesUrls($idSite, $period, $date) + {} + function getNumberOfDistinctPartners($idSite, $period, $date) + {} + function getNumberOfDistinctPartnersUrls($idSite, $period, $date) + {} +} + +function Piwik_getSearchEngineUrlFromName($name) +{ + if(isset($GLOBALS['Piwik_SearchEngines_NameToUrl'][$name])) + { + $url = 'http://'.$GLOBALS['Piwik_SearchEngines_NameToUrl'][$name]; + } + else + { + $url = 'URL unknown!'; + } + return $url; +} + +function Piwik_getSearchEngineLogoFromName($url) +{ + $path = PIWIK_PLUGINS_PATH . '/Referers/images/searchEngines/%s.png'; + + $beginningUrl = strpos($url,'//')+2; + $normalPath = sprintf($path, substr($url,$beginningUrl)); + + // flags not in the package ! + if(!file_exists($normalPath)) + { + return sprintf($path, 'xx'); + } + return $normalPath; +} + +function Piwik_getRefererTypeLabel($label) +{ + $indexTranslation = ''; + switch($label) + { + case Piwik_Common::REFERER_TYPE_DIRECT_ENTRY: + $indexTranslation = 'Referers_DirectEntry'; + break; + case Piwik_Common::REFERER_TYPE_SEARCH_ENGINE: + $indexTranslation = 'Referers_SearchEngines'; + break; + case Piwik_Common::REFERER_TYPE_WEBSITE: + $indexTranslation = 'Referers_Websites'; + break; + case Piwik_Common::REFERER_TYPE_PARTNER: + $indexTranslation = 'Referers_Partners'; + break; + case Piwik_Common::REFERER_TYPE_NEWSLETTER: + $indexTranslation = 'Referers_Newsletters'; + break; + case Piwik_Common::REFERER_TYPE_CAMPAIGN: + $indexTranslation = 'Referers_Campaigns'; + break; + } + return Piwik_Translate($indexTranslation); +} +//'Referers_keywordBySearchEngine', +//'Referers_searchEngineByKeyword', +//'Referers_type', + +//'Referers_keywordByCampaign', +//'Referers_urlByWebsite', +//'Referers_urlByPartner', + diff --git a/plugins/Referers/lang/en.php b/plugins/Referers/lang/en.php new file mode 100644 index 0000000000..9d6248eaa6 --- /dev/null +++ b/plugins/Referers/lang/en.php @@ -0,0 +1,10 @@ +<?php +$translations = array( + 'Referers_DirectEntry' => 'Direct Entry', + 'Referers_SearchEngines' => 'Search Engines', + 'Referers_Websites' => 'Websites', + 'Referers_Partners' => 'Partners', + 'Referers_Newsletters' => 'Newsletters', + 'Referers_Campaigns' => 'Campaigns', +); +?> diff --git a/plugins/UserCountry.php b/plugins/UserCountry.php index dfab3a1b97..2076a43932 100644 --- a/plugins/UserCountry.php +++ b/plugins/UserCountry.php @@ -15,6 +15,7 @@ class Piwik_UserCountry extends Piwik_Plugin 'author' => 'Piwik', 'homepage' => 'http://piwik.org/', 'version' => '0.1', + 'translationAvailable' => true, ); return $info; diff --git a/plugins/UserCountry/API.php b/plugins/UserCountry/API.php new file mode 100644 index 0000000000..b797f17220 --- /dev/null +++ b/plugins/UserCountry/API.php @@ -0,0 +1,78 @@ +<?php + +require_once "DataFiles/Countries.php"; + +class Piwik_UserCountry_API extends Piwik_Apiable +{ + static private $instance = null; + protected function __construct() + { + parent::__construct(); + } + + static public function getInstance() + { + if (self::$instance == null) + { + $c = __CLASS__; + self::$instance = new $c(); + } + return self::$instance; + } + + public function getCountry( $idSite, $period, $date ) + { + Piwik::checkUserHasViewAccess( $idSite ); + $archive = Piwik_Archive::build($idSite, $date, $period ); + $dataTable = $archive->getDataTable('UserCountry_country'); + $dataTable->queueFilter('Piwik_DataTable_Filter_ColumnCallbackAddDetail', array('label', 'code', create_function('$label', 'return $label;'))); + $dataTable->queueFilter('Piwik_DataTable_Filter_ColumnCallbackAddDetail', array('label', 'flag', 'Piwik_getFlagFromCode')); + $dataTable->queueFilter('Piwik_DataTable_Filter_ColumnCallbackReplace', array('label', 'Piwik_CountryTranslate')); + $dataTable->queueFilter('Piwik_DataTable_Filter_ReplaceColumnNames'); + return $dataTable; + } + public function getContinent( $idSite, $period, $date ) + { + Piwik::checkUserHasViewAccess( $idSite ); + $archive = Piwik_Archive::build($idSite, $date, $period ); + $dataTable = $archive->getDataTable('UserCountry_continent'); + $dataTable->queueFilter('Piwik_DataTable_Filter_ColumnCallbackAddDetail', array('label', 'code', create_function('$label', 'return $label;'))); + $dataTable->queueFilter('Piwik_DataTable_Filter_ColumnCallbackReplace', array('label', 'Piwik_ContinentTranslate')); + $dataTable->queueFilter('Piwik_DataTable_Filter_ReplaceColumnNames'); + return $dataTable; + } + +} + +function Piwik_getFlagFromCode($code) +{ + $path = PIWIK_PLUGINS_PATH . '/UserCountry/flags/%s.png'; + + $normalPath = sprintf($path,$code); + + // flags not in the package ! + if(!file_exists($normalPath)) + { + return sprintf($path, 'xx'); + } + return $normalPath; +} + +function Piwik_ContinentTranslate($label) +{ + if($label == 'unk') + { + return Piwik_Translate('General_Unknown'); + } + + return Piwik_Translate('continent_'. $label); +} +function Piwik_CountryTranslate($label) +{ + if($label == 'xx') + { + return Piwik_Translate('General_Unknown'); + } + + return Piwik_Translate('country_'. $label); +}
\ No newline at end of file diff --git a/plugins/UserCountry/lang/en.php b/plugins/UserCountry/lang/en.php new file mode 100644 index 0000000000..1a5d880d4e --- /dev/null +++ b/plugins/UserCountry/lang/en.php @@ -0,0 +1,263 @@ +<?php +$translations = array ( + + // Countries + 'country_ac' => 'Ascension Islands', + 'country_ad' => 'Andorra', + 'country_ae' => 'United Arab Emirates', + 'country_af' => 'Afghanistan', + 'country_ag' => 'Antigua and Barbuda', + 'country_ai' => 'Anguilla', + 'country_al' => 'Albania', + 'country_am' => 'Armenia', + 'country_an' => 'Netherlands Antilles', + 'country_ao' => 'Angola', + 'country_aq' => 'Antarctica', + 'country_ar' => 'Argentina', + 'country_as' => 'American Samoa', + 'country_at' => 'Austria', + 'country_au' => 'Australia', + 'country_aw' => 'Aruba', + 'country_az' => 'Azerbaijan', + 'country_ba' => 'Bosnia and Herzegovina', + 'country_bb' => 'Barbados', + 'country_bd' => 'Bangladesh', + 'country_be' => 'Belgium', + 'country_bf' => 'Burkina Faso', + 'country_bg' => 'Bulgaria', + 'country_bh' => 'Bahrain', + 'country_bi' => 'Burundi', + 'country_bj' => 'Benin', + 'country_bm' => 'Bermuda', + 'country_bn' => 'Bruneo', + 'country_bo' => 'Bolivia', + 'country_br' => 'Brazil', + 'country_bs' => 'Bahamas', + 'country_bt' => 'Bhutan', + 'country_bv' => 'Bouvet Island', + 'country_bw' => 'Botswana', + 'country_by' => 'Belarus', + 'country_bz' => 'Belize', + 'country_ca' => 'Canada', + 'country_cc' => 'Cocos (Keeling) Islands', + 'country_cd' => 'Congo, The Democratic Republic of the', + 'country_cf' => 'Central African Republic', + 'country_cg' => 'Congo', + 'country_ch' => 'Switzerland', + 'country_ci' => "Cote D'Ivoire", + 'country_ck' => 'Cook Islands', + 'country_cl' => 'Chile', + 'country_cm' => 'Cameroon', + 'country_cn' => 'China', + 'country_co' => 'Colombia', + 'country_cr' => 'Costa Rica', + 'country_cs' => 'Serbia Montenegro', + 'country_cu' => 'Cuba', + 'country_cv' => 'Cape Verde', + 'country_cx' => 'Christmas Island', + 'country_cy' => 'Cyprus', + 'country_cz' => 'Czech Republic', + 'country_de' => 'Germany', + 'country_dj' => 'Djibouti', + 'country_dk' => 'Denmark', + 'country_dm' => 'Dominica', + 'country_do' => 'Dominican Republic', + 'country_dz' => 'Algeria', + 'country_ec' => 'Ecuador', + 'country_ee' => 'Estonia', + 'country_eg' => 'Egypt', + 'country_eh' => 'Western Sahara', + 'country_er' => 'Eritrea', + 'country_es' => 'Spain', + 'country_et' => 'Ethiopia', + 'country_fi' => 'Finland', + 'country_fj' => 'Fiji', + 'country_fk' => 'Falkland Islands (Malvinas)', + 'country_fm' => 'Micronesia, Federated States of', + 'country_fo' => 'Faroe Islands', + 'country_fr' => 'France', + 'country_ga' => 'Gabon', + 'country_gd' => 'Grenada', + 'country_ge' => 'Georgia', + 'country_gf' => 'French Guyana', + 'country_gg' => 'Guernsey', + 'country_gh' => 'Ghana', + 'country_gi' => 'Gibraltar', + 'country_gl' => 'Greenland', + 'country_gm' => 'Gambia', + 'country_gn' => 'Guinea', + 'country_gp' => 'Guadeloupe', + 'country_gq' => 'Equatorial Guinea', + 'country_gr' => 'Greece', + 'country_gs' => 'South Georgia and the South Sandwich Islands', + 'country_gt' => 'Guatemala', + 'country_gu' => 'Guam', + 'country_gw' => 'Guinea-Bissau', + 'country_gy' => 'Guyana', + 'country_hk' => 'Hong Kong', + 'country_hm' => 'Heard Island and McDonald Islands', + 'country_hn' => 'Honduras', + 'country_hr' => 'Croatia', + 'country_ht' => 'Haiti', + 'country_hu' => 'Hungary', + 'country_id' => 'Indonesia', + 'country_ie' => 'Ireland', + 'country_il' => 'Israel', + 'country_im' => 'Man Island', + 'country_in' => 'India', + 'country_io' => 'British Indian Ocean Territory', + 'country_iq' => 'Iraq', + 'country_ir' => 'Iran, Islamic Republic of', + 'country_is' => 'Iceland', + 'country_it' => 'Italy', + 'country_je' => 'Jersey', + 'country_jm' => 'Jamaica', + 'country_jo' => 'Jordan', + 'country_jp' => 'Japan', + 'country_ke' => 'Kenya', + 'country_kg' => 'Kyrgyzstan', + 'country_kh' => 'Cambodia', + 'country_ki' => 'Kiribati', + 'country_km' => 'Comoros', + 'country_kn' => 'Saint Kitts and Nevis', + 'country_kp' => "Korea, Democratic People's Republic of", + 'country_kr' => 'Korea, Republic of', + 'country_kw' => 'Kuwait', + 'country_ky' => 'Cayman Islands', + 'country_kz' => 'Kazakhstan', + 'country_la' => 'Laos', + 'country_lb' => 'Lebanon', + 'country_lc' => 'Saint Lucia', + 'country_li' => 'Liechtenstein', + 'country_lk' => 'Sri Lanka', + 'country_lr' => 'Liberia', + 'country_ls' => 'Lesotho', + 'country_lt' => 'Lithuania', + 'country_lu' => 'Luxembourg', + 'country_lv' => 'Latvia', + 'country_ly' => 'Libya', + 'country_ma' => 'Morocco', + 'country_mc' => 'Monaco', + 'country_md' => 'Moldova, Republic of', + 'country_mg' => 'Madagascar', + 'country_mh' => 'Marshall Islands', + 'country_mk' => 'Macedonia', + 'country_ml' => 'Mali', + 'country_mm' => 'Myanmar', + 'country_mn' => 'Mongolia', + 'country_mo' => 'Macau', + 'country_mp' => 'Northern Mariana Islands', + 'country_mq' => 'Martinique', + 'country_mr' => 'Mauritania', + 'country_ms' => 'Montserrat', + 'country_mt' => 'Malta', + 'country_mu' => 'Mauritius', + 'country_mv' => 'Maldives', + 'country_mw' => 'Malawi', + 'country_mx' => 'Mexico', + 'country_my' => 'Malaysia', + 'country_mz' => 'Mozambique', + 'country_na' => 'Namibia', + 'country_nc' => 'New Caledonia', + 'country_ne' => 'Niger', + 'country_nf' => 'Norfolk Island', + 'country_ng' => 'Nigeria', + 'country_ni' => 'Nicaragua', + 'country_nl' => 'Netherlands', + 'country_no' => 'Norway', + 'country_np' => 'Nepal', + 'country_nr' => 'Nauru', + 'country_nu' => 'Niue', + 'country_nz' => 'New Zealand', + 'country_om' => 'Oman', + 'country_pa' => 'Panama', + 'country_pe' => 'Peru', + 'country_pf' => 'French Polynesia', + 'country_pg' => 'Papua New Guinea', + 'country_ph' => 'Philippines', + 'country_pk' => 'Pakistan', + 'country_pl' => 'Poland', + 'country_pm' => 'Saint Pierre and Miquelon', + 'country_pn' => 'Pitcairn', + 'country_pr' => 'Puerto Rico', + 'country_ps' => 'Palestinian Territory', + 'country_pt' => 'Portugal', + 'country_pw' => 'Palau', + 'country_py' => 'Paraguay', + 'country_qa' => 'Qatar', + 'country_re' => 'Reunion Island', + 'country_ro' => 'Romania', + 'country_ru' => 'Russian Federation', + 'country_rs' => 'Russia', + 'country_rw' => 'Rwanda', + 'country_sa' => 'Saudi Arabia', + 'country_sb' => 'Solomon Islands', + 'country_sc' => 'Seychelles', + 'country_sd' => 'Sudan', + 'country_se' => 'Sweden', + 'country_sg' => 'Singapore', + 'country_sh' => 'Saint Helena', + 'country_si' => 'Slovenia', + 'country_sj' => 'Svalbard', + 'country_sk' => 'Slovakia', + 'country_sl' => 'Sierra Leone', + 'country_sm' => 'San Marino', + 'country_sn' => 'Senegal', + 'country_so' => 'Somalia', + 'country_sr' => 'Suriname', + 'country_st' => 'Sao Tome and Principe', + 'country_su' => 'Old U.S.S.R', + 'country_sv' => 'El Salvador', + 'country_sy' => 'Syrian Arab Republic', + 'country_sz' => 'Swaziland', + 'country_tc' => 'Turks and Caicos Islands', + 'country_td' => 'Chad', + 'country_tf' => 'French Southern Territories', + 'country_tg' => 'Togo', + 'country_th' => 'Thailand', + 'country_tj' => 'Tajikistan', + 'country_tk' => 'Tokelau', + 'country_tm' => 'Turkmenistan', + 'country_tn' => 'Tunisia', + 'country_to' => 'Tonga', + 'country_tp' => 'East Timor', + 'country_tr' => 'Turkey', + 'country_tt' => 'Trinidad and Tobago', + 'country_tv' => 'Tuvalu', + 'country_tw' => 'Taiwan', + 'country_tz' => 'Tanzania, United Republic of', + 'country_ua' => 'Ukraine', + 'country_ug' => 'Uganda', + 'country_uk' => 'United Kingdom', + 'country_gb' => 'Great Britain', + 'country_um' => 'United States Minor Outlying Islands', + 'country_us' => 'United States', + 'country_uy' => 'Uruguay', + 'country_uz' => 'Uzbekistan', + 'country_va' => 'Vatican City', + 'country_vc' => 'Saint Vincent and the Grenadines', + 'country_ve' => 'Venezuela', + 'country_vg' => 'Virgin Islands, British', + 'country_vi' => 'Virgin Islands, U.S.', + 'country_vn' => 'Vietnam', + 'country_vu' => 'Vanuatu', + 'country_wf' => 'Wallis and Futuna', + 'country_ws' => 'Samoa', + 'country_ye' => 'Yemen', + 'country_yt' => 'Mayotte', + 'country_yu' => 'Yugoslavia', + 'country_za' => 'South Africa', + 'country_zm' => 'Zambia', + 'country_zr' => 'Zaire', + 'country_zw' => 'Zimbabwe', + + // Continents + 'continent_eur' => 'Europe', + 'continent_afr' => 'Africa', + 'continent_asi' => 'Asia', + 'continent_ams' => 'South and Central America', + 'continent_amn' => 'North America', + 'continent_oce' => 'Oceania', + +); + diff --git a/plugins/UserSettings/API.php b/plugins/UserSettings/API.php index 45f8e513ef..808cd582ff 100644 --- a/plugins/UserSettings/API.php +++ b/plugins/UserSettings/API.php @@ -26,43 +26,41 @@ class Piwik_UserSettings_API extends Piwik_Apiable Piwik::checkUserHasViewAccess( $idSite ); $archive = Piwik_Archive::build($idSite, $date, $period ); $dataTable = $archive->getDataTable('UserSettings_resolution'); + $dataTable->queueFilter('Piwik_DataTable_Filter_ReplaceColumnNames'); return $dataTable; } - + public function getConfiguration( $idSite, $period, $date ) { Piwik::checkUserHasViewAccess( $idSite ); $archive = Piwik_Archive::build($idSite, $date, $period ); $dataTable = $archive->getDataTable('UserSettings_configuration'); - $filter = new Piwik_DataTable_Filter_ColumnCallbackReplace($dataTable, 'label', 'Piwik_getConfigurationLabel'); - + $dataTable->queueFilter('Piwik_DataTable_Filter_ColumnCallbackReplace', array('label', 'Piwik_getConfigurationLabel')); + $dataTable->queueFilter('Piwik_DataTable_Filter_ReplaceColumnNames'); return $dataTable; } + public function getOS( $idSite, $period, $date ) { Piwik::checkUserHasViewAccess( $idSite ); $archive = Piwik_Archive::build($idSite, $date, $period ); $dataTable = $archive->getDataTable('UserSettings_os'); - - - $filter = new Piwik_DataTable_Filter_ColumnCallbackAddDetail($dataTable, 'label', 'shortLabel', 'Piwik_getOSShortLabel'); - $filter = new Piwik_DataTable_Filter_ColumnCallbackReplace($dataTable, 'label', 'Piwik_getOSLabel'); + $dataTable->queueFilter('Piwik_DataTable_Filter_ReplaceColumnNames'); + $dataTable->queueFilter('Piwik_DataTable_Filter_ColumnCallbackAddDetail', array( 'label', 'shortLabel', 'Piwik_getOSShortLabel') ); + $dataTable->queueFilter('Piwik_DataTable_Filter_ColumnCallbackReplace', array( 'label', 'Piwik_getOSLabel') ); return $dataTable; } - - + public function getBrowser( $idSite, $period, $date ) { Piwik::checkUserHasViewAccess( $idSite ); $archive = Piwik_Archive::build($idSite, $date, $period ); $dataTable = $archive->getDataTable('UserSettings_browser'); - - - $filter = new Piwik_DataTable_Filter_ColumnCallbackAddDetail($dataTable, 'label', 'logo', 'Piwik_getBrowsersLogo'); - $filter = new Piwik_DataTable_Filter_ColumnCallbackAddDetail($dataTable, 'label', 'shortLabel', 'Piwik_getBrowserShortLabel'); - $filter = new Piwik_DataTable_Filter_ColumnCallbackReplace($dataTable, 'label', 'Piwik_getBrowserLabel'); - + $dataTable->queueFilter('Piwik_DataTable_Filter_ReplaceColumnNames'); + $dataTable->queueFilter('Piwik_DataTable_Filter_ColumnCallbackAddDetail', array('label', 'logo', 'Piwik_getBrowsersLogo')); + $dataTable->queueFilter('Piwik_DataTable_Filter_ColumnCallbackAddDetail', array('label', 'shortLabel', 'Piwik_getBrowserShortLabel')); + $dataTable->queueFilter('Piwik_DataTable_Filter_ColumnCallbackReplace', array('label', 'Piwik_getBrowserLabel')); return $dataTable; } @@ -71,11 +69,9 @@ class Piwik_UserSettings_API extends Piwik_Apiable Piwik::checkUserHasViewAccess( $idSite ); $archive = Piwik_Archive::build($idSite, $date, $period ); $dataTable = $archive->getDataTable('UserSettings_browserType'); - - - $filter = new Piwik_DataTable_Filter_ColumnCallbackAddDetail($dataTable, 'label', 'shortLabel', 'ucfirst'); - $filter = new Piwik_DataTable_Filter_ColumnCallbackReplace($dataTable, 'label', 'Piwik_getBrowserTypeLabel'); - + $dataTable->queueFilter('Piwik_DataTable_Filter_ReplaceColumnNames'); + $dataTable->queueFilter('Piwik_DataTable_Filter_ColumnCallbackAddDetail', array('label', 'shortLabel', 'ucfirst')); + $dataTable->queueFilter('Piwik_DataTable_Filter_ColumnCallbackReplace', array('label', 'Piwik_getBrowserTypeLabel')); return $dataTable; } @@ -84,23 +80,20 @@ class Piwik_UserSettings_API extends Piwik_Apiable Piwik::checkUserHasViewAccess( $idSite ); $archive = Piwik_Archive::build($idSite, $date, $period ); $dataTable = $archive->getDataTable('UserSettings_wideScreen'); - - - $filter = new Piwik_DataTable_Filter_ColumnCallbackReplace($dataTable, 'label', 'ucfirst'); - + $dataTable->queueFilter('Piwik_DataTable_Filter_ReplaceColumnNames'); + $dataTable->queueFilter('Piwik_DataTable_Filter_ColumnCallbackReplace', array('label', 'ucfirst')); return $dataTable; } + public function getPlugin( $idSite, $period, $date ) { Piwik::checkUserHasViewAccess( $idSite ); $archive = Piwik_Archive::build($idSite, $date, $period ); $dataTable = $archive->getDataTable('UserSettings_plugin'); - - $filter = new Piwik_DataTable_Filter_ColumnCallbackReplace($dataTable, 'label', 'ucfirst'); - + $dataTable->queueFilter('Piwik_DataTable_Filter_ReplaceColumnNames'); + $dataTable->queueFilter('Piwik_DataTable_Filter_ColumnCallbackReplace', array('label', 'ucfirst')); return $dataTable; - } - + } } function Piwik_getOSLabel($oldLabel) @@ -111,6 +104,8 @@ function Piwik_getOSLabel($oldLabel) } return 'UNK'; } + + function Piwik_getOSShortLabel($oldLabel) { if(isset($GLOBALS['Piwik_Oslist_IdToShortLabel'][$oldLabel])) @@ -119,6 +114,7 @@ function Piwik_getOSShortLabel($oldLabel) } return 'UNK'; } + function Piwik_getBrowserTypeLabel($oldLabel) { if(isset(Piwik_UserSettings::$browserType_display[$oldLabel])) @@ -134,11 +130,18 @@ function Piwik_getConfigurationLabel($str) $values = explode(";", $str); $os = Piwik_getOSLabel($values[0]); - $browser = $GLOBALS['Piwik_BrowserList_IdToLabel'][$values[1]]; + $name = $values[1]; + $browser = 'Unknown'; + if(isset($GLOBALS['Piwik_BrowserList_IdToLabel'][$name])) + { + $browser = $GLOBALS['Piwik_BrowserList_IdToLabel'][$name]; + } + $resolution = $values[2]; return $os . " / " . $browser . " / " . $resolution; } + function Piwik_getBrowserLabel($oldLabel) { $name = Piwik_getBrowserId($oldLabel); @@ -176,4 +179,4 @@ function Piwik_getBrowsersLogo($label) $id = Piwik_getBrowserId($label); return "/plugins/UserSettings/images/browsers/". $id . ".gif"; } -?> + diff --git a/plugins/VisitFrequency/API.php b/plugins/VisitFrequency/API.php new file mode 100644 index 0000000000..b42cb23dfc --- /dev/null +++ b/plugins/VisitFrequency/API.php @@ -0,0 +1,37 @@ +<?php +class Piwik_VisitFrequency_API extends Piwik_Apiable +{ + static private $instance = null; + protected function __construct() + { + parent::__construct(); + } + + static public function getInstance() + { + if (self::$instance == null) + { + $c = __CLASS__; + self::$instance = new $c(); + } + return self::$instance; + } + + public function getSummary( $idSite, $period, $date ) + { + Piwik::checkUserHasViewAccess( $idSite ); + $archive = Piwik_Archive::build($idSite, $date, $period ); + $toFetch = array( + 'nb_visits_returning', + 'nb_actions_returning', + 'max_actions_returning', + 'sum_visit_length_returning', + 'bounce_count_returning', + ); + $dataTable = $archive->getDataTableFromNumeric($toFetch); + + return $dataTable; + } + +} + diff --git a/plugins/VisitTime.php b/plugins/VisitTime.php index 6cc1fbf1c2..4a4171b832 100644 --- a/plugins/VisitTime.php +++ b/plugins/VisitTime.php @@ -37,17 +37,16 @@ class Piwik_VisitTime extends Piwik_Plugin ); return $hooks; } - - + function archiveMonth( $notification ) { $this->archiveProcessing = $notification->getNotificationObject(); - + $dataTableToSum = array( 'VisitTime_localTime', 'VisitTime_serverTime', ); - + $this->archiveProcessing->archiveDataTable($dataTableToSum); } public function archiveDay( $notification ) diff --git a/plugins/VisitTime/API.php b/plugins/VisitTime/API.php new file mode 100644 index 0000000000..951aedf43a --- /dev/null +++ b/plugins/VisitTime/API.php @@ -0,0 +1,48 @@ +<?php + +class Piwik_VisitTime_API extends Piwik_Apiable +{ + static private $instance = null; + protected function __construct() + { + parent::__construct(); + } + + static public function getInstance() + { + if (self::$instance == null) + { + $c = __CLASS__; + self::$instance = new $c(); + } + return self::$instance; + } + + protected function getDataTable($name, $idSite, $period, $date ) + { + Piwik::checkUserHasViewAccess( $idSite ); + + $archive = Piwik_Archive::build($idSite, $date, $period ); + $dataTable = $archive->getDataTable($name); + $dataTable->queueFilter('Piwik_DataTable_Filter_Sort', array('label', 'asc')); + $dataTable->queueFilter('Piwik_DataTable_Filter_ColumnCallbackReplace', array('label', 'Piwik_getTimeLabel')); + $dataTable->queueFilter('Piwik_DataTable_Filter_ReplaceColumnNames'); + + return $dataTable; + } + + public function getVisitInformationPerLocalTime( $idSite, $period, $date ) + { + return $this->getDataTable('VisitTime_localTime', $idSite, $period, $date ); + } + + public function getVisitInformationPerServerTime( $idSite, $period, $date ) + { + return $this->getDataTable('VisitTime_serverTime', $idSite, $period, $date ); + } +} + +function Piwik_getTimeLabel($label) +{ + return $label . "h"; +}
\ No newline at end of file diff --git a/plugins/VisitorInterest.php b/plugins/VisitorInterest.php index aeedd8826a..90093ab12f 100644 --- a/plugins/VisitorInterest.php +++ b/plugins/VisitorInterest.php @@ -126,13 +126,17 @@ class Piwik_VisitorInterest extends Piwik_Plugin { $minGap = $gap[0] * 60; $maxGap = $gap[1] * 60; - $gapName = "'$minGap-$maxGap'"; + + if($minGap == 0 || $minGap == 0.5) + { + $gapName = "'".$minGap."-".$maxGap."'"; + } $select[] = "sum(case when visit_total_time between $minGap and $maxGap then 1 else 0 end) as $gapName "; } else { $minGap = $gap[0] * 60; - $gapName = "'$minGap+'"; + $gapName = "'$minGap'"; $select[] = "sum(case when visit_total_time > $minGap then 1 else 0 end) as $gapName "; } } diff --git a/plugins/VisitorInterest/API.php b/plugins/VisitorInterest/API.php new file mode 100644 index 0000000000..3eee441bfb --- /dev/null +++ b/plugins/VisitorInterest/API.php @@ -0,0 +1,84 @@ +<?php + +class Piwik_VisitorInterest_API extends Piwik_Apiable +{ + static private $instance = null; + protected function __construct() + { + parent::__construct(); + } + + static public function getInstance() + { + if (self::$instance == null) + { + $c = __CLASS__; + self::$instance = new $c(); + } + return self::$instance; + } + + + public function getNumberOfVisitsPerVisitDuration( $idSite, $period, $date ) + { + Piwik::checkUserHasViewAccess( $idSite ); + $archive = Piwik_Archive::build($idSite, $date, $period ); + $dataTable = $archive->getDataTable('VisitorInterest_timeGap'); + $dataTable->queueFilter('Piwik_DataTable_Filter_ReplaceColumnNames'); + $dataTable->queueFilter('Piwik_DataTable_Filter_ColumnCallbackReplace', array('label', 'Piwik_getDurationLabel')); + + return $dataTable; + } + + + public function getNumberOfVisitsPerPage( $idSite, $period, $date ) + { + Piwik::checkUserHasViewAccess( $idSite ); + $archive = Piwik_Archive::build($idSite, $date, $period ); + $dataTable = $archive->getDataTable('VisitorInterest_pageGap'); + $dataTable->queueFilter('Piwik_DataTable_Filter_ReplaceColumnNames'); + $dataTable->queueFilter('Piwik_DataTable_Filter_ColumnCallbackReplace', array('label', 'Piwik_getPageGapLabel')); + + return $dataTable; + } +} + +function Piwik_getDurationLabel($label) +{ + if(($pos = strpos($label,'-')) !== false) + { + $min = substr($label, 0, $pos); + $max = substr($label, $pos+1); + + if($min == 0 || $min == 30) + { + return $min.'-'.$max.'s'; + } + else + { + $min = $min / 60; + $max = $max / 60; + return $min.'-'.$max.' min'; + } + } + else + { + $time = intval($label) / 60; + return '+'.$time.' min'; + } +} + +function Piwik_getPageGapLabel($label) +{ + if(($pos = strpos($label,'-')) !== false) + { + $min = substr($label, 0, $pos); + $max = substr($label, $pos+1); + + if($min == $max) + { + return $min; + } + } + return $label; +} diff --git a/tests/modules/DataTable.test.php b/tests/modules/DataTable.test.php index e99b732cc4..086205804d 100644 --- a/tests/modules/DataTable.test.php +++ b/tests/modules/DataTable.test.php @@ -511,6 +511,57 @@ class Test_Piwik_DataTable extends UnitTestCase } /** + * Test to sort by label queing the filter + */ + function test_filter_Queue_SortString() + { + + $idcol = Piwik_DataTable_Row::COLUMNS; + + $table = new Piwik_DataTable; + $rows = array( + array( $idcol => array('label'=>'google')),//0 + array( $idcol => array('label'=>'tsk')),//1 + array( $idcol => array('label'=>'Q*(%&*("$&%*(&"$*")"))')),//2 + ); + $table->loadFromArray( $rows ); + + $expectedtable = new Piwik_DataTable; + $rows = array( + array( $idcol => array('label'=>'google')),//0 + array( $idcol => array('label'=>'Q*(%&*("$&%*(&"$*")"))')),//2 + array( $idcol => array('label'=>'tsk')),//1 + ); + $expectedtable->loadFromArray( $rows ); + + $expectedtableReverse = new Piwik_DataTable; + $expectedtableReverse->loadFromArray(array_reverse($rows)); + + $tableCopy = clone $table; + $this->assertTrue(Piwik_DataTable::isEqual($tableCopy, $table)); + + // queue the filter and check the table didnt change + $table->queueFilter("Piwik_DataTable_Filter_Sort", array('label', 'asc')); + $this->assertTrue(Piwik_DataTable::isEqual($tableCopy, $table)); + + // apply filter and check the table is sorted + $table->applyQueuedFilters(); + $this->assertTrue(Piwik_DataTable::isEqual($expectedtable, $table)); + + // apply one more filter check it hasnt changed + $table->queueFilter("Piwik_DataTable_Filter_Sort", array('label', 'desc')); + $this->assertTrue(Piwik_DataTable::isEqual($expectedtable, $table)); + + // now apply the second sort and check it is correctly sorted + $table->applyQueuedFilters(); + $this->assertTrue(Piwik_DataTable::isEqual($expectedtableReverse, $table)); + + // do one more time to make sure it doesnt change + $table->applyQueuedFilters(); + $this->assertTrue(Piwik_DataTable::isEqual($expectedtableReverse, $table)); + } + + /** * Test to sort by visit */ function test_filter_SortNumeric() |