plugins/admin/PluginManager.php
changeset 527 21e11f564463
parent 526 b2fb50d572c7
child 529 7803c9db3506
equal deleted inserted replaced
526:b2fb50d572c7 527:21e11f564463
    24  * format, and we think the best way to do this is with JSON. It is important that plugins define both the current version and
    24  * format, and we think the best way to do this is with JSON. It is important that plugins define both the current version and
    25  * a list of all past versions, and then have upgrade sections telling which version they go from and which one they go to.
    25  * a list of all past versions, and then have upgrade sections telling which version they go from and which one they go to.
    26  * 
    26  * 
    27  * The format for the special comment blocks is:
    27  * The format for the special comment blocks is:
    28  <code>
    28  <code>
    29  /**!blocktype( param1 = "value1" [ param2 = "value2" ... ] )**
    29  /**!blocktype( param1 = "value1"; [ param2 = "value2"; ... ] )**
    30  
    30  
    31  ... block content ...
    31  ... block content ...
    32  
    32  
    33  **!* / (remove that last space)
    33  **!* / (remove that last space)
    34  </code>
    34  </code>
    56  </code>
    56  </code>
    57  * This is the format for language data:
    57  * This is the format for language data:
    58  <code>
    58  <code>
    59  /**!language**
    59  /**!language**
    60  {
    60  {
       
    61    // each entry at this level should be an ISO-639-1 language code.
    61    eng: {
    62    eng: {
       
    63      // from here on in is the standard langauge file format
    62      categories: [ 'meta', 'foo', 'bar' ],
    64      categories: [ 'meta', 'foo', 'bar' ],
    63      strings: {
    65      strings: {
    64        meta: {
    66        meta: {
    65          foo: "Foo strings",
    67          foo: "Foo strings",
    66          bar: "Bar strings"
    68          bar: "Bar strings"
    84  
    86  
    85  **!* /
    87  **!* /
    86  </code>
    88  </code>
    87  * And finally, the format for upgrade schemas:
    89  * And finally, the format for upgrade schemas:
    88  <code>
    90  <code>
    89  /**!upgrade from = "0.1-alpha1" to = "0.1-alpha2" **
    91  /**!upgrade from = "0.1-alpha1"; to = "0.1-alpha2"; **
    90  
    92  
    91  **!* /
    93  **!* /
    92  </code>
    94  </code>
       
    95  * As a courtesy to your users, we ask that you also include an "uninstall" block that reverses any changes your plugin makes
       
    96  * to the database upon installation. The syntax is identical to that of the install block.
       
    97  * 
    93  * Remember that upgrades will always be done incrementally, so if the user is upgrading 0.1-alpha2 to 0.1, Enano's plugin
    98  * Remember that upgrades will always be done incrementally, so if the user is upgrading 0.1-alpha2 to 0.1, Enano's plugin
    94  * engine will run the 0.1-alpha2 to 0.1-beta1 upgrader, then the 0.1-beta1 to 0.1 upgrader, going by the versions listed in
    99  * engine will run the 0.1-alpha2 to 0.1-beta1 upgrader, then the 0.1-beta1 to 0.1 upgrader, going by the versions listed in
    95  * the example metadata block above.
   100  * the example metadata block above. As with the standard Enano installer, prefixing a query with '@' will cause it to be
       
   101  * performed "blindly", e.g. not checked for errors.
    96  * 
   102  * 
    97  * All of this information is effective as of Enano 1.1.4.
   103  * All of this information is effective as of Enano 1.1.4.
    98  */
   104  */
    99 
   105 
   100 // Plugin manager "2.0"
   106 // Plugin manager "2.0"
   108     $login_link = makeUrlNS('Special', 'Login/' . $paths->nslist['Special'] . 'Administration', 'level=' . USER_LEVEL_ADMIN, true);
   114     $login_link = makeUrlNS('Special', 'Login/' . $paths->nslist['Special'] . 'Administration', 'level=' . USER_LEVEL_ADMIN, true);
   109     echo '<h3>' . $lang->get('adm_err_not_auth_title') . '</h3>';
   115     echo '<h3>' . $lang->get('adm_err_not_auth_title') . '</h3>';
   110     echo '<p>' . $lang->get('adm_err_not_auth_body', array( 'login_link' => $login_link )) . '</p>';
   116     echo '<p>' . $lang->get('adm_err_not_auth_body', array( 'login_link' => $login_link )) . '</p>';
   111     return;
   117     return;
   112   }
   118   }
       
   119   
       
   120   $plugin_list = $plugins->get_plugin_list();
   113   
   121   
   114   // Are we processing an AJAX request from the smartform?
   122   // Are we processing an AJAX request from the smartform?
   115   if ( $paths->getParam(0) == 'action.json' )
   123   if ( $paths->getParam(0) == 'action.json' )
   116   {
   124   {
   117     // Set to application/json to discourage advertisement scripts
   125     // Set to application/json to discourage advertisement scripts
   131         // Is the action to perform specified?
   139         // Is the action to perform specified?
   132         if ( isset($request['mode']) )
   140         if ( isset($request['mode']) )
   133         {
   141         {
   134           switch ( $request['mode'] )
   142           switch ( $request['mode'] )
   135           {
   143           {
       
   144             case 'install':
       
   145               // did they specify a plugin to operate on?
       
   146               if ( !isset($request['plugin']) )
       
   147               {
       
   148                 $return = array(
       
   149                   'mode' => 'error',
       
   150                   'error' => 'No plugin specified.',
       
   151                 );
       
   152                 break;
       
   153               }
       
   154               
       
   155               $return = $plugins->install_plugin($request['plugin'], $plugin_list);
       
   156               break;
       
   157             case 'upgrade':
       
   158               // did they specify a plugin to operate on?
       
   159               if ( !isset($request['plugin']) )
       
   160               {
       
   161                 $return = array(
       
   162                   'mode' => 'error',
       
   163                   'error' => 'No plugin specified.',
       
   164                 );
       
   165                 break;
       
   166               }
       
   167               
       
   168               $return = $plugins->upgrade_plugin($request['plugin'], $plugin_list);
       
   169               break;
       
   170             case 'uninstall':
       
   171               // did they specify a plugin to operate on?
       
   172               if ( !isset($request['plugin']) )
       
   173               {
       
   174                 $return = array(
       
   175                   'mode' => 'error',
       
   176                   'error' => 'No plugin specified.',
       
   177                 );
       
   178                 break;
       
   179               }
       
   180               
       
   181               $return = $plugins->uninstall_plugin($request['plugin'], $plugin_list);
       
   182               break;
       
   183             case 'disable':
       
   184             case 'enable':
       
   185               $flags_col = ( $request['mode'] == 'disable' ) ?
       
   186                             "plugin_flags | "  . PLUGIN_DISABLED :
       
   187                             "plugin_flags & ~" . PLUGIN_DISABLED;
       
   188               // did they specify a plugin to operate on?
       
   189               if ( !isset($request['plugin']) )
       
   190               {
       
   191                 $return = array(
       
   192                   'mode' => 'error',
       
   193                   'error' => 'No plugin specified.',
       
   194                 );
       
   195                 break;
       
   196               }
       
   197               // is the plugin in the directory and already installed?
       
   198               if ( !isset($plugin_list[$request['plugin']]) || (
       
   199                   isset($plugin_list[$request['plugin']]) && !$plugin_list[$request['plugin']]['installed']
       
   200                 ))
       
   201               {
       
   202                 $return = array(
       
   203                   'mode' => 'error',
       
   204                   'error' => 'Invalid plugin specified.',
       
   205                 );
       
   206                 break;
       
   207               }
       
   208               // get plugin id
       
   209               $dataset =& $plugin_list[$request['plugin']];
       
   210               if ( empty($dataset['plugin id']) )
       
   211               {
       
   212                 $return = array(
       
   213                   'mode' => 'error',
       
   214                   'error' => 'Couldn\'t retrieve plugin ID.',
       
   215                 );
       
   216                 break;
       
   217               }
       
   218               // perform update
       
   219               $q = $db->sql_query('UPDATE ' . table_prefix . "plugins SET plugin_flags = $flags_col WHERE plugin_id = {$dataset['plugin id']};");
       
   220               if ( !$q )
       
   221                 $db->die_json();
       
   222               
       
   223               $return = array(
       
   224                 'success' => true
       
   225               );
       
   226               break;
       
   227             case 'import':
       
   228               // import all of the plugin_* config entries
       
   229               $q = $db->sql_query('SELECT config_name, config_value FROM ' . table_prefix . "config WHERE config_name LIKE 'plugin_%';");
       
   230               if ( !$q )
       
   231                 $db->die_json();
       
   232               
       
   233               while ( $row = $db->fetchrow($q) )
       
   234               {
       
   235                 $plugin_filename = preg_replace('/^plugin_/', '', $row['config_name']);
       
   236                 if ( isset($plugin_list[$plugin_filename]) && !@$plugin_list[$plugin_filename]['installed'] )
       
   237                 {
       
   238                   $return = $plugins->install_plugin($plugin_filename, $plugin_list);
       
   239                   if ( !$return['success'] )
       
   240                     break 2;
       
   241                   if ( $row['config_value'] == '0' )
       
   242                   {
       
   243                     $fn_db = $db->escape($plugin_filename);
       
   244                     $q = $db->sql_query('UPDATE ' . table_prefix . "plugins SET plugin_flags = plugin_flags | " . PLUGIN_DISABLED . " WHERE plugin_filename = '$fn_db';");
       
   245                     if ( !$q )
       
   246                       $db->die_json();
       
   247                   }
       
   248                 }
       
   249               }
       
   250               $db->free_result($q);
       
   251               
       
   252               $q = $db->sql_query('DELETE FROM ' . table_prefix . "config WHERE config_name LIKE 'plugin_%';");
       
   253               if ( !$q )
       
   254                 $db->die_json();
       
   255               
       
   256               $return = array('success' => true);
       
   257               break;
   136             default:
   258             default:
   137               // The requested action isn't something this script knows how to do
   259               // The requested action isn't something this script knows how to do
   138               $return = array(
   260               $return = array(
   139                 'mode' => 'error',
   261                 'mode' => 'error',
   140                 'error' => 'Unknown mode "' . $request['mode'] . '" sent in request'
   262                 'error' => 'Unknown mode "' . $request['mode'] . '" sent in request'
   176   
   298   
   177   //
   299   //
   178   // Not a JSON request, output normal HTML interface
   300   // Not a JSON request, output normal HTML interface
   179   //
   301   //
   180   
   302   
   181   // Scan all plugins
       
   182   $plugin_list = array();
       
   183   
       
   184   if ( $dirh = @opendir( ENANO_ROOT . '/plugins' ) )
       
   185   {
       
   186     while ( $dh = @readdir($dirh) )
       
   187     {
       
   188       if ( !preg_match('/\.php$/i', $dh) )
       
   189         continue;
       
   190       $fullpath = ENANO_ROOT . "/plugins/$dh";
       
   191       // it's a PHP file, attempt to read metadata
       
   192       // pass 1: try to read a !info block
       
   193       $blockdata = $plugins->parse_plugin_blocks($fullpath, 'info');
       
   194       if ( empty($blockdata) )
       
   195       {
       
   196         // no !info block, check for old header
       
   197         $fh = @fopen($fullpath, 'r');
       
   198         if ( !$fh )
       
   199           // can't read, bail out
       
   200           continue;
       
   201         $plugin_data = array();
       
   202         for ( $i = 0; $i < 8; $i++ )
       
   203         {
       
   204           $plugin_data[] = @fgets($fh, 8096);
       
   205         }
       
   206         // close our file handle
       
   207         fclose($fh);
       
   208         // is the header correct?
       
   209         if ( trim($plugin_data[0]) != '<?php' || trim($plugin_data[1]) != '/*' )
       
   210         {
       
   211           // nope. get out.
       
   212           continue;
       
   213         }
       
   214         // parse all the variables
       
   215         $plugin_meta = array();
       
   216         for ( $i = 2; $i <= 7; $i++ )
       
   217         {
       
   218           if ( !preg_match('/^([A-z0-9 ]+?): (.+?)$/', trim($plugin_data[$i]), $match) )
       
   219             continue 2;
       
   220           $plugin_meta[ strtolower($match[1]) ] = $match[2];
       
   221         }
       
   222       }
       
   223       else
       
   224       {
       
   225         // parse JSON block
       
   226         $plugin_data =& $blockdata[0]['value'];
       
   227         $plugin_data = enano_clean_json(enano_trim_json($plugin_data));
       
   228         try
       
   229         {
       
   230           $plugin_meta_uc = enano_json_decode($plugin_data);
       
   231         }
       
   232         catch ( Exception $e )
       
   233         {
       
   234           continue;
       
   235         }
       
   236         // convert all the keys to lowercase
       
   237         $plugin_meta = array();
       
   238         foreach ( $plugin_meta_uc as $key => $value )
       
   239         {
       
   240           $plugin_meta[ strtolower($key) ] = $value;
       
   241         }
       
   242       }
       
   243       if ( !isset($plugin_meta) || !is_array(@$plugin_meta) )
       
   244       {
       
   245         // parsing didn't work.
       
   246         continue;
       
   247       }
       
   248       // check for required keys
       
   249       $required_keys = array('plugin name', 'plugin uri', 'description', 'author', 'version', 'author uri');
       
   250       foreach ( $required_keys as $key )
       
   251       {
       
   252         if ( !isset($plugin_meta[$key]) )
       
   253           // not set, skip this plugin
       
   254           continue 2;
       
   255       }
       
   256       // decide if it's a system plugin
       
   257       $plugin_meta['system plugin'] = in_array($dh, $plugins->system_plugins);
       
   258       // reset installed variable
       
   259       $plugin_meta['installed'] = false;
       
   260       $plugin_meta['status'] = 0;
       
   261       // all checks passed
       
   262       $plugin_list[$dh] = $plugin_meta;
       
   263     }
       
   264   }
       
   265   // gather info about installed plugins
       
   266   $q = $db->sql_query('SELECT plugin_filename, plugin_version, plugin_flags FROM ' . table_prefix . 'plugins;');
       
   267   if ( !$q )
       
   268     $db->_die();
       
   269   while ( $row = $db->fetchrow() )
       
   270   {
       
   271     if ( !isset($plugin_list[ $row['plugin_filename'] ]) )
       
   272     {
       
   273       // missing plugin file, don't report (for now)
       
   274       continue;
       
   275     }
       
   276     $filename =& $row['plugin_filename'];
       
   277     $plugin_list[$filename]['installed'] = true;
       
   278     $plugin_list[$filename]['status'] = PLUGIN_INSTALLED;
       
   279     if ( $row['plugin_version'] != $plugin_list[$filename]['version'] )
       
   280     {
       
   281       $plugin_list[$filename]['status'] |= PLUGIN_OUTOFDATE;
       
   282       $plugin_list[$filename]['version installed'] = $row['plugin_version'];
       
   283     }
       
   284     if ( $row['plugin_flags'] & PLUGIN_DISABLED )
       
   285     {
       
   286       $plugin_list[$filename]['status'] |= PLUGIN_DISABLED;
       
   287     }
       
   288   }
       
   289   $db->free_result();
       
   290   
       
   291   // sort it all out by filename
       
   292   ksort($plugin_list);
       
   293   
       
   294   // start printing things out
   303   // start printing things out
   295   acp_start_form();
       
   296   ?>
   304   ?>
   297   <div class="tblholder">
   305   <div class="tblholder">
   298     <table border="0" cellspacing="1" cellpadding="5">
   306     <table border="0" cellspacing="1" cellpadding="5">
   299       <?php
   307       <?php
   300       $rowid = '2';
   308       $rowid = '2';
   322         }
   330         }
   323         else if ( $data['installed'] && $data['status'] & PLUGIN_OUTOFDATE )
   331         else if ( $data['installed'] && $data['status'] & PLUGIN_OUTOFDATE )
   324         {
   332         {
   325           $color = '_red';
   333           $color = '_red';
   326           $status = $lang->get('acppl_lbl_status_need_upgrade');
   334           $status = $lang->get('acppl_lbl_status_need_upgrade');
   327           $buttons = 'uninstall|update';
   335           $buttons = 'uninstall|upgrade';
   328         }
   336         }
   329         else if ( $data['installed'] && $data['status'] & PLUGIN_DISABLED )
   337         else if ( $data['installed'] && $data['status'] & PLUGIN_DISABLED )
   330         {
   338         {
   331           $color = '_red';
   339           $color = '_red';
   332           $status = $lang->get('acppl_lbl_status_disabled');
   340           $status = $lang->get('acppl_lbl_status_disabled');
   387         echo "<tr>
   395         echo "<tr>
   388                 <td class=\"row{$rowid}$color\">
   396                 <td class=\"row{$rowid}$color\">
   389                   <div style=\"float: right;\">
   397                   <div style=\"float: right;\">
   390                     <b>$status</b>
   398                     <b>$status</b>
   391                   </div>
   399                   </div>
   392                   <div style=\"cursor: pointer;\" onclick=\"if ( !this.fx ) this.fx = new Spry.Effect.Blind('plugininfo_$uuid', { duration: 500, from: '0%', to: '100%', toggle: true }); this.fx.start();\"
   400                   <div style=\"cursor: pointer;\" onclick=\"if ( !this.fx ) this.fx = new Spry.Effect.Blind('plugininfo_$uuid', { duration: 500, from: '0%', to: '100%', toggle: true }); this.fx.start();\">
   393                     $plugin_basics
   401                     $plugin_basics
   394                   </div>
   402                   </div>
   395                   <span class=\"menuclear\"></span>
   403                   <span class=\"menuclear\"></span>
   396                   <div id=\"plugininfo_$uuid\" style=\"display: none;\">
   404                   <div id=\"plugininfo_$uuid\" style=\"display: none;\">
   397                     $desc
   405                     $desc
   408       }
   416       }
   409       ?>
   417       ?>
   410     </table>
   418     </table>
   411   </div>
   419   </div>
   412   <?php
   420   <?php
   413   echo '</form>';
   421   // are there still old style plugin entries?
       
   422   $q = $db->sql_query('SELECT 1 FROM ' . table_prefix . "config WHERE config_name LIKE 'plugin_%';");
       
   423   if ( !$q )
       
   424     $db->_die();
       
   425   
       
   426   $count = $db->numrows();
       
   427   $db->free_result($q);
       
   428   
       
   429   if ( $count > 0 )
       
   430   {
       
   431     echo '<h3>' . $lang->get('acppl_msg_old_entries_title') . '</h3>';
       
   432     echo '<p>' . $lang->get('acppl_msg_old_entries_body') . '</p>';
       
   433     echo '<p><a class="abutton abutton_green" href="#" onclick="ajaxPluginAction(\'import\', \'\', false); return false;">' . $lang->get('acppl_btn_import_old') . '</a></p>';
       
   434   }
   414 }
   435 }