Drupal 8.0.0 beta 12. More info: https://www.drupal.org/node/2514176

This commit is contained in:
Pantheon Automation 2015-08-17 17:00:26 -07:00 committed by Greg Anderson
commit 9921556621
13277 changed files with 1459781 additions and 0 deletions

View file

@ -0,0 +1,4 @@
access_log:
enabled: false
max_lifetime: 259200
count_content_views: 0

View file

@ -0,0 +1,33 @@
# Schema for the configuration files of the statistics module.
statistics.settings:
type: config_object
label: 'Statistics settings'
mapping:
access_log:
type: mapping
label: 'Access log settings'
mapping:
enabled:
type: boolean
label: 'Enable'
max_lifetime:
type: integer
label: 'Maximum lifetime'
count_content_views:
type: integer
label: 'Count content views'
block.settings.statistics_popular_block:
type: block_settings
label: 'Popular content block settings'
mapping:
top_day_num:
type: integer
label: 'Number of day\s top views to display'
top_all_num:
type: integer
label: 'Number of all time views to display'
top_last_num:
type: integer
label: 'Number of most recent views to display'

View file

@ -0,0 +1,144 @@
<?php
/**
* @file
* Contains \Drupal\statistics\Plugin\Block\StatisticsPopularBlock.
*/
namespace Drupal\statistics\Plugin\Block;
use Drupal\Core\Access\AccessResult;
use Drupal\Core\Block\BlockBase;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Session\AccountInterface;
/**
* Provides a 'Popular content' block.
*
* @Block(
* id = "statistics_popular_block",
* admin_label = @Translation("Popular content")
* )
*/
class StatisticsPopularBlock extends BlockBase {
/**
* Number of day's top views to display.
*
* @var int
*/
protected $day_list;
/**
* Number of all time views to display.
*
* @var int
*/
protected $all_time_list;
/**
* Number of most recent views to display.
*
* @var int
*/
protected $last_list;
/**
* {@inheritdoc}
*/
public function defaultConfiguration() {
return array(
'top_day_num' => 0,
'top_all_num' => 0,
'top_last_num' => 0
);
}
/**
* {@inheritdoc}
*/
protected function blockAccess(AccountInterface $account) {
$access = AccessResult::allowedIfHasPermission($account, 'access content');
if ($account->hasPermission('access content')) {
$daytop = $this->configuration['top_day_num'];
if (!$daytop || !($result = statistics_title_list('daycount', $daytop)) || !($this->day_list = node_title_list($result, $this->t("Today's:")))) {
return AccessResult::forbidden()->inheritCacheability($access);
}
$alltimetop = $this->configuration['top_all_num'];
if (!$alltimetop || !($result = statistics_title_list('totalcount', $alltimetop)) || !($this->all_time_list = node_title_list($result, $this->t('All time:')))) {
return AccessResult::forbidden()->inheritCacheability($access);
}
$lasttop = $this->configuration['top_last_num'];
if (!$lasttop || !($result = statistics_title_list('timestamp', $lasttop)) || !($this->last_list = node_title_list($result, $this->t('Last viewed:')))) {
return AccessResult::forbidden()->inheritCacheability($access);
}
return $access;
}
return AccessResult::forbidden()->inheritCacheability($access);
}
/**
* {@inheritdoc}
*/
public function blockForm($form, FormStateInterface $form_state) {
// Popular content block settings.
$numbers = array(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 15, 20, 25, 30, 40);
$numbers = array('0' => $this->t('Disabled')) + array_combine($numbers, $numbers);
$form['statistics_block_top_day_num'] = array(
'#type' => 'select',
'#title' => $this->t("Number of day's top views to display"),
'#default_value' => $this->configuration['top_day_num'],
'#options' => $numbers,
'#description' => $this->t('How many content items to display in "day" list.'),
);
$form['statistics_block_top_all_num'] = array(
'#type' => 'select',
'#title' => $this->t('Number of all time views to display'),
'#default_value' => $this->configuration['top_all_num'],
'#options' => $numbers,
'#description' => $this->t('How many content items to display in "all time" list.'),
);
$form['statistics_block_top_last_num'] = array(
'#type' => 'select',
'#title' => $this->t('Number of most recent views to display'),
'#default_value' => $this->configuration['top_last_num'],
'#options' => $numbers,
'#description' => $this->t('How many content items to display in "recently viewed" list.'),
);
return $form;
}
/**
* {@inheritdoc}
*/
public function blockSubmit($form, FormStateInterface $form_state) {
$this->configuration['top_day_num'] = $form_state->getValue('statistics_block_top_day_num');
$this->configuration['top_all_num'] = $form_state->getValue('statistics_block_top_all_num');
$this->configuration['top_last_num'] = $form_state->getValue('statistics_block_top_last_num');
}
/**
* {@inheritdoc}
*/
public function build() {
$content = array();
if ($this->day_list) {
$content['top_day'] = $this->day_list;
$content['top_day']['#suffix'] = '<br />';
}
if ($this->all_time_list) {
$content['top_all'] = $this->all_time_list;
$content['top_all']['#suffix'] = '<br />';
}
if ($this->last_list) {
$content['top_last'] = $this->last_list;
$content['top_last']['#suffix'] = '<br />';
}
return $content;
}
}

View file

