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