Support Joomla!

Joomla! 1.5 Documentation

Packages

Package: Joomla-Framework

Developer Network License

The Joomla! Developer Network content is © copyright 2006 by the individual contributors and can be used in accordance with the Creative Commons License, Attribution- NonCommercial- ShareAlike 2.5
Source code for file /joomla/installer/installer.php

Documentation is available at installer.php

  1. <?php
  2. /**
  3.  * @version        $Id: installer.php 6612 2007-02-13 00:47:52Z Jinx $
  4.  * @package        Joomla.Framework
  5.  * @subpackage    Installer
  6.  * @copyright    Copyright (C) 2005 - 2007 Open Source Matters. All rights reserved.
  7.  * @license        GNU/GPL, see LICENSE.php
  8.  *  Joomla! is free software. This version may have been modified pursuant
  9.  *  to the GNU General Public License, and as distributed it includes or
  10.  *  is derivative of works licensed under the GNU General Public License or
  11.  *  other free or open source software licenses.
  12.  *  See COPYRIGHT.php for copyright notices and details.
  13.  */
  14.  
  15. // Check to ensure this file is within the rest of the framework
  16. defined('JPATH_BASE'or die();
  17.  
  18. jimport('joomla.filesystem.*');
  19.  
  20. /**
  21.  * Joomla base installer class
  22.  *
  23.  * @author        Louis Landry <[email protected]>
  24.  * @package        Joomla.Framework
  25.  * @subpackage    Installer
  26.  * @since        1.5
  27.  */
  28. class JInstaller extends JObject
  29. {
  30.     /**
  31.      * Array of paths needed by the installer
  32.      * @var array 
  33.      */
  34.     var $_paths = array();
  35.  
  36.     /**
  37.      * The installation manifest XML object
  38.      * @var object 
  39.      */
  40.     var $_manifest = null;
  41.  
  42.     /**
  43.      * True if existing files can be overwritten
  44.      * @var boolean 
  45.      */
  46.     var $_overwrite = false;
  47.  
  48.     /**
  49.      * A database connector object
  50.      * @var object 
  51.      */
  52.     var $_db = null;
  53.  
  54.     /**
  55.      * Associative array of package installer handlers
  56.      * @var array 
  57.      */
  58.     var $_adapters = array();
  59.  
  60.     /**
  61.      * Stack of installation steps
  62.      *     - Used for installation rollback
  63.      * @var array 
  64.      */
  65.     var $_stepStack = array();
  66.  
  67.     /**
  68.      * The output from the install/uninstall scripts
  69.      * @var string 
  70.      */
  71.     var $message = null;
  72.  
  73.     /**
  74.      * Constructor
  75.      *
  76.      * @access protected
  77.      */
  78.     function __construct()
  79.     {
  80.         $this->_db =JFactory::getDBO();
  81.     }
  82.  
  83.     /**
  84.      * Returns a reference to the global Installer object, only creating it
  85.      * if it doesn't already exist.
  86.      *
  87.      * @static
  88.      * @return    object    An installer object
  89.      * @since 1.5
  90.      */
  91.     function &getInstance()
  92.     {
  93.         static $instance;
  94.  
  95.         if (!isset ($instance)) {
  96.             $instance new JInstaller();
  97.         }
  98.         return $instance;
  99.     }
  100.  
  101.     /**
  102.      * Get the allow overwrite switch
  103.      *
  104.      * @access    public
  105.      * @return    boolean    Allow overwrite switch
  106.      * @since    1.5
  107.      */
  108.     function getOverwrite()
  109.     {
  110.         return $this->_overwrite;
  111.     }
  112.  
  113.     /**
  114.      * Set the allow overwrite switch
  115.      *
  116.      * @access    public
  117.      * @param    boolean    $state    Overwrite switch state
  118.      * @return    boolean    Previous value
  119.      * @since    1.5
  120.      */
  121.     function setOverwrite($state=false)
  122.     {
  123.         $tmp $this->_overwrite;
  124.         if ($state{
  125.             $this->_overwrite = true;
  126.         else {
  127.             $this->_overwrite = false;
  128.         }
  129.         return $tmp;
  130.     }
  131.  
  132.     /**
  133.      * Get the database connector object
  134.      *
  135.      * @access    public
  136.      * @return    object    Database connector object
  137.      * @since    1.5
  138.      */
  139.     function &getDBO()
  140.     {
  141.         return $this->_db;
  142.     }
  143.  
  144.     /**
  145.      * Get the installation manifest object
  146.      *
  147.      * @access    public
  148.      * @return    object    Manifest object
  149.      * @since    1.5
  150.      */
  151.     function &getManifest()
  152.     {
  153.         if (!is_object($this->_manifest)) {
  154.             $this->_findManifest();
  155.         }
  156.         return $this->_manifest;
  157.     }
  158.  
  159.     /**
  160.      * Get an installer path by name
  161.      *
  162.      * @access    public
  163.      * @param    string    $name        Path name
  164.      * @param    string    $default    Default value
  165.      * @return    string    Path
  166.      * @since    1.5
  167.      */
  168.     function getPath($name$default=null)
  169.     {
  170.         return (!empty($this->_paths[$name])) $this->_paths[$name$default;
  171.     }
  172.  
  173.     /**
  174.      * Sets an installer path by name
  175.      *
  176.      * @access    public
  177.      * @param    string    $name    Path name
  178.      * @param    string    $value    Path
  179.      * @return    void 
  180.      * @since    1.5
  181.      */
  182.     function setPath($name$value)
  183.     {
  184.         $this->_paths[$name$value;
  185.     }
  186.  
  187.     /**
  188.      * Pushes a step onto the installer stack for rolling back steps
  189.      *
  190.      * @access    public
  191.      * @param    array    $step    Installer step
  192.      * @return    void 
  193.      * @since    1.5
  194.      */
  195.     function pushStep($step)
  196.     {
  197.         $this->_stepStack[$step;
  198.     }
  199.  
  200.     /**
  201.      * Set an installer adapter by name
  202.      *
  203.      * @access    public
  204.      * @param    string    $name        Adapter name
  205.      * @param    object    $adapter    Installer adapter object
  206.      * @return    boolean True if successful
  207.      * @since    1.5
  208.      */
  209.     function setAdapter($name$adapter=null)
  210.     {
  211.         if (!is_object($adapter)) {
  212.             // Try to load the adapter object
  213.             jimport('joomla.installer.adapters.'.strtolower($name));
  214.             $class 'JInstaller'.ucfirst($name);
  215.             if (!class_exists($class)) {
  216.                 return false;
  217.             }
  218.             $adapter new $class($this);
  219.         }
  220.         $this->_adapters[$name=$adapter;
  221.         return true;
  222.     }
  223.  
  224.     /**
  225.      * Installation abort method
  226.      *
  227.      * @access    public
  228.      * @param    string    $msg    Abort message from the installer
  229.      * @param    string    $type    Package type if defined
  230.      * @return    boolean    True if successful
  231.      * @since    1.5
  232.      */
  233.     function abort($msg=null$type=null)
  234.     {
  235.         // Initialize variables
  236.         $retval true;
  237.         $step array_pop($this->_stepStack);
  238.  
  239.         // Raise abort warning
  240.         if ($msg{
  241.             JError::raiseWarning(100$msg);
  242.         }
  243.  
  244.         while ($step != null)
  245.         {
  246.             switch ($step['type'])
  247.             {
  248.                 case 'file' :
  249.                     // remove the file
  250.                     $stepval JFile::delete($step['path']);
  251.                     break;
  252.  
  253.                 case 'folder' :
  254.                     // remove the folder
  255.                     $stepval JFolder::delete($step['path']);
  256.                     break;
  257.  
  258.                 case 'query' :
  259.                     // placeholder in case this is necessary in the future
  260.                     break;
  261.  
  262.                 default :
  263.                     if ($type && is_object($this->_adapters[$type])) {
  264.                         // Build the name of the custom rollback method for the type
  265.                         $method '_rollback_'.$step['type'];
  266.                         // Custom rollback method handler
  267.                         if (method_exists($this->_adapters[$type]$method)) {
  268.                             $stepval $this->_adapters[$type]->$method($step);
  269.                         }
  270.                     }
  271.                     break;
  272.             }
  273.  
  274.             // Only set the return value if it is false
  275.             if ($stepval === false{
  276.                 $retval false;
  277.             }
  278.  
  279.             // Get the next step and continue
  280.             $step array_pop($this->_stepStack);
  281.         }
  282.  
  283.         return $retval;
  284.     }
  285.  
  286.     /**
  287.      * Package installation method
  288.      *
  289.      * @access    public
  290.      * @param    string    $path    Path to package source folder
  291.      * @return    boolean    True if successful
  292.      * @since    1.5
  293.      */
  294.     function install($path=null)
  295.     {
  296.         if ($path && JFolder::exists($path)) {
  297.             $this->setPath('source'$path);
  298.         else {
  299.             return $this->abort(JText::_('Install path does not exist'));
  300.         }
  301.  
  302.         if (!$this->setupInstall()) {
  303.             return $this->abort(JText::_('Unable to detect manifest file'));
  304.         }
  305.  
  306.         /*
  307.          * LEGACY CHECK
  308.          */
  309.         $root        =$this->_manifest->document;
  310.         $version    $root->attributes('version');
  311.         $rootName    $root->name();
  312.         $config        &JFactory::getConfig();
  313.         if ((version_compare($version'1.5''<'|| $rootName == 'mosinstall'&& !$config->getValue('config.legacy')) {
  314.             return $this->abort(JText::_('MUSTENABLELEGACY'));
  315.         }
  316.  
  317.         $type $root->attributes('type');
  318.  
  319.         // Needed for legacy reasons ... to be deprecated in next minor release
  320.         if ($type == 'mambot'{
  321.             $type 'plugin';
  322.         }
  323.  
  324.         if (is_object($this->_adapters[$type])) {
  325.             return $this->_adapters[$type]->install();
  326.         }
  327.         return false;
  328.     }
  329.  
  330.     /**
  331.      * Package update method
  332.      *
  333.      * @access    public
  334.      * @param    string    $path    Path to package source folder
  335.      * @return    boolean    True if successful
  336.      * @since    1.5
  337.      */
  338.     function update($path=null)
  339.     {
  340.         if ($path && JFolder::exists($path)) {
  341.             $this->setPath('source'$path);
  342.         else {
  343.             $this->abort(JText::_('Update path does not exist'));
  344.         }
  345.  
  346.         if (!$this->setupInstall()) {
  347.             return $this->abort(JText::_('Unable to detect manifest file'));
  348.         }
  349.  
  350.         /*
  351.          * LEGACY CHECK
  352.          */
  353.         $root        =$this->_manifest->document;
  354.         $version    $root->attributes('version');
  355.         $rootName    $root->name();
  356.         $config        &JFactory::getConfig();
  357.         if ((version_compare($version'1.5''<'|| $rootName == 'mosinstall'&& !$config->getValue('config.legacy')) {
  358.             return $this->abort(JText::_('MUSTENABLELEGACY'));
  359.         }
  360.  
  361.         $type $root->attributes('type');
  362.  
  363.         // Needed for legacy reasons ... to be deprecated in next minor release
  364.         if ($type == 'mambot'{
  365.             $type 'plugin';
  366.         }
  367.  
  368.         if (is_object($this->_adapters[$type])) {
  369.             $this->_adapters[$type]->update();
  370.         }
  371.     }
  372.  
  373.     /**
  374.      * Package uninstallation method
  375.      *
  376.      * @access    public
  377.      * @param    string    $type    Package type
  378.      * @param    mixed    $identifier    Package identifier for adapter
  379.      * @param    int        $cid    Application ID
  380.      * @return    boolean    True if successful
  381.      * @since    1.5
  382.      */
  383.     function uninstall($type$identifier$cid=0)
  384.     {
  385.         if (!isset($this->_adapters[$type]|| !is_object($this->_adapters[$type])) {
  386.             if (!$this->setAdapter($type)) {
  387.                 return false;
  388.             }
  389.         }
  390.         if (is_object($this->_adapters[$type])) {
  391.             $this->_adapters[$type]->uninstall($identifier$cid);
  392.         }
  393.     }
  394.  
  395.     /**
  396.      * Prepare for installation: this method sets the installation directory, finds
  397.      * and checks the installation file and verifies the installation type
  398.      *
  399.      * @access public
  400.      * @return boolean True on success
  401.      * @since 1.0
  402.      */
  403.     function setupInstall()
  404.     {
  405.         // We need to find the installation manifest file
  406.         if (!$this->_findManifest()) {
  407.             return false;
  408.         }
  409.  
  410.         // Load the adapter(s) for the install manifest
  411.         $root =$this->_manifest->document;
  412.         $type $root->attributes('type');
  413.  
  414.         // Needed for legacy reasons ... to be deprecated in next minor release
  415.         if ($type == 'mambot'{
  416.             $type 'plugin';
  417.         }
  418.  
  419.         // Lazy load the adapter
  420.         if (!isset($this->_adapters[$type]|| !is_object($this->_adapters[$type])) {
  421.             if (!$this->setAdapter($type)) {
  422.                 return false;
  423.             }
  424.         }
  425.  
  426.         return true;
  427.     }
  428.  
  429.     /**
  430.      * Backward compatible Method to parse through a queries element of the
  431.      * installation manifest file and take appropriate action.
  432.      *
  433.      * @access    public
  434.      * @param    object    $element     The xml node to process
  435.      * @return    mixed    Number of queries processed or False on error
  436.      * @since    1.5
  437.      */
  438.     function parseQueries($element)
  439.     {
  440.         // Get the database connector object
  441.         $db $this->_db;
  442.  
  443.         if (!is_a($element'JSimpleXMLElement'|| !count($element->children())) {
  444.             // Either the tag does not exist or has no children therefore we return zero files processed.
  445.             return 0;
  446.         }
  447.  
  448.         // Get the array of query nodes to process
  449.         $queries $element->children();
  450.         if (count($queries== 0{
  451.             // No queries to process
  452.             return 0;
  453.         }
  454.  
  455.         // Process each query in the $queries array (children of $tagName).
  456.         foreach ($queries as $query)
  457.         {
  458.             $db->setQuery($query->data());
  459.             if (!$db->query()) {
  460.                 JError::raiseWarning(1'JInstaller::install: '.JText::_('SQL Error')." ".$db->stderr(true));
  461.                 return false;
  462.             }
  463.         }
  464.         return (int) count($queries);
  465.     }
  466.  
  467.     /**
  468.      * Method to extract the name of a discreet installation sql file from the installation manifest file.
  469.      *
  470.      * @access    public
  471.      * @param    object    $element     The xml node to process
  472.      * @param    string    $version    The database connector to use
  473.      * @return    mixed    Number of queries processed or False on error
  474.      * @since    1.5
  475.      */
  476.     function parseSQLFiles($element)
  477.     {
  478.         // Initialize variables
  479.         $queries array();
  480.         $db $this->_db;
  481.         $dbDriver strtolower($db->get('name'));
  482.         if ($dbDriver == 'mysqli'{
  483.             $dbDriver 'mysql';
  484.         }
  485.         $dbCharset ($db->hasUTF()) 'utf8' '';
  486.  
  487.         if (!is_a($element'JSimpleXMLElement')) {
  488.             // The tag does not exist.
  489.             return 0;
  490.         }
  491.  
  492.         // Get the array of file nodes to process
  493.         $files $element->children();
  494.         if (count($files== 0{
  495.             // No files to process
  496.             return 0;
  497.         }
  498.  
  499.         // Get the name of the sql file to process
  500.         $sqlfile '';
  501.         foreach ($files as $file)
  502.         {
  503.             $fCharset (strtolower($file->attributes('charset')) == 'utf8''utf8' '';
  504.             $fDriver  strtolower($file->attributes('driver'));
  505.             if ($fDriver == 'mysqli'{
  506.                 $fDriver 'mysql';
  507.             }
  508.  
  509.             if$fCharset == $dbCharset && $fDriver == $dbDriver{
  510.                 $sqlfile $file->data();
  511.                 // Check that sql files exists before reading. Otherwise raise error for rollback
  512.                 if !file_exists$this->getPath('extension_administrator').DS.$sqlfile ) ) {
  513.                     return false;
  514.                 }
  515.                 $buffer file_get_contents($this->getPath('extension_administrator').DS.$sqlfile);
  516.  
  517.                 // Graceful exit and rollback if read not successful
  518.                 if $buffer === false {
  519.                     return false;
  520.                 }
  521.  
  522.                 // Create an array of queries from the sql file
  523.                 $queries JInstallerHelper::splitSql($buffer);
  524.  
  525.                 if (count($queries== 0{
  526.                     // No queries to process
  527.                     return 0;
  528.                 }
  529.  
  530.                 // Process each query in the $queries array (split out of sql file).
  531.                 foreach ($queries as $query)
  532.                 {
  533.                     $query trim($query);
  534.                     if ($query != '' && $query{0!= '#'{
  535.                         $db->setQuery($query);
  536.                         if (!$db->query()) {
  537.                             JError::raiseWarning(1'JInstaller::install: '.JText::_('SQL Error')." ".$db->stderr(true));
  538.                             return false;
  539.                         }
  540.                     }
  541.                 }
  542.             }
  543.         }
  544.  
  545.         return (int) count($queries);
  546.     }
  547.  
  548.     /**
  549.      * Method to parse through a files element of the installation manifest and take appropriate
  550.      * action.
  551.      *
  552.      * @access    public
  553.      * @param    object    $element     The xml node to process
  554.      * @param    int        $cid        Application ID of application to install to
  555.      * @return    boolean    True on success
  556.      * @since    1.5
  557.      */
  558.     function parseFiles($element$cid=0)
  559.     {
  560.         // Initialize variables
  561.         $copyfiles array ();
  562.  
  563.         // Get the client info
  564.         jimport('joomla.application.helper');
  565.         $client JApplicationHelper::getClientInfo($cid);
  566.  
  567.         if (!is_a($element'JSimpleXMLElement'|| !count($element->children())) {
  568.             // Either the tag does not exist or has no children therefore we return zero files processed.
  569.             return 0;
  570.         }
  571.  
  572.         // Get the array of file nodes to process
  573.         $files $element->children();
  574.         if (count($files== 0{
  575.             // No files to process
  576.             return 0;
  577.         }
  578.  
  579.         /*
  580.          * Here we set the folder we are going to remove the files from.  There are a few
  581.          * special cases that need to be considered for certain reserved tags.
  582.          *
  583.          *     - 'media' Files are copied to the JPATH_BASE/images/stories/ folder
  584.          *     - 'languages' Files are copied to JPATH_BASE/languages/ folder
  585.          */
  586.         switch ($element->name())
  587.         {
  588.             case 'media':
  589.                 if ($element->attributes('destination')) {
  590.                     $folder $element->attributes('destination');
  591.                 else {
  592.                     $folder 'stories';
  593.                 }
  594.                 $destintion $client->path.DS.'images'.DS.$folder;
  595.                 break;
  596.  
  597.             case 'languages':
  598.                 $destination $client->path.DS.'language';
  599.                 break;
  600.  
  601.             default:
  602.                 if ($client{
  603.                     $pathname 'extension_'.$client->name;
  604.                     $destination $this->getPath($pathname);
  605.                 else {
  606.                     $pathname 'extension_root';
  607.                     $destination $this->getPath($pathname);
  608.                 }
  609.                 break;
  610.         }
  611.  
  612.         /*
  613.          * Here we set the folder we are going to copy the files from.
  614.          *
  615.          * Does the element have a folder attribute?
  616.          *
  617.          * If so this indicates that the files are in a subdirectory of the source
  618.          * folder and we should append the folder attribute to the source path when
  619.          * copying files.
  620.          */
  621.         if ($folder $element->attributes('folder')) {
  622.             $source $this->getPath('source').DS.$folder;
  623.         else {
  624.             $source $this->getPath('source');
  625.         }
  626.  
  627.         // Process each file in the $files array (children of $tagName).
  628.         foreach ($files as $file{
  629.             /*
  630.              * If the file is a language, we must handle it differently.  Language files
  631.              * go in a subdirectory based on the language code, ie.
  632.              *
  633.              *         <language tag="en-US">en-US.mycomponent.ini</language>
  634.              *
  635.              * would go in the en-US subdirectory of the languages directory.
  636.              *
  637.              * We will only install language files where a core language pack
  638.              * already exists.
  639.              */
  640.             if ($file->name(== 'language' && $file->attributes('tag'!= ''{
  641.                 $path['src']    $source.DS.$file->data();
  642.                 $path['dest']    $destination.DS.$file->attributes('tag').DS.basename($file->data());
  643.  
  644.                 // If the language folder is not present, then the core pack hasn't been installed... ignore
  645.                 if (!JFolder::exists(dirname($path['dest']))) {
  646.                     continue;
  647.                 }
  648.             else {
  649.                 $path['src']    $source.DS.$file->data();
  650.                 $path['dest']    $destination.DS.$file->data();
  651.             }
  652.  
  653.             // Is this path a file or folder?
  654.             $path['type']    $file->name(== 'folder''folder' 'file';
  655.  
  656.             /*
  657.              * Before we can add a file to the copyfiles array we need to ensure
  658.              * that the folder we are copying our file to exits and if it doesn't,
  659.              * we need to create it.
  660.              */
  661.             if (basename($path['dest']!= $path['dest']{
  662.                 $newdir dirname($path['dest']);
  663.  
  664.                 if (!JFolder::create($newdir)) {
  665.                     JError::raiseWarning(1'JInstaller::install: '.JText::_('Failed to create directory').' "'.$newdir.'"');
  666.                     return false;
  667.                 }
  668.             }
  669.  
  670.             // Add the file to the copyfiles array
  671.             $copyfiles[$path;
  672.         }
  673.  
  674.         return $this->copyFiles($copyfiles);
  675.     }
  676.  
  677.     /**
  678.      * Method to parse through a languages element of the installation manifest and take appropriate
  679.      * action.
  680.      *
  681.      * @access    public
  682.      * @param    object    $element     The xml node to process
  683.      * @param    int        $cid        Application ID of application to install to
  684.      * @return    boolean    True on success
  685.      * @since    1.5
  686.      */
  687.     function parseLanguages($element$cid=0)
  688.     {
  689.         // Initialize variables
  690.         $copyfiles array ();
  691.  
  692.         // Get the client info
  693.         jimport('joomla.application.helper');
  694.         $client JApplicationHelper::getClientInfo($cid);
  695.  
  696.         if (!is_a($element'JSimpleXMLElement'|| !count($element->children())) {
  697.             // Either the tag does not exist or has no children therefore we return zero files processed.
  698.             return 0;
  699.         }
  700.  
  701.         // Get the array of file nodes to process
  702.         $files $element->children();
  703.         if (count($files== 0{
  704.             // No files to process
  705.             return 0;
  706.         }
  707.  
  708.         /*
  709.          * Here we set the folder we are going to copy the files to.
  710.          *
  711.          * 'languages' Files are copied to JPATH_BASE/language/ folder
  712.          */
  713.         $destination $client->path.DS.'language';
  714.  
  715.         /*
  716.          * Here we set the folder we are going to copy the files from.
  717.          *
  718.          * Does the element have a folder attribute?
  719.          *
  720.          * If so this indicates that the files are in a subdirectory of the source
  721.          * folder and we should append the folder attribute to the source path when
  722.          * copying files.
  723.          */
  724.         if ($folder $element->attributes('folder')) {
  725.             $source $this->getPath('source').DS.$folder;
  726.         else {
  727.             $source $this->getPath('source');
  728.         }
  729.  
  730.         // Process each file in the $files array (children of $tagName).
  731.         foreach ($files as $file{
  732.             /*
  733.              * Language files go in a subfolder based on the language code, ie.
  734.              *
  735.              *         <language tag="en-US">en-US.mycomponent.ini</language>
  736.              *
  737.              * would go in the en-US subdirectory of the language folder.
  738.              *
  739.              * We will only install language files where a core language pack
  740.              * already exists.
  741.              */
  742.             if ($file->attributes('tag'!= ''{
  743.                 $path['src']    $source.DS.$file->data();
  744.                 $path['dest']    $destination.DS.$file->attributes('tag').DS.basename($file->data());
  745.  
  746.                 // If the language folder is not present, then the core pack hasn't been installed... ignore
  747.                 if (!JFolder::exists(dirname($path['dest']))) {
  748.                     continue;
  749.                 }
  750.             else {
  751.                 $path['src']    $source.DS.$file->data();
  752.                 $path['dest']    $destination.DS.$file->data();
  753.             }
  754.  
  755.             /*
  756.              * Before we can add a file to the copyfiles array we need to ensure
  757.              * that the folder we are copying our file to exits and if it doesn't,
  758.              * we need to create it.
  759.              */
  760.             if (basename($path['dest']!= $path['dest']{
  761.                 $newdir dirname($path['dest']);
  762.  
  763.                 if (!JFolder::create($newdir)) {
  764.                     JError::raiseWarning(1'JInstaller::install: '.JText::_('Failed to create directory').' "'.$newdir.'"');
  765.                     return false;
  766.                 }
  767.             }
  768.  
  769.             // Add the file to the copyfiles array
  770.             $copyfiles[$path;
  771.         }
  772.  
  773.         return $this->copyFiles($copyfiles);
  774.     }
  775.  
  776.     /**
  777.      * Method to parse through a media element of the installation manifest and take appropriate
  778.      * action.
  779.      *
  780.      * @access    public
  781.      * @param    object    $element     The xml node to process
  782.      * @param    int        $cid        Application ID of application to install to
  783.      * @return    boolean    True on success
  784.      * @since    1.5
  785.      */
  786.     function parseMedia($element$cid=0)
  787.     {
  788.         // Initialize variables
  789.         $copyfiles array ();
  790.  
  791.         // Get the client info
  792.         jimport('joomla.application.helper');
  793.         $client JApplicationHelper::getClientInfo($cid);
  794.  
  795.         if (!is_a($element'JSimpleXMLElement'|| !count($element->children())) {
  796.             // Either the tag does not exist or has no children therefore we return zero files processed.
  797.             return 0;
  798.         }
  799.  
  800.         // Get the array of file nodes to process
  801.         $files $element->children();
  802.         if (count($files== 0{
  803.             // No files to process
  804.             return 0;
  805.         }
  806.  
  807.         /*
  808.          * Here we set the folder we are going to copy the files to.
  809.          *     Default 'media' Files are copied to the JPATH_BASE/images folder
  810.          */
  811.         $folder ($element->attributes('destination')) DS.$element->attributes('destination'null;
  812.         $destination JPath::clean($client->path.DS.'images'.$folder);
  813.  
  814.         /*
  815.          * Here we set the folder we are going to copy the files from.
  816.          *
  817.          * Does the element have a folder attribute?
  818.          *
  819.          * If so this indicates that the files are in a subdirectory of the source
  820.          * folder and we should append the folder attribute to the source path when
  821.          * copying files.
  822.          */
  823.         if ($folder $element->attributes('folder')) {
  824.             $source $this->getPath('source').DS.$folder;
  825.         else {
  826.             $source $this->getPath('source');
  827.         }
  828.  
  829.         // Process each file in the $files array (children of $tagName).
  830.         foreach ($files as $file)
  831.         {
  832.             $path['src']    $source.DS.$file->data();
  833.             $path['dest']    $destination.DS.$file->data();
  834.  
  835.             /*
  836.              * Before we can add a file to the copyfiles array we need to ensure
  837.              * that the folder we are copying our file to exits and if it doesn't,
  838.              * we need to create it.
  839.              */
  840.             if (basename($path['dest']!= $path['dest']{
  841.                 $newdir dirname($path['dest']);
  842.  
  843.                 if (!JFolder::create($newdir)) {
  844.                     JError::raiseWarning(1'JInstaller::install: '.JText::_('Failed to create directory').' "'.$newdir.'"');
  845.                     return false;
  846.                 }
  847.             }
  848.  
  849.             // Add the file to the copyfiles array
  850.             $copyfiles[$path;
  851.         }
  852.  
  853.         return $this->copyFiles($copyfiles);
  854.     }
  855.  
  856.     /**
  857.      * Method to parse the parameters of an extension, build the INI
  858.      * string for it's default parameters, and return the INI string.
  859.      *
  860.      * @access    public
  861.      * @return    string    INI string of parameter values
  862.      * @since    1.5
  863.      */
  864.     function getParams()
  865.     {
  866.         // Get the manifest document root element
  867.         $root $this->_manifest->document;
  868.  
  869.         // Get the element of the tag names
  870.         $element =$root->getElementByPath('params');
  871.         if (!is_a($element'JSimpleXMLElement'|| !count($element->children())) {
  872.             // Either the tag does not exist or has no children therefore we return zero files processed.
  873.             return null;
  874.         }
  875.  
  876.         // Get the array of parameter nodes to process
  877.         $params $element->children();
  878.         if (count($params== 0{
  879.             // No params to process
  880.             return null;
  881.         }
  882.  
  883.         // Process each parameter in the $params array.
  884.         $ini null;
  885.         foreach ($params as $param{
  886.             if (!$name $param->attributes('name')) {
  887.                 continue;
  888.             }
  889.  
  890.             if (!$value $param->attributes('default')) {
  891.                 continue;
  892.             }
  893.  
  894.             $ini .= $name."=".$value."\n";
  895.         }
  896.         return $ini;
  897.     }
  898.  
  899.     /**
  900.      * Copy files from source directory to the target directory
  901.      *
  902.      * @access    public
  903.      * @param    array $files array with filenames
  904.      * @param    boolean $overwrite True if existing files can be replaced
  905.      * @return    boolean True on success
  906.      * @since    1.5
  907.      */
  908.     function copyFiles($files$overwrite=null)
  909.     {
  910.         /*
  911.          * To allow for manual override on the overwriting flag, we check to see if
  912.          * the $overwrite flag was set and is a boolean value.  If not, use the object
  913.          * allowOverwrite flag.
  914.          */
  915.         if (is_null($overwrite|| !is_bool($overwrite)) {
  916.             $overwrite $this->_overwrite;
  917.         }
  918.  
  919.         /*
  920.          * $files must be an array of filenames.  Verify that it is an array with
  921.          * at least one file to copy.
  922.          */
  923.         if (is_array($files&& count($files0)
  924.         {
  925.             foreach ($files as $file)
  926.             {
  927.                 // Get the source and destination paths
  928.                 $filesource    JPath::clean($file['src']);
  929.                 $filedest    JPath::clean($file['dest']);
  930.                 $filetype    array_key_exists('type'$file$file['type''file';
  931.  
  932.                 if (!file_exists($filesource)) {
  933.                     /*
  934.                      * The source file does not exist.  Nothing to copy so set an error
  935.                      * and return false.
  936.                      */
  937.                     JError::raiseWarning(1'JInstaller::install: '.JText::sprintf('File does not exist'$filesource));
  938.                     return false;
  939.                 elseif (file_exists($filedest&& !$overwrite{
  940.                         /*
  941.                          * The destination file already exists and the overwrite flag is false.
  942.                          * Set an error and return false.
  943.                          */
  944.                         JError::raiseWarning(1'JInstaller::install: '.JText::sprintf('WARNSAME'$filedest));
  945.                         return false;
  946.                 else {
  947.  
  948.                     // Copy the folder or file to the new location.
  949.                     if $filetype == 'folder'{
  950.  
  951.                         if (!(JFolder::copy($filesource$filedestnull$overwrite))) {
  952.                             JError::raiseWarning(1'JInstaller::install: '.JText::sprintf('Failed to copy folder to'$filesource$filedest));
  953.                             return false;
  954.                         }
  955.  
  956.                         $step array ('type' => 'folder''path' => $filedest);
  957.                     else {
  958.  
  959.                         if (!(JFile::copy($filesource$filedest))) {
  960.                             JError::raiseWarning(1'JInstaller::install: '.JText::sprintf('Failed to copy file to'$filesource$filedest));
  961.                             return false;
  962.                         }
  963.  
  964.                         $step array ('type' => 'file''path' => $filedest);
  965.                     }
  966.  
  967.                     /*
  968.                      * Since we copied a file/folder, we want to add it to the installation step stack so that
  969.                      * in case we have to roll back the installation we can remove the files copied.
  970.                      */
  971.                     $this->_stepStack[$step;
  972.                 }
  973.             }
  974.         else {
  975.  
  976.             /*
  977.              * The $files variable was either not an array or an empty array
  978.              */
  979.             return false;
  980.         }
  981.         return count($files);
  982.     }
  983.  
  984.     /**
  985.      * Method to parse through a files element of the installation manifest and remove
  986.      * the files that were installed
  987.      *
  988.      * @access    public
  989.      * @param    object    $element     The xml node to process
  990.      * @param    int        $cid        Application ID of application to remove from
  991.      * @return    boolean    True on success
  992.      * @since    1.5
  993.      */
  994.     function removeFiles($element$cid=0)
  995.     {
  996.         // Initialize variables
  997.         $removefiles array ();
  998.         $retval true;
  999.  
  1000.         // Get the client info
  1001.         jimport('joomla.application.helper');
  1002.         $client JApplicationHelper::getClientInfo($cid);
  1003.  
  1004.         if (!is_a($element'JSimpleXMLElement'|| !count($element->children())) {
  1005.             // Either the tag does not exist or has no children therefore we return zero files processed.
  1006.             return true;
  1007.         }
  1008.  
  1009.         // Get the array of file nodes to process
  1010.         $files $element->children();
  1011.         if (count($files== 0{
  1012.             // No files to process
  1013.             return true;
  1014.         }
  1015.  
  1016.         /*
  1017.          * Here we set the folder we are going to remove the files from.  There are a few
  1018.          * special cases that need to be considered for certain reserved tags.
  1019.          */
  1020.         switch ($element->name())
  1021.         {
  1022.             case 'media':
  1023.                 if ($element->attributes('destination')) {
  1024.                     $folder $element->attributes('destination');
  1025.                 else {
  1026.                     $folder 'stories';
  1027.                 }
  1028.                 $source $client->path.DS.'images'.DS.$folder;
  1029.                 break;
  1030.  
  1031.             case 'languages':
  1032.                 $source $client->path.DS.'language';
  1033.                 break;
  1034.  
  1035.             default:
  1036.                 if ($client{
  1037.                     $pathname 'extension_'.$client->name;
  1038.                     $source $this->getPath($pathname);
  1039.                 else {
  1040.                     $pathname 'extension_root';
  1041.                     $source $this->getPath($pathname);
  1042.                 }
  1043.                 break;
  1044.         }
  1045.  
  1046.         // Process each file in the $files array (children of $tagName).
  1047.         foreach ($files as $file)
  1048.         {
  1049.             /*
  1050.              * If the file is a language, we must handle it differently.  Language files
  1051.              * go in a subdirectory based on the language code, ie.
  1052.              *
  1053.              *         <language tag="en_US">en_US.mycomponent.ini</language>
  1054.              *
  1055.              * would go in the en_US subdirectory of the languages directory.
  1056.              */
  1057.             if ($file->name(== 'language' && $file->attributes('tag'!= ''{
  1058.                 $path $source.DS.$file->attributes('tag').DS.basename($file->data());
  1059.             else {
  1060.                 $path $source.DS.$file->data();
  1061.             }
  1062.  
  1063.             /*
  1064.              * Actually delete the files/folders
  1065.              */
  1066.             if (is_dir($path)) {
  1067.                 $val JFolder::delete($path);
  1068.             else {
  1069.                 $val JFile::delete($path);
  1070.             }
  1071.  
  1072.             if ($val === false{
  1073.                 $retval false;
  1074.             }
  1075.         }
  1076.  
  1077.         return $retval;
  1078.     }
  1079.  
  1080.     /**
  1081.      * Copies the installation manifest file to the extension folder in the given client
  1082.      *
  1083.      * @access    public
  1084.      * @param    int        $cid    Where to copy the installfile [optional: defaults to 1 (admin)]
  1085.      * @return    boolean    True on success, False on error
  1086.      * @since    1.5
  1087.      */
  1088.     function copyManifest($cid=1)
  1089.     {
  1090.         // Get the client info
  1091.         jimport('joomla.application.helper');
  1092.         $client JApplicationHelper::getClientInfo($cid);
  1093.  
  1094.         $path['src'$this->getPath('manifest');
  1095.  
  1096.         if ($client{
  1097.             $pathname 'extension_'.$client->name;
  1098.             $path['dest']  $this->getPath($pathname).DS.basename($this->getPath('manifest'));
  1099.         else {
  1100.             $pathname 'extension_root';
  1101.             $path['dest']  $this->getPath($pathname).DS.basename($this->getPath('manifest'));
  1102.         }
  1103.         return $this->copyFiles(array ($path)true);
  1104.     }
  1105.  
  1106.     /**
  1107.      * Tries to find the package manifest file
  1108.      *
  1109.      * @access private
  1110.      * @return boolean True on success, False on error
  1111.      * @since 1.0
  1112.      */
  1113.     function _findManifest()
  1114.     {
  1115.         // Get an array of all the xml files from teh installation directory
  1116.         $xmlfiles JFolder::files($this->getPath('source')'.xml$'truetrue);
  1117.         // If at least one xml file exists
  1118.         if (count($xmlfiles0{
  1119.             foreach ($xmlfiles as $file)
  1120.             {
  1121.                 // Is it a valid joomla installation manifest file?
  1122.                 $manifest $this->_isManifest($file);
  1123.                 if (!is_null($manifest)) {
  1124.  
  1125.                     // If the root method attribute is set to upgrade, allow file overwrite
  1126.                     $root =$manifest->document;
  1127.                     if ($root->attributes('method'== 'upgrade'{
  1128.                         $this->_overwrite = true;
  1129.                     }
  1130.  
  1131.                     // Set the manifest object and path
  1132.                     $this->_manifest =$manifest;
  1133.                     $this->setPath('manifest'$file);
  1134.  
  1135.                     // Set the installation source path to that of the manifest file
  1136.                     $this->setPath('source'dirname($file));
  1137.                     return true;
  1138.                 }
  1139.             }
  1140.  
  1141.             // None of the xml files found were valid install files
  1142.             JError::raiseWarning(1'JInstaller::install: '.JText::_('ERRORJOSXMLSETUP'));
  1143.             return false;
  1144.         else {
  1145.             // No xml files were found in the install folder
  1146.             JError::raiseWarning(1'JInstaller::install: '.JText::_('ERRORXMLSETUP'));
  1147.             return false;
  1148.         }
  1149.     }
  1150.  
  1151.     /**
  1152.      * Is the xml file a valid Joomla installation manifest file
  1153.      *
  1154.      * @access    private
  1155.      * @param    string    $file    An xmlfile path to check
  1156.      * @return    mixed    A JSimpleXML document, or null if the file failed to parse
  1157.      * @since    1.5
  1158.      */
  1159.     function &_isManifest($file)
  1160.     {
  1161.         // Initialize variables
  1162.         $null    null;
  1163.         $xml    =JFactory::getXMLParser('Simple');
  1164.  
  1165.         // If we cannot load the xml file return null
  1166.         if (!$xml->loadFile($file)) {
  1167.             // Free up xml parser memory and return null
  1168.             unset ($xml);
  1169.             return $null;
  1170.         }
  1171.  
  1172.         /*
  1173.          * Check for a valid XML root tag.
  1174.          * @todo: Remove backwards compatability in a future version
  1175.          * Should be 'install', but for backward compatability we will accept 'mosinstall'.
  1176.          */
  1177.         $root =$xml->document;
  1178.         if ($root->name(!= 'install' && $root->name(!= 'mosinstall'{
  1179.             // Free up xml parser memory and return null
  1180.             unset ($xml);
  1181.             return $null;
  1182.         }
  1183.  
  1184.         // Valid manifest file return the object
  1185.         return $xml;
  1186.     }
  1187. }
  1188.  
  1189. /**
  1190.  * Installer helper class
  1191.  *
  1192.  * @static
  1193.  * @author        Louis Landry <[email protected]>
  1194.  * @package        Joomla.Framework
  1195.  * @subpackage    Installer
  1196.  * @since        1.5
  1197.  */
  1198. {
  1199.     /**
  1200.      * Downloads a package
  1201.      *
  1202.      * @static
  1203.      * @param string URL of file to download
  1204.      * @param string Download target filename [optional]
  1205.      * @return mixed Path to downloaded package or boolean false on failure
  1206.      * @since 1.5
  1207.      */
  1208.     function downloadPackage($url$target false)
  1209.     {
  1210.         $config =JFactory::getConfig();
  1211.  
  1212.         // Capture PHP errors
  1213.         $php_errormsg 'Error Unknown';
  1214.         ini_set('track_errors'true);
  1215.  
  1216.         // Set user agent
  1217.         ini_set('user_agent'"Joomla! 1.5 Installer");
  1218.  
  1219.         // Open the remote server socket for reading
  1220.         $inputHandle fopen($url"r");
  1221.         if (!$inputHandle{
  1222.             JError::raiseWarning(42'Remote Server connection failed: '.$php_errormsg);
  1223.             return false;
  1224.         }
  1225.  
  1226.         $meta_data stream_get_meta_data($inputHandle);
  1227.         foreach ($meta_data['wrapper_data'as $wrapper_data)
  1228.         {
  1229.             if (substr($wrapper_data0strlen("Content-Disposition")) == "Content-Disposition"{
  1230.                 $contentfilename explode ("\""$wrapper_data);
  1231.                 $target $contentfilename[1];
  1232.             }
  1233.         }
  1234.  
  1235.         // Set the target path if not given
  1236.         if (!$target{
  1237.             $target $config->getValue('config.tmp_path').DS.JInstallerHelper::getFilenameFromURL($url);
  1238.         else {
  1239.             $target $config->getValue('config.tmp_path').DS.basename($target);
  1240.         }
  1241.  
  1242.         // Initialize contents buffer
  1243.         $contents null;
  1244.  
  1245.         while (!feof($inputHandle))
  1246.         {
  1247.             $contents .= fread($inputHandle4096);
  1248.             if ($contents == false{
  1249.                 JError::raiseWarning(44'Failed reading network resource: '.$php_errormsg);
  1250.                 return false;
  1251.             }
  1252.         }
  1253.  
  1254.         // Write buffer to file
  1255.         JFile::write($target$contents);
  1256.  
  1257.         // Close file pointer resource
  1258.         fclose($inputHandle);
  1259.  
  1260.         // Return the name of the downloaded package
  1261.         return basename($target);
  1262.     }
  1263.  
  1264.     /**
  1265.      * Unpacks a file and verifies it as a Joomla element package
  1266.      * Supports .gz .tar .tar.gz and .zip
  1267.      *
  1268.      * @static
  1269.      * @param string $p_filename The uploaded package filename or install directory
  1270.      * @return boolean True on success, False on error
  1271.      * @since 1.5
  1272.      */
  1273.     function unpack($p_filename)
  1274.     {
  1275.         // Path to the archive
  1276.         $archivename $p_filename;
  1277.  
  1278.         // Temporary folder to extract the archive into
  1279.         $tmpdir uniqid('install_');
  1280.  
  1281.         // Clean the paths to use for archive extraction
  1282.         $extractdir JPath::clean(dirname($p_filename).DS.$tmpdir);
  1283.         $archivename JPath::clean($archivename);
  1284.  
  1285.         // do the unpacking of the archive
  1286.         $result JArchive::extract$archivename$extractdir);
  1287.  
  1288.         if $result === false {
  1289.             return false;
  1290.         }
  1291.  
  1292.  
  1293.         /*
  1294.          * Lets set the extraction directory and package file in the result array so we can
  1295.          * cleanup everything properly later on.
  1296.          */
  1297.         $retval['extractdir'$extractdir;
  1298.         $retval['packagefile'$archivename;
  1299.  
  1300.         /*
  1301.          * Try to find the correct install directory.  In case the package is inside a
  1302.          * subdirectory detect this and set the install directory to the correct path.
  1303.          *
  1304.          * List all the items in the installation directory.  If there is only one, and
  1305.          * it is a folder, then we will set that folder to be the installation folder.
  1306.          */
  1307.         $dirList array_merge(JFolder::files($extractdir'')JFolder::folders($extractdir''));
  1308.  
  1309.         if (count($dirList== 1)
  1310.         {
  1311.             if (JFolder::exists($extractdir.DS.$dirList[0]))
  1312.             {
  1313.                 $extractdir JPath::clean($extractdir.DS.$dirList[0]);
  1314.             }
  1315.         }
  1316.  
  1317.         /*
  1318.          * We have found the install directory so lets set it and then move on
  1319.          * to detecting the extension type.
  1320.          */
  1321.         $retval['dir'$extractdir;
  1322.  
  1323.         /*
  1324.          * Get the extension type and return the directory/type array on success or
  1325.          * false on fail.
  1326.          */
  1327.         if ($retval['type'JInstallerHelper::detectType($extractdir))
  1328.         {
  1329.             return $retval;
  1330.         else
  1331.         {
  1332.             return false;
  1333.         }
  1334.     }
  1335.  
  1336.     /**
  1337.      * Method to detect the extension type from a package directory
  1338.      *
  1339.      * @static
  1340.      * @param string $p_dir Path to package directory
  1341.      * @return mixed Extension type string or boolean false on fail
  1342.      * @since 1.5
  1343.      */
  1344.     function detectType($p_dir)
  1345.     {
  1346.         // Search the install dir for an xml file
  1347.         $files JFolder::files($p_dir'\.xml$'truetrue);
  1348.  
  1349.         if (count($files0)
  1350.         {
  1351.  
  1352.             foreach ($files as $file)
  1353.             {
  1354.                 $xmlDoc JFactory::getXMLParser();
  1355.                 $xmlDoc->resolveErrors(true);
  1356.  
  1357.                 if (!$xmlDoc->loadXML($filefalsetrue))
  1358.                 {
  1359.                     // Free up memory from DOMIT parser
  1360.                     unset ($xmlDoc);
  1361.                     continue;
  1362.                 }
  1363.                 $root $xmlDoc->documentElement;
  1364.  
  1365.                 if ($root->getTagName(!= "install" && $root->getTagName(!= 'mosinstall')
  1366.                 {
  1367.                     continue;
  1368.                 }
  1369.  
  1370.                 $type $root->getAttribute('type');
  1371.                 // Free up memory from DOMIT parser
  1372.                 unset ($xmlDoc);
  1373.                 return $type;
  1374.             }
  1375.  
  1376.             JError::raiseWarning(1JText::_('ERRORNOTFINDJOOMLAXMLSETUPFILE'));
  1377.             // Free up memory from DOMIT parser
  1378.             unset ($xmlDoc);
  1379.             return false;
  1380.         else
  1381.         {
  1382.             JError::raiseWarning(1JText::_('ERRORNOTFINDXMLSETUPFILE'));
  1383.             return false;
  1384.         }
  1385.     }
  1386.  
  1387.     /**
  1388.      * Gets a file name out of a url
  1389.      *
  1390.      * @static
  1391.      * @param string $url URL to get name from
  1392.      * @return mixed String filename or boolean false if failed
  1393.      * @since 1.5
  1394.      */
  1395.     function getFilenameFromURL($url)
  1396.     {
  1397.         if (is_string($url)) {
  1398.             $parts explode('/'$url);
  1399.             return $parts[count($parts1];
  1400.         }
  1401.         return false;
  1402.     }
  1403.  
  1404.     /**
  1405.      * Clean up temporary uploaded package and unpacked extension
  1406.      *
  1407.      * @static
  1408.      * @param string $p_file Path to the uploaded package file
  1409.      * @param string $resultdir Path to the unpacked extension
  1410.      * @return boolean True on success
  1411.      * @since 1.5
  1412.      */
  1413.     function cleanupInstall($package$resultdir)
  1414.     {
  1415.         $config =JFactory::getConfig();
  1416.  
  1417.         // Does the unpacked extension directory exist?
  1418.         if (is_dir($resultdir)) {
  1419.             JFolder::delete($resultdir);
  1420.         }
  1421.  
  1422.         // Is the package file a valid file?
  1423.         if (is_file($package)) {
  1424.             JFile::delete($package);
  1425.         elseif (is_file(JPath::clean($config->getValue('config.tmp_path').DS.$package))) {
  1426.             // It might also be just a base filename
  1427.             JFile::delete(JPath::clean($config->getValue('config.tmp_path').DS.$package));
  1428.         }
  1429.     }
  1430.  
  1431.     /**
  1432.      * Splits contents of a sql file into array of discreet queries
  1433.      * queries need to be delimited with end of statement marker ';'
  1434.      * @param string 
  1435.      * @return array 
  1436.      */
  1437.     function splitSql($sql)
  1438.     {
  1439.         $sql trim($sql);
  1440.         $sql preg_replace("/\n\#[^\n]*/"''"\n".$sql);
  1441.         $buffer array ();
  1442.         $ret array ();
  1443.         $in_string false;
  1444.  
  1445.         for ($i 0$i strlen($sql1$i ++)
  1446.         {
  1447.             if ($sql[$i== ";" && !$in_string{
  1448.                 $ret[substr($sql0$i);
  1449.                 $sql substr($sql$i +1);
  1450.                 $i 0;
  1451.             }
  1452.  
  1453.             if ($in_string && ($sql[$i== $in_string&& $buffer[1!= "\\"{
  1454.                 $in_string false;
  1455.             elseif (!$in_string && ($sql[$i== '"' || $sql[$i== "'"&& (!isset ($buffer[0]|| $buffer[0!= "\\")) {
  1456.                 $in_string $sql[$i];
  1457.             if (isset ($buffer[1])) {
  1458.                 $buffer[0$buffer[1];
  1459.             }
  1460.             $buffer[1$sql[$i];
  1461.         }
  1462.  
  1463.         if (!empty ($sql)) {
  1464.             $ret[$sql;
  1465.         }
  1466.         return ($ret);
  1467.     }
  1468. }
  1469. ?>

Documentation generated on Mon, 05 Mar 2007 21:08:39 +0000 by phpDocumentor 1.3.1