@ -0,0 +1,104 @@
<?php
/**
* @file
* Contains \Drupal\statistics\StatisticsSettingsForm.
*/
namespace Drupal\statistics;
use Drupal\Core\Extension\ModuleHandlerInterface;
use Drupal\Core\Form\ConfigFormBase;
use Drupal\Core\Config\ConfigFactoryInterface;
use Drupal\Core\Form\FormStateInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
/**
* Configure statistics settings for this site.
*/
class StatisticsSettingsForm extends ConfigFormBase {
/**
* The module handler.
*
* @var \Drupal\Core\Extension\ModuleHandlerInterface
*/
protected $moduleHandler;
/**
* Constructs a \Drupal\user\StatisticsSettingsForm object.
*
* @param \Drupal\Core\Config\ConfigFactoryInterface $config_factory
* The factory for configuration objects.
* @param \Drupal\Core\Extension\ModuleHandlerInterface $module_handler
* The module handler.
*/
public function __construct(ConfigFactoryInterface $config_factory, ModuleHandlerInterface $module_handler) {
parent::__construct($config_factory);
$this->moduleHandler = $module_handler;
}
/**
* {@inheritdoc}
*/
public static function create(ContainerInterface $container) {
return new static(
$container->get('config.factory'),
$container->get('module_handler')
);
}
/**
* {@inheritdoc}
*/
public function getFormId() {
return 'statistics_settings_form';
}
/**
* {@inheritdoc}
*/
protected function getEditableConfigNames() {
return ['statistics.settings'];
}
/**
* {@inheritdoc}
*/
public function buildForm(array $form, FormStateInterface $form_state) {
$config = $this->config('statistics.settings');
// Content counter settings.
$form['content'] = array(
'#type' => 'details',
'#title' => t('Content viewing counter settings'),
'#open' => TRUE,
);
$form['content']['statistics_count_content_views'] = array(
'#type' => 'checkbox',
'#title' => t('Count content views'),
'#default_value' => $config->get('count_content_views'),
'#description' => t('Increment a counter each time content is viewed.'),
);
return parent::buildForm($form, $form_state);
}
/**
* {@inheritdoc}
*/
public function submitForm(array &$form, FormStateInterface $form_state) {
$this->config('statistics.settings')
->set('count_content_views', $form_state->getValue('statistics_count_content_views'))
->save();
// The popular statistics block is dependent on these settings, so clear the
// block plugin definitions cache.
if ($this->moduleHandler->moduleExists('block')) {
\Drupal::service('plugin.manager.block')->clearCachedDefinitions();
}
parent::submitForm($form, $form_state);
}
}

View file

@ -0,0 +1,162 @@
<?php
/**
* @file
* Contains \Drupal\statistics\Tests\StatisticsAdminTest.
*/
namespace Drupal\statistics\Tests;
use Drupal\simpletest\WebTestBase;
/**
* Tests the statistics admin.
*
* @group statistics
*/
class StatisticsAdminTest extends WebTestBase {
/**
* Modules to enable.
*
* @var array
*/
public static $modules = array('node', 'statistics');
/**
* A user that has permission to administer statistics.
*
* @var \Drupal\user\UserInterface
*/
protected $privilegedUser;
/**
* A page node for which to check content statistics.
*
* @var \Drupal\node\NodeInterface
*/
protected $testNode;
/**
* The Guzzle HTTP client.
*
* @var \GuzzleHttp\ClientInterface;
*/
protected $client;
protected function setUp() {
parent::setUp();
// Create Basic page node type.
if ($this->profile != 'standard') {
$this->drupalCreateContentType(array('type' => 'page', 'name' => 'Basic page'));
}
$this->privilegedUser = $this->drupalCreateUser(array('administer statistics', 'view post access counter', 'create page content'));
$this->drupalLogin($this->privilegedUser);
$this->testNode = $this->drupalCreateNode(array('type' => 'page', 'uid' => $this->privilegedUser->id()));
$this->client = \Drupal::httpClient();
$this->client->setDefaultOption('config/curl', array(CURLOPT_TIMEOUT => 10));
}
/**
* Verifies that the statistics settings page works.
*/
function testStatisticsSettings() {
$config = $this->config('statistics.settings');
$this->assertFalse($config->get('count_content_views'), 'Count content view log is disabled by default.');
// Enable counter on content view.
$edit['statistics_count_content_views'] = 1;
$this->drupalPostForm('admin/config/system/statistics', $edit, t('Save configuration'));
$config = $this->config('statistics.settings');
$this->assertTrue($config->get('count_content_views'), 'Count content view log is enabled.');
// Hit the node.
$this->drupalGet('node/' . $this->testNode->id());
// Manually calling statistics.php, simulating ajax behavior.
$nid = $this->testNode->id();
$post = array('nid' => $nid);
global $base_url;
$stats_path = $base_url . '/' . drupal_get_path('module', 'statistics'). '/statistics.php';
$this->client->post($stats_path, array('body' => $post));
// Hit the node again (the counter is incremented after the hit, so
// "1 view" will actually be shown when the node is hit the second time).
$this->drupalGet('node/' . $this->testNode->id());
$this->client->post($stats_path, array('body' => $post));
$this->assertText('1 view', 'Node is viewed once.');
$this->drupalGet('node/' . $this->testNode->id());
$this->client->post($stats_path, array('body' => $post));
$this->assertText('2 views', 'Node is viewed 2 times.');
}
/**
* Tests that when a node is deleted, the node counter is deleted too.
*/
function testDeleteNode() {
$this->config('statistics.settings')->set('count_content_views', 1)->save();
$this->drupalGet('node/' . $this->testNode->id());
// Manually calling statistics.php, simulating ajax behavior.
$nid = $this->testNode->id();
$post = array('nid' => $nid);
global $base_url;
$stats_path = $base_url . '/' . drupal_get_path('module', 'statistics'). '/statistics.php';
$this->client->post($stats_path, array('body' => $post));
$result = db_select('node_counter', 'n')
->fields('n', array('nid'))
->condition('n.nid', $this->testNode->id())
->execute()
->fetchAssoc();
$this->assertEqual($result['nid'], $this->testNode->id(), 'Verifying that the node counter is incremented.');
$this->testNode->delete();
$result = db_select('node_counter', 'n')
->fields('n', array('nid'))
->condition('n.nid', $this->testNode->id())
->execute()
->fetchAssoc();
$this->assertFalse($result, 'Verifying that the node counter is deleted.');
}
/**
* Tests that cron clears day counts and expired access logs.
*/
function testExpiredLogs() {
$this->config('statistics.settings')
->set('count_content_views', 1)
->save();
\Drupal::state()->set('statistics.day_timestamp', 8640000);
$this->drupalGet('node/' . $this->testNode->id());
// Manually calling statistics.php, simulating ajax behavior.
$nid = $this->testNode->id();
$post = array('nid' => $nid);
global $base_url;
$stats_path = $base_url . '/' . drupal_get_path('module', 'statistics'). '/statistics.php';
$this->client->post($stats_path, array('body' => $post));
$this->drupalGet('node/' . $this->testNode->id());
$this->client->post($stats_path, array('body' => $post));
$this->assertText('1 view', 'Node is viewed once.');
// statistics_cron() will subtract
// statistics.settings:accesslog.max_lifetime config from REQUEST_TIME in
// the delete query, so wait two secs here to make sure the access log will
// be flushed for the node just hit.
sleep(2);
$this->cronRun();
$this->drupalGet('admin/reports/pages');
$this->assertNoText('node/' . $this->testNode->id(), 'No hit URL found.');
$result = db_select('node_counter', 'nc')
->fields('nc', array('daycount'))
->condition('nid', $this->testNode->id(), '=')
->execute()
->fetchField();
$this->assertFalse($result, 'Daycounter is zero.');
}
}

