PEAR_PackageUpdate_Web
[ class tree: PEAR_PackageUpdate_Web ] [ index: PEAR_PackageUpdate_Web ] [ all elements ]

Source for file Web.php

Documentation is available at Web.php

  1. <?php
  2. /*
  3.  * This is a HTML driver for PEAR_PackageUpdate.
  4.  *
  5.  * PHP versions 4 and 5
  6.  *
  7.  * LICENSE: This source file is subject to version 3.01 of the PHP license
  8.  * that is available through the world-wide-web at the following URI:
  9.  * http://www.php.net/license/3_01.txt.  If you did not receive a copy of
  10.  * the PHP License and are unable to obtain it through the web, please
  11.  * send a note to license@php.net so we can mail you a copy immediately.
  12.  *
  13.  * @category   PEAR
  14.  * @package    PEAR_PackageUpdate_Web
  15.  * @author     Laurent Laville <pear@laurent-laville.org>
  16.  * @copyright  2006-2007 Laurent Laville
  17.  * @license    http://www.php.net/license/3_01.txt  PHP License 3.01
  18.  * @version    CVS: $Id: Web.php,v 1.6 2007/06/23 20:52:29 farell Exp $
  19.  * @since      File available since Release 0.1.0
  20.  */
  21.  
  22. require_once 'HTML/QuickForm.php';
  23.  
  24. /**
  25.  * This is a HTML driver for PEAR_PackageUpdate.
  26.  *
  27.  * A package to make adding self updating functionality to other
  28.  * packages easy.
  29.  *
  30.  * The interface for this package must allow for the following
  31.  * functionality:
  32.  * - check to see if a new version is available for a given
  33.  *   package on a given channel
  34.  *   - check minimum state
  35.  * - present information regarding the upgrade (version, size)
  36.  *   - inform user about dependencies
  37.  * - allow user to confirm or cancel upgrade
  38.  * - download and install the package
  39.  * - track preferences on a per package basis
  40.  *   - don't ask again
  41.  *   - don't ask until next release
  42.  *   - only ask for state XXXX or higher
  43.  *   - bug/minor/major updates only
  44.  * - update channel automatically
  45.  * - force application to exit when upgrade complete
  46.  *   - PHP-GTK/CLI apps must exit to allow classes to reload
  47.  *   - web front end could send headers to reload certain page
  48.  *
  49.  * This class is simply a wrapper for PEAR classes that actually
  50.  * do the work.
  51.  *
  52.  * EXAMPLE:
  53.  * <code>
  54.  * <?php
  55.  *  class Goo {
  56.  *      function __construct()
  57.  *      {
  58.  *          // Check for updates...
  59.  *          require_once 'PEAR/PackageUpdate.php';
  60.  *          $ppu =& PEAR_PackageUpdate::factory('Web', 'XML_RPC', 'pear');
  61.  *          if ($ppu !== false) {
  62.  *              if ($ppu->checkUpdate()) {
  63.  *                  // Use a dialog window to ask permission to update.
  64.  *                  if ($ppu->presentUpdate()) {
  65.  *                      if ($ppu->update()) {
  66.  *                          // If the update succeeded, the application should
  67.  *                          // be restarted.
  68.  *                          $ppu->forceRestart();
  69.  *                      }
  70.  *                  }
  71.  *              }
  72.  *          }
  73.  *          // ...
  74.  *      }
  75.  *      // ...
  76.  *  }
  77.  * ?>
  78.  * </code>
  79.  *
  80.  * @category   PEAR
  81.  * @package    PEAR_PackageUpdate_Web
  82.  * @author     Laurent Laville <pear@laurent-laville.org>
  83.  * @copyright  2006-2007 Laurent Laville
  84.  * @license    http://www.php.net/license/3_01.txt  PHP License 3.01
  85.  * @version    Release: @package_version@
  86.  * @since      Class available since Release 0.1.0
  87.  */
  88.  
  89. class PEAR_PackageUpdate_Web extends PEAR_PackageUpdate
  90. {
  91.     /**
  92.      * The main Dialog widget.
  93.      *
  94.      * @access public
  95.      * @var    object 
  96.      * @since  0.1.0
  97.      */
  98.     var $mainwidget;
  99.  
  100.     /**
  101.      * The preference Dialog widget.
  102.      *
  103.      * @access public
  104.      * @var    object 
  105.      * @since  0.1.0
  106.      */
  107.     var $prefwidget;
  108.  
  109.     /**
  110.      * The error Dialog widget.
  111.      *
  112.      * @access public
  113.      * @var    object 
  114.      * @since  0.1.0
  115.      */
  116.     var $errwidget;
  117.  
  118.     /**
  119.      * Style sheet for the custom layout
  120.      *
  121.      * @var    string 
  122.      * @access public
  123.      * @since  0.3.0
  124.      */
  125.     var $css;
  126.  
  127.     /**
  128.      * Creates the dialog that will ask the user if it is ok to update.
  129.      *
  130.      * @access protected
  131.      * @return void 
  132.      * @since  0.1.0
  133.      */
  134.     function createMainDialog()
  135.     {
  136.         // Create the dialog
  137.         $this->mainwidget = new HTML_QuickForm('infoPPU');
  138.         $this->mainwidget->removeAttribute('name');        // XHTML compliance
  139.  
  140.         // Create a title string.
  141.         $title 'Update available for: ' $this->packageName;
  142.         $this->mainwidget->addElement('header'''$title);
  143.  
  144.         // Create an image placeholder and the message for the dialog.
  145.         $msg  'A new version of ' $this->packageName ' ';
  146.         $msg .= " is available.\n\nWould you like to upgrade?";
  147.         $this->mainwidget->addElement('static''message''<div id="widget-icon-info"></div>'nl2br($msg));
  148.  
  149.         // The update details.
  150.         $this->mainwidget->addElement('text''current_version''Current Version:');
  151.         $this->mainwidget->addElement('text''release_version''Release Version:');
  152.         $this->mainwidget->addElement('text''release_date''Release Date:');
  153.         $this->mainwidget->addElement('text''release_state''Release State:');
  154.         $this->mainwidget->addElement('static''release_notes''Release Notes:',
  155.             '<div class="autoscroll">' .
  156.             nl2br($this->info['releasenotes'].
  157.             '</div>'
  158.         );
  159.         $this->mainwidget->addElement('text''release_by''Released By:');
  160.         $current_version ($this->instVersion === '0.0.0''- None -' $this->instVersion;
  161.  
  162.         $this->mainwidget->setDefaults(array(
  163.             'current_version' => $current_version,
  164.             'release_version' => $this->latestVersion,
  165.             'release_date'    => $this->info['releasedate'],
  166.             'release_state'   => $this->info['state'],
  167.             'release_by'      => $this->info['doneby']
  168.         ));
  169.  
  170.         $buttons = array();
  171.         // Add the preferences button.
  172.         $buttons[&HTML_QuickForm::createElement('submit''btnPrefs''Preferences');
  173.  
  174.         // Add the yes/no buttons.
  175.         $buttons[&HTML_QuickForm::createElement('submit''mainBtnNo''No');
  176.         $buttons[&HTML_QuickForm::createElement('submit''mainBtnYes''Yes');
  177.  
  178.         $this->mainwidget->addGroup($buttons'buttons''''&nbsp;'false);
  179.  
  180.         $this->mainwidget->freeze();
  181.     }
  182.  
  183.     /**
  184.      * Creates the dialog that will ask the user for his preferences.
  185.      *
  186.      * @access protected
  187.      * @return void 
  188.      * @since  0.1.0
  189.      */
  190.     function createPrefDialog($prefs)
  191.     {
  192.         // Create the dialog
  193.         $this->prefwidget = new HTML_QuickForm('prefPPU');
  194.         $this->prefwidget->removeAttribute('name');        // XHTML compliance
  195.  
  196.         // Create the preferences dialog title.
  197.         $title $this->packageName ' Update Preferences';
  198.         $this->prefwidget->addElement('header'''$title);
  199.  
  200.         // It needs a check box for "Don't ask again"
  201.         $this->prefwidget->addElement('checkbox''dontAsk''''Don\'t ask me again');
  202.         // Set the default.
  203.         if (isset($prefs[PEAR_PACKAGEUPDATE_PREF_NOUPDATES])) {
  204.             $this->prefwidget->setDefaults(array('dontAsk' => $prefs[PEAR_PACKAGEUPDATE_PREF_NOUPDATES]));
  205.         }
  206.  
  207.         // It needs a check box for the next release.
  208.         $this->prefwidget->addElement('checkbox''nextRelease''''Don\'t ask again until the next release.');
  209.         // Set the default.
  210.         if (isset($prefs[PEAR_PACKAGEUPDATE_PREF_NEXTRELEASE])) {
  211.             $this->prefwidget->setDefaults(array('nextRelease' => $prefs[PEAR_PACKAGEUPDATE_PREF_NEXTRELEASE]));
  212.         }
  213.  
  214.         // It needs a radio group for the state.
  215.         $allStates = array();
  216.         $allStates[&HTML_QuickForm::createElement('radio'nullnull'All states''all');
  217.         $allStates[&HTML_QuickForm::createElement('radio'nullnull'devel'PEAR_PACKAGEUPDATE_STATE_DEVEL);
  218.         $allStates[&HTML_QuickForm::createElement('radio'nullnull'alpha'PEAR_PACKAGEUPDATE_STATE_ALPHA);
  219.         $allStates[&HTML_QuickForm::createElement('radio'nullnull'beta'PEAR_PACKAGEUPDATE_STATE_BETA);
  220.         $allStates[&HTML_QuickForm::createElement('radio'nullnull'stable'PEAR_PACKAGEUPDATE_STATE_STABLE);
  221.         $this->prefwidget->addGroup($allStates'allStates''Only ask when the state is at least:''<br />');
  222.         // Set the default.
  223.         $stateDef (isset($prefs[PEAR_PACKAGEUPDATE_PREF_STATE])) ?
  224.             $prefs[PEAR_PACKAGEUPDATE_PREF_STATE'all';
  225.         $this->prefwidget->setDefaults(array('allStates' => $stateDef));
  226.  
  227.  
  228.         // It needs a radio group for the type.
  229.         $allTypes = array();
  230.         $allTypes[&HTML_QuickForm::createElement('radio'nullnull'All Release Types''all');
  231.         $allTypes[&HTML_QuickForm::createElement('radio'nullnull'Bug fix'PEAR_PACKAGEUPDATE_TYPE_BUG);
  232.         $allTypes[&HTML_QuickForm::createElement('radio'nullnull'Minor'PEAR_PACKAGEUPDATE_TYPE_MINOR);
  233.         $allTypes[&HTML_QuickForm::createElement('radio'nullnull'Major'PEAR_PACKAGEUPDATE_TYPE_MAJOR);
  234.         $this->prefwidget->addGroup($allTypes'allTypes''Only ask when the type is at least:''<br />');
  235.         // Set the default.
  236.         $typeDef (isset($prefs[PEAR_PACKAGEUPDATE_PREF_TYPE])) ?
  237.             $prefs[PEAR_PACKAGEUPDATE_PREF_TYPE'all';
  238.         $this->prefwidget->setDefaults(array('allTypes' => $typeDef));
  239.  
  240.         $buttons = array();
  241.         // Add the yes/no buttons.
  242.         $buttons[&HTML_QuickForm::createElement('submit''prefBtnNo''No');
  243.         $buttons[&HTML_QuickForm::createElement('submit''prefBtnYes''Yes');
  244.  
  245.         $this->prefwidget->addGroup($buttons'buttons''''&nbsp;'false);
  246.     }
  247.  
  248.     /**
  249.      * Creates the dialog that will show errors to the user.
  250.      *
  251.      * @access protected
  252.      * @return void 
  253.      * @since  0.1.0
  254.      */
  255.     function createErrorDialog($context = false)
  256.     {
  257.         // Don't do anything if the dialog already exists.
  258.         if (isset($this->errwidget)) {
  259.             return;
  260.         }
  261.  
  262.         // Create the dialog
  263.         $this->errwidget = new HTML_QuickForm('errorPPU');
  264.         $this->errwidget->removeAttribute('name');        // XHTML compliance
  265.  
  266.         // Create a title string.
  267.         $title 'Error(s) occured while trying to Update for: ' $this->packageName;
  268.         $this->errwidget->addElement('header'''$title);
  269.  
  270.         // Create an image placeholder and the message for the dialog.
  271.         $this->errwidget->addElement('static''icon''<div id="widget-icon-error"></div>');
  272.         $this->errwidget->addElement('static''message''Message:');
  273.  
  274.         if ($context{
  275.             // The error context details.
  276.             $this->errwidget->addElement('text''context_file''File:');
  277.             $this->errwidget->addElement('text''context_line''Line:');
  278.             $this->errwidget->addElement('text''context_function''Function:');
  279.             $this->errwidget->addElement('text''context_class''Class:');
  280.         }
  281.  
  282.         $buttons = array();
  283.         // Add the Ok button.
  284.         $buttons[&HTML_QuickForm::createElement('submit''errorBtnOk''Ok');
  285.  
  286.         $this->errwidget->addGroup($buttons'buttons''''&nbsp;'false);
  287.  
  288.         $this->errwidget->freeze();
  289.     }
  290.  
  291.     /**
  292.      * Creates and runs a dialog for setting preferences.
  293.      *
  294.      * @access public
  295.      * @return boolean true if the preferences were set and saved.
  296.      * @since  0.1.0
  297.      */
  298.     function prefDialog()
  299.     {
  300.         // The preferences dialog needs to have some inputs for the user.
  301.         // Get the current preferences so that defaults can be set.
  302.         $prefs $this->getPackagePreferences();
  303.  
  304.         // Create the preference dialog widget.
  305.         $this->createPrefDialog($prefs);
  306.  
  307.         $renderer &$this->getHtmlRendererWithoutLabel($this->prefwidget);
  308.  
  309.         // Get Html code to display
  310.         $html $this->toHtml($renderer);
  311.  
  312.         // Run the dialog and return whether or not the user clicked "Yes".
  313.         if ($this->prefwidget->validate()) {
  314.             $safe $this->prefwidget->exportValues();
  315.  
  316.             if (isset($safe['prefBtnYes'])) {
  317.                 // Get all of the preferences.
  318.                 $prefs = array();
  319.  
  320.                 // Check for the don't ask preference.
  321.                 $prefs[PEAR_PACKAGEUPDATE_PREF_NOUPDATES= isset($safe['dontAsk']);
  322.  
  323.                 // Check for next version.
  324.                 $prefs[PEAR_PACKAGEUPDATE_PREF_NEXTRELEASE= isset($safe['nextRelease']);
  325.  
  326.                 // Check for type.
  327.                 $prefs[PEAR_PACKAGEUPDATE_PREF_TYPE$safe['allTypes'];
  328.  
  329.                 // Check for state.
  330.                 $prefs[PEAR_PACKAGEUPDATE_PREF_STATE$safe['allStates'];
  331.  
  332.                 // Save the preferences.
  333.                 return $this->setPreferences($prefs);
  334.  
  335.             elseif (isset($safe['prefBtnNo'])) {
  336.                 return false;
  337.             }
  338.         }
  339.         echo $html;
  340.         exit();
  341.     }
  342.  
  343.     /**
  344.      * Redirects or exits to force the user to restart the application.
  345.      *
  346.      * @access public
  347.      * @return void 
  348.      * @since  0.1.0
  349.      */
  350.     function forceRestart()
  351.     {
  352.         // Reload current page.
  353.         header('Location: ' $_SERVER['PHP_SELF']);
  354.         exit();
  355.     }
  356.  
  357.     /**
  358.      * Checks to see if an update is available,
  359.      * and if package was already installed.
  360.      *
  361.      * Respects the user preferences when determining if an
  362.      * update is available. Returns true if an update is available
  363.      * and the user may want to update the package.
  364.      *
  365.      * @access public
  366.      * @return boolean true if an update is available.
  367.      * @since  0.4.0
  368.      */
  369.     function checkUpdate()
  370.     {
  371.         if (parent::checkUpdate()) {
  372.             if ($this->instVersion == '0.0.0'{
  373.                 $this->pushError(PEAR_PACKAGEUPDATE_ERROR_NOTINSTALLED,
  374.                     'warning'array('packagename' => $this->packageName)
  375.                     );
  376.             }
  377.         }
  378.     }
  379.  
  380.     /**
  381.      * Presents the user with the option to update.
  382.      *
  383.      * @access public
  384.      * @return boolean true if the user would like to update the package.
  385.      * @since  0.1.0
  386.      */
  387.     function presentUpdate()
  388.     {
  389.         // Make sure the info has been grabbed.
  390.         // This will just return if the info has already been grabbed.
  391.         $this->getPackageInfo();
  392.  
  393.         // Create the main dialog widget.
  394.         $this->createMainDialog();
  395.  
  396.         $renderer &$this->getHtmlRendererWithLabel($this->mainwidget);
  397.  
  398.         // Get Html code to display
  399.         $html $this->toHtml($renderer);
  400.  
  401.         // Run the dialog and return whether or not the user clicked "Yes".
  402.         if ($this->mainwidget->validate()) {
  403.             $safe $this->mainwidget->exportValues();
  404.  
  405.             if (isset($safe['mainBtnYes'])) {
  406.                 return true;
  407.             elseif (isset($safe['mainBtnNo'])) {
  408.                 return false;
  409.             else {
  410.                 $this->prefDialog();
  411.             }
  412.         }
  413.         echo $html;
  414.         exit();
  415.     }
  416.  
  417.     /**
  418.      * Presents an error in a dialog window.
  419.      *
  420.      * @access public
  421.      * @param  boolean $context  true if you want to have error context details
  422.      * @return boolean true if an error was displayed.
  423.      * @since  0.1.0
  424.      */
  425.     function errorDialog($context = false)
  426.     {
  427.         // Check to see if there are errors into stack.
  428.         if ($this->hasErrors()) {
  429.             $error $this->popError();
  430.  
  431.             // Create the error dialog widget.
  432.             $this->createErrorDialog($context);
  433.             $this->errwidget->setConstants(array('message' => $error['message']));
  434.             // Fill the context details
  435.             if ($context{
  436.                 $file $line $function $class '';
  437.  
  438.                 if (isset($error['context']['file'])) {
  439.                     $file $error['context']['file'];
  440.                 }
  441.                 if (isset($error['context']['line'])) {
  442.                     $line $error['context']['line'];
  443.                 }
  444.                 if (isset($error['context']['function'])) {
  445.                     $function $error['context']['function'];
  446.                 }
  447.                 if (isset($error['context']['class'])) {
  448.                     $class $error['context']['class'];
  449.                 }
  450.  
  451.                 $this->errwidget->setConstants(array(
  452.                     'context_file' => $file,
  453.                     'context_line' => $line,
  454.                     'context_function' => $function,
  455.                     'context_class' => $class
  456.                     ));
  457.             }
  458.  
  459.             $renderer &$this->getHtmlRendererWithLabel($this->errwidget);
  460.  
  461.             // Get Html code to display
  462.             $html $this->toHtml($renderer);
  463.  
  464.             // Run the dialog.
  465.             if ($this->errwidget->validate()) {
  466.                 return true;
  467.             }
  468.             echo $html;
  469.             exit();
  470.         }
  471.         // Nothing to do.
  472.         return false;
  473.     }
  474.  
  475.     /**
  476.      * Returns HTML renderer for a dialog with input labels and values
  477.      *
  478.      * @access protected
  479.      * @return object  instance of a QuickForm renderer
  480.      * @since  0.1.0
  481.      */
  482.     function &getHtmlRendererWithLabel(&$widget)
  483.     {
  484.         // Templates string
  485.         $formTemplate "\n<form{attributes}>"
  486.             . "\n<table class=\"dialogbox\">"
  487.             . "\n{content}"
  488.             . "\n</table>"
  489.             . "\n</form>";
  490.  
  491.         $headerTemplate "\n<tr>"
  492.             . "\n\t<td class=\"widget-header\" colspan=\"2\">"
  493.             . "\n\t\t{header}"
  494.             . "\n\t</td>"
  495.             . "\n</tr>";
  496.  
  497.         $elementTemplate "\n<tr>"
  498.             . "\n\t<td class=\"widget-label\"><!-- BEGIN label -->{label}<!-- END label --></td>"
  499.             . "\n\t<td class=\"widget-input\">{element}</td>"
  500.             . "\n</tr>";
  501.  
  502.         $elementNavig "\n<tr class=\"widget-buttons\">"
  503.             . "\n\t<td>&nbsp;</td>"
  504.             . "\n\t<td>{element}</td>"
  505.             . "\n</tr>";
  506.  
  507.         $renderer =$widget->defaultRenderer();
  508.  
  509.         $renderer->setFormTemplate($formTemplate);
  510.         $renderer->setHeaderTemplate($headerTemplate);
  511.         $renderer->setElementTemplate($elementTemplate);
  512.         $renderer->setElementTemplate($elementNavig'buttons');
  513.  
  514.         $widget->accept($renderer);
  515.  
  516.         return $renderer;
  517.     }
  518.  
  519.     /**
  520.      * Returns HTML renderer for a dialog with only input values (no labels)
  521.      *
  522.      * @access protected
  523.      * @return object  instance of a QuickForm renderer
  524.      * @since  0.1.0
  525.      */
  526.     function &getHtmlRendererWithoutLabel(&$widget)
  527.     {
  528.         // Templates string
  529.         $formTemplate "\n<form{attributes}>"
  530.             . "\n<table class=\"dialogbox\">"
  531.             . "\n{content}"
  532.             . "\n</table>"
  533.             . "\n</form>";
  534.  
  535.         $headerTemplate "\n<tr>"
  536.             . "\n\t<td class=\"widget-header\">"
  537.             . "\n\t\t{header}"
  538.             . "\n\t</td>"
  539.             . "\n</tr>";
  540.  
  541.         $elementTemplate "\n<tr>"
  542.             . "\n\t<td class=\"widget-input\">{element}</td>"
  543.             . "\n</tr>";
  544.  
  545.         $elementNavig "\n<tr class=\"widget-buttons\">"
  546.             . "\n\t<td>{element}</td>"
  547.             . "\n</tr>";
  548.  
  549.         $elementRadio "\n<tr>"
  550.             . "\n\t<td class=\"widget-input\"><!-- BEGIN label -->{label}<!-- END label --><br />{element}</td>"
  551.             . "\n</tr>";
  552.  
  553.         $renderer =$widget->defaultRenderer();
  554.  
  555.         $renderer->setFormTemplate($formTemplate);
  556.         $renderer->setHeaderTemplate($headerTemplate);
  557.         $renderer->setElementTemplate($elementTemplate);
  558.         $renderer->setElementTemplate($elementNavig'buttons');
  559.         $renderer->setElementTemplate($elementRadio'allStates');
  560.         $renderer->setElementTemplate($elementRadio'allTypes');
  561.  
  562.         $widget->accept($renderer);
  563.  
  564.         return $renderer;
  565.     }
  566.  
  567.     /**
  568.      * Returns the custom style sheet to use for layout
  569.      *
  570.      * @param  bool  $content (optional) Either return css filename or string contents
  571.      * @return string 
  572.      * @access public
  573.      * @since  0.3.0
  574.      */
  575.     function getStyleSheet($content = true)
  576.     {
  577.         if ($content{
  578.             $styles file_get_contents($this->css);
  579.         else {
  580.             $styles $this->css;
  581.         }
  582.         return $styles;
  583.     }
  584.  
  585.     /**
  586.      * Set the custom style sheet to use your own styles
  587.      *
  588.      * @param  string  $css (optional) File to read user-defined styles from
  589.      * @return bool    True if custom styles, false if default styles applied
  590.      * @access public
  591.      * @since  0.3.0
  592.      */
  593.     function setStyleSheet($css = null)
  594.     {
  595.         // default stylesheet is into package data directory
  596.         if (!isset($css)) {
  597.             $this->css = '@data_dir@' . DIRECTORY_SEPARATOR
  598.                  . '@package_name@' . DIRECTORY_SEPARATOR
  599.                  . 'ppu.css';
  600.         }
  601.  
  602.         $res = isset($css&& file_exists($css);
  603.         if ($res{
  604.             $this->css = $css;
  605.         }
  606.         return $res;
  607.     }
  608.  
  609.     /**
  610.      * Returns HTML code of a dialog box.
  611.      *
  612.      * @access public
  613.      * @return string 
  614.      * @since  0.1.0
  615.      */
  616.     function toHtml($renderer)
  617.     {
  618.         if (!isset($this->css)) {
  619.             // when no user-styles defined, used the default values
  620.             $this->setStyleSheet();
  621.         }
  622.         $styles $this->getStyleSheet();
  623.  
  624.         $body $renderer->toHtml();
  625.  
  626.         $html = <<<HTML
  627. <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
  628. <html>
  629. <head>
  630. <title>PEAR_PackageUpdate Web Frontend</title>
  631. <meta http-equiv="content-type" content="text/html; charset=UTF-8" />
  632. <style type="text/css">
  633. <!--
  634. $styles
  635.  -->
  636. </style>
  637. </head>
  638. <body>
  639. $body
  640. </body>
  641. </html>
  642. HTML;
  643.         return $html;
  644.     }
  645. }
  646. ?>

Documentation generated on Mon, 11 Mar 2019 15:06:31 -0400 by phpDocumentor 1.4.4. PEAR Logo Copyright © PHP Group 2004.