PageRenderTime 54ms CodeModel.GetById 22ms RepoModel.GetById 0ms app.codeStats 0ms

/system/expressionengine/controllers/cp/addons_plugins.php

https://bitbucket.org/studiobreakfast/sync
PHP | 755 lines | 473 code | 130 blank | 152 comment | 87 complexity | 32a69c87e1f93686d55b5534f7dc434e MD5 | raw file
  1. <?php if ( ! defined('BASEPATH')) exit('No direct script access allowed');
  2. /**
  3. * ExpressionEngine - by EllisLab
  4. *
  5. * @package ExpressionEngine
  6. * @author EllisLab Dev Team
  7. * @copyright Copyright (c) 2003 - 2012, EllisLab, Inc.
  8. * @license http://expressionengine.com/user_guide/license.html
  9. * @link http://expressionengine.com
  10. * @since Version 2.0
  11. * @filesource
  12. */
  13. // ------------------------------------------------------------------------
  14. /**
  15. * ExpressionEngine Plugin Administration Class
  16. *
  17. * @package ExpressionEngine
  18. * @subpackage Control Panel
  19. * @category Control Panel
  20. * @author EllisLab Dev Team
  21. * @link http://expressionengine.com
  22. */
  23. class Addons_plugins extends CI_Controller {
  24. var $paths = array();
  25. /**
  26. * Constructor
  27. *
  28. * @access public
  29. */
  30. function __construct()
  31. {
  32. parent::__construct();
  33. if ( ! $this->cp->allowed_group('can_access_addons', 'can_access_plugins'))
  34. {
  35. show_error(lang('unauthorized_access'));
  36. }
  37. $this->lang->loadfile('admin');
  38. }
  39. // --------------------------------------------------------------------
  40. /**
  41. * Plugin Homepage
  42. *
  43. * @access public
  44. * @return void
  45. */
  46. function index()
  47. {
  48. $this->load->library('table');
  49. $this->cp->set_variable('cp_page_title', lang('plugins'));
  50. $this->cp->set_breadcrumb(BASE.AMP.'C=addons', lang('addons'));
  51. $this->jquery->tablesorter('.mainTable', '{
  52. headers: {2: {sorter: false}},
  53. widgets: ["zebra"]
  54. }');
  55. $this->javascript->output('
  56. $(".toggle_all").toggle(
  57. function(){
  58. $(".mainTable tbody tr").addClass("selected");
  59. $("input.toggle").each(function() {
  60. this.checked = true;
  61. });
  62. }, function (){
  63. $(".mainTable tbody tr").removeClass("selected");
  64. $("input.toggle").each(function() {
  65. this.checked = false;
  66. });
  67. }
  68. );
  69. ');
  70. $this->javascript->compile();
  71. // Grab the data
  72. $plugins = $this->_get_installed_plugins();
  73. // Remote Plugins Disabled - no 2.0 feed
  74. // $remote = $this->_get_available_plugins($plugins);
  75. $remote = array();
  76. // Check folder permissions for all paths
  77. $is_writable = TRUE;
  78. foreach(array(PATH_PI, PATH_THIRD) as $path)
  79. {
  80. if ( ! is_really_writable($path))
  81. {
  82. $is_writable = FALSE;
  83. }
  84. }
  85. // Check dependencies
  86. $curl_installed = ( ! extension_loaded('curl') || ! function_exists('curl_init')) ? FALSE : TRUE;
  87. $sortby = FALSE;
  88. $sort_url = FALSE;
  89. if (count($remote) > 0)
  90. {
  91. $qm = ($this->config->item('force_query_string') == 'y') ? '' : '?';
  92. $total_rows = count($remote);
  93. $base = BASE.AMP.'C=addons_plugins';
  94. $perpage = ( ! $this->input->get_post('perpage')) ? 10 : $this->input->get_post('perpage');
  95. $page = ( ! $this->input->get_post('page')) ? 0 : $this->input->get_post('page');
  96. $sortby = ( ! $this->input->get_post('sortby')) ? '' : $this->input->get_post('sortby');
  97. if ($sortby == 'alpha')
  98. {
  99. $sort_url = $base;
  100. $base .= AMP.'sortby=alpha';
  101. usort($remote, array($this, '_plugin_title_sorter'));
  102. }
  103. else
  104. {
  105. $sort_url = $base.AMP.'sortby=alpha';
  106. }
  107. // Build the pagination
  108. $this->load->library('pagination');
  109. $config['base_url'] = $base;
  110. $config['total_rows'] = $total_rows;
  111. $config['per_page'] = $perpage;
  112. $config['page_query_string'] = TRUE;
  113. $config['query_string_segment'] = 'page';
  114. $config['full_tag_open'] = '<p id="paginationLinks">';
  115. $config['full_tag_close'] = '</p>';
  116. $config['prev_link'] = '<img src="'.$this->cp->cp_theme_url.'images/pagination_prev_button.gif" width="13" height="13" alt="&lt;" />';
  117. $config['next_link'] = '<img src="'.$this->cp->cp_theme_url.'images/pagination_next_button.gif" width="13" height="13" alt="&gt;" />';
  118. $config['first_link'] = '<img src="'.$this->cp->cp_theme_url.'images/pagination_first_button.gif" width="13" height="13" alt="&lt; &lt;" />';
  119. $config['last_link'] = '<img src="'.$this->cp->cp_theme_url.'images/pagination_last_button.gif" width="13" height="13" alt="&gt; &gt;" />';
  120. $this->pagination->initialize($config);
  121. // Extract the current page
  122. $remote = array_slice($remote, $page, $perpage-1);
  123. // Prep for output
  124. foreach ($remote as $key => $item)
  125. {
  126. $attr = explode('|', $item['dc']['subject']);
  127. $remote[$key]['dl_url'] = $attr[0];
  128. $remote[$key]['version'] = $attr[1];
  129. $remote[$key]['require'] = ( ! $attr[2] ) ? '' : $attr[2];
  130. $remote[$key]['link'] = $this->functions->fetch_site_index().$qm.'URL='.$item['link'];
  131. $remote[$key]['description'] = $this->functions->word_limiter($item['description'], '20');
  132. }
  133. }
  134. // Assemble view variables
  135. $vars['is_writable'] = $is_writable;
  136. $vars['remote_install'] = ( ! $is_writable OR ! $curl_installed) ? FALSE : TRUE;
  137. $vars['sort'] = $sortby;
  138. $vars['sort_url'] = $sort_url;
  139. $vars['plugins'] = $plugins;
  140. $vars['remote'] = $remote;
  141. $this->load->view('addons/plugin_manager', $vars);
  142. }
  143. // --------------------------------------------------------------------
  144. /**
  145. * Plugin Details
  146. *
  147. * Show all the plugin information
  148. *
  149. * @access public
  150. * @return void
  151. */
  152. function info()
  153. {
  154. $name = $this->input->get('name');
  155. // Basic security check
  156. if ( ! $name OR ! preg_match("/^[a-z0-9][\w.-]*$/i", $name))
  157. {
  158. $this->session->set_flashdata('message_failure', lang('no_additional_info'));
  159. $this->functions->redirect(BASE.AMP.'C=addons_plugins');
  160. }
  161. $this->load->library('table');
  162. $this->jquery->tablesorter('.mainTable', '{
  163. headers: {1: {sorter: false}},
  164. widgets: ["zebra"]
  165. }');
  166. $this->javascript->compile();
  167. $plugin = $this->_get_plugin_info($name);
  168. $this->cp->set_variable('cp_page_title', $plugin['pi_name']);
  169. // a bit of a breadcrumb override is needed
  170. $this->cp->set_variable('cp_breadcrumbs', array(
  171. BASE.AMP.'C=addons' => lang('addons'),
  172. BASE.AMP.'C=addons_plugins'=> lang('addons_plugins')
  173. ));
  174. $this->load->view('addons/plugin_info', array('plugin' => $plugin));
  175. }
  176. // --------------------------------------------------------------------
  177. /**
  178. * Plugin Remove Confirm
  179. *
  180. * Confirm Plugin Deletion
  181. *
  182. * @access public
  183. * @return void
  184. */
  185. function remove_confirm()
  186. {
  187. if ($this->config->item('demo_date') != FALSE)
  188. {
  189. show_error(lang('unauthorized_access'));
  190. }
  191. $this->load->helper('file');
  192. $this->cp->set_variable('cp_page_title', lang('plugin_delete_confirm'));
  193. $this->cp->set_breadcrumb(BASE.AMP.'C=addons', lang('addons'));
  194. $hidden = $this->input->post('toggle');
  195. if (count($hidden) == 0)
  196. {
  197. $this->functions->redirect(BASE.AMP.'C=addons_plugins');
  198. }
  199. $vars['message'] = (count($hidden) > 1) ? 'plugin_multiple_confirm' : 'plugin_single_confirm';
  200. $vars['hidden'] = $hidden;
  201. $this->load->view('addons/plugin_delete', $vars);
  202. }
  203. // --------------------------------------------------------------------
  204. /**
  205. * Delete Plugins
  206. *
  207. * Remove Plugin Files
  208. *
  209. * @access public
  210. * @return void
  211. */
  212. function remove()
  213. {
  214. if ($this->config->item('demo_date') != FALSE)
  215. {
  216. show_error(lang('unauthorized_access'));
  217. }
  218. $plugins = $this->input->post('deleted');
  219. $cp_message_success = '';
  220. $cp_message_failure = '';
  221. if ( ! is_array($plugins))
  222. {
  223. $this->functions->redirect(BASE.AMP.'C=addons_plugins');
  224. }
  225. foreach($plugins as $name)
  226. {
  227. // We now have more than one path, so we try them all
  228. $success = FALSE;
  229. if (@unlink(PATH_PI.'pi.'.$name.'.php'))
  230. {
  231. $success = TRUE;
  232. }
  233. else
  234. {
  235. // first thing's first, let's make sure this isn't part of a package
  236. $files = glob(PATH_THIRD.$name.'/*.php');
  237. $pi_key = array_search(PATH_THIRD.$name.'/pi.'.$name.'.php', $files);
  238. // remove this file from the list
  239. unset($files[$pi_key]);
  240. // any other PHP files in this directory? If not, balleet!
  241. if (empty($files))
  242. {
  243. $this->functions->delete_directory(PATH_THIRD.$name, TRUE);
  244. $success = TRUE;
  245. }
  246. }
  247. if ($success)
  248. {
  249. $cp_message_success .= ($success) ? lang('plugin_removal_success') : lang('plugin_removal_error');
  250. $cp_message_success .= ' '.ucwords(str_replace("_", " ", $name)).'<br>';
  251. }
  252. else
  253. {
  254. $cp_message_failure .= ($success) ? lang('plugin_removal_success') : lang('plugin_removal_error');
  255. $cp_message_failure .= ' '.ucwords(str_replace("_", " ", $name)).'<br>';
  256. }
  257. }
  258. if ($cp_message_success != '')
  259. {
  260. $cp_message['message_success'] = $cp_message_success;
  261. }
  262. if ($cp_message_failure != '')
  263. {
  264. $cp_message['message_failure'] = $cp_message_failure;
  265. }
  266. $this->session->set_flashdata($cp_message);
  267. $this->functions->redirect(BASE.AMP.'C=addons_plugins');
  268. }
  269. // --------------------------------------------------------------------
  270. /**
  271. * Install a Plugin
  272. *
  273. * Downloads and Installs a Plugin
  274. *
  275. * @access public
  276. * @return void
  277. */
  278. function install()
  279. {
  280. if ($this->config->item('demo_date') != FALSE)
  281. {
  282. show_error(lang('unauthorized_access'));
  283. }
  284. @include_once(APPPATH.'libraries/Pclzip.php');
  285. if ( ! is_really_writable(PATH_THIRD))
  286. {
  287. $this->session->set_flashdata('message_failure', lang('plugin_folder_not_writable'));
  288. $this->functions->redirect(BASE.AMP.'C=addons_plugins');
  289. }
  290. if ( ! extension_loaded('curl') OR ! function_exists('curl_init'))
  291. {
  292. $this->session->set_flashdata('message_failure', lang('plugin_no_curl_support'));
  293. $this->functions->redirect(BASE.AMP.'C=addons_plugins');
  294. }
  295. $file = $this->input->get_post('file');
  296. $local_name = basename($file);
  297. $local_file = PATH_THIRD.$local_name;
  298. // Get the remote file
  299. $c = curl_init($file);
  300. curl_setopt($c, CURLOPT_RETURNTRANSFER, 1);
  301. // prevent a PHP warning on certain servers
  302. if ( ! ini_get('safe_mode') && ! ini_get('open_basedir'))
  303. {
  304. curl_setopt($c, CURLOPT_FOLLOWLOCATION, 1);
  305. }
  306. $code = curl_exec($c);
  307. curl_close($c);
  308. $file_info = pathinfo($local_file);
  309. if ($file_info['extension'] == 'txt' ) // Get rid of any notes/headers in the TXT file
  310. {
  311. $code = strstr($code, '<?php');
  312. }
  313. if ( ! $fp = fopen($local_file, FOPEN_WRITE_CREATE_DESTRUCTIVE))
  314. {
  315. $this->session->set_flashdata('message_failure', lang('plugin_problem_creating_file'));
  316. $this->functions->redirect(BASE.AMP.'C=addons_plugins');
  317. }
  318. flock($fp, LOCK_EX);
  319. fwrite($fp, $code);
  320. flock($fp, LOCK_UN);
  321. fclose($fp);
  322. @chmod($local_file, 0777);
  323. // Check file information so we know what to do with it
  324. if ($file_info['extension'] == 'txt' ) // We've got a TXT file!
  325. {
  326. $new_file = basename($local_file, '.txt');
  327. if ( ! rename($local_file, PATH_THIRD.$new_file))
  328. {
  329. $cp_type = 'message_failure';
  330. $cp_message = lang('plugin_install_other');
  331. }
  332. else
  333. {
  334. @chmod($new_file, 0777);
  335. $cp_type = 'message_success';
  336. $cp_message = lang('plugin_install_success');
  337. }
  338. }
  339. else if ($file_info['extension'] == 'zip' ) // We've got a ZIP file!
  340. {
  341. // Unzip and install plugin
  342. if (class_exists('PclZip'))
  343. {
  344. // The chdir breaks CI's view loading, so we
  345. // store a reference and reset after the unzip
  346. $_ref = getcwd();
  347. $zip = new PclZip($local_file);
  348. $temp_dir = PATH_THIRD.'47346fc7580de7596d7df8d115a3545d';
  349. mkdir($temp_dir);
  350. chdir($temp_dir);
  351. $ok = @$zip->extract('');
  352. unlink($local_file);
  353. if ($ok)
  354. {
  355. // check if the file is sitting right here
  356. $pi_files = glob($temp_dir.'/pi.*.php');
  357. if (empty($pi_files))
  358. {
  359. // check directories (GLOB_ONLYDIR not available on Windows < PHP 4.3.3, too bad...)
  360. // stop at first plugin file found to keep things sane
  361. foreach (glob($temp_dir.'/*', GLOB_ONLYDIR) as $dir)
  362. {
  363. $pi_files = glob($dir.'/pi.*.php');
  364. if ( ! empty($pi_files))
  365. {
  366. break;
  367. }
  368. }
  369. }
  370. if (empty($pi_files))
  371. {
  372. $cp_type = 'message_failure';
  373. $cp_message = lang('plugin_error_no_plugins_found');
  374. }
  375. else
  376. {
  377. $filename = basename($pi_files[0]);
  378. $package = substr($filename, 3, -4);
  379. // does this add-on already exist?
  380. if (is_dir(PATH_THIRD.$package))
  381. {
  382. $cp_type = 'message_failure';
  383. $cp_message = lang('plugin_error_package_already_exists');
  384. }
  385. else
  386. {
  387. mkdir(PATH_THIRD.$package);
  388. rename(rtrim(substr($pi_files[0], 0, - strlen($filename)), '/'), PATH_THIRD.$package);
  389. $cp_type = 'message_success';
  390. $cp_message = lang('plugin_install_success');
  391. }
  392. }
  393. }
  394. else
  395. {
  396. $cp_type = 'message_failure';
  397. $cp_message = lang('plugin_error_uncompress');
  398. }
  399. // cleanup temp zip directory
  400. $this->functions->delete_directory($temp_dir, TRUE);
  401. // Fix loader scope
  402. chdir($_ref);
  403. }
  404. else
  405. {
  406. $cp_type = 'message_failure';
  407. $cp_message = lang('plugin_error_no_zlib');
  408. }
  409. }
  410. else
  411. {
  412. $cp_type = 'message_failure';
  413. $cp_message = lang('plugin_install_other');
  414. }
  415. $this->session->set_flashdata($cp_type, $cp_message);
  416. $this->functions->redirect(BASE.AMP.'C=addons_plugins');
  417. }
  418. // --------------------------------------------------------------------
  419. /**
  420. * Sorting Callback
  421. *
  422. * @access private
  423. * @return mixed array of plugin data
  424. */
  425. function _plugin_title_sorter($a, $b)
  426. {
  427. return strnatcasecmp($a['title'], $b['title']);
  428. }
  429. // --------------------------------------------------------------------
  430. /**
  431. * Get installed plugins
  432. *
  433. * Get a list of installed plugins
  434. *
  435. * @access private
  436. * @return mixed array of plugin data
  437. */
  438. function _get_installed_plugins()
  439. {
  440. $this->load->helper('file');
  441. $ext_len = strlen('.php');
  442. $plugin_files = array();
  443. $plugins = array();
  444. // Get a list of all plugins
  445. // first party first!
  446. if (($list = get_filenames(PATH_PI)) !== FALSE)
  447. {
  448. foreach ($list as $file)
  449. {
  450. if (strncasecmp($file, 'pi.', 3) == 0 &&
  451. substr($file, -$ext_len) == '.php' &&
  452. strlen($file) > 7 &&
  453. in_array(substr($file, 3, -$ext_len), $this->core->native_plugins))
  454. {
  455. $plugin_files[$file] = PATH_PI.$file;
  456. }
  457. }
  458. }
  459. // third party, in packages
  460. if (($map = directory_map(PATH_THIRD, 2)) !== FALSE)
  461. {
  462. foreach ($map as $pkg_name => $files)
  463. {
  464. if ( ! is_array($files))
  465. {
  466. $files = array($files);
  467. }
  468. foreach ($files as $file)
  469. {
  470. if (is_array($file))
  471. {
  472. // we're only interested in the top level files for the addon
  473. continue;
  474. }
  475. // we gots a plugin?
  476. if (strncasecmp($file, 'pi.', 3) == 0 &&
  477. substr($file, -$ext_len) == '.php' &&
  478. strlen($file) > strlen('pi.'.'.php'))
  479. {
  480. if (substr($file, 3, -$ext_len) == $pkg_name)
  481. {
  482. $plugin_files[$file] = PATH_THIRD.$pkg_name.'/'.$file;
  483. }
  484. }
  485. }
  486. }
  487. }
  488. ksort($plugin_files);
  489. // Grab the plugin data
  490. foreach ($plugin_files as $file => $path)
  491. {
  492. // Used as a fallback name and url identifier
  493. $filename = substr($file, 3, -$ext_len);
  494. // Magpie maight already be in use for an accessory or other function
  495. // If so, we still need the $plugin_info, so we'll open it up and
  496. // harvest what we need. This is a special exception for Magpie.
  497. if ($file == 'pi.magpie.php' &&
  498. in_array($path, get_included_files()) &&
  499. class_exists('Magpie'))
  500. {
  501. $contents = file_get_contents($path);
  502. $start = strpos($contents, '$plugin_info');
  503. $length = strpos($contents, 'Class Magpie') - $start;
  504. eval(substr($contents, $start, $length));
  505. }
  506. @include_once($path);
  507. if (isset($plugin_info) && is_array($plugin_info))
  508. {
  509. // Third party?
  510. $plugin_info['installed_path'] = $path;
  511. // fallback on the filename if no name is given
  512. if ( ! isset($plugin_info['pi_name']) OR $plugin_info['pi_name'] == '')
  513. {
  514. $plugin_info['pi_name'] = $filename;
  515. }
  516. if ( ! isset($plugin_info['pi_version']))
  517. {
  518. $plugin_info['pi_version'] = '--';
  519. }
  520. $plugins[$filename] = $plugin_info;
  521. }
  522. else
  523. {
  524. log_message('error', "Invalid Plugin Data: {$filename}");
  525. }
  526. unset($plugin_info);
  527. }
  528. return $plugins;
  529. }
  530. // --------------------------------------------------------------------
  531. /**
  532. * Get plugin info
  533. *
  534. * Check for a plugin and get it's information
  535. *
  536. * @access private
  537. * @param string plugin filename
  538. * @return mixed array of plugin data
  539. */
  540. function _get_plugin_info($filename = '')
  541. {
  542. if ( ! $filename)
  543. {
  544. return FALSE;
  545. }
  546. $path = PATH_PI.'pi.'.$filename.'.php';
  547. if ( ! file_exists($path))
  548. {
  549. $path = PATH_THIRD.$filename.'/pi.'.$filename.'.php';
  550. if ( ! file_exists($path))
  551. {
  552. return FALSE;
  553. }
  554. }
  555. // Magpie maight already be in use for an accessory or other function
  556. // If so, we still need the $plugin_info, so we'll open it up and
  557. // harvest what we need. This is a special exception for Magpie.
  558. if ($filename == 'magpie' AND in_array($path, get_included_files()) AND class_exists('Magpie'))
  559. {
  560. $contents = file_get_contents($path);
  561. $start = strpos($contents, '$plugin_info');
  562. $length = strpos($contents, 'Class Magpie') - $start;
  563. eval(substr($contents, $start, $length));
  564. }
  565. @include_once($path);
  566. if ( ! isset($plugin_info) OR ! is_array($plugin_info))
  567. {
  568. return FALSE;
  569. }
  570. // We need to clean up for display, might as
  571. // well do it here and keep the view tidy
  572. foreach ($plugin_info as $key => $val)
  573. {
  574. if ($key == 'pi_author_url')
  575. {
  576. $qm = ($this->config->item('force_query_string') == 'y') ? '' : '?';
  577. $val = prep_url($val);
  578. $val = anchor($this->functions->fetch_site_index().$qm.'URL='.$val, $val);
  579. }
  580. else if ($key == 'pi_usage')
  581. {
  582. $val = nl2br(htmlspecialchars($val));
  583. }
  584. $plugin_info[$key] = $val;
  585. }
  586. return $plugin_info;
  587. }
  588. // --------------------------------------------------------------------
  589. /**
  590. * Get available plugins
  591. *
  592. * Grab the EE rss feed of updated plugins
  593. *
  594. * @access private
  595. * @param mixed array of local plugins
  596. * @return mixed array of available plugins
  597. */
  598. function _get_available_plugins($local = array())
  599. {
  600. if ( ! defined('MAGPIE_CACHE_AGE'))
  601. {
  602. define('MAGPIE_CACHE_AGE', 60*60*24*3); // set cache to 3 days
  603. }
  604. if ( ! defined('MAGPIE_CACHE_DIR'))
  605. {
  606. define('MAGPIE_CACHE_DIR', APPPATH.'cache/magpie_cache/');
  607. }
  608. if ( ! defined('MAGPIE_DEBUG'))
  609. {
  610. define('MAGPIE_DEBUG', 0);
  611. }
  612. if (class_exists('Magpie'))
  613. {
  614. $plugins = fetch_rss('http://expressionengine.com/feeds/pluginlist/', 60*60*24); // one req/day
  615. if (count($plugins->items) > 0)
  616. {
  617. return $plugins->items;
  618. }
  619. }
  620. return array();
  621. }
  622. }
  623. // END CLASS
  624. /* End of file addons_plugins.php */
  625. /* Location: ./system/expressionengine/controllers/cp/addons_plugins.php */