View file

@ -0,0 +1,102 @@
<?php
/**
* @file
* Contains \Drupal\statistics\Tests\StatisticsLoggingTest.
*/
namespace Drupal\statistics\Tests;
use Drupal\simpletest\WebTestBase;
/**
* Tests request logging for cached and uncached pages.
*
* We subclass WebTestBase rather than StatisticsTestBase, because we
* want to test requests from an anonymous user.
*
* @group statistics
*/
class StatisticsLoggingTest extends WebTestBase {
/**
* Modules to enable.
*
* @var array
*/
public static $modules = array('node', 'statistics', 'block');
/**
* User with permissions to create and edit pages.
*
* @var \Drupal\user\UserInterface
*/
protected $authUser;
/**
* The Guzzle HTTP client.
*
* @var \GuzzleHttp\ClientInterface;
*/
protected $client;
protected function setUp() {
parent::setUp();
// Create Basic page node type.
if ($this->profile != 'standard') {
$this->drupalCreateContentType(array('type' => 'page', 'name' => 'Basic page'));
}
$this->authUser = $this->drupalCreateUser(array('access content', 'create page content', 'edit own page content'));
// Ensure we have a node page to access.
$this->node = $this->drupalCreateNode(array('title' => $this->randomMachineName(255), 'uid' => $this->authUser->id()));
// Enable access logging.
$this->config('statistics.settings')
->set('count_content_views', 1)
->save();
// Clear the logs.
db_truncate('node_counter');
$this->client = \Drupal::httpClient();
$this->client->setDefaultOption('config/curl', array(CURLOPT_TIMEOUT => 10));
}
/**
* Verifies node hit counter logging and script placement.
*/
function testLogging() {
global $base_url;
$path = 'node/' . $this->node->id();
$module_path = drupal_get_path('module', 'statistics');
$stats_path = $base_url . '/' . $module_path . '/statistics.php';
$expected_library = $module_path . '/statistics.js';
$expected_settings = '"statistics":{"data":{"nid":"' . $this->node->id() . '"}';
// Verify that logging scripts are not found on a non-node page.
$this->drupalGet('node');
$this->assertNoRaw($expected_library, 'Statistics library JS not found on node page.');
$this->assertNoRaw($expected_settings, 'Statistics settings not found on node page.');
// Verify that logging scripts are not found on a non-existent node page.
$this->drupalGet('node/9999');
$this->assertNoRaw($expected_library, 'Statistics library JS not found on non-existent node page.');
$this->assertNoRaw($expected_settings, 'Statistics settings not found on non-existent node page.');
// Verify that logging scripts are found on a valid node page.
$this->drupalGet($path);
$this->assertRaw($expected_library, 'Found statistics library JS on node page.');
$this->assertRaw($expected_settings, 'Found statistics settings on node page.');
// Manually call statistics.php to simulate ajax data collection behavior.
$nid = $this->node->id();
$post = array('nid' => $nid);
$this->client->post($stats_path, array('body' => $post));
$node_counter = statistics_get($this->node->id());
$this->assertIdentical($node_counter['totalcount'], '1');
}
}

View file

