Commit c6f8449f authored by Dries's avatar Dries

Issue #1203886 by catch, sun, David_Rothstein, RobLoach, tim.plunkett,...

Issue #1203886 by catch, sun, David_Rothstein, RobLoach, tim.plunkett, greggles, alexpott, DamienMcKenna: Remove the PHP module from Drupal core
parent c435ef8d
......@@ -106,9 +106,6 @@ protected function checkAccess(EntityInterface $entity, $operation, $langcode, A
// listed in $block->pages.
$page_match = !($visibility['path']['visibility'] xor $page_match);
}
elseif (module_exists('php')) {
$page_match = php_eval($visibility['path']['pages']);
}
// If there are page visibility restrictions and this page does not
// match, deny access.
......
......@@ -157,14 +157,6 @@ public function form(array $form, array &$form_state) {
);
$description = $this->t("Specify pages by using their paths. Enter one path per line. The '*' character is a wildcard. Example paths are %user for the current user's page and %user-wildcard for every user page. %front is the front page.", array('%user' => 'user', '%user-wildcard' => 'user/*', '%front' => '<front>'));
if ($this->moduleHandler->moduleExists('php') && $access) {
$options += array(BLOCK_VISIBILITY_PHP => $this->t('Pages on which this PHP code returns <code>TRUE</code> (experts only)'));
$title = $this->t('Pages or PHP code');
$description .= ' ' . $this->t('If the PHP option is chosen, enter PHP code between %php. Note that executing incorrect PHP code can break your Drupal site.', array('%php' => '<?php ?>'));
}
else {
$title = $this->t('Pages');
}
$form['visibility']['path']['visibility'] = array(
'#type' => 'radios',
'#title' => $this->t('Show block on specific pages'),
......@@ -173,7 +165,7 @@ public function form(array $form, array &$form_state) {
);
$form['visibility']['path']['pages'] = array(
'#type' => 'textarea',
'#title' => '<span class="visually-hidden">' . $title . '</span>',
'#title' => '<span class="visually-hidden">' . $this->t('Pages') . '</span>',
'#default_value' => !empty($visibility['path']['pages']) ? $visibility['path']['pages'] : '',
'#description' => $description,
);
......
......@@ -20,7 +20,7 @@ class FilterSecurityTest extends WebTestBase {
*
* @var array
*/
public static $modules = array('node', 'php', 'filter_test');
public static $modules = array('node', 'filter_test');
/**
* A user with administrative permissions.
......
format: php_code
name: 'PHP code'
status: '1'
weight: '11'
cache: '0'
filters:
php_code:
id: php_code
module: php
status: '1'
weight: '0'
settings: { }
langcode: und
<?php
/**
* @file
* Contains \Drupal\php\Plugin\Condition\Php.
*/
namespace Drupal\php\Plugin\Condition;
use Drupal\Core\Condition\ConditionPluginBase;
use Drupal\Core\Condition\Annotation\Condition;
use Drupal\Core\Annotation\Translation;
/**
* Provides a 'Php' condition.
*
* @Condition(
* id = "php",
* label = @Translation("PHP")
* )
*/
class Php extends ConditionPluginBase {
/**
* {@inheritdoc}
*/
public function buildForm(array $form, array &$form_state) {
$form = parent::buildForm($form, $form_state);
if (empty($this->configuration['php'])) {
// Initialize an empty value.
$this->configuration['php'] = FALSE;
}
$form['php'] = array(
'#type' => 'textarea',
'#title' => t('When the following PHP return TRUE (experts only)'),
'#default_value' => $this->configuration['php'],
'#description' => t('Enter PHP code between <?php ?>. Note that executing incorrect PHP code can break your Drupal site. Return TRUE in order for this condition to evaluate as TRUE.'),
'#access' => user_access('use PHP for settings')
);
return $form;
}
/**
* {@inheritdoc}
*/
public function submitForm(array &$form, array &$form_state) {
$this->configuration['php'] = $form_state['values']['php'];
parent::submitForm($form, $form_state);
}
/**
* {@inheritdoc}
*/
public function summary() {
if (!empty($this->configuration['php'])) {
return t('When the given PHP evaluates as @state.',
array(
'@state' => !empty($this->configuration['negate']) ? 'FALSE' : 'TRUE'
)
);
}
else {
return t('No PHP code has been provided.');
}
}
/**
* {@inheritdoc}
*/
public function evaluate() {
return php_eval($this->configuration['php']);
}
}
<?php
/**
* @file
* Contains \Drupal\php\Plugin\Filter\Php.
*/
namespace Drupal\php\Plugin\Filter;
use Drupal\filter\Annotation\Filter;
use Drupal\Core\Annotation\Translation;
use Drupal\filter\Plugin\FilterBase;
/**
* Provides PHP code filter. Use with care.
*
* @Filter(
* id = "php_code",
* module = "php",
* title = @Translation("PHP evaluator"),
* description = @Translation("Executes a piece of PHP code. The usage of this filter should be restricted to administrators only!"),
* type = FILTER_TYPE_MARKUP_LANGUAGE,
* cache = FALSE
* )
*/
class Php extends FilterBase {
/**
* {@inheritdoc}
*/
public function process($text, $langcode, $cache, $cache_id) {
return php_eval($text);
}
/**
* {@inheritdoc}
*/
public function tips($long = FALSE) {
if ($long) {
$output = '<h4>' . t('Using custom PHP code') . '</h4>';
$output .= '<p>' . t('Custom PHP code may be embedded in some types of site content, including posts and blocks. While embedding PHP code inside a post or block is a powerful and flexible feature when used by a trusted user with PHP experience, it is a significant and dangerous security risk when used improperly. Even a small mistake when posting PHP code may accidentally compromise your site.') . '</p>';
$output .= '<p>' . t('If you are unfamiliar with PHP, SQL, or Drupal, avoid using custom PHP code within posts. Experimenting with PHP may corrupt your database, render your site inoperable, or significantly compromise security.') . '</p>';
$output .= '<p>' . t('Notes:') . '</p>';
$output .= '<ul><li>' . t('Remember to double-check each line for syntax and logic errors <strong>before</strong> saving.') . '</li>';
$output .= '<li>' . t('Statements must be correctly terminated with semicolons.') . '</li>';
$output .= '<li>' . t('Global variables used within your PHP code retain their values after your script executes.') . '</li>';
$output .= '<li>' . t('<code>register_globals</code> is <strong>turned off</strong>. If you need to use forms, understand and use the functions in <a href="@formapi">the Drupal Form API</a>.', array('@formapi' => url('http://api.drupal.org/api/group/form_api/8'))) . '</li>';
$output .= '<li>' . t('Use a <code>print</code> or <code>return</code> statement in your code to output content.') . '</li>';
$output .= '<li>' . t('Develop and test your PHP code using a separate test script and sample database before deploying on a production site.') . '</li>';
$output .= '<li>' . t('Consider including your custom PHP code within a site-specific module or theme rather than embedding it directly into a post or block.') . '</li>';
$output .= '<li>' . t('Be aware that the ability to embed PHP code within content is provided by the PHP Filter module. If this module is disabled or deleted, then blocks and posts with embedded PHP may display, rather than execute, the PHP code.') . '</li></ul>';
$output .= '<p>' . t('A basic example: <em>Creating a "Welcome" block that greets visitors with a simple message.</em>') . '</p>';
$output .= '<ul><li>' . t('<p>Add a custom block to your site, named "Welcome" . With its text format set to "PHP code" (or another format supporting PHP input), add the following in the Block body:</p>
<pre>
print t(\'Welcome visitor! Thank you for visiting.\');
</pre>') . '</li>';
$output .= '<li>' . t('<p>To display the name of a registered user, use this instead:</p>
<pre>
global $user;
if ($user->isAuthenticated()) {
print t(\'Welcome @name! Thank you for visiting.\', array(\'@name\' => user_format_name($user)));
}
else {
print t(\'Welcome visitor! Thank you for visiting.\');
}
</pre>') . '</li></ul>';
$output .= '<p>' . t('<a href="@drupal">Drupal.org</a> offers <a href="@php-snippets">some example PHP snippets</a>, or you can create your own with some PHP experience and knowledge of the Drupal system.', array('@drupal' => url('http://drupal.org'), '@php-snippets' => url('http://drupal.org/documentation/customization/php-snippets'))) . '</p>';
return $output;
}
else {
return t('You may post PHP code. You should include &lt;?php ?&gt; tags.');
}
}
}
<?php
/**
* @file
* Contains \Drupal\php\Plugin\views\argument_default\Php.
*/
namespace Drupal\php\Plugin\views\argument_default;
use Drupal\views\Annotation\ViewsArgumentDefault;
use Drupal\Core\Annotation\Translation;
use Drupal\views\Plugin\views\argument_default\ArgumentDefaultPluginBase;
/**
* Default argument plugin to provide a PHP code block.
*
* @ViewsArgumentDefault(
* id = "php",
* title = @Translation("PHP Code")
* )
*/
class Php extends ArgumentDefaultPluginBase {
protected function defineOptions() {
$options = parent::defineOptions();
$options['code'] = array('default' => '');
return $options;
}
public function buildOptionsForm(&$form, &$form_state) {
parent::buildOptionsForm($form, $form_state);
$form['code'] = array(
'#type' => 'textarea',
'#title' => t('PHP contextual filter code'),
'#default_value' => $this->options['code'],
'#description' => t('Enter PHP code that returns a value to use for this filter. Do not use &lt;?php ?&gt;. You must return only a single value for just this filter. Some variables are available: the view object will be "$view". The argument handler will be "$argument", for example you may change the title used for substitutions for this argument by setting "argument->validated_title"".'),
);
// Only do this if using one simple standard form gadget
$this->checkAccess($form, 'code');
}
/**
* Only let users with PHP block visibility permissions set/modify this
* default plugin.
*/
public function access() {
return user_access('use PHP for settings');
}
public function getArgument() {
// set up variables to make it easier to reference during the argument.
$view = &$this->view;
$argument = &$this->argument;
ob_start();
$result = eval($this->options['code']);
ob_end_clean();
return $result;
}
}
<?php
/**
* @file
* Contains \Drupal\views\Plugin\views\argument_validator\Php.
*/
namespace Drupal\php\Plugin\views\argument_validator;
use Drupal\views\Annotation\ViewsArgumentValidator;
use Drupal\Core\Annotation\Translation;
use Drupal\views\Plugin\views\argument_validator\ArgumentValidatorPluginBase;
/**
* Provide PHP code to validate whether or not an argument is ok.
*
* @ViewsArgumentValidator(
* id = "php",
* title = @Translation("PHP Code")
* )
*/
class Php extends ArgumentValidatorPluginBase {
protected function defineOptions() {
$options = parent::defineOptions();
$options['code'] = array('default' => '');
return $options;
}
public function buildOptionsForm(&$form, &$form_state) {
parent::buildOptionsForm($form, $form_state);
$form['code'] = array(
'#type' => 'textarea',
'#title' => t('PHP validate code'),
'#default_value' => $this->options['code'],
'#description' => t('Enter PHP code that returns TRUE or FALSE. No return is the same as FALSE, so be SURE to return something if you do not want to declare the argument invalid. Do not use &lt;?php ?&gt;. The argument to validate will be "$argument" and the view will be "$view". You may change the argument by setting "$handler->argument". You may change the title used for substitutions for this argument by setting "$handler->validated_title".'),
);
$this->checkAccess($form, 'code');
}
/**
* Only let users with PHP block visibility permissions set/modify this
* validate plugin.
*/
public function access() {
return user_access('use PHP for settings');
}
public function validateArgument($argument) {
// set up variables to make it easier to reference during the argument.
$view = &$this->view;
$handler = &$this->argument;
ob_start();
$result = eval($this->options['code']);
ob_end_clean();
return $result;
}
}
<?php
/**
* @file
* Contains \Drupal\php\Tests\Condition\PhpConditionTest.
*/
namespace Drupal\php\Tests\Condition;
use Drupal\simpletest\DrupalUnitTestBase;
/**
* Tests the php condition.
*/
class PhpConditionTest extends DrupalUnitTestBase {
/**
* The condition plugin manager.
*
* @var \Drupal\Core\Condition\ConditionManager
*/
protected $manager;
/**
* Modules to enable.
*
* @var array
*/
public static $modules = array('system', 'php');
public static function getInfo() {
return array(
'name' => 'PHP Condition Plugin',
'description' => 'Tests that the PHP Condition, provided by the php module, is working properly.',
'group' => 'Condition API',
);
}
protected function setUp() {
parent::setUp();
$this->manager = $this->container->get('plugin.manager.condition');
}
/**
* Tests conditions.
*/
public function testConditions() {
// Grab the PHP condition and configure it to check against a php snippet.
$condition = $this->manager->createInstance('php')
->setConfig('php', '<?php return TRUE; ?>');
$this->assertTrue($condition->execute(), 'PHP condition passes as expected.');
// Check for the proper summary.
$this->assertEqual($condition->summary(), 'When the given PHP evaluates as TRUE.');
// Set the PHP snippet to return FALSE.
$condition->setConfig('php', '<?php return FALSE; ?>');
$this->assertFalse($condition->execute(), 'PHP condition fails as expected.');
// Negate the condition.
$condition->setConfig('negate', TRUE);
// Check for the proper summary.
$this->assertEqual($condition->summary(), 'When the given PHP evaluates as FALSE.');
// Reverse the negation.
$condition->setConfig('negate', FALSE);
// Set and empty snippet.
$condition->setConfig('php', FALSE);
// Check for the proper summary.
$this->assertEqual($condition->summary(), 'No PHP code has been provided.');
}
}
<?php
/**
* @file
* Definition of Drupal\php\Tests\PhpAccessTest.
*/
namespace Drupal\php\Tests;
/**
* Tests to make sure access to the PHP filter is properly restricted.
*/
class PhpAccessTest extends PhpTestBase {
public static function getInfo() {
return array(
'name' => 'PHP filter access check',
'description' => 'Make sure that users who don\'t have access to the PHP filter can\'t see it.',
'group' => 'PHP',
);
}
/**
* Makes sure that the user can't use the PHP filter when not given access.
*/
function testNoPrivileges() {
// Create node with PHP filter enabled.
$web_user = $this->drupalCreateUser(array('access content', 'create page content', 'edit own page content'));
$this->drupalLogin($web_user);
$node = $this->createNodeWithCode();
// Make sure that the PHP code shows up as text.
$this->drupalGet('node/' . $node->id());
$this->assertText('print', 'PHP code was not evaluated.');
// Make sure that user doesn't have access to filter.
$this->drupalGet('node/' . $node->id() . '/edit');
$this->assertNoRaw('<option value="' . $this->php_code_format->format . '">', 'PHP code format not available.');
}
}
<?php
/**
* @file
* Definition of Drupal\php\Tests\PhpFilterTest.
*/
namespace Drupal\php\Tests;
/**
* Tests to make sure the PHP filter actually evaluates PHP code when used.
*/
class PhpFilterTest extends PhpTestBase {
public static function getInfo() {
return array(
'name' => 'PHP filter functionality',
'description' => 'Make sure that PHP filter properly evaluates PHP code when enabled.',
'group' => 'PHP',
);
}
/**
* Makes sure that the PHP filter evaluates PHP code when used.
*/
function testPhpFilter() {
// Log in as a user with permission to use the PHP code text format.
$php_code_permission = entity_load('filter_format', 'php_code')->getPermissionName();
$web_user = $this->drupalCreateUser(array('access content', 'create page content', 'edit own page content', $php_code_permission));
$this->drupalLogin($web_user);
// Create a node with PHP code in it.
$node = $this->createNodeWithCode();
// Make sure that the PHP code shows up as text.
$this->drupalGet('node/' . $node->id());
$this->assertText('php print');
// Change filter to PHP filter and see that PHP code is evaluated.
$edit = array();
$edit['body[0][format]'] = $this->php_code_format->format;
$this->drupalPostForm('node/' . $node->id() . '/edit', $edit, t('Save'));
$this->assertRaw(t('Basic page %title has been updated.', array('%title' => $node->label())), 'PHP code filter turned on.');
// Make sure that the PHP code shows up as text.
$this->assertNoText('print "SimpleTest PHP was executed!"', "PHP code isn't displayed.");
$this->assertText('SimpleTest PHP was executed!', 'PHP code has been evaluated.');
}
}
<?php
/**
* @file
* Definition of Drupal\php\Tests\PhpTestBase.
*/
namespace Drupal\php\Tests;
use Drupal\simpletest\WebTestBase;
/**
* Defines a base PHP test case class.
*/
abstract class PhpTestBase extends WebTestBase {
/**
* Modules to enable.
*
* @var array
*/
public static $modules = array('php');
protected $php_code_format;
function setUp() {
parent::setUp();
// Create Basic page node type.
$this->drupalCreateContentType(array('type' => 'page', 'name' => 'Basic page'));
// Create and login admin user.
$admin_user = $this->drupalCreateUser(array('administer filters'));
$this->drupalLogin($admin_user);
// Verify that the PHP code text format was inserted.
$php_format_id = 'php_code';
$this->php_code_format = entity_load('filter_format', $php_format_id);
$this->assertEqual($this->php_code_format->name, 'PHP code', 'PHP code text format was created.');
// Verify that the format has the PHP code filter enabled.
$filters = $this->php_code_format->filters();
$this->assertTrue($filters->get('php_code')->status, 'PHP code filter is enabled.');
// Verify that the format exists on the administration page.
$this->drupalGet('admin/config/content/formats');
$this->assertText('PHP code', 'PHP code text format was created.');
// Verify that anonymous and authenticated user roles do not have access.
$this->drupalGet('admin/config/content/formats/manage/' . $php_format_id);
$this->assertFieldByName('roles[' . DRUPAL_ANONYMOUS_RID . ']', FALSE, 'Anonymous users do not have access to PHP code format.');
$this->assertFieldByName('roles[' . DRUPAL_AUTHENTICATED_RID . ']', FALSE, 'Authenticated users do not have access to PHP code format.');
}
/**
* Creates a test node with PHP code in the body.
*
* @return stdObject Node object.
*/
function createNodeWithCode() {
return $this->drupalCreateNode(array('body' => array(array('value' => '<?php print "SimpleTest PHP was executed!"; ?>'))));
}
}
<?php
/**
* @file
* Contains \Drupal\php\Tests\Plugin\views\ArgumentValidatorTest.
*/
namespace Drupal\php\Tests\Plugin\views;
use Drupal\views\Tests\ViewUnitTestBase;
/**
* Tests Views argument validators.
*
* @see \Drupal\php\Plugin\views\argument_validator\Php
*/
class PhpArgumentValidatorTest extends ViewUnitTestBase {
/**
* Views used by this test.
*
* @var array
*/
public static $testViews = array('test_view_argument_validate_php');
/**
* Modules to enable.
*
* @var array
*/
public static $modules = array('php');
public static function getInfo() {
return array(
'name' => 'PHP argument validator',
'group' => 'Views Plugins',
'description' => 'Test PHP argument validator.',
);
}
/**
* Tests the validateArgument question.
*/
public function testArgumentValidatePhp() {
$string = $this->randomName();
$view = views_get_view('test_view_argument_validate_php');
$view->setDisplay();
$view->displayHandlers->get('default')->options['arguments']['null']['validate_options']['code'] = 'return $argument == \''. $string .'\';';
$view->initHandlers();
$this->assertTrue($view->argument['null']->validateArgument($string));
// Reset saved argument validation.
$view->argument['null']->argument_validated = NULL;
$this->assertFalse($view->argument['null']->validateArgument($this->randomName()));
}
}
name: 'PHP Filter'
type: module
description: 'Allows embedded PHP code/snippets to be evaluated.'
package: Core
version: VERSION
core: 8.x
<?php
/**
* @file
* Install, update and uninstall functions for the php module.
*/
/**
* Implements hook_disable().
*/
function php_disable() {
drupal_set_message(t('The PHP module has been disabled. Any existing content that was using the PHP filter will now be visible in plain text. This might pose a security risk by exposing sensitive information, if any, used in the PHP code.'));
}
<?php
/**
* @file
* Additional filter for PHP input.
*/
/**
* Implements hook_help().
*/
function php_help($path, $arg) {
switch ($path) {
case 'admin/help#php':
$output = '';
$output .= '<h3>' . t('About') . '</h3>';
$output .= '<p>' . t('The PHP Filter module adds a PHP filter to your site, for use with <a href="@filter">text formats</a>. This filter adds the ability to execute PHP code in any text field that uses a text format (such as the body of a content item or the text of a comment). <a href="@php-net">PHP</a> is a general-purpose scripting language widely-used for web development, and is the language with which Drupal has been developed. For more information, see the online handbook entry for the <a href="@php">PHP Filter module</a>.', array('@filter' => url('admin/help/filter'), '@php-net' => 'http://www.php.net', '@php' => 'http://drupal.org/documentation/modules/php')) . '</p>';
$output .= '<h3>' . t('Uses') . '</h3>';
$output .= '<dl>';
$output .= '<dt>' . t('Enabling execution of PHP in text fields') . '</dt>';
$output .= '<dd>' . t('The PHP Filter module allows users with the proper permissions to include custom PHP code that will get executed when pages of your site are processed. While this is a powerful and flexible feature if used by a trusted user with PHP experience, it is a significant and dangerous security risk in the hands of a malicious or inexperienced user. Even a trusted user may accidentally compromise the site by entering malformed or incorrect PHP code. Only the most trusted users should be granted permission to use the PHP filter, and all PHP code added through the PHP filter should be carefully examined before use. <a href="@php-snippets">Example PHP snippets</a> can be found on Drupal.org.', array('@php-snippets' => url('http://drupal.org/documentation/customization/php-snippets'))) . '</dd>';
$output .= '</dl>';
return $output;
}
}
/**
* Implements hook_permission().
*/
function php_permission() {
return array(
'use PHP for settings' => array(
'title' => t('Use PHP for settings'),
'restrict access' => TRUE,
),
);
}
/**
* Evaluates a string of PHP code.
*
* This is a wrapper around PHP's eval(). It uses output buffering to capture
* both returned and printed text. Unlike eval(), we require code to be
* surrounded by <?php ?> tags; in other words, we evaluate the code as if it
* were a stand-alone PHP file.
*
* Using this wrapper also ensures that the PHP code which is evaluated can not
* overwrite any variables in the calling code, unlike a regular eval() call.
*
* This function is also used as an implementation of