@ -0,0 +1,57 @@
<?php
/**
* @file
* Contains \Drupal\statistics\Tests\StatisticsReportsTest.
*/
namespace Drupal\statistics\Tests;
/**
* Tests display of statistics report blocks.
*
* @group statistics
*/
class StatisticsReportsTest extends StatisticsTestBase {
/**
* Tests the "popular content" block.
*/
function testPopularContentBlock() {
// Clear the block cache to load the Statistics module's block definitions.
$this->container->get('plugin.manager.block')->clearCachedDefinitions();
// Visit a node to have something show up in the block.
$node = $this->drupalCreateNode(array('type' => 'page', 'uid' => $this->blockingUser->id()));
$this->drupalGet('node/' . $node->id());
// Manually calling statistics.php, simulating ajax behavior.
$nid = $node->id();
$post = http_build_query(array('nid' => $nid));
$headers = array('Content-Type' => 'application/x-www-form-urlencoded');
global $base_url;
$stats_path = $base_url . '/' . drupal_get_path('module', 'statistics'). '/statistics.php';
$client = \Drupal::httpClient();
$client->setDefaultOption('config/curl', array(CURLOPT_TIMEOUT => 10));
$client->post($stats_path, array('headers' => $headers, 'body' => $post));
// Configure and save the block.
$this->drupalPlaceBlock('statistics_popular_block', array(
'label' => 'Popular content',
'top_day_num' => 3,
'top_all_num' => 3,
'top_last_num' => 3,
));
// Get some page and check if the block is displayed.
$this->drupalGet('user');
$this->assertText('Popular content', 'Found the popular content block.');
$this->assertText("Today's", "Found today's popular content.");
$this->assertText('All time', 'Found the all time popular content.');
$this->assertText('Last viewed', 'Found the last viewed popular content.');
// statistics.module doesn't use node entities, prevent the node language
// from being added to the options.
$this->assertRaw(\Drupal::l($node->label(), $node->urlInfo('canonical', ['language' => NULL])), 'Found link to visited node.');
}
}

View file

@ -0,0 +1,55 @@
<?php
/**
* @file
* Contains \Drupal\statistics\Tests\StatisticsTestBase.
*/
namespace Drupal\statistics\Tests;
use Drupal\simpletest\WebTestBase;
/**
* Defines a base class for testing the Statistics module.
*/
abstract class StatisticsTestBase extends WebTestBase {
/**
* Modules to enable.
*
* @var array
*/
public static $modules = array('node', 'block', 'ban', 'statistics');
/**
* User with permissions to ban IP's.
*
* @var \Drupal\user\UserInterface
*/
protected $blockingUser;
protected function setUp() {
parent::setUp();
// Create Basic page node type.
if ($this->profile != 'standard') {
$this->drupalCreateContentType(array('type' => 'page', 'name' => 'Basic page'));
}
// Create user.
$this->blockingUser = $this->drupalCreateUser(array(
'access administration pages',
'access site reports',
'ban IP addresses',
'administer blocks',
'administer statistics',
'administer users',
));
$this->drupalLogin($this->blockingUser);
// Enable logging.
$this->config('statistics.settings')
->set('count_content_views', 1)
->save();
}
}

View file

@ -0,0 +1,56 @@
<?php
/**
* @file
* Contains \Drupal\statistics\Tests\StatisticsTokenReplaceTest.
*/
namespace Drupal\statistics\Tests;
/**
* Generates text using placeholders for dummy content to check statistics token
* replacement.
*
* @group statistics
*/
class StatisticsTokenReplaceTest extends StatisticsTestBase {
/**
* Creates a node, then tests the statistics tokens generated from it.
*/
function testStatisticsTokenReplacement() {
$language_interface = \Drupal::languageManager()->getCurrentLanguage();
// Create user and node.
$user = $this->drupalCreateUser(array('create page content'));
$this->drupalLogin($user);
$node = $this->drupalCreateNode(array('type' => 'page', 'uid' => $user->id()));
// Hit the node.
$this->drupalGet('node/' . $node->id());
// Manually calling statistics.php, simulating ajax behavior.
$nid = $node->id();
$post = http_build_query(array('nid' => $nid));
$headers = array('Content-Type' => 'application/x-www-form-urlencoded');
global $base_url;
$stats_path = $base_url . '/' . drupal_get_path('module', 'statistics'). '/statistics.php';
$client = \Drupal::httpClient();
$client->setDefaultOption('config/curl', array(CURLOPT_TIMEOUT => 10));
$client->post($stats_path, array('headers' => $headers, 'body' => $post));
$statistics = statistics_get($node->id());
// Generate and test tokens.
$tests = array();
$tests['[node:total-count]'] = 1;
$tests['[node:day-count]'] = 1;
$tests['[node:last-view]'] = format_date($statistics['timestamp']);
$tests['[node:last-view:short]'] = format_date($statistics['timestamp'], 'short');
// Test to make sure that we generated something for each token.
$this->assertFalse(in_array(0, array_map('strlen', $tests)), 'No empty tokens generated.');
foreach ($tests as $input => $expected) {
$output = \Drupal::token()->replace($input, array('node' => $node), array('langcode' => $language_interface->getId()));
$this->assertEqual($output, $expected, format_string('Statistics token %token replaced.', array('%token' => $input)));
}
}
}

View file

@ -0,0 +1,95 @@
<?php
/**
* @file
* Contains \Drupal\statistics\Tests\Views\IntegrationTest.
*/
namespace Drupal\statistics\Tests\Views;
use Drupal\views\Tests\ViewTestBase;
use Drupal\views\Tests\ViewTestData;
/**
* Tests basic integration of views data from the statistics module.
*
* @group statistics
* @see
*/
class IntegrationTest extends ViewTestBase {
/**
* Modules to enable.
*
* @var array
*/
public static $modules = array('statistics', 'statistics_test_views', 'node');
/**
* Stores the user object that accesses the page.
*
* @var \Drupal\user\UserInterface
*/
protected $webUser;
/**
* Stores the node object which is used by the test.
*
* @var \Drupal\node\Entity\Node
*/
protected $node;
/**
* Views used by this test.
*
* @var array
*/
public static $testViews = array('test_statistics_integration');
protected function setUp() {
parent::setUp();
ViewTestData::createTestViews(get_class($this), array('statistics_test_views'));
// Create a new user for viewing nodes.
$this->webUser = $this->drupalCreateUser(array('access content'));
$this->drupalCreateContentType(array('type' => 'page'));
$this->node = $this->drupalCreateNode(array('type' => 'page'));
// Enable access logging.
$this->config('statistics.settings')
->set('access_log.enabled', 1)
->set('count_content_views', 1)
->save();
$this->drupalLogin($this->webUser);
}
/**
* Tests the integration of the {node_counter} table in views.
*/
public function testNodeCounterIntegration() {
$this->drupalGet('node/' . $this->node->id());
// Manually calling statistics.php, simulating ajax behavior.
// @see \Drupal\statistics\Tests\StatisticsLoggingTest::testLogging().
global $base_url;
$stats_path = $base_url . '/' . drupal_get_path('module', 'statistics'). '/statistics.php';
$client = \Drupal::httpClient();
$client->setDefaultOption('config/curl', array(CURLOPT_TIMEOUT => 10));
$client->post($stats_path, array('body' => array('nid' => $this->node->id())));
$this->drupalGet('test_statistics_integration');
$expected = statistics_get($this->node->id());
// Convert the timestamp to year, to match the expected output of the date
// handler.
$expected['timestamp'] = date('Y', $expected['timestamp']);
foreach ($expected as $field => $value) {
$xpath = "//div[contains(@class, views-field-$field)]/span[@class = 'field-content']";
$this->assertFieldByXpath($xpath, $value, "The $field output matches the expected.");
}
}
}

View file

@ -0,0 +1,7 @@
name: Statistics
type: module
description: 'Logs content statistics for your site.'
package: Core
version: VERSION
core: 8.x
configure: statistics.settings

View file

@ -0,0 +1,59 @@
<?php
/**
* @file
* Install and update functions for the Statistics module.
*/
/**
* Implements hook_uninstall().
*/
function statistics_uninstall() {
// Remove states.
\Drupal::state()->delete('statistics.node_counter_scale');
\Drupal::state()->delete('statistics.day_timestamp');
}
/**
* Implements hook_schema().
*/
function statistics_schema() {
$schema['node_counter'] = array(
'description' => 'Access statistics for {node}s.',
'fields' => array(
'nid' => array(
'description' => 'The {node}.nid for these statistics.',
'type' => 'int',
'unsigned' => TRUE,
'not null' => TRUE,
'default' => 0,
),
'totalcount' => array(
'description' => 'The total number of times the {node} has been viewed.',
'type' => 'int',
'unsigned' => TRUE,
'not null' => TRUE,
'default' => 0,
'size' => 'big',
),
'daycount' => array(
'description' => 'The total number of times the {node} has been viewed today.',
'type' => 'int',
'unsigned' => TRUE,
'not null' => TRUE,
'default' => 0,
'size' => 'medium',
),
'timestamp' => array(
'description' => 'The most recent time the {node} has been viewed.',
'type' => 'int',
'unsigned' => TRUE,
'not null' => TRUE,
'default' => 0,
),
),
'primary key' => array('nid'),
);
return $schema;
}

View file

@ -0,0 +1,18 @@
/**
* @file
* Statistics functionality.
*/
(function ($, Drupal, drupalSettings) {
"use strict";
$(document).ready(function () {
$.ajax({
type: "POST",
cache: false,
url: drupalSettings.statistics.url,
data: drupalSettings.statistics.data
});
});
})(jQuery, Drupal, drupalSettings);

View file

@ -0,0 +1,8 @@
drupal.statistics:
version: VERSION
js:
statistics.js: {}
dependencies:
- core/jquery
- core/drupal
- core/drupalSettings

View file

@ -0,0 +1,6 @@
statistics.settings:
title: Statistics
description: 'Control details about what and how your site logs content statistics.'
route_name: statistics.settings
parent: system.admin_config_system
weight: -15

View file

@ -0,0 +1,204 @@
<?php
/**
* @file
* Logs and displays content statistics for a site.
*/
use Drupal\Core\Entity\EntityInterface;
use Drupal\Core\Entity\Display\EntityViewDisplayInterface;
use Drupal\Core\Routing\RouteMatchInterface;
use Drupal\Core\Url;
use Drupal\node\NodeInterface;
/**
* Implements hook_help().
*/
function statistics_help($route_name, RouteMatchInterface $route_match) {
switch ($route_name) {
case 'help.page.statistics':
$output = '';
$output .= '<h3>' . t('About') . '</h3>';
$output .= '<p>' . t('The Statistics module shows you how often content is viewed. This is useful in determining which pages of your site are most popular. For more information, see <a href="!statistics_do">the online documentation for the Statistics module</a>.', array('!statistics_do' => 'https://www.drupal.org/documentation/modules/statistics/')) . '</p>';
$output .= '<h3>' . t('Uses') . '</h3>';
$output .= '<dl>';
$output .= '<dt>' . t('Displaying popular content') . '</dt>';
$output .= '<dd>' . t('The module includes a <em>Popular content</em> block that displays the most viewed pages today and for all time, and the last content viewed. To use the block, enable <em>Count content views</em> on the <a href="!statistics-settings">Statistics page</a>, and then you can enable and configure the block on the <a href="!blocks">Block layout page</a>.', array('!statistics-settings' => \Drupal::url('statistics.settings'), '!blocks' => (\Drupal::moduleHandler()->moduleExists('block')) ? \Drupal::url('block.admin_display') : '#')) . '</dd>';
$output .= '<dt>' . t('Page view counter') . '</dt>';
$output .= '<dd>' . t('The Statistics module includes a counter for each page that increases whenever the page is viewed. To use the counter, enable <em>Count content views</em> on the <a href="!statistics-settings">Statistics page</a>, and set the necessary <a href="!permissions">permissions</a> (<em>View content hits</em>) so that the counter is visible to the users.', array('!statistics-settings' => \Drupal::url('statistics.settings'), '!permissions' => \Drupal::url('user.admin_permissions', array(), array('fragment' => 'module-statistics')))) . '</dd>';
$output .= '</dl>';
return $output;
case 'statistics.settings':
return '<p>' . t('Settings for the statistical information that Drupal will keep about the site.') . '</p>';
}
}
/**
* Implements hook_ENTITY_TYPE_view() for node entities.
*/
function statistics_node_view(array &$build, EntityInterface $node, EntityViewDisplayInterface $display, $view_mode) {
if (!$node->isNew() && $view_mode == 'full' && node_is_page($node) && empty($node->in_preview)) {
$build['statistics_content_counter']['#attached']['library'][] = 'statistics/drupal.statistics';
$settings = array('data' => array('nid' => $node->id()), 'url' => Url::fromUri('base:' . drupal_get_path('module', 'statistics') . '/statistics.php')->toString());
$build['statistics_content_counter']['#attached']['drupalSettings']['statistics'] = $settings;
}
}
/**
* Implements hook_node_links_alter().
*/
function statistics_node_links_alter(array &$node_links, NodeInterface $entity, array &$context) {
if ($context['view_mode'] != 'rss') {
if (\Drupal::currentUser()->hasPermission('view post access counter')) {
$statistics = statistics_get($entity->id());
if ($statistics) {
$links['statistics_counter']['title'] = \Drupal::translation()->formatPlural($statistics['totalcount'], '1 view', '@count views');
$node_links['statistics'] = array(
'#theme' => 'links__node__statistics',
'#links' => $links,
'#attributes' => array('class' => array('links', 'inline')),
);
}
}
}
}
/**
* Implements hook_cron().
*/
function statistics_cron() {
$statistics_timestamp = \Drupal::state()->get('statistics.day_timestamp') ?: 0;
if ((REQUEST_TIME - $statistics_timestamp) >= 86400) {
// Reset day counts.
db_update('node_counter')
->fields(array('daycount' => 0))
->execute();
\Drupal::state()->set('statistics.day_timestamp', REQUEST_TIME);
}
// Calculate the maximum of node views, for node search ranking.
\Drupal::state()->set('statistics.node_counter_scale', 1.0 / max(1.0, db_query('SELECT MAX(totalcount) FROM {node_counter}')->fetchField()));
}
/**
* Returns the most viewed content of all time, today, or the last-viewed node.
*
* @param string $dbfield
* The database field to use, one of:
* - 'totalcount': Integer that shows the top viewed content of all time.
* - 'daycount': Integer that shows the top viewed content for today.
* - 'timestamp': Integer that shows only the last viewed node.
* @param int $dbrows
* The number of rows to be returned.
*
* @return SelectQuery|FALSE
* A query result containing the node ID, title, user ID that owns the node,
* and the username for the selected node(s), or FALSE if the query could not
* be executed correctly.
*/
function statistics_title_list($dbfield, $dbrows) {
if (in_array($dbfield, array('totalcount', 'daycount', 'timestamp'))) {
$query = db_select('node_field_data', 'n');
$query->addTag('node_access');
$query->join('node_counter', 's', 'n.nid = s.nid');
$query->join('users_field_data', 'u', 'n.uid = u.uid');
return $query
->fields('n', array('nid', 'title'))
->fields('u', array('uid', 'name'))
->condition($dbfield, 0, '<>')
->condition('n.status', 1)
// @todo This should be actually filtering on the desired node status
// field language and just fall back to the default language.
->condition('n.default_langcode', 1)
->condition('u.default_langcode', 1)
->orderBy($dbfield, 'DESC')
->range(0, $dbrows)
->execute();
}
return FALSE;
}
/**
* Retrieves a node's "view statistics".
*
* @param int $nid
* The node ID.
*
* @return array
* An associative array containing:
* - totalcount: Integer for the total number of times the node has been
* viewed.
* - daycount: Integer for the total number of times the node has been viewed
* "today". For the daycount to be reset, cron must be enabled.
* - timestamp: Integer for the timestamp of when the node was last viewed.
*/
function statistics_get($nid) {
if ($nid > 0) {
// Retrieve an array with both totalcount and daycount.
return db_query('SELECT totalcount, daycount, timestamp FROM {node_counter} WHERE nid = :nid', array(':nid' => $nid), array('target' => 'replica'))->fetchAssoc();
}
}
/**
* Implements hook_ENTITY_TYPE_predelete() for node entities.
*/
function statistics_node_predelete(EntityInterface $node) {
// Clean up statistics table when node is deleted.
db_delete('node_counter')
->condition('nid', $node->id())
->execute();
}
/**
* Implements hook_ranking().
*/
function statistics_ranking() {
if (\Drupal::config('statistics.settings')->get('count_content_views')) {
return array(
'views' => array(
'title' => t('Number of views'),
'join' => array(
'type' => 'LEFT',
'table' => 'node_counter',
'alias' => 'node_counter',
'on' => 'node_counter.nid = i.sid',
),
// Inverse law that maps the highest view count on the site to 1 and 0
// to 0. Note that the ROUND here is necessary for PostgreSQL and SQLite
// in order to ensure that the :statistics_scale argument is treated as
// a numeric type, because the PostgreSQL PDO driver sometimes puts
// values in as strings instead of numbers in complex expressions like
// this.
'score' => '2.0 - 2.0 / (1.0 + node_counter.totalcount * (ROUND(:statistics_scale, 4)))',
'arguments' => array(':statistics_scale' => \Drupal::state()->get('statistics.node_counter_scale') ?: 0),
),
);
}
}
/**
* Implements hook_preprocess_HOOK() for block templates.
*/
function statistics_preprocess_block(&$variables) {
if ($variables['configuration']['provider'] == 'statistics') {
$variables['attributes']['role'] = 'navigation';
}
}
/**
* Implements hook_block_alter().
*
* Removes the "popular" block from display if the module is not configured
* to count content views.
*/
function statistics_block_alter(&$definitions) {
$statistics_count_content_views = \Drupal::config('statistics.settings')->get('count_content_views');
if (empty($statistics_count_content_views)) {
unset($definitions['statistics_popular_block']);
}
}

View file

@ -0,0 +1,4 @@
administer statistics:
title: 'Administer statistics'
view post access counter:
title: 'View content hits'

View file

@ -0,0 +1,38 @@
<?php
/**
* @file
* Handles counts of node views via AJAX with minimal bootstrap.
*/
use Drupal\Core\DrupalKernel;
use Symfony\Component\HttpFoundation\Request;
chdir('../../..');
$autoloader = require_once 'autoload.php';
$kernel = DrupalKernel::createFromRequest(Request::createFromGlobals(), $autoloader, 'prod');
$kernel->boot();
$views = $kernel->getContainer()
->get('config.factory')
->get('statistics.settings')
->get('count_content_views');
if ($views) {
$nid = filter_input(INPUT_POST, 'nid', FILTER_VALIDATE_INT);
if ($nid) {
\Drupal::database()->merge('node_counter')
->key('nid', $nid)
->fields(array(
'daycount' => 1,
'totalcount' => 1,
'timestamp' => REQUEST_TIME,
))
->expression('daycount', 'daycount + 1')
->expression('totalcount', 'totalcount + 1')
->execute();
}
}

View file

@ -0,0 +1,7 @@
statistics.settings:
path: '/admin/config/system/statistics'
defaults:
_form: 'Drupal\statistics\StatisticsSettingsForm'
_title: 'Statistics'
requirements:
_permission: 'administer statistics'

View file

@ -0,0 +1,64 @@
<?php
/**
* @file
* Builds placeholder replacement tokens for node visitor statistics.
*/
/**
* Implements hook_token_info().
*/
function statistics_token_info() {
$node['total-count'] = array(
'name' => t("Number of views"),
'description' => t("The number of visitors who have read the node."),
);
$node['day-count'] = array(
'name' => t("Views today"),
'description' => t("The number of visitors who have read the node today."),
);
$node['last-view'] = array(
'name' => t("Last view"),
'description' => t("The date on which a visitor last read the node."),
'type' => 'date',
);
return array(
'tokens' => array('node' => $node),
);
}
/**
* Implements hook_tokens().
*/
function statistics_tokens($type, $tokens, array $data = array(), array $options = array()) {
$token_service = \Drupal::token();
$replacements = array();
if ($type == 'node' & !empty($data['node'])) {
$node = $data['node'];
foreach ($tokens as $name => $original) {
if ($name == 'total-count') {
$statistics = statistics_get($node->id());
$replacements[$original] = $statistics['totalcount'];
}
elseif ($name == 'day-count') {
$statistics = statistics_get($node->id());
$replacements[$original] = $statistics['daycount'];
}
elseif ($name == 'last-view') {
$statistics = statistics_get($node->id());
$replacements[$original] = format_date($statistics['timestamp']);
}
}
if ($created_tokens = $token_service->findWithPrefix($tokens, 'last-view')) {
$statistics = statistics_get($node->id());
$replacements += $token_service->generate('date', $created_tokens, array('date' => $statistics['timestamp']), $options);
}
}
return $replacements;
}

View file

@ -0,0 +1,76 @@
<?php
/**
* @file
* Provide views data for statistics.module.
*/
/**
* Implements hook_views_data().
*/
function statistics_views_data() {
$data['node_counter']['table']['group'] = t('Content statistics');
$data['node_counter']['table']['join'] = array(
'node_field_data' => array(
'left_field' => 'nid',
'field' => 'nid',
),
);
$data['node_counter']['totalcount'] = array(
'title' => t('Total views'),
'help' => t('The total number of times the node has been viewed.'),
'field' => array(
'id' => 'numeric',
'click sortable' => TRUE,
),
'filter' => array(
'id' => 'numeric',
),
'argument' => array(
'id' => 'numeric',
),
'sort' => array(
'id' => 'standard',
),
);
$data['node_counter']['daycount'] = array(
'title' => t('Views today'),
'help' => t('The total number of times the node has been viewed today.'),
'field' => array(
'id' => 'numeric',
'click sortable' => TRUE,
),
'filter' => array(
'id' => 'numeric',
),
'argument' => array(
'id' => 'numeric',
),
'sort' => array(
'id' => 'standard',
),
);
$data['node_counter']['timestamp'] = array(
'title' => t('Most recent view'),
'help' => t('The most recent time the node has been viewed.'),
'field' => array(
'id' => 'date',
'click sortable' => TRUE,
),
'filter' => array(
'id' => 'date',
),
'argument' => array(
'id' => 'date',
),
'sort' => array(
'id' => 'standard',
),
);
return $data;
}

View file

@ -0,0 +1,9 @@
name: 'Statistics test views'
type: module
description: 'Provides default views for views statistics tests.'
package: Testing
version: VERSION
core: 8.x
dependencies:
- statistics
- views

View file

@ -0,0 +1,250 @@
langcode: en
status: true
dependencies:
module:
- node
- user
id: test_statistics_integration
label: 'Test statistics integration'
module: views
description: ''
tag: ''
base_table: node_field_data
base_field: nid
core: 8.x
display:
default:
display_plugin: default
id: default
display_title: Master
position: null
display_options:
access:
type: perm
cache:
type: tag
query:
type: views_query
exposed_form:
type: basic
pager:
type: none
options:
offset: 0
style:
type: default
row:
type: fields
fields:
title:
id: title
table: node_field_data
field: title
label: ''
alter:
alter_text: false
make_link: false
absolute: false
trim: false
word_boundary: false
ellipsis: false
strip_tags: false
html: false
hide_empty: false
empty_zero: false
plugin_id: field
entity_type: node
entity_field: title
timestamp:
id: timestamp
table: node_counter
field: timestamp
relationship: none
group_type: group
admin_label: ''
label: 'Most recent view'
exclude: false
alter:
alter_text: false
text: ''
make_link: false
path: ''
absolute: false
external: false
replace_spaces: false
path_case: none
trim_whitespace: false
alt: ''
rel: ''
link_class: ''
prefix: ''
suffix: ''
target: ''
nl2br: false
max_length: 0
word_boundary: true
ellipsis: true
more_link: false
more_link_text: ''
more_link_path: ''
strip_tags: false
trim: false
preserve_tags: ''
html: false
element_type: ''
element_class: ''
element_label_type: ''
element_label_class: ''
element_label_colon: true
element_wrapper_type: ''
element_wrapper_class: ''
element_default_classes: true
empty: ''
hide_empty: false
empty_zero: false
hide_alter_empty: true
date_format: html_year
custom_date_format: ''
timezone: ''
plugin_id: date
totalcount:
id: totalcount
table: node_counter
field: totalcount
relationship: none
group_type: group
admin_label: ''
label: 'Total views'
exclude: false
alter:
alter_text: false
text: ''
make_link: false
path: ''
absolute: false
external: false
replace_spaces: false
path_case: none
trim_whitespace: false
alt: ''
rel: ''
link_class: ''
prefix: ''
suffix: ''
target: ''
nl2br: false
max_length: 0
word_boundary: true
ellipsis: true
more_link: false
more_link_text: ''
more_link_path: ''
strip_tags: false
trim: false
preserve_tags: ''
html: false
element_type: ''
element_class: ''
element_label_type: ''
element_label_class: ''
element_label_colon: true
element_wrapper_type: ''
element_wrapper_class: ''
element_default_classes: true
empty: ''
hide_empty: false
empty_zero: false
hide_alter_empty: true
set_precision: false
precision: 0
decimal: .
separator: ''
format_plural: false
format_plural_string: "1\x03@count"
prefix: ''
suffix: ''
plugin_id: numeric
daycount:
id: daycount
table: node_counter
field: daycount
relationship: none
group_type: group
admin_label: ''
label: 'Views today'
exclude: false
alter:
alter_text: false
text: ''
make_link: false
path: ''
absolute: false
external: false
replace_spaces: false
path_case: none
trim_whitespace: false
alt: ''
rel: ''
link_class: ''
prefix: ''
suffix: ''
target: ''
nl2br: false
max_length: 0
word_boundary: true
ellipsis: true
more_link: false
more_link_text: ''
more_link_path: ''
strip_tags: false
trim: false
preserve_tags: ''
html: false
element_type: ''
element_class: ''
element_label_type: ''
element_label_class: ''
element_label_colon: true
element_wrapper_type: ''
element_wrapper_class: ''
element_default_classes: true
empty: ''
hide_empty: false
empty_zero: false
hide_alter_empty: true
set_precision: false
precision: 0
decimal: .
separator: ''
format_plural: false
format_plural_string: "1\x03@count"
prefix: ''
suffix: ''
plugin_id: numeric
filters:
status:
value: true
table: node_field_data
field: status
id: status
expose:
operator: ''
group: 1
plugin_id: boolean
entity_type: node
entity_field: status
sorts:
created:
id: created
table: node_field_data
field: created
order: DESC
entity_type: node
entity_field: created
page_1:
display_plugin: page
id: page_1
display_title: Page
position: null
display_options:
path: test_statistics